feat(spec)!: retire the overloaded managedBy: 'system' bucket → system-data (#3355) - #4660
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 6 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…stem-data` (#3355) ADR-0103 split `system` additively in v16: the 20 engine-owned objects moved to the new explicit `engine-owned`, the 8 admin/user-writable ones stayed behind. The surviving value therefore names the half that already moved out — "system" on precisely the objects a user writes — leaving an author choosing between `system` and `engine-owned` with nothing in the vocabulary to choose on. v17 finishes the split: the residue is renamed `system-data`, the bare value is retired from the load path with a prescription, and the affordance default flips from LOCKED to WRITABLE (so the 8 objects' `userActions` re-open blocks become redundant and are deleted). Stored metadata is converted by the ADR-0087 entry `object-managed-by-system-to-system-data`, not reinterpreted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
…ata` equivalence and the mis-assignment refusal (#3355) The PR body claimed "no enforcement moves". These make it evidence: - per-object before/after pins for all 8 objects (4 packages): the v16 shape (`system` + a `userActions` re-open block) and the v17 shape (`system-data`, no block) must resolve the same create/edit/delete/exportCsv. The one deliberate delta — `import` false→true, per the maintainer's adjudication — gets its own named assertion so it cannot move silently. - `/me/permissions` clamp: same allowEdit/Create/Delete for both declaration shapes, plus a pin that a userActions-less `system-data` member is not clamped (the inverted false-negative removing `system` from GUARDED_WRITE_BUCKETS avoids). - write guard: `ENGINE_OWNED_BUCKETS` excludes both `system` and `system-data`. - new authoring-time refusal `assertSystemDataIsWritable`: `system-data` on an object granting no user write is a contradiction with no honest reading, and the writable default means the mistake now hands out generic CRUD. No guard covers the bucket, so `create()` is the only place to catch it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
…close-out (#3355) - gen:schema / gen:spec-changes / gen:upgrade-guide / gen:docs / gen:api-surface - objects.mdx: the bucket table, the engine-owned-vs-system-data section, the `userActions`-narrows contract and a v16 upgrade callout - ADR-0103: a v17 addendum completing D5 (which had explicitly deferred the removal), recording the rejected alternatives, the default flip and its one affordance consequence, and the new mis-assignment refusal All 11 spec gates pass (liveness, empty-state, authorable-surface, docs, api-surface, spec-changes, upgrade-guide, skill-refs, skill-docs, skill-examples, variant-docs). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
…etired literal (#3355) The equivalence pins built their v16 side by passing `managedBy: 'system'` to `resolveCrudAffordances` — but v17 deletes that row from `CRUD_AFFORDANCE_DEFAULTS`, so the lookup fell through to the `platform` default and reconstructed a baseline with `import: true`. The pins caught it (that is what they are for): the `expect(v16).toEqual(V16_EXPECTED)` guard failed rather than the comparison silently passing against a wrong baseline. ADR-0103 D5 gave `engine-owned` the byte-identical locked row `system` carried in v16, so it is an exact stand-in, and the V16_EXPECTED assertion keeps it honest. Also adds the NodeNext `.js` extensions the new test files needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
…3355) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
6679098 to
77f29a4
Compare
|
PM:暂缓转正,保持 draft,等维护者(人)裁决一个此前被误当作"已裁决"的点。 实现质量没问题,门禁也齐(spec 11 项全过、7 个包 18/18 test + 18/18 typecheck、objectui 侧 4263 例通过),我要求的三条 pin 也都到位——尤其是新增的作者时拒绝 但有一处溯源问题必须先纠正。 本 issue 上 09:49 与 09:59 那两条"裁决"署的是 Claude Code,是早前 PM 会话写的,不是维护者本人。agent 据此把"默认 affordance 含
授权层未被绕过(每行写入仍由 DelegatedAdminGate / RLS / 权限集裁决),affordance 只是 UI 入口——但一份错误 CSV 即一次大规模授权,风险面确有变化。agent 自己也把这条标为 security-adjacent 并指出"裁决时可能未考虑批量绑定权限集这一具体场景";它是对的,因为作出该裁决的是上游 agent。 PM 建议(待维护者定):桶默认给 另记两条:
Generated by Claude Code |
Two conflicts, both in the protocol 17 narrative where concurrent retirement PRs each appended a paragraph: - packages/spec/src/migrations/registry.ts — #4509 (unwarnable mapping / contextSelector keys) landed on main with a "Finally, ..." paragraph while this branch had one of its own. Both retirements are real and both belong in the v17 story, so both paragraphs are kept: the managedBy rename reworded to "Separately, ..." and placed first, with #4509 keeping the closing position. No content dropped from either side. conversionIds merged cleanly and carries both sets. - docs/protocol-upgrade-guide.md — generated from the registry above, so it was regenerated rather than hand-reconciled. Verified after the merge: 69/69 turbo test+typecheck tasks across the 8 affected packages, and all 17 spec check gates.
Fixes #3355
配套 UI 侧 PR:objectstack-ai/objectui#3214。两边需要一起合——
ManagedByBucket是本仓枚举的镜像。背景:v16 的加法式拆分留下了一个名不副实的桶
ADR-0103 在 v16 拆分了被重载的
managedBy: 'system',而且是加法式拆的:20 个引擎自有对象迁到了新的、显式的engine-owned,而 8 个 admin/user 可写的对象——RBAC 关联表(sys_user_position、sys_user_permission_set、sys_position_permission_set)、sys_user_preference、sys_approval_delegation,以及三张消息配置表——留在了system上。对一个不能破坏作者代码的 v16 来说这是对的做法,但它把枚举留在了一个幸存的值恰好在描述已经搬走的那一半的状态:
system挂在用户真正会写的对象上。这不是措辞洁癖。作者在
system和engine-owned之间做选择时,词汇表里没有任何可供判断的依据,于是任何善意读名字的人都会把这个桶重新重载一遍——尤其是模型作者,因为 "system table" 在其他所有代码库里读起来都是"引擎拥有它"。scope 校验(issue 是 v16 时期写的):正文那张 8 行表与当前 main 零漂移——8 个对象、归属包、
userActions声明全部一致。方案:
system→system-data,并从加载路径上退休system-data,不是platform-data(与既有platform相邻混淆),不是复用config(sys_user_preference是用户自有而非 admin-authored,且config抑制 CSV 导入)。platform,那是租户建模的)/data 归 admin 或用户(对比engine-owned,那里引擎两者都拥有)。create/edit/delete/import/exportCsv: true。因为 v16 已经把引擎侧抽干了,这次转换是一对一的机械改名,没有任何判断余地。
存量元数据是被转换的,不是被重新解释的
ADR-0087 D2 转换条目
object-managed-by-system-to-system-data,带 fixture,retiredFromLoadPath: true。放在normalizeStackInput的存活窗口条目会跑在枚举之前、静默吸收该值,作者(或模型)就会继续写这个改名本来要让他们忘掉的名字;存量sys_metadata行与os migrate meta --from 16才是includeRetired的接缝。import由 false 变 truesystem默认 LOCKED,8 个对象各自用userActions: { create: true, edit: true, delete: true }把写入再打开一遍。system-data默认 WRITABLE,这些块已删除;userActions今后只用来收窄。那 8 个
userActions块只重开了 create/edit/delete,所以 CSVimport在 v16 解析为 false;system-data的默认行让它变成 true。这意味着三张 RBAC 关联表在 UI 上多出 CSV 导入入口。这只是 affordance——CSV 导入写的每一行仍然经过DelegatedAdminGate/ RLS / 权限集裁决,没有绕过任何授权;风险在杠杆而非授权边界。enforcement 没有位移——由测试承担,不由论证承担
引擎写入守卫、
DelegatedAdminGate、RLS、权限集,全部基于解析后的 affordance 和调用主体裁决,从不基于桶名。system-data只是加入platform/config的行列,成为 fail-closed 守卫不覆盖的桶——可写的默认值没有什么可以"关闭"的。8 个对象此前通过守卫(靠userActions),现在依然通过(靠桶默认值)。对应的 pin:
plugin-security/service-messaging/platform-objects/plugin-approvals四个包,覆盖全部 8 个对象):v16 形态与 v17 形态解析出的 create/edit/delete/exportCsv 必须逐个相等;漏删或错删某个userActions会直接变红。/me/permissions钳制:同一份授权输入下,v16 形态与 v17 形态的allowEdit/Create/Delete必须完全一致;另加一条"无userActions的system-data成员不被钳制"。该函数直接读userActions而非解析后的 affordance,所以若继续钳制这个桶,会对引擎乐意写入的表报allowEdit: false——正是它要避免的假阴性,只是反了过来。ENGINE_OWNED_BUCKETS同时排除system与system-data;并 pin 了system-data在任何写操作下都不被拒绝。新增:误配的授权时拒绝
"审阅时肉眼可见"不是闸门。默认值翻转之后,把一个引擎自有对象误标进这个桶不再无害:它会凭空得到通用 CRUD affordance,而这个桶又没有守卫兜底。
ObjectSchema.create()现在拒绝managedBy: 'system-data'且解析后 affordance 不含任何 create/edit/delete 的对象——这是一个从声明本身即可计算、无需调用图分析、且没有诚实读法的矛盾。报错直接指向engine-owned/append-only。阈值刻意是"一个写入动词都没有",不是"任何收窄":
system-data+userActions: { create: false, delete: false }(只可编辑的配置表)是合法收窄,照常通过。位置在create()——ADR-0077 的授权面,与既有的warnGenericPasswordFields同侧;存量行经转换到达的是 1:1 改名后的可写值,让加载因已落盘的元数据失败会把授权缺陷变成事故。变更范围
packages/spec/src/data/object.zod.ts— 枚举改名 + 退休处方;CRUD_AFFORDANCE_DEFAULTS默认值翻转;新增assertSystemDataIsWritablepackages/spec/src/conversions/registry.ts— ADR-0087 转换条目 + fixturepackages/spec/src/migrations/registry.ts— protocol 17 step 叙述 +conversionIdspackages/plugins/plugin-security/src/system-write-guard.ts—ENGINE_OWNED_BUCKETS移除systempackages/plugins/plugin-hono-server/src/current-user-endpoints.ts—GUARDED_WRITE_BUCKETS同步userActionsdocs/adr/0103-*.md— v17 addendum 收官(D5 原文即把该改名记为 v17 目标,本 PR 完成它,不推翻任何既有决定)content/docs/data-modeling/objects.mdx— 桶表、engine-ownedvssystem-data段落、v16 升级 calloutspec-changes.json、protocol-upgrade-guide.md、references/data/object.mdxcontent/docs/releases/未触碰(AGENTS.md 守则),发布说明的输入是 changeset + ADR-0087 登记表。验证
spec 全部 17 个 check 门禁通过。
已 merge 当前
main(5966c2abe)并在 merge 后重跑上述全部。该 merge 解了两处冲突,均在 protocol 17 的叙述段落——#4509 与本 PR 各自追加了一段,两段都保留(本 PR 这段改为非收尾语气并前置),docs/protocol-upgrade-guide.md为生成物、重新生成而非手工调和。🤖 Generated with Claude Code
https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5