Skip to content

feat(spec): HookContext.session 补声明 positions / preserveAudit(#5605) - #5722

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5605-session-positions-declare
Aug 6, 2026
Merged

feat(spec): HookContext.session 补声明 positions / preserveAudit(#5605)#5722
os-zhuang merged 1 commit into
mainfrom
claude/issue-5605-session-positions-declare

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #5605

按维护者 2026-08-06 的裁定 A:两个键都补进 HookContextSchema.session,并用 .describe() 把边界钉死。

这是 #5050 的镜像,不是它的另一半措辞

session.rolesdeclared-never-produced —— 声明了、没人产,所以 #5050 把它退役成墓碑。这两个键是 produced-never-declared —— 引擎在产、消费方在读、文档在教,契约里没有,所以补声明。同一个 session 块、方向相反、修法相反,这就是 #5605 单独立单的理由。

前提复核(对 origin/main 逐条核实,四条全部成立)

  • 生产方:packages/objectql/src/engine.tsbuildSession() —— :1494 写 positions: execCtx.positions,:1518 条件写 preserveAudit
  • 消费方:packages/objectql/src/plugin.ts:782 const preserveAudit = session?.preserveAudit === true;(该处 session 形参是 any,所以类型层看不见这条读)。
  • 文档在教:content/docs/kernel/runtime-services/examples.mdx:37sharing-service.mdx:67,都是 positions: ctx.session?.positions
  • 契约没有:补之前 session 只有 userId / actor / organizationId / accessToken / isSystem / skipTriggers / skipAutomations + roles 墓碑。

⚠️ issue 指出该文件近期被 #5621 / #5668 连改两次 —— 已基于合并后的当前 origin/main 重新定位,行号与结构均以本分支基线为准。

先证红(两条通道,都是改动前在 origin/main 上实测)

parse 通道 —— HookContextSchema 刻意非 strict(文件头有说明:它是引擎交给 handler 的运行时形状,strict 会让引擎侧任何一次内部增强变成消费方的破坏性变更)。代价就是未声明的键被静默 strip:

输入 session: { userId: 'u1', positions: ['sales_manager'], preserveAudit: true }
parse 输出   : {"userId":"u1"}
has positions     : false
has preserveAudit : false

生成的 reference 页恰恰以 HookContextSchema.parse(data) 作为消费示例 —— 照文档消费,调用方的任职信息就掉在地上。

tsc 通道 —— 按 content/docs/automation/index.mdx 的写法把 handler 标成 (ctx: HookContext):

error TS2339: Property 'positions' does not exist on type '{ userId?: string | undefined; ... }'.
error TS2339: Property 'preserveAudit' does not exist on type '{ userId?: string | undefined; ... }'.

两页 runtime-services 示例之所以看不出来,是因为它们把 ctx 标成了 any照文档抄 + 照文档标类型 = 编译失败

改了什么

1. 两个键的声明(packages/spec/src/data/hook.zod.ts),都放在 roles 墓碑上方 —— 墓碑注释自己写明的排序约束(见下文「渲染器陷阱」)。

  • positions: z.array(z.string()).optional()
  • preserveAudit: z.boolean().optional()

2. .describe() 按裁定措辞钉边界。 这不是装饰,是这单需要维护者拍板的全部原因:positions可读上下文,不是授权输入。hook 可以读它去描述调用方(转给 sharing service 当评估上下文、调整消息、打日志),但不得用它自己做访问判断 —— 权限由 security service 在 ExecutionContext 上裁决(能力授予 permissions、任职 positions、以及派生的 posture,ADR-0095 D3)。一个用这个数组重新决定访问的 hook,是在一个拿不到授权模型的地方重判一件已经判过的事 —— 结构上正是 roles 墓碑要防的那个错误,只是换了一代词汇。措辞纪律与 roles 退役处方一致,针对的是下一个作者(尤其 AI)。

preserveAudit 的 describe 写它真实的消费语义:#3493 的历史导入保留标记,server-set、opt-in、普通写不带,由内置审计 hook 读取,用来保留调用方提交的 updated_at / updated_by 而不是盖上导入时刻 —— 是一条盖戳策略,同样不是授权输入。

3. Pin(packages/spec/src/data/hook.test.ts,新增一个 describe 块),与 #5050 的墓碑块并列,两块互为镜像正好把这张契约表的两个漂移方向都钉住。

反向验证(方向先预测,再运行)

预测:删掉任一声明,两条通道都应转红。实测:

  • parse 通道:vitest run src/data/hook.test.ts4 failed | 68 passed(两条 preserve、buildSession() 形状、describe 边界)。
  • tsc 通道:check:test-typechecksrc/data/hook.test.ts: 9 type error(s) in a file the ledger does not cover。该文件不在 test-typecheck-debt.json 里,所以这是硬门,不是被 ledger 兜住的软红。

一条诚实说明:新增的 5 条断言里,keeps both OPTIONAL 在反向验证下仍然绿。它断言的是「缺席」,而键被 strip 之后同样缺席 —— 它是伴随断言,不是 pin(少写 .optional() 会让它转红,这是它留下的理由)。测试注释里已如实写明「不要把它当作声明存在的证据」,以免下一个读者把一条因空而绿的断言读成覆盖。这里没有用 @ts-expect-error:本单要证的事实是「文档教的代码编译」,所以类型 pin 是一条正向标注读,回退时的失败形态是硬类型错误,而不是 TS2578 未使用指令。

生成物与渲染器陷阱(#5606)

roles 墓碑注释写明:生成的 reference 把内联对象渲染成前四个声明键加省略号,而 z.never() 没有 JSON-Schema type,会印成 any —— 所以墓碑必须待在形状底部,否则 references/data/hook.mdx 会开始宣传 roles?: any。新键因此加在墓碑上方,落在第 8/9 位。

结果:content/docs/references/data/hook.mdx 的 session 行一字未变,仍是 { userId?: string; actor?: string; organizationId?: string; accessToken?: string; … }。已两路确认 —— check:docs 绿(文件无需重新生成),并逐行目视核对生成页。

pnpm --filter @objectstack/spec check:generated:10/10 全绿(api-surface 读的是 built dist,必须先 build 才有效,已 build 后复跑)。

一处刻意未提交:跑生成器时 authorable-surface.base.json 被重锚到本分支的 merge base,带进了别人落的 api/Discovery:scoping 两个键 + baseRev。那不是本次改动的产物,已回退 —— 该 gate 复跑后自述「trails the merge base by 2 key(s) — expected right after a surface change lands」,是信息性提示,不红。删除类 ratchet 的锚点不该搭在无关 PR 里顺带前移。

验证

命令 结果
pnpm --filter @objectstack/spec test 317 passed (317) / 8088 passed (8088);合入 main 后复跑 8090 passed (8090)
pnpm --filter @objectstack/spec typecheck 绿(tsc --noEmit + check:test-typecheck OK),合入后复跑仍绿
pnpm --filter @objectstack/spec check:generated All 10 generated artifacts are up to date,合入后复跑仍 10/10
pnpm --filter @objectstack/objectql typecheck 绿
pnpm --filter @objectstack/objectql test 121 passed (121) / 1970 passed (1970)
node scripts/check-nul-bytes.mjs OK(另做控制字符自扫,无命中)
远端 CI(本 PR head) 24 项 check runs 全部 success/skipped,无 failure

objectql 一并跑了,因为它是生产方:buildSession() 的返回值现在被真正声明的类型覆盖(此前 as HookContext['session'] 把两个键遮了过去)。

影响面

纯增量:两个键都 optional,形状仍非 strict,现存 context / handler / 存量元数据都不受影响。HookContext 按操作构造、从不落库,没有任何东西需要迁移。changeset 记 @objectstack/spec minor。

一处如实说明:本地有两个未推上来的提交

本分支在本地还有两个提交没能推送 —— push 被拒,原因是本 PR 已被(非本 session 的动作)标记 ready 并加入 merge queue,排队中的分支不允许更新。两个提交都不含内容变更:

  1. 一个 origin/main 的合并提交(AGENTS.md §10 的合并后复验;merge queue 本身就是把 PR 作为「合并到当前 main 的结果」来构建的,所以这个合并对正确性是冗余的);
  2. 一处纯注释措辞订正 —— 上面「诚实说明」那段的行内注释原写作「四条兄弟断言」,准确说法是「五条兄弟里的四条,第五条是 tsc 通道的 pin,vitest 判不了它」。

即入队的 head 与我的最终状态在本 PR 涉及的文件上只差这 6 行注释。留此说明以免后来者对不上分支状态;若需要,订正可作为后续小 PR。

附带发现(未在本 PR 修)

#5720 —— 同两页文档还在教 ctx.services?.sharing?.canEdit(...),而 hook 上下文(引擎构造的 handler ctx 与 body-runner 的沙箱 ctx)都没有 services 这个键(它是 action ctx 的词汇)。可选链短路成 undefined,if (!ok) throw 于是无条件抛出 —— 照抄这个示例的 hook 会拒掉该对象上的每一次写入。同一个 ctx: any 标注同时架空了那段 {/* os:check */} 门禁,也正是它掩盖了本单的 TS2339。修法取决于「hook 访问 kernel service 的受支持通道是什么」这个待定问题,故按 Prime Directive #10 单独立单,未在本 PR 顺手改文档。

…5605)

Two keys the engine produces, consumers read and the docs teach were missing
from HookContextSchema.session. Declared per the maintainer ruling (A) on
#5605 — the mirror of the #5050 `session.roles` retirement: that key was
declared-never-produced (removed), these two are produced-never-declared
(added).

Because the shape is deliberately non-strict, the omission was silent:
`HookContextSchema.parse(ctx)` — the call the generated reference documents —
stripped both keys, and a handler typed `(ctx: HookContext)` as the automation
docs teach hit TS2339 on `ctx.session?.positions`. The two runtime-services
pages that teach that exact read compile only because they annotate `ctx` as
`any`.

`positions` carries the ruling's boundary wording in its `.describe()`:
readable context for hooks, never an authorization input — privilege is judged
by the security service on the ExecutionContext (permissions / positions /
derived posture), never by testing this array in a hook. Same discipline as the
`roles` tombstone, which the new keys sit ABOVE so the generated reference's
four-key inline summary does not surface the tombstone as `roles?: any`.

`preserveAudit` documents its real consumer semantics: the #3493
historical-import flag read by the built-in audit hook to keep a
caller-supplied updated_at/updated_by instead of stamping the import instant.

Both optional and additive; contexts are built per operation and never stored,
so there is nothing to migrate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@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 2:07am

Request Review

@github-actions github-actions Bot added the size/m label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 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.

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

2 participants