Skip to content

feat(plugin-security): C2-β — explain 引擎 record 粒度行级归因 (#2920)#2939

Merged
os-zhuang merged 3 commits into
mainfrom
claude/authz-c2b-explain-record
Jul 15, 2026
Merged

feat(plugin-security): C2-β — explain 引擎 record 粒度行级归因 (#2920)#2939
os-zhuang merged 3 commits into
mainfrom
claude/authz-c2b-explain-record

Conversation

@os-zhuang

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

Copy link
Copy Markdown
Contributor

C2-β — explain 引擎 record 粒度行级归因

引擎侧实现,基于已合并的 C2-α 契约(explain.zod.tsrecordId / ExplainMatchedRule / ExplainRecordAttribution / ExplainDecision.record / ExplainLayer.kernelTier / AuthzPosture / tenant_isolation 层)。tracking issue #2920

UI 侧对端 PR:objectstack-ai/objectui#2502(同分支名)。

实现结构

explain(principal, object, operation, recordId?):透传 recordId;缺省时对象级行为完全不变。带 recordId 时叠加行级归因,全部复用 enforcement 同一批函数(explained-by-construction):

  • tenant_isolation Layer 0:作为永远最先的层 prepend;每层打 kernelTier(layer_0_tenant vs layer_1_business),区分「租户墙挡」还是「业务 RLS 挡」。
  • 每层 record 归因(tenant / owd_baseline / sharing / rls):outcome(admitted/excluded/not_evaluated)、有效 rowFiltermatchesRecord(用 @objectstack/formulamatchesFilterCondition 对同一条 FilterCondition 求值——第三个 canonical backend,与查询/SQL 一致)、命中 rules[](kind ∈ tenant_filter/owd_baseline/ownership/record_share/sharing_rule/team/rls_policy,含 grants/via/effect)。
  • 顶层 record 判定:visible + decidedBy 决定性层。读走复合行过滤匹配,写走 sharing service 的 canEdit(均为 enforcement 原语)。
  • principal.posture:ADR-0095 D2 档位的 B2 stand-in 派生(复用 resolveAuthzContext 已投影的 platform_admin / org-admin 角色证据)。B2 未合并,标注 TODO(B2) 待换成正式 posture resolver;当前四档能填满(PLATFORM_ADMIN/TENANT_ADMIN/MEMBER/EXTERNAL)。

explained-by-construction / 无漂移

  • computeRlsFilter 重构为 computeLayeredRlsFilter(暴露 { layer0, layer1 } 拆分)+ 薄 andComposeLayers 包装 —— 单一代码路径,行级归因取自与有效过滤同源的 layer0/layer1。
  • 行级匹配用 matchesFilterCondition;写用 sharing service canEdit;shares 来自 listShares。不另写第二套判定。
  • 记录级 deps 全部 optional:无 plugin-sharing 的部署 sharing 层报 not_evaluated,record 路径仍工作。

REST

security.explain(GET/POST)接受可选 recordId(经 ExplainRequestSchema 校验)。

向后兼容

recordId 的对象级请求输出 byte-identical:9 层不变、无 tenant_isolation、无 kernelTier、无 posture、无 record。已有对象级测试无改动全绿。

验证

  • explain-engine.test.ts:14 原测(对象级向后兼容)+ 10 新测(带/不带 recordId、Layer 0 vs Layer 1 归因、共享准入/排除、记录不存在、写走 canEdit、无 deps 降级)= 24 全绿
  • pnpm --filter @objectstack/plugin-security test = 376 全绿
  • plugin-security / rest 包 build(含 DTS tsc)通过。

存疑点

  • posture 依赖 B2 未合并 → 用派生 stand-in(有 TODO),档位值已稳定。
  • group/role/unit 收件人的 share 未做展开 → rules[] 中标 neutral(诚实,不臆断);顶层 visible 仍由 sharing 复合过滤/canEdit 决定,不受影响。

Closes part of #2920.

🤖 Generated with Claude Code

https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs

带 recordId 时,explain 在对象级流水线上叠加行级归因,全部复用 enforcement 同一批函数(explained-by-construction):

- tenant_isolation Layer 0 prepend + 每层 kernelTier(layer_0_tenant vs layer_1_business)
- 每层 record 归因(tenant/owd_baseline/sharing/rls):outcome、有效 rowFilter、
  matchesRecord(用 @objectstack/formula 的 matchesFilterCondition 求值)、命中 rules[]
- 顶层 record 判定(visible + decidedBy);读走复合行过滤匹配,写走 sharing service canEdit
- principal.posture(ADR-0095 D2 B2 stand-in 派生,留 TODO)
- computeRlsFilter 拆为 computeLayeredRlsFilter({layer0,layer1})单一路径,不与执行漂移
- REST security.explain 接受可选 recordId

无 recordId 的对象级请求输出 byte-identical(向后兼容)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 15, 2026 1:01am

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Jul 15, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/plugin-security, @objectstack/rest.

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

  • content/docs/api/error-catalog.mdx (via @objectstack/rest)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest)
  • content/docs/api/index.mdx (via @objectstack/rest)
  • 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 packages/plugins/plugin-security, packages/rest)
  • 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, @objectstack/rest)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-security, @objectstack/rest)
  • content/docs/releases/implementation-status.mdx (via @objectstack/plugin-security, @objectstack/rest)
  • content/docs/releases/v12.mdx (via @objectstack/rest)
  • 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.

claude added 2 commits July 15, 2026 00:55
…e derivePosture

Merge B2/B4 into C2-β and replace the explain-engine posture stand-in's
PLATFORM_ADMIN/TENANT_ADMIN/MEMBER derivation with the single core
derivePosture (ADR-0095 D3 — capability-derived, closes the TODO(B2)). The
guest→EXTERNAL mapping stays in the explanation surface (the enforcement
resolver's floor is MEMBER).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants