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-zhuangos-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

vercelBot commented Jul 15, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 15, 2026 1:01am

Request Review

@github-actionsgithub-actionsBot 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-zhuang@claude