Skip to content

refactor(spec)!: 退役 HookContext.session.roles —— 声明过、被两条死分支读过、从未被生产 (#5050) - #5621

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5050-retire-session-roles
Aug 5, 2026
Merged

refactor(spec)!: 退役 HookContext.session.roles —— 声明过、被两条死分支读过、从未被生产 (#5050)#5621
os-zhuang merged 2 commits into
mainfrom
claude/issue-5050-retire-session-roles

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5050

前提复核(动手前对 origin/main 实测)

问题 结论 证据
键还在吗 packages/spec/src/data/hook.zod.ts:374 roles: z.array(z.string()).optional()
还有消费方吗 全仓 session.roles 命中只剩 #4839 留下的注释/pin/changeset,以及 spec 自己的夹具与技能文档
有生产方吗 零(hook 路径) buildSession()(packages/objectql/src/engine.ts:1479)逐字段构造 —— userId / organizationId / positions / accessToken / isSystem / actor / skip 标记 —— 没有 roles 写入点

跨仓按 #4895 的双方向做(#4865 的教训:退役前必须有阳性对照):

  • cloud:session.roles 零命中;同一轮反查阳性 —— 它的 hook 消费方确实在读 hookContext?.session?.userId(service-cloud/src/marketplace-visibility-plugin.ts:98control-plane-org-scope-plugin.ts:237-248)。即「grep 能找到东西,只是找不到这个键」。
  • objectui:零命中;反查阳性(roles 在该仓存在,但都是 /auth/meuser 载荷 —— app-shell/src/layout/AppHeader.tsx:457 等,另一张面,不受影响)。

结论:前提成立。

退役路线

HookContextSchema 刻意不是 .strict()(文件头注释写明理由:引擎给上下文加字段——如 #3712provenance——不应变成消费方的破坏性变更)。所以按 playbook §2:

ADR-0087 处置:语义迁移,不是 conversion(重点,请审这一条)

没有做 D2 conversion,是有意的:HookContext 是引擎每次操作现建的运行时上下文,从不落库 —— 没有任何 sys_metadata 行、example 或 template 能携带这个键,os migrate meta 无源可改。造一个没有对象的 conversion 只会让升级指南宣传一层不存在的覆盖。

按仓内既有判例走 SemanticMigration(MIGRATIONS_BY_MAJOR[17].semantic[]),与 openApi31(#4579)、activationEvents(#4657)、workflow 服务槽(#4451)同形:

  • 新条目 hook-context-session-roles-retired,surface: data.hookContext.session.roles
  • 处方因此仍然进 spec-changes.json、生成的升级指南、spec_changes MCP 工具
  • 墓碑的 guidance 里没有 os migrate meta 那句(playbook 约定:只有 conversion 真的改源码时才写)

闸门这边也自洽:build-schemas.ts 的 (b) 闸只走顶层键,session 是内联嵌套对象(快照里只有 data/HookContext:session 一行,没有 session.roles),所以它既不要求也不阻拦 —— 判据与闸门给出同一个答案。

四张 ratchet 零变化 —— 这是正常读数

按 playbook「先定路线再决定该期待什么读数」:本次是内联嵌套键收窄,def 还在、导出面不变,所以 api-surface / authorable-surface / api-surface-signatures / json-schema.manifest 全部字节不变(与 #4391 枚举值收窄同类,而非 #4834 整 def 删除)。check:authorable-surface 前后皆绿即为此。

authorable-surface.base.json 的变动是 gen:schema 的机械重锚(baseRev 指向本分支的 merge base),同 a9f32df / cdfbee2 等 spec PR 的既有行为。

台账

packages/spec/liveness/hook.json 治理的是 HookSchema(可授权元数据类型),HookContextSchema 不在 walk 里,session.roles 从来没有台账行 —— 所以既没有要留的墓碑行,也没有要删的孤儿行。已核对,无改动。

一处真实的坑:文档把墓碑宣传成 any

墓碑一开始留在原位(session 的第 4 个键),重生成后 content/docs/references/data/hook.mdx 变成:

session | { userId?: string; actor?: string; organizationId?: string; roles?: any; … }

z.never() 没有 JSON-Schema type,渲染器落到 prop.type || 'any';而内联 shape 摘要只印前 4 个键、放不下 [REMOVED] 处方。退役反而把这个键宣传成"随便写"的自由槽,正是 ADR-0033 陷阱对着文档的一面。

本 PR 的处理:把墓碑挪到 shape 底部,摘要因此只展示 4 个活键(userId / actor / organizationId / accessToken),并在源码注释里写明为什么,防止后人"整理"回去。两个真实通道(tsc + parse)完全不受影响。

⚠️ 这是绕开,不是修好,只对「墓碑不在前 4 位」的情况有效。渲染器缺陷本身已另开 #5606 —— 它现在就在伤 references/ui/theme.mdx:130({ base?: string; heading?: any; mono?: any },两个 #5021 的墓碑嵌套两层、整页没有任何一处出现它们的处方,描述列还是空的)。修它要全仓重生成 references,不该搭在本 PR 上。

消费半径扫描的收获(#5046 的教训:按规则被谁消费扫,不是按改了哪个包扫)

扫 HookContext 的全部导入方时发现 packages/runtime/src/action-execution.ts:694buildActionSession() 确实写了 roles: ec.positions,而且它的注释自称 "mirroring the hook ctx.session shape"。

这不证伪本次退役:那是 action body 的 ctx.session,另一个对象,裸 any,不经任何 schema,永远不会变成 HookContext(沙箱侧 ScriptContext.session?: unknown)。但它确实会让后来者拿着「我在 action 里明明读到了 session.roles」来推翻这里的零生产方结论 —— 这正是 #4865 的形状。所以:

测试与反向自证

pin 测试 5 条(packages/spec/src/data/hook.test.ts),方向在跑之前就先定好:还原 roles: z.array(z.string()).optional() 应当让 parse 断言转红、并让两条 @ts-expect-error 报 TS2578(#5478 之后 spec 测试层真的进 tsc,类型 pin 是活的)。实测:

还原后 vitest run src/data/hook.test.ts:

Tests  3 failed | 63 passed (66)
 FAIL  > session.roles retirement (#5050) > REJECTS an authored `roles`, with the prescription in the message
 FAIL  > session.roles retirement (#5050) > names the live vocabulary rather than only refusing
 FAIL  > session.roles retirement (#5050) > fails tsc at the producer — the channel that outranks the parse here

还原后 tsc --noEmit --project tsconfig.test.json:

src/data/hook.test.ts(952,9): error TS2578: Unused '@ts-expect-error' directive.
src/data/hook.test.ts(964,7): error TS2578: Unused '@ts-expect-error' directive.

恢复墓碑后两者皆绿。预测方向 = 实测方向。

其中一条 pin 值得单独说:「墓碑 ≠ 变 strict」 —— 断言一个未知键仍然被静默 strip,只有退役键才响。这条是把「为什么不能直接删」和「为什么不能顺手加 .strict()」两个判断一起钉住。

正式验证(worktree 内,统一走 flock /tmp/os-heavy-verify.lock):

pnpm --filter @objectstack/spec test        → Test Files 314 passed (314) / Tests 8015 passed (8015)
pnpm --filter @objectstack/spec typecheck   → tsc --noEmit 通过;check:test-typecheck: OK
check:generated                             → ✓ All 10 generated artifacts are up to date.
check:liveness / check:empty-state / check:authorable-surface / check:api-surface /
check:spec-changes / check:upgrade-guide / check:skill-refs / check:skill-docs /
check:skill-examples                        → 9/9 PASS
node scripts/check-nul-bytes.mjs            → OK(另对本次全部改动文件自查 0x00-0x1f,零命中)

改了什么

  • packages/spec/src/data/hook.zod.ts —— 墓碑 + 就地注释(含"墓碑放底部"的理由与 [runtime] action body 的 ctx.session 仍在生产 roles(值是 ec.positions)—— 自称「mirroring hook ctx.session」,而 hook 侧该键已按 ADR-0049 退役 #5613 邻居声明)
  • packages/spec/src/migrations/registry.ts —— 语义迁移条目 + step17 rationale 段落
  • packages/spec/src/data/hook.test.ts —— 2 处夹具重判 + 5 条 pin
  • skills/objectstack-data/references/data-hooks.md —— 3 处:字段表、类型清单、以及那个用死键做脱敏判断的示例(原写法 isAdmin 恒为 undefined,改为按 isSystem 豁免,并写明按角色豁免应当落在字段级权限)
  • packages/plugins/plugin-approvals/src/admin-exemption-retired.test.ts —— 仅注释:原文说"spec 现在声明 roles",退役后改为过去式(该文件的 pin 与仍然拼 roles: ['admin'] 的夹具故意保留,它们证明这个拼法在运行时同样什么都不授予)
  • 生成物:spec-changes.jsondocs/protocol-upgrade-guide.mdcontent/docs/references/data/hook.mdxauthorable-surface.base.json
  • changeset:@objectstack/spec major

顺带登记的三个 issue(Prime Directive #10,均未实现、未指派)

🤖 Generated with Claude Code

https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D


Generated by Claude Code

… two dead branches, never produced (#5050)

`session.roles` on the runtime hook context had neither end: declared in
`data/hook.zod.ts`, read only by the two plugin-approvals admin exemptions
deleted in #4839 (PR #5049), and never written by `buildSession()` or anything
else feeding a HookContext. ADR-0049 enforce-or-remove disposition: REMOVE.

- tombstoned with `retiredKey()` (HookContextSchema is deliberately not
  `.strict()`, so a plain delete would strip the key silently — #3733/ADR-0104)
- placed BELOW the live keys: the reference generator renders a `z.never()` as
  `any` inside an inline shape summary, so in its original 4th position it made
  `references/data/hook.mdx` advertise `roles?: any` (renderer gap filed #5606)
- ADR-0087: a SemanticMigration (`hook-context-session-roles-retired`), NOT a
  D2 conversion — a HookContext is built per operation and never stored, so no
  source exists to rewrite (the `openApi31` / `activationEvents` shape)
- pins both channels: the parse prescription and two `@ts-expect-error`
  directives, live since #5286/#5478 put the test layer in front of tsc
- skills/objectstack-data hook reference no longer teaches the dead key

Cross-repo consumer check ran in both directions (cloud/objectui, #4895's
discipline). The action body's `ctx.session` is a different, untyped object
that does carry `roles` — named explicitly here and filed as #5613 so it is
not mistaken for a producer of this key.

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

vercel Bot commented Aug 5, 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 5, 2026 9:26pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/m labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

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.

Copy link
Copy Markdown
Contributor Author

PM 预记(session_018fxLGQdatPbBUvCgiVxg6D):本 PR 的 ESLint job 将因 #5604(main 侧 check:engine-double-contract 断裂,#5584 遗留,与本 diff 无关)而红——验收时不计入本单质量账,#5604 修复落地后合 main 重跑。

「ADR-0087 处置:语义迁移而非 conversion」一条 PM 初审认可(否决窗口开放):HookContext 是运行时现建、从不落库,conversion 无源可改,MIGRATIONS_BY_MAJOR[17].semantic[]openApi31/activationEvents/workflow 槽三判例同形,且 build-schemas (b) 闸(仅顶层键)给出同一答案——判据与闸门自洽。维护者如要求 conversion 形式,回一句即改。

changeset 为 major(退役),与 v17 pre 窗口内既有退役单(如 #5293)同待遇,check-changeset-no-major 结果以 CI 为准。


Generated by Claude Code

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 protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[spec] 退役 HookContext session.roles —— #4839 双删后零消费方零生产方(ADR-0049)

2 participants