Skip to content

feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′)#3245

Merged
os-zhuang merged 3 commits into
mainfrom
claude/authz-improvement-tasks-1f5u82
Jul 19, 2026
Merged

feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′)#3245
os-zhuang merged 3 commits into
mainfrom
claude/authz-improvement-tasks-1f5u82

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

目的

#3211 的收尾 PR:记录 M3 裁决(maintainer,2026-07-18)——原 P2「Layer 1 tier 收敛读 posture」否决,ADR-0099 修正为两轴模型——并落 P2′ 钉住格把修正后的模型钉进 CI。docs + test,零行为变更。

背景:P2 前核实推翻了「行为保持」声明

D2 表 Layer 1 行写着 tier 读 posture、「per today's semantics」——核实证明不是今天的语义:Layer 1 的 tier 输入是每对象超级位,一个可委派给非管理员的原语(自定义集给审计员岗授「某对象全部可见」= Salesforce 对象级 View All 同款)。posture 是每主体一个 rung,结构上装不下每对象范围。照原文强制会 (a) 让一个已声明、可授予的位条件性失效(ADR-0049 禁止的 declared-but-unenforced 类),(b) 砍掉主流委派能力、逼出过度授权。

内容

1. ADR-0099 两轴 Amendment(docs)

  • 两轴,每轴单一权威:posture=边界轴(每主体,管阶梯两端——Layer 0 豁免 P1 已落 + EXTERNAL 替换 P3 门控);per-object 超级位=范围轴(每主体×每对象,Layer 1 tier 唯一输入,语义不变)。
  • D1「单一裁决输入」精化为「每轴单一权威」——治的病(多站点派生)保持死透。
  • 新增 I7(I5 对偶):范围轴永不越 posture 未开的边界(超级位持有者低于 PLATFORM_ADMIN 仍被租户墙——P1 已强制)。
  • I2 澄清:嵌套是每 rung 的 floor 不变量;加法式对象级授权在 rung 内加宽不违反阶梯。
  • P2 → P2′;四处内联标记(Status / D1 bullet / D2 表 / Sequencing)指向 Amendment。

2. P2′ 钉住格(test,authz-matrix-gate.test.ts

  • 种子等价格:种子面「持超级位 ⟺ posture ≥ TENANT_ADMIN」一致(两轴在种子面重合——正是收敛「看似安全」的原因,直到委派格暴露它们是两个事实)。
  • 委派格(LOAD-BEARING):MEMBER 持 invoice_auditor(某 private 对象委派 view/modify all)→ Layer 1 短路(组织内全行)被 Layer 0 墙住(org-1)。审计员模式,未来任何「posture 收敛清理」必须保持它绿、否则静默删掉该能力。
  • I7 格:该持有者写侧被墙(pre-image=org-1,非 BYPASS)、供异租户 org_id 的 insert 被拒。
  • 对照格:无委派的 member 在 private 对象被 DENY → 证明位是真实可授予能力、非条件性死位。

3. changeset

@objectstack/plugin-security patch(test-only,循 #3218 先例)。

裁决依据(四视角)

① 主流平台(Salesforce 对象级 View All / Dynamics org 级实体访问 / Frappe)委派原语不绑管理员人格;② posture(每主体) 与位(每主体×对象) 粒度不可互换,压轴=类型错误;③ 审计员/风控式 view-all 委派是低代码日常;④ 条件性死位是 AI 作者最踩的静默 miss 类(#3003/#3005 同族)。两轴给 AI 的规则一句话:位定对象内行范围,posture 定租户边界

验证(全绿)

  • pnpm --filter @objectstack/plugin-security test510 passed / 20 files(含 P2′ 全部钉住格)
  • tsc --noEmit(plugin-security)通过;check:role-word / check:doc-authoring clean;plugin-security build 绿
  • G2 单一权威核查:边界事实一处派生(core resolver)+ Layer 0 门消费(fallback 探针只收窄);范围事实仅 hasSuperuser*Bypass 求值,无第三处派生 ✓

收口

合并后 #3211 走 G2 关闭(P3/EXTERNAL 留 #2776 item 6;advisory lint 另立后续 issue 追踪)。

Refs #3211 · ADR-0099 / #3109 · #3218(P0)· #3226(P1)· ADR-0049 · #2946 Finding 2

🤖 Generated with Claude Code

https://claude.ai/code/session_01DAHp4K7FvyMPBY1DPNkmRu

…bject 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
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Error Error Jul 19, 2026 2:15am

Request Review

…yer 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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/plugin-security.

11 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/getting-started/cli.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authorization.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/index.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-security)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-security)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang os-zhuang changed the title docs(adr): ADR-0099 two-axis amendment — Layer 1 tier stays the per-object scope bit; P2 → P2′ (#3211 M3 裁决) feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′) Jul 19, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review July 19, 2026 02:57
@os-zhuang
os-zhuang merged commit f9b118d into main Jul 19, 2026
15 of 16 checks passed
@os-zhuang
os-zhuang deleted the claude/authz-improvement-tasks-1f5u82 branch July 19, 2026 05:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants