Repository navigation
authz(ADR-0099): P0–P2 落地 — posture 成为分级裁决唯一输入(Layer 0 豁免门 + Layer 1 级判定收敛) #3211
Description
Activity
M1 完成(PR #3218,draft,CI 待跑)+ ADR-0099 已转 Accepted
M1(P0 等价门)已实现并全绿:
plugin-security498 passed / 20 files。同 PR 携带 ADR-0099 Proposed → Accepted 转态(maintainer 2026-07-18 批准)+ Acceptance addendum。G1 裁决输入 — 分歧名单(M1 的侦察产出)
种子形态(platform_admin / org_admin / member / W1 fixture)探针与 rung 全部一致——D1 的行为保持声明在种子面成立。分歧仅存在于两类对抗形态:
# 主体形态 今天(探针) P1 翻转后(rung) 恢复路径 (a) 作用域 admin_full_accessgrant(sys_user_permission_set.organization_id非空)在 private / platform-global / better-auth 对象上跨租户豁免 墙到自己 org 补授 unscoped admin_full_access(b) 自定义权限集零散授平台专属能力(如仅 studio.access,且另有超级位)同上 同上 同上 方向性已被 I3 格证明:rung ⊆ 探针 → P1 只可能收窄豁免,不可能放宽。两形态已在 matrix gate 以
KNOWN DIVERGENCE格钉住,posture-blind staging 格锁定了翻转前行为(P1 时逐格翻绿)。待 maintainer(G1 剩余两项)
- 逐条裁决上表两行收窄可接受(预期结论:接受——「持有零散平台能力 ≠ 平台运维」正是 fix(plugin-security): 堵跨租户 UPDATE 写 + org_admin private 对象越墙(security) #2946 Finding 2 教训的延长线;若存在依赖场景,先补 unscoped grant 再翻转)。
- 确认 M2 发布窗口(不进 15.2.0 / chore: version packages (rc) #3098,落下一 minor + release-notes callout)。
裁决通过后 M2 即可开工(落点与 fallback 语义 ADR D1 已定:两信号并存 rung 胜出、分歧记 defect 日志、执行较窄者)。
Generated by Claude Code
G1 决策门 — 已闭合(maintainer 裁决,2026-07-18)
① 分歧收窄裁决:两条均接受
# 形态 定性 裁决 (a) 作用域 admin_full_accessgrant今天豁免跨租户,违背授权者「你是 org X 的管理员」意图,且与 explain 已显示的 MEMBER 标签当面矛盾(#2949)。P1 后墙到本 org = 语义对齐。 ✅ 接受 (b) 自定义集零散授平台专属能力(如 studio.access)+ 另持超级位一次「可用 Studio」式的普通授权即可静默重开 #2946 Finding 2 修复的跨租户后门。P1 从结构上封死(posture 只从非作用域 grant 派生,任何能力拼装到不了 PLATFORM_ADMIN)= 完成 #2946 未竟部分,非新收窄。这正是 ADR-0099 不变量 I1 的原文所防。 ✅ 接受 方向由 I3 格证明:rung ⊆ 探针 → 只收窄、不放宽,无反向越权风险。爆炸半径仅 EE 多组织 shared-DB(开源单组织 / cloud env-per-DB 零影响)。
② M2 配套三件(随 P1 落地)
- defect 日志:探针 true / rung false 时打 WARN(含主体 id + 命中形态 a/b),受影响部署升级前后均有面包屑。
- release-notes 升级前自查指引:扫
sys_user_permission_set中admin_full_access的作用域授权行 + 自建权限集systemPermissions∩ {manage_metadata,manage_platform_settings,studio.access,manage_users};恢复路径 = 补授 unscopedadmin_full_access。 - 文档立规矩一句话:跨租户运维权 = unscoped
admin_full_access(唯一路径);零散平台能力只控功能入口,不控数据边界。
③ 发布窗口
P1 不进 15.2.0(changesets PR #3098),落下一个 minor + release-notes callout。
残余缝隙(已登记,非阻塞)
P1 收窄仅覆盖携带 posture 的路径(REST/runtime/MCP 用户请求,#2956 已覆盖 = 全部外部攻击面)。不带 posture 的内部调用方(delegated-admin 桥、sharing 服务、
getReadFilter消费者)走探针 fallback、保持旧行为,直至 ADR-0096 D3 消灭手搓 context。M2 的 fallback 矩阵格钉住这些路径。
G1 闭合 → M2 前置 = 「M1 PR #3218 合并」+「G1 裁决」,后者已达成。M1 CI 全绿、已转 ready-for-review,待合并后即按 harness 的 merged-branch 流程从最新 main 重开分支开工 M2。
Generated by Claude Code
- added a commit that references this issue
on Jul 18, 2026 M2(P1 翻转)完成 — PR #3226(draft,CI 待跑)
Layer 0 跨租户豁免门已从能力探针翻转为读携带
ctx.posturerung,探针降为 resolver-less fallback。M1(#3218)已合并、G1 已裁决,两前置满足后开工。落地
- 核心翻转
computeLayeredRlsFilter:携带 rung 权威、无 rung 探针兜底;读写共用一决策(computeWriteTenantCheckFilter复用),不可能漂移。 - 配套三件:① defect 日志
[authz/ADR-0099]WARN(探针↔rung 分歧);② changeset 带 release-notes 升级前自查指引 + 恢复路径;③authorization.mdx立规矩一句话。dogfoodmulti-tenant-exemption-postureledger note 同步更新。退役security-plugin.ts:71-77过时注释。 - 行为收窄:G1 裁决的两类主体(作用域 grant / 零散平台能力)墙到本 org,恢复 = 补 unscoped grant。(b) 实质封 fix(plugin-security): 堵跨租户 UPDATE 写 + org_admin private 对象越墙(security) #2946 能力拼装后门(I1)。真 admin 仍豁免、org_admin 仍被墙、单组织零影响。
验证(全绿)
plugin-security503 passed ·dogfood296 passed(含更新 ledger 格)·build71/71 · tsc + role-word + doc-authoring clean。进度
- M1 — P0 等价门(test(security): ADR-0099 P0 — probe vs carried-rung equivalence gate; accept ADR-0099 (#3211 M1) #3218 ✅ merged)+ ADR-0099 Accepted
- G1 — 裁决门(两条收窄接受 + 三件配套 + 下一 minor)
- M2 — P1 翻转(feat(security): ADR-0099 P1 — Layer 0 exemption reads the carried posture rung (#3211 M2) #3226,CI 待跑)
- M3 — P2 Layer 1 级判定收敛(前置:feat(security): ADR-0099 P1 — Layer 0 exemption reads the carried posture rung (#3211 M2) #3226 合并;预期纯行为保持)
M3 前置是 #3226 合并。合并后按 merged-branch 流程重开分支开工。
Generated by Claude Code
- 核心翻转
- added a commit that references this issue
on Jul 18, 2026 M2 已合并 ✅ +
⚠️ M3 落点核实推翻了「纯行为保持」判断M2(P1 豁免门翻转)已 merge 进 main(#3226)。进度:M1 ✅ · G1 ✅ · M2 ✅ · M3
⚠️ 见下。核实结论:M3 不是免费收尾,是 M2 的同构收窄——且 blast radius 可能更大
动 M3 前对 Layer 1 短路做了落点级核实,修正 v1.2 计划里「M3 纯行为保持、预期零 delta」的判断——那是错的。
证据链(
resolve-authz-context.ts+security-plugin.ts:2482+default-permission-sets.ts):- Layer 1 短路现在的条件是
posturePermits && superuserBypass——tier 判据是持超级位(viewAllRecords/modifyAllRecords,来自任一权限集的对象 rule)。 - ADR-0099 D1/D2 要 tier 收敛读 posture ≥ TENANT_ADMIN;而
TENANT_ADMIN ← 持有名为organization_admin的权限集(ctx.permissions装的是权限集名字,:262/:305)。 - 种子面同源:只有
admin_full_access/organization_admin授超级位,二者都 posture ≥ TENANT_ADMIN。 - 对抗面分歧:一个自定义权限集给普通 member 授某对象
viewAllRecords(不叫 organization_admin)→ 持超级位、但 posture=MEMBER。M3 收敛后:该 member 在 private 对象上从「短路看组织内全行」收窄为「business RLS 按 ownership/sharing 收窄」。
为什么这个 delta 可能比 M2 更要紧
M2 的分歧形态(作用域 grant / 零散平台能力)罕见。M3 的分歧形态是 「把『查看某对象全部记录』委派给非管理员角色」——这是常见、合法的授权模式(典型:「审计员可读全部发票」)。因为超级位只在 private/platform-global 对象上短路(#3005:public 对象上超级位本就不生效),M3 的 delta 精确命中**「private 敏感对象上的 view-all 委派」**——这类委派 M3 后会被 ownership 墙掉。
换句话说:M3 严格照 ADR-0099 D2 收敛,会让「给 member 授 viewAllRecords」这个位在 private 对象上失效(posture 定 tier → MEMBER 走 business RLS)。这可能不是我们想在没有 maintainer 明确裁决下做的收窄。
这是一个架构裁决点,不是我能默认推进的
ADR-0099 I5 原文:「Posture selects tier, never side: no rung grants a read/write bypass that the per-side capability bit does not」——它约束的是「posture 不能给位没给的」。但反方向(位给了、posture 没到 tier)ADR 用 D2 表判「MEMBER → business RLS applies」,即位被 posture 压制。这正是上面的 delta。
需要你(maintainer)先定方向,再谈实现:
选项 A — 照 ADR-0099 D2 严格收敛:tier=posture,member 的自定义 view-all 在 private 对象上被压制(fail-safe 收窄)。走 M2 同款流程:等价门侦察 → 分歧名单 → G 裁决 → 翻转 + release-notes。若采纳,需接受「view-all 委派在 private 对象上要求 organization_admin 或更高」这一收窄。
选项 B — Layer 1 tier 保持读超级位(不收敛),只在文档/ADR 注明:承认 Layer 1 的 tier 事实上由超级位驱动(与 posture 高度但非完全重合),M3 缩减为「加一道等价门钉住『超级位 ⊇ posture≥TENANT_ADMIN 的种子等价』+ 注释对齐」,不改行为。这尊重了现有 view-all 委派语义,把 posture-tier 收敛留给未来(需要时再 breaking)。
选项 C — 折中:tier =
posture≥TENANT_ADMINOR 超级位(即位仍能短路,posture 只补充)。但这弱化了 ADR-0099「单一裁决输入」的初衷,且引入第三种语义,我不推荐。我的建议:选项 B。M1/M2 已经拿下 ADR-0099 最有价值的部分(Layer 0 豁免的分叉 bug 类根治——#2946 Finding 2 那条真实漏洞线)。Layer 1 的 tier 收敛(D2)收益是「概念统一」,但代价是压制一个合法且常见的委派模式(private view-all),这个 breaking 值得单独立项、按需触发,而不是作为收尾顺手做。选 B 则 M3 是零风险的「等价门 + 注释对齐」,#3211 干净收口,posture-tier 收敛留 ADR 追踪待未来。
等你选方向。选 A 我按 M2 流程做等价门 + 出分歧名单;选 B 我做等价门 + 注释,零行为变更即可收口;选 C 我们再议。
Generated by Claude Code
- Layer 1 短路现在的条件是
M3′(P2′ 钉住格)完成 + G2 核查通过 — 全部开发闭环,待 #3245 合并收口
两轴模型的钉住格已实现并入 PR #3245(现为「ADR 修正 + P2′ 钉住格 + changeset」完整 PR)。
M3′ 落地(
authz-matrix-gate.test.ts,纯测试零行为变更)- 种子等价格:种子面「持超级位 ⟺ posture ≥ TENANT_ADMIN」一致;
- 委派格(LOAD-BEARING):MEMBER 持
invoice_auditor(某 private 对象委派 view/modify all)→ Layer 1 短路(组织内全行)+ Layer 0 墙住(org-1)——审计员模式钉进 CI,防未来「posture 收敛清理」静默删掉它; - I7 格:该持有者写侧被墙(pre-image=org-1,非 BYPASS)、供异租户 org_id 的 insert 被拒;
- 对照格:无委派的 member 在 private 对象被 DENY → 位是真实可授予能力、非条件性死位。
验证(全绿)
plugin-security510 passed · tsc 通过 ·check:role-word/check:doc-authoringclean · plugin-security build 绿。G2 收尾核查
- 每轴单一权威 grep 核查 ✅:边界事实(posture)一处派生(core
resolve-authz-context)+ 一处消费(computeLayeredRlsFilter的 Layer 0 门,ctx.posture权威、fallback 探针只收窄);范围事实(超级位)仅PermissionEvaluator.hasSuperuser*Bypass求值(Layer 1 短路 + write check 都调它),全仓无第三处派生。 - enforcement 与 explain 读同一携带值 ✅(fix(plugin-security): explain posture 证据对齐 enforcement 派生(消除标签漂移) #2949 教训格常绿,M1/M2 已覆盖)
- advisory lint 后续 issue 已开 → authz: 高危位(viewAllRecords/modifyAllRecords)授予的授权期 advisory lint #3247(高危位授予的授权期提示,独立小项)
- cloud 侧 e2e smoke:framework pin 升级后跑 cloud#835(本 PR 是 docs+test、无行为变更,理论上零 fallout;仍建议 pin 升级时顺跑一次)
- close 本 issue — 待 feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′) #3245 合并
进度总览
- M1 — P0 等价门 + ADR Accepted(test(security): ADR-0099 P0 — probe vs carried-rung equivalence gate; accept ADR-0099 (#3211 M1) #3218 ✅ merged)
- G1 — 裁决门
- M2 — P1 Layer 0 豁免门翻转(feat(security): ADR-0099 P1 — Layer 0 exemption reads the carried posture rung (#3211 M2) #3226 ✅ merged)
- G-M3 — Layer 1 裁决(原 P2 否决 → 两轴模型)
- M3/M3′ — 两轴 Amendment + P2′ 钉住格(feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′) #3245,评审中)
- G2 收口 — 待 feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′) #3245 合并后 close
所有开发已闭环。ADR-0099 以「边界轴收敛完成(M1/M2 根治 #2946 分叉 bug 类)+ 范围轴显式确认并钉死(M3′)」的完整姿态落地,比勉强做完原 P2 去砍一个合法委派更经得起回看。剩一步纯行政:你 review/merge #3245 后我 close #3211。
Generated by Claude Code
- added a commit that references this issue
on Jul 19, 2026 ✅ 收口 — ADR-0099 P0→P2′ 全链完成,#3245 已合并
PR #3245 合并进 main,本 tracking 的全部开发闭环。G2 核查通过,关闭 issue。
交付全景
里程碑 PR 内容 M1 — P0 等价门 + ADR Accepted #3218 ✅ 探针提为导出纯函数 + authz-matrix-gate等价/KNOWN DIVERGENCE/I2/I3/I4 格;产出分歧名单G1 — 收窄裁决 — 两条收窄接受((b) 封 #2946 能力拼装后门)+ 三件配套 + 落下一 minor M2 — P1 Layer 0 豁免门翻转 #3226 ✅ 豁免门读携带 ctx.posturerung、探针降 resolver-less fallback、读写共用一决策、[authz/ADR-0099]defect 面包屑、过时注释退役G-M3 — Layer 1 裁决 — 原 P2 否决 → 两轴模型(四视角:主流平台 / 粒度不可互换 / 委派是低代码日常 / AI 静默 miss 防错) M3/M3′ — 两轴 Amendment + P2′ 钉住格 #3245 ✅ ADR 两轴修正 + I7 + 委派格(审计员模式钉进 CI) 最终模型(两轴,每轴单一权威)
- posture = 边界轴(每主体):管阶梯两端——Layer 0 跨租户豁免(P1 已强制)+ EXTERNAL 整体替换(P3 demand-gated)。
- per-object 超级位 = 范围轴(每主体×每对象):Layer 1 tier 唯一输入,语义不变;
viewAllRecords/modifyAllRecords可委派给任意岗位。 - 不变量 I7(新):范围轴永不越 posture 未开的边界(超级位持有者低于 PLATFORM_ADMIN 仍被租户墙)。
- ADR-0099 治的病(多站点派生 → fix(plugin-security): 堵跨租户 UPDATE 写 + org_admin private 对象越墙(security) #2946/fix(plugin-security): explain posture 证据对齐 enforcement 派生(消除标签漂移) #2949 分叉 bug 类)根治且钉死:每轴一处派生、一处消费,全仓 grep 无第三处(G2 核查✓)。
G2 收尾核查
- 每轴单一权威 grep 核查 ✅
- enforcement 与 explain 读同一携带值 ✅(fix(plugin-security): explain posture 证据对齐 enforcement 派生(消除标签漂移) #2949 教训格常绿)
- advisory lint 后续 issue 已开 → authz: 高危位(viewAllRecords/modifyAllRecords)授予的授权期 advisory lint #3247
- cloud 侧 e2e:本链 docs+test 无行为变更(M2 的 Layer 0 翻转已随其自身验证),cloud pin 升级时顺跑 cloud#835 即可,非本 issue 阻塞项
遗留(已妥善转出,非本 issue 范围)
- authz: 高危位(viewAllRecords/modifyAllRecords)授予的授权期 advisory lint #3247 — 高危位授予的授权期 advisory lint(独立小项)。
- 原 P2(Layer 1 tier 收敛) — 已否决并记入 ADR-0099 Amendment;若未来重启须另立 ADR 面对「废对象级 view-all 委派」的 breaking + 迁移路径。
- P3(EXTERNAL rung 激活) — demand-gated,留 Permission Model v2 — named follow-up ADRs (post-ADR-0090 parking lot) #2776 item 6(门户产品轨立项时)。
ADR-0099 以「边界轴收敛完成(根治真实分叉 bug)+ 范围轴显式确认并钉死(保住合法委派)」的完整姿态落地。Close。
Generated by Claude Code
G2 补充证据 — cloud 侧多组织 e2e 实测,M2 零 fallout ✅
收口时 G2 清单里唯一「待验证」项(cloud 侧企业多组织 e2e)现已本地实测确认。cloud 仓库拉入会话、framework pin(d1d1c40 = #3232,已含 M2 #3226)link 到含 M2/M3′ 的 framework,跑企业授权栈全套。
结果:
@objectstack/security-enterprise— 164 tests / 20 files 全绿关键的跨租户隔离 smoke(
multi-org-tenant-isolation.integration.test.ts,open-core dogfood 跑不到的企业 shared-DB 路径)4/4 通过:- org admin 持
viewAllRecords看组织内全行、但被 Layer 0 墙 ✅ —— 这正是 M2 + 两轴模型的真机对照:TENANT_ADMIN 持超级位 = Layer 1 组织内全行(范围轴),posture 定边界 = 不跨 Layer 0(边界轴)。M3′ 委派格的企业版。 - member owner-scoped + tenant-isolated ✅
- forged
organization_idINSERT 被 auto-stamp 覆盖(security(authz): 多组织下伪造 organization_id 的 insert 可越租户墙 — Layer 0 未门控 insert post-image #2937)✅ - reassign
organization_idUPDATE 不越墙(fix(plugin-security): 堵跨租户 UPDATE 写 + org_admin private 对象越墙(security) #2946/security: 服务端 readonly 字段在 UPDATE 时未强制(可被用户上下文覆盖) #2948)✅ —— 运行时 log 实证:[Security] Layer 0 tenant CHECK FAILED on update — write denied (fail-closed)
外加
conformance+position-anchor-unit-scope+governance等其余 160 测试全绿。结论:M2 的 Layer 0 豁免门翻转在企业多组织路径零 fallout。 跨租户四个 crown-jewel 不变量端到端保持。ADR-0099 实现链的最后一块验证补齐,G2 完整闭合。
注:cloud pin(d1d1c40)含 M2 但不含 M3′——符合预期(M3′ 是纯 docs+test 零行为变更,cloud 无需);cloud CI 下次 bump pin 到含 M3′ 的 framework 时预期同样零 fallout。
Generated by Claude Code
- org admin 持
- marked tracking(authz): ADR-0099 posture 裁决分级 — P0–P3 实现分期 #3312 as a duplicate of this issue
on Jul 19, 2026
背景
ADR-0099(PR #3109,merged 2026-07-17)是 ADR-0095 posture 阶梯的裁决收尾篇:阶梯派生了(B2/B4,#2938)、#2956 把 rung 挂上了
ExecutionContext、explain 侧已收敛读ctx.posture(#2949)——但 enforcement 侧没有任何分级裁决读它,每个判定站点仍从 capability 位重新推导主体等级。这类「多站点独立派生」已在一个发布周期里踩出两次分叉 bug:#2946 Finding 2(org admin 越租户墙)、#2949(explain 与 enforcement 的 posture 标签漂移)。本 issue 承接 ADR-0099 的 P0 → P1 → P2′ 实现;从 tracking #2920(已收口)移交。
修订说明
v1.1(2026-07-18):经落点级复核,P1 由原文「小改动、行为保持」修正为「fail-safe 方向的收窄,但确有行为变更」——探针与 rung 的证据不同源,P0 等价门预期不会全绿,其产出是需逐条裁决的 delta 名单。
v1.2(2026-07-18):加入完整开发计划(里程碑 PR 拆分 + 两个 maintainer 决策门、验证矩阵、风险与回滚、发布节奏)。
v1.3(2026-07-18,M3 裁决):M2 合并后、动 M3 前的核实推翻了「M3 纯行为保持」——Layer 1 短路的 tier 判据(持超级位)是每主体×每对象的委派原语(自定义集给非管理员岗授某对象
viewAllRecords= 审计员模式 = Salesforce 对象级 View All 同款),posture(每主体一个 rung)结构上无法表达;严格照 D2 收敛会让该位条件性失效(ADR-0049 禁止的 declared-but-unenforced 类)并砍掉主流委派能力。maintainer 四视角综合裁决(主流平台 / 长期架构 / 业务 / AI 防错,见 issue 讨论):选 B+ —— 原 P2 否决,ADR-0099 修正为两轴模型(posture=边界轴管阶梯两端,per-object 位=范围轴管 Layer 1 tier,「单一裁决输入」精化为「每轴单一权威」,新增不变量 I7)。ADR 修正已出 PR #3245;原 M3 方案作废,替换为下方 M3′。进度
开发计划(v1.3 修订后)
已完成里程碑(存档)
authz-matrix-gate.test.ts等价格/KNOWN DIVERGENCE 格/I2/I3/I4 staging 格。产出分歧名单:(a) 作用域admin_full_accessgrant、(b) 零散平台专属能力——两者探针 true / rung MEMBER。[authz/ADR-0099]defect 面包屑、:71-77过时注释退役、dogfood ledger 更新。plugin-security 503 / dogfood 296 / build 71/71 全绿。M3′ — P2′:两轴模型钉住格(≤1 PD,前置 PR #3245 合并)
性质:纯测试 + 已随 #3245 的 ADR 修正。零行为变更。
落点与内容:
ADR-0099 两轴 Amendment→ PR feat(authz): ADR-0099 two-axis model — Layer 1 tier stays the per-object scope bit; amendment + P2′ pins (#3211 M3/M3′) #3245(D2 Layer 1 行修正、每轴单一权威、I7、I2 澄清、P2 superseded 标记)。authz-matrix-gate.test.ts):viewAllRecords→ 组织内全行可见 且 被 Layer 0 墙住(I7)——把「合法委派」钉进 CI,防未来任何「posture 收敛清理」静默砍掉它;organization_id的写被拒(若 M2 既有格已覆盖则引用注明)。viewAllRecords/modifyAllRecords时的授权期提示(复用describeHighPrivilegeBits)。判据:零行为变更;plugin-security 全套绿;docs 门绿。
G2 — 收尾核查
hasSuperuser*Bypass求值;全仓无第三处派生;非目标
执行约束
关联
ADR-0099 / #3109 · #3218(M1/P0)· #3226(M2/P1)· #3245(两轴 Amendment)· ADR-0095 · #2956 · #2949 · #2946 Finding 2 · #2937 · ADR-0049(declared-but-unenforced 禁令,G-M3 裁决依据)· tracking #2920(已收口)· #2776 item 6(EXTERNAL 产品轨)