Skip to content

fix(plugin-security): 派生 capability 只 reconcile 自己那半边的行,admin/package 行不再每 boot 被占位符覆盖 (#5876) - #5934

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-5876-derived-pass-managed-by-guard
Aug 6, 2026
Merged

fix(plugin-security): 派生 capability 只 reconcile 自己那半边的行,admin/package 行不再每 boot 被占位符覆盖 (#5876)#5934
baozhoutao merged 1 commit into
mainfrom
claude/issue-5876-derived-pass-managed-by-guard

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5876

缺陷

bootstrapSystemCapabilities 的 seed 循环按 name 找到行就刷 label/description,不看 managed_by —— 而它正上方的注释写的正好相反(do NOT clobber admin edits)。#2909 T3 真正做成 seed-once 的只有 scope,而且只有 scope

这条循环里有两半:

  • curated 半边(PLATFORM_CAPABILITIES):label/description 确由平台作者撰写,新版本发新文案是正当的 reconcile;
  • 派生半边(permission set 的 systemPermissions[] 里出现、又无人声明的字符串):labelhumanize(name)descriptionCapability 加名字加句点(例如 Capability showcase.export_data.),根本不存在“平台作者写的文案” —— 它是从被授予的那个字符串生成的占位符。

把占位符刷到别人写的行上不是 reconcile,是每次启动把作者覆盖掉一次,而且外部看不出任何异常(静默数据丢失)。可达链窄但真实,且要求 admin 行先于授权存在:管理员在 Setup 建能力 X(managed_by:'admin',ADR-0066 写守卫留给 admin 的唯一 provenance)→ 装上一个 bootstrap permission set 授予 X 的应用 → 此后每次启动改写一次。反向顺序不可达:派生占位符一旦建成 managed_by:'platform',写守卫就不再允许 admin 编辑它。

改法(窄修:按 managed_by 守卫)

派生半边只 reconcile 它自己拥有的行:非 curated 名下的 managed_by:'platform' 行只可能是上一次启动留下的自家占位符。admin 行、package 行、以及 provenance 缺失的行(sys_capability.managed_byrequired + defaultValue: 'admin',所以“证不出是我们的”一律按不是处理)原样保留,并计入新增的 skippedAuthored

skippedAuthored 进启动摘要而不是 warn:能力照常解析、作者写的文案本就是更好的那份,这不是降级(#4632 的判定问题答“否”)。

curated 半边行为不变 —— 理由如上,issue 正文也已给出。scope 两边都仍是 seed-once(#2909 T3)。

注释与行为重新一致:改的是行为,不是把注释改去迁就缺陷。模块头也补上了“两半权限不同”的说明。

⛔ 没有静默扩面的那一问

issue 末尾还问了更宽的一问:派生 pass 是否根本不该 reconcile 任何它没有创建的行。本 PR 不回答它,行为改动严格限于 managed_by 守卫。

值得记下的一个观察(供该决策件参考,不构成本 PR 的改动):对派生半边而言,这两问的答案恰好重合 —— 非 curated 名下的 platform 行只可能由这同一段派生逻辑写出,所以“只碰 platform 行”实际就等于“只碰自己创建的行”。真正还没回答的是 curated 半边要不要也守卫(即平台发新文案是否应当越过一个 admin 行)。那需要先判断 curated 名下出现非 platform 行是否可达 —— 本 PR 不做这个判断,也没有为它写 pin(为一个未定的行为写 pin 等于把它固化)。

先红后绿 / 方向

新 pin 在未修复的源码上跑,7 条红(方向与预期一致):

#5875 那条 REVERSE pin 的红绿变化(方向说明)

原 pin 名为 REVERSE: dropping that name from the list lets the derivation overwrite the admin row,钉的是旧事实:把名字从 materializedCapabilityNames 里拿掉,派生逻辑就会把 admin 行改写成 Showcase Export Data。它当时在 main 上是绿的 —— 它钉的正是本 issue 的缺陷,#5875 用它来说明“unowned 路径为什么要检查已有行”。

修复后该事实不再成立,所以不是删它,而是按 PM 指引翻转为钉新语义:同一夹具、同样的空跳过表,断言 admin 行原样保留(Admin Made / Admin wrote this. / managed_by: 'admin'),并在注释里写清这条反面事实已被 #5876 修掉、以及跳过表在这个形状上不再是唯一防线。翻转前后:旧断言在修复后会红(所以必须翻)、新断言在修复前红、修复后绿

同时新增一条正向 pin,让探针有区分力(守卫不能把正当的 reconcile 一起关掉):

  • DISCRIMINATION (#5876): with the name dropped, a PLATFORM placeholder is still refreshed —— 同一夹具、同样的空跳过表,只把 provenance 换成 platform,reconcile 照旧发生。这条在修复前后都绿,这正是它的用处:它是对照组,证明红的那几条红在“provenance”,不是红在“派生逻辑不写了”。
  • bootstrap-system-capabilities.test.ts 里的 POSITIVE CONTROL: still refreshes its OWN platform placeholderthe guard is scoped to the DERIVED half — curated names still refresh 是同一目的的另外两条。

层次关系也一并说清:materializedCapabilityNames(#4967 Part 1,调用点声明谁已被别的 pass 落地)与本 PR 的守卫(写入点强制执行)是两层。对 admin 行这个具体形状,守卫之后跳过表不再是唯一防线;但跳过表仍然表达启动顺序契约,并且覆盖守卫看不见的东西 —— 一个从未被任何包声明过的 admin 行不会出现在任何 materializedNames 里,那正是本 issue 的可达链,只有写入点的守卫拦得住。

验证

pnpm --workspace-concurrency=2 --filter @objectstack/plugin-security test -- --maxWorkers=2
  Test Files  35 passed (35)
       Tests  768 passed (768)

pnpm --workspace-concurrency=2 --filter @objectstack/plugin-security typecheck
  tsc --noEmit   (无输出 = 通过)

node scripts/check-nul-bytes.mjs             → OK (5723 files)
node scripts/check-engine-double-contract.mjs → OK (70 pinned, 133 DEBT, 2 exempt)
node scripts/check-adr-anchors.mjs           → OK
eslint(三个改动文件)                          → 无输出

未新增任何 fake engine:两个测试文件复用各自原有的 makeQl(find/insert/update,无 delete),engine-double-contract 的 DEBT 账本条目计数未变。

以上均在合入最新 origin/main(a6b3ee7a1)、pnpm install --frozen-lockfile 并重建依赖后重跑过一遍。

文件面

  • packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts
  • packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts
  • packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts
  • .changeset/derived-capability-managed-by-guard.md(user-visible:admin 写的 label/description 不再每 boot 丢失)

🤖 Generated with Claude Code

https://claude.ai/code/session_01JwwiU9bjhwy2SWj13ho8uv


Generated by Claude Code

… 行不再每 boot 被占位符覆盖 (#5876)

bootstrapSystemCapabilities 的 seed 循环按名字找到行就刷 label/description,
不看 managed_by —— 而它上方的注释写的正好相反("do NOT clobber admin edits")。
#2909 T3 真正做成 seed-once 的只有 scope。

派生名(非 curated)的 label 是 humanize(name)、description 是
`Capability <name>.`,都由被授予的字符串生成,并没有"平台作者写的文案"可言。
于是管理员在 Setup 里写的 label/description 每次启动都被改写成 humanize 占位符
—— 静默数据丢失,外部看不出任何异常。

改法:派生半边只 reconcile 它自己拥有的行 —— 非 curated 名下的
managed_by:'platform' 行只可能是上一次启动留下的自家占位符;admin / package /
provenance 缺失的行原样保留,并计入新增的 skippedAuthored(计入启动摘要而非
warn:能力照常解析、作者写的文案更好,不是降级,#4632)。

curated 半边行为不变:那些定义确由平台作者撰写,新版本发新文案是正当的
reconcile。scope 两边都仍是 seed-once。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JwwiU9bjhwy2SWj13ho8uv
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 12:26pm

Request Review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/deployment/cli.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/services-checklist.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.

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

2 participants