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

vercelBot commented Aug 6, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 6, 2026 12:26pm

Request Review

@github-actions

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

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

2 participants

@baozhoutao@claude