Skip to content

docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的 ctx.services (#5720) - #5938

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5720-hook-ctx-api-docs
Aug 6, 2026
Merged

docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的 ctx.services (#5720)#5938
os-zhuang merged 1 commit into
mainfrom
claude/issue-5720-hook-ctx-api-docs

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #5720

按维护者 2026-08-06 批复裁「改教 ctx.api」执行:docs-only,⛔ 不新增「hook ctx 注入 services」公共契约 —— packages/spec/**packages/objectql/** 零改动。

为什么是缺陷

hook 上下文由引擎逐键构造(object / event / input / session / provenance / user / api / transaction / ql,engine.ts 五处构造点同构),沙箱 buildHookSandboxContext 同样不产 —— 从来没有 services。照抄旧示例写 beforeUpdate 授权检查,ctx.services?.sharing?.canEdit(…) 因可选链短路成 undefined,if (!ok) throw new Error('PERMISSION_DENIED') 于是无条件抛出:该对象上所有更新一律 403,而失败方向伪装成"正常拒绝",极难归因到文档。

services.* 本身不是幻觉,是这几页记录的契约签名面(索引页 binding note 已写明:开放框架不往 hook ctx 注入 services,托管运行时才提供该绑定)。缺陷在于把它当成 hook 能拿到的东西来教。

改了什么

content/docs/kernel/runtime-services/examples.mdx

  • 页首新增一张「各运行面真实拿到的数据通道」表(hook → ctx.api;flow script 函数 → 无;plugin → 插件上下文),并点名 ctx.services 的短路后果。
  • 第 1 节(旧:flow custom node 用 ctx.services.data)改写为真实通道:声明式 get_record 节点读行 → script 节点把变量映射进注册函数的 inputs → 函数返回值由后续声明式节点落库。
  • 第 2 节(旧:hook 手查 ctx.services?.sharing?.canEdit)改为 hook 的真实数据通道 ctx.api.object(…);示例语义(写前校验 + 拒绝路径)保留,但换成引擎无法代劳的业务规则(合同金额 vs 客户信用额度)。共享强制由 plugin-sharing 的引擎中间件按动词自动执行(update 走 canEdit、delete 走 canDelete,拒绝抛 FORBIDDEN),hook 手查是冗余教学,故删。
  • 两块入参 ctx: any(ctx: HookContext)([spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 落地后 session.positions 已可正常标类型)。

content/docs/kernel/runtime-services/sharing-service.mdx

  • 新增「Enforcement is automatic — do not re-check it in a hook」一节:中间件按动词自动执行 + hook 无 services 键 + hook 自己的通道是 ctx.api
  • Example 改为「由持有该服务的代码调用」(契约类型 ISharingService,来自 @objectstack/spec/contracts),并首次给该块补 os:check 标记 —— 本页自称 canonical source 是那个契约文件,现在真的被钉住了。

与 issue / 裁定子断言不符的两处(按实测办)

  1. examples.mdx 第 1 节不是「同病同修 ctx.api。实测它是 flow script 节点调用的注册函数:按契约是纯函数(handlerContract: 'pure',A script node's function is contractually pure, but nothing enforces it — a function that writes makes its run under-report #4396),运行时只交 input / variables / automation / logger,没有任何数据句柄 —— 既没有 services,也没有 ctx.api(skills/objectstack-automation/SKILL.md 原话:hooks get ctx.api; flow functions don't)。所以第 1 节不能改成 ctx.api,只能改成"读在声明式节点、算在纯函数"。旧示例是双重失真:键不存在,且它教的行为本身违反纯函数契约。
  2. sharing-service.mdx 的 Example 不是 action 面。实测它教的是 hook:注释自称 "The hook exposes the caller's org as organizationId",读的是 ctx.input.id / ctx.session(action ctx 是 params / record / recordId)。故按 hook 处置,不是"仅标注语境"。

验证

反向验证(方向先定后跑:预期红) —— 把两处函数体的 any 换成如实的 HookContext 后编译,两处都报同一条:

old1.ts(5,24): error TS2339: Property 'services' does not exist on type '{ object: string; event: …; input: Record< string, unknown >; … 8 more …; }'
old2.ts(5,39): error TS2339: Property 'services' does not exist on type '{ … }'

即:键确实不存在,而 check:skill-examples 此前是绿的 —— ctx: any 让块内每一次属性访问都不被检查,os:check 标记成了空门(issue 正文第 2 点)。

新示例不止过类型,还真解析/真跑:

  • defineFlow 实际 parse 通过(flow parsed ok: sales_order_roll_up_totals 5 nodes);
  • defineHook 实际 parse 通过,并用手搭 ctx 跑了两条路径:超限 → rejected as expected: VALIDATION_FAILED: contract amount exceeds the account credit limit;未超限 → within limit: passed (expected)。旧示例是"恒抛",新示例只在业务规则被违反时抛。

门禁(全绿)

结果
check:skill-examples ✅ 207 prose examples type-check —— 206 → 207(sharing-service 新增 1 块);runtime-services 三块从"标记了但零覆盖"变为真检查
check:doc-authoring ✓ 362 files clean
check:nul-bytes OK (scanned 5723 tracked text file(s))
check:docs-audit-scope ✓ scope in sync (178 hand-written docs),releases 只读未动
fumadocs MDX 生成 通过(新增的 Callout 内嵌表格可解析)

docs-only,不发布任何包 ⇒ 无 changeset,已加 skip-changeset 标签。

分开交付(不在本 PR)


🤖 Generated with Claude Code

https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE


Generated by Claude Code

…s` (#5720)

hook 上下文由引擎逐键构造(object/event/input/session/provenance/user/api/
transaction/ql),从来没有 `services` 键;`services.*` 是本页记录的**契约签名面**
(见 runtime-services 索引页的 binding note),托管运行时才注入。照抄旧示例的
`beforeUpdate` 因可选链短路成 `undefined`,`if (!ok) throw` 会无条件拒掉每一次
写入,失败方向还伪装成"正常拒绝"。

- examples.mdx 第 1 节:实测 flow `script` 函数按契约是纯函数
  (`handlerContract: 'pure'`),运行时只交 input/variables/automation/logger,
  **没有任何数据句柄**——既无 `services` 也无 `ctx.api`。故改写为真实通道:
  声明式 `get_record` 读行、`script` 节点把变量映射进函数 `inputs`、函数返回值
  由后续声明式节点落库。整块过 `defineFlow` + `defineStack` 真实解析。
- examples.mdx 第 2 节:改为 hook 的真实数据通道 `ctx.api`,示例语义(写前校验 +
  拒绝路径)保留,但换成引擎无法代劳的**业务**规则;共享强制由 plugin-sharing 的
  引擎中间件按动词自动执行(update → canEdit,delete → canDelete,拒绝抛
  FORBIDDEN),hook 手查是冗余教学,故删除。
- 两块示例入参从 `ctx: any` 改 `(ctx: HookContext)`:`any` 让 `{/* os:check */}`
  变成空门(块内每次属性访问都不被检查)。反向验证:把旧函数体按 HookContext
  如实标注后,两处 `ctx.services` 均报 TS2339 —— 键确实不存在,而门此前是绿的。
- sharing-service.mdx:裸 `services.sharing` 的 Example 实测教的是 hook 语境
  (注释自称 "The hook exposes …"、读 ctx.input/ctx.session),不是 action 面。
  改为"由持有该服务的代码调用"(契约类型 ISharingService),并新增一节写明强制
  自动执行、hook 不得复查;该块补 os:check 标记,首次真正把本页签名钉在契约上。

门禁:check:skill-examples 206 → 207 块全绿(runtime-services 三块从
"标记了但零覆盖"变为真检查);check:doc-authoring 362 文件干净;
check:nul-bytes、check:docs-audit-scope 均绿。

Closes #5720

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
@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:38pm

Request Review

Copy link
Copy Markdown
Contributor Author

分开交付的三件已落地(均未认领、按 PD #10 不夹带进本 PR):

标签回读(bots 落定后):documentationsize/mskip-changeset —— 并集写入,bots 的两个标签未被覆盖。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 12:56
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 99c29f4 Aug 6, 2026
25 of 26 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5720-hook-ctx-api-docs branch August 6, 2026 13:00
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 skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[docs] runtime-services 的 hook 示例在教 ctx.services —— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入

1 participant