docs(adr): ADR-0099 — posture-adjudicated tiering, EXTERNAL enforcement path, no explicit deny#3109
Merged
Merged
Conversation
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
os-zhuang
marked this pull request as ready for review
July 17, 2026 10:02
This was referenced Jul 18, 2026
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>
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
目的
ADR-0095(posture 阶梯 + Layer 0,已 Accepted/实现)的裁决收尾篇:阶梯派生了、#2956 把 rung 挂上了
ExecutionContext——但没有任何 enforcement 裁决读它,每个分级判定仍在各自站点用 capability 位重新推导主体等级。这正是一个发布周期里踩了两次的分叉类 bug(#2946 Finding 2:org admin 越租户墙;#2949:explain 与 enforcement 的 posture 分叉)。决策
ctx.posture;hasPlatformAdminPosture能力探针降级为「无 resolver 上下文」的 fallback(且只允许收窄)。行为保持,先在 authz 矩阵落「探针 vs rung 等价」门再翻转。security-plugin.ts:71-77那条「posture 未透传」的过时注释(fix(authz): carry derived posture rung on ExecutionContext (#2947) #2956 已消除其前提)随 P1 退役。audience:'external'→derivePosture→ 仅显式共享的注入规则),矩阵格现在就落、实现按需求门控(门户/外部主体类型出现时,即 ADR-0090 后续 Migrate from npm to pnpm #6)。owner型共享规则(sharing.zod.ts:104仍 experimental)明确排除在 rung 之外直至单独 enforce。与 ADR-0096 的分工
正交、刻意分篇:0096 管准入(这个调用能否进 engine、以谁的身份);0099 管分级(已准入的主体在每层拿到哪一档行)。若 P1–P3 出现 breaking 面,与 0096 D5 翻转搭同一个 major 窗口。
分期(全部证据门控)
P0 矩阵格(本 ADR 随行)→ P1 Layer 0 豁免门 + explain 收敛读 rung(#2956 后已解锁,小)→ P2 Layer 1 级判定收敛 → P3 EXTERNAL(门户立项时)。
编号与验证
check:role-word✓(新文件 0 命中)·check:doc-authoring✓(195 files clean)。关联:#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