Skip to content

docs(adr): ADR-0099 — posture-adjudicated tiering, EXTERNAL enforcement path, no explicit deny - #3109

Merged
os-zhuang merged 1 commit into
mainfrom
claude/adr-0099-posture-adjudication
Jul 17, 2026
Merged

docs(adr): ADR-0099 — posture-adjudicated tiering, EXTERNAL enforcement path, no explicit deny#3109
os-zhuang merged 1 commit into
mainfrom
claude/adr-0099-posture-adjudication

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

目的

ADR-0095(posture 阶梯 + Layer 0,已 Accepted/实现)的裁决收尾篇:阶梯派生了、#2956 把 rung 挂上了 ExecutionContext——但没有任何 enforcement 裁决读它,每个分级判定仍在各自站点用 capability 位重新推导主体等级。这正是一个发布周期里踩了两次的分叉类 bug(#2946 Finding 2:org admin 越租户墙;#2949:explain 与 enforcement 的 posture 分叉)。

决策

与 ADR-0096 的分工

正交、刻意分篇:0096 管准入(这个调用能否进 engine、以谁的身份);0099 管分级(已准入的主体在每层拿到哪一档行)。若 P1–P3 出现 breaking 面,与 0096 D5 翻转搭同一个 major 窗口。

分期(全部证据门控)

