Repository navigation
Closed
Labels
domain:specDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpm:queueReady for the PM dispatch loopReady for the PM dispatch looptarget:v17Release blocker for v17 — on the board until fixed, dropped as no longer valid, or accepted for GARelease blocker for v17 — on the board until fixed, dropped as no longer valid, or accepted for GA
Description
事实(对 origin/main 核实)
packages/spec/src/data/hook.zod.ts 里,hook 的主数据通道是这样声明的:
/**
* Cross-Object API
* …
* Usage in hooks:
* const users = ctx.api.object('user');
* const admin = await users.findOne({ where: { role: 'admin' } });
*/
api: z.unknown().optional().describe('Cross-object data access (ScopedContext)'),
于是 HookContext['api'] 推断为 unknown,JSDoc 里那两行示例本身编译不过。实测(用 check:skill-examples 同一套 tsconfig,对已构建的 @objectstack/spec 声明编译):
error TS18046: 'ctx.api' is of type 'unknown'. // ctx.api.object('x')
为什么是缺陷而不是风格问题
- 整个语料库都在教这个写法。
skills/objectstack-data/references/data-hooks.md:732-745(handler: async (ctx: HookContext) => { const users = ctx.api?.object('user'); … })、content/docs/automation/hooks.mdx:173-224、content/docs/api/error-handling-server.mdx:289/382、content/docs/automation/hook-bodies.mdx的能力表 …… 都是(ctx: HookContext)+ctx.api.object(…)。这些块都没有os:check标记,所以从没被编译过 —— 一旦标记就会全红。也就是说:AI 与人照抄的首选写法,在类型层面是不成立的,只是没有任何门看见。 - 它把「消费者兜底」写成了唯一出路。 [docs] runtime-services 的 hook 示例在教
ctx.services—— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720 的 PR docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的ctx.services(#5720) #5938 需要一个真编译的 hook 示例,只能在示例里写const api = ctx.api as CrossObjectApi(自建局部结构类型)。这正是 contract-first 反对的方向:每个消费者各自 cast 一遍,cast 的形状彼此不一致,而真值(ObjectQL 的ScopedContext:object(name)→ repo,transaction(cb))在契约里一个字都没有。 - 对照组说明这不是「Zod 表达不了就只能 unknown」。 同仓
packages/spec/src/contracts/已经用普通 TS interface 声明了一堆运行时服务面(IDataEngine、ISharingService、IObjectQLEngine…),transaction/ql这些同样"引擎侧对象"的键也是z.unknown(),但它们不是文档教读者天天调的东西 ——api是。
待定(不预设结论,交维护者/分诊)
- A. 在
packages/spec/src/contracts/声明作用域跨对象 API 的接口(如IScopedContext:object(name)返回一个 repo 接口,transaction(cb)),HookContext.api的 TS 类型指向它(Zod 侧可继续z.custom/z.unknown,运行时不校验活对象),让ctx.api.object('x').findOne(…)直接编译过。成本:要把 repo 的方法面(find/findOne/count/aggregate/insert/update/upsert/delete)定成公共契约,并与ScopedContext实现对齐(此后二者漂移会被check:api-surface之类的门看见 —— 这正是收益)。 - B. 什么都不改,把语料库改成显式窄化(每处示例自带 cast)。成本:教 AI 写 cast,且 cast 形状无人校验;
api的真实面继续只存在于 objectql 的实现里。 - C. 折中:只声明
object(name)与transaction(cb)两个成员(文档实际教的全集),其余留待有消费者时再加 —— 与仓内「有证据才声明」的惯例一致(IDataEngine的#4251注释就是这个论证)。
我倾向 C:它让文档现在教的写法真的成立(收益立刻兑现),声明面又不超过已被消费的部分;A 的完整 repo 契约可以在有第二个消费者时按同一论证增补。但这是公共契约拍板,故不自行实现。
关联
- [docs] runtime-services 的 hook 示例在教
ctx.services—— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720 / PR docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的ctx.services(#5720) #5938(hook 文档改教ctx.api;因该 PR 派发面packages/spec/**零改动,故此项单独记录) - [spec]
check:skill-examples应拒绝 os:check 块内把入参标any—— 标记了却零覆盖的空门 #5943(check:skill-examples禁 os:check 块内入参标any)—— 若本单按 A/C 修好,那些语料块才可能真的进os:check;否则它们进门就红。
未加标签、未认领,交 PM 分诊定级。
Metadata
Metadata
Assignees
Labels
domain:specDomain lane — seat card indexed by label:pm:seatDomain lane — seat card indexed by label:pm:seatpm:queueReady for the PM dispatch loopReady for the PM dispatch looptarget:v17Release blocker for v17 — on the board until fixed, dropped as no longer valid, or accepted for GARelease blocker for v17 — on the board until fixed, dropped as no longer valid, or accepted for GA
Activity
claude commentedon Aug 6, 2026
分诊:
needs-user-decision+domain:spec(路由标签,方向未定)。object()+transaction()vs 不声明、语料库各自 cast)。正文作者倾向 C 且明说「公共契约拍板,故不自行实现」—— 这正是决策箱的形状,分诊不代拍。packages/spec/src/data/hook.zod.ts(api: z.unknown()及其 JSDoc)+ A/C 需新增packages/spec/src/contracts/(与IDataEngine/IObjectQLEngine同一层)。两处都在packages/spec⇒domain:spec座位。check:skill-examples应拒绝 os:check 块内把入参标any—— 标记了却零覆盖的空门 #5943(check:skill-examples禁 os:check 块内any,本轮已入队 spec-tooling)有真实先后关系 —— 语料库里那些ctx.api.object(…)块只有在本单按 A/C 落地后才可能真进os:check,否则进门就红。⛔ 未在此写Blocked-by::[spec]check:skill-examples应拒绝 os:check 块内把入参标any—— 标记了却零覆盖的空门 #5943 是门禁本身,不依赖本单;依赖只发生在「把那些块补标 os:check」的后续动作上,由承接方在各自单上处理。HookContext.api/ctx.api/ScopedContext仅命中本单与 [spec]check:skill-examples应拒绝 os:check 块内把入参标any—— 标记了却零覆盖的空门 #5943 / [docs] runtime-services 的 hook 示例在教ctx.services—— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720(同批派生,各有独立落点),无重复入口。origin/main上hook.zod.ts最近改动为 [#5704 批次2] B 类替身改名 makeMemoryDriver→makeStubDriver(objectql 22 + runtime 2)+ Q2-B 保留注释 #5784→test(objectql,runtime): B 类替身改名 makeMemoryDriver→makeStubDriver(26 文件)+ Q2-B 保留注释 #5797(测试替身改名),api: z.unknown()现状未变;[spec] HookContext.session 少声明了positions/preserveAudit—— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605(session 键补声明)是被同一个any掩盖的另一半,不与本单重叠。前提成立。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
claude commentedon Aug 7, 2026
Ruling (maintainer, 2026-08-07): option C — declare the minimum
IScopedContextthat the corpus actually teaches.HookContext.apistops beingz.unknown()and gains an interface coveringobject(name)(returning the repo face the docs actually use — at minimumfindOne/find) andtransaction(cb). Everything else waits for a second consumer, which is the same "declare what has evidence" disciplineIDataEnginewas argued into being under (#4251).Option B (teach explicit casts everywhere) is the wrong direction outright: it teaches AI authors to cast and leaves the shape unchecked by anyone. Option A (the full eight-method contract) can grow later under the same rule.
This is a v17 first-hour-experience blocker: a new user copying the documented first hook currently gets TS18046, and the JSDoc example in the spec file itself does not compile. Landing C also makes #5943's gate able to bring those corpus blocks under
os:check.Implementation note: align the declared face with objectql's actual
ScopedContextand re-runcheck:api-surface.Operator: PM session
session_01GcjbQLUQKysMU9uXB34iyv; maintainer ruling 2026-08-07 (decision-inbox round 2). Veto window open — comment or reopen to overturn.Generated by Claude Code
fix(tooling): derive check-engine-double-contract's own corpus figure…
fix(tooling): read a defaulted mock initializer, name the inheritance…
fix(engine): `ObjectRepository` declares the `findOne` / `update` sha…
fix(spec): make the hook face's structurally-referenced types nameabl…
fix(spec): take the two names the finding owes, and decline the third…
docs(spec): correct the declined-leg reason — the churn claim was cac…
feat(spec): publish the typed hook `ctx.api` face — HookApi / HookObj…
test(spec): contracts and conversions test titles state each cited de…
test(spec): the fourth data/ file group's test titles state each cite…