P0 矩阵格(本 ADR 随行)→ P1 Layer 0 豁免门 + explain 收敛读 rung(#2956 后已解锁,小)→ P2 Layer 1 级判定收敛 → P3 EXTERNAL(门户立项时)。

编号与验证

  • 0099 推送前已查 open PR 无认领(仓库此前发生过 0096/0097 两次碰撞)。
  • check:role-word ✓(新文件 0 命中)· check:doc-authoring ✓(195 files clean)。
  • 纯 docs 变更,无代码/changeset。

关联:#2920 · #2946 Finding 2 · #2949 · #2947#2956 · ADR-0095/0096/0090/0093/0094/0066。

🤖 Generated with Claude Code

https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs


Generated by Claude Code

…nt path, no explicit deny
Follow-through on ADR-0095: the derived posture rung is carried on the
ExecutionContext since #2956 but no enforcement decision reads it — each tier
decision re-derives principal tier from capability bits at its own site, the
divergence class behind #2946 Finding 2 (org admin crossed the tenant wall)
and #2949 (explain/enforcement posture split).
- D1: posture becomes the single tier-adjudication input (Layer 0 exemption,
Layer 1 short-circuit tier, explain); capability probe demoted to narrowing
fallback; behavior-preserving behind an equivalence gate in the authz matrix.
- D2: normative posture → layer action map with six invariants (TENANT_ADMIN
never crosses Layer 0; enforcement and explain read the same carried value;
posture selects tier, never side; never bypasses system-only write guards).
- D3: EXTERNAL rung activation chain (audience:'external' → derivePosture →
explicit-shares-only injection), matrix cells land now, implementation
demand-gated on an external principal type (portal track).
- D4: explicit deny/muting rejected as an adjudication primitive; needs mapped
to capability gates / FLS / admission / validity windows; assembly-time
muting reserved to a future permission-set-groups ADR.
Composes with ADR-0096 (admission vs tiering — orthogonal axes). Number 0099
verified free of open-PR claims before push.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
@vercel

vercelBot commented Jul 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 17, 2026 9:57am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation size/m labels Jul 17, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 17, 2026 10:02
@os-zhuang
os-zhuang merged commit a34b11c into mainJul 17, 2026
15 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0099-posture-adjudication branch July 17, 2026 10:02
os-zhuang added a commit that referenced this pull request Jul 18, 2026
…ture rung (#3211 M2) (#3226)
The Layer 0 cross-tenant exemption gate in computeLayeredRlsFilter now
reads the carried ctx.posture rung (#2956) as authoritative: crossing the
tenant wall requires PLATFORM_ADMIN. The hasPlatformAdminPosture capability
probe demotes to a fallback for resolver-less contexts (delegated-admin
bridge, sharing service, getReadFilter consumers), where behavior is
byte-for-byte unchanged. Read and write (insert/update post-image) tenant
checks share the one decision, so they cannot drift. A probe/rung
disagreement logs an [authz/ADR-0099] defect breadcrumb and enforces the
narrower rung verdict. Retires the stale 'posture not plumbed' comment.
Security narrowing (multi-org / @objectstack/organizations only, the G1-
adjudicated deltas): a scoped admin_full_access grant (a) and a piecemeal
platform-capability grant (b) resolve below PLATFORM_ADMIN and are now
walled to their own org on private / platform-global / better-auth objects,
where the posture-blind probe used to exempt them. Fail-safe (rung is a
strict subset of the probe, ADR-0099 I3); recover with an unscoped
admin_full_access grant. Single-org / env-per-database unaffected.
authz-matrix-gate.test.ts: new ADR-0099 P1 describe (narrowing read/write,
PLATFORM_ADMIN stays exempt, TENANT_ADMIN stays walled per I1, resolver-less
fallback preserved, defect-breadcrumb on disagreement / silence on
agreement). dogfood multi-tenant-exemption-posture ledger note updated.
Docs: authorization.mdx records the unscoped-grant rule.
Verified: plugin-security 503 passed, dogfood 296 passed, build 71/71, tsc +
check:role-word + check:doc-authoring clean.
Refs #3211 · ADR-0099 / #3109 · #2946 Finding 2 · #2956
Claude-Session: https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu
Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang added a commit that referenced this pull request Jul 19, 2026
…ect scope bit; amendment + P2′ pins (#3211 M3/M3′) (#3245)
* docs(adr): ADR-0099 two-axis amendment — Layer 1 tier stays the per-object scope bit; P2 superseded by P2' (#3211)
The pre-P2 verification found D2's Layer 1 row was not behavior-preserving
as written: Layer 1's tier input is the per-object super-bit, a
per-principal-x-per-object delegation primitive (custom set granting
viewAllRecords on one object to a non-admin position — the auditor
pattern, the industry-standard shape). Posture is per-principal and
structurally cannot carry per-object scope; enforcing the MEMBER cell
literally would inert a declared grantable bit (the ADR-0049
declared-but-unenforced class) and delete a mainstream delegation
capability.
Amendment (maintainer, 2026-07-18): two orthogonal axes, each with a
single authority — posture is the boundary axis (Layer 0 exemption at
PLATFORM_ADMIN, EXTERNAL replacement; both ladder ends), the per-object
super-bits are the scope axis (the only Layer 1 tier input, unchanged).
D1's single-input principle refines to single-authority-per-axis; new
invariant I7 (scope never crosses a boundary posture has not opened —
enforced by P1); I2 clarified as a per-rung floor. Original P2 rejected;
P2' = documentation cells only (seeded-face agreement, the delegation
cell, I7), zero behavior change.
Inline markers added at the D1 bullet, D2 table, and Sequencing P2 entry;
full rationale in the Amendment section. Docs-only; no changeset.
check:role-word OK - check:doc-authoring 200 files clean.
Refs #3211 - ADR-0099 / #3109 / #3218 / #3226
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu
* test(security): ADR-0099 P2′ — pin the per-object super-bit as the Layer 1 scope axis (#3211 M3′)
The two-axis Amendment's documentation cells (zero behavior change). The
original P2 (collapse Layer 1 tier onto posture) was rejected; these cells
pin the corrected model in CI:
- seeded-face agreement: every seeded super-bit holder is already
>= TENANT_ADMIN, so the two axes coincide on the seeded surface (why the
collapse looked safe until the delegation case);
- the delegation cell (LOAD-BEARING): a MEMBER holding a delegated
per-object viewAllRecords/modifyAllRecords on a private object
short-circuits Layer 1 (sees all rows in-org) AND stays walled by Layer 0
(org-1 only) — the auditor pattern; a future posture-convergence cleanup
must keep this green or it silently deletes the capability;
- I7: the holder cannot read/write/insert cross-tenant (the scope axis never
crosses a boundary posture has not opened);
- contrast: a plain member without the bit is denied on the private object,
so the bit is a real grantable capability, not conditionally inert
(the ADR-0049 class the collapse would have introduced).
New fixture invoice_auditor (delegated per-object super-bit). G2 audit:
single authority per axis holds — boundary fact derived once (core resolver)
+ consumed at the Layer 0 gate (fallback probe narrows only); scope fact
evaluated only via hasSuperuser*Bypass.
plugin-security 510 passed; tsc + check:role-word + check:doc-authoring clean;
plugin-security build green.
Refs #3211 - ADR-0099 two-axis Amendment (#3245)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu
* chore: changeset for ADR-0099 P2′ two-axis matrix cells (#3211)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/m

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude