Skip to content

fix(objectql): hook 层用 Logger 契约形状写诊断,不再自带方言 (#5637) - #5780

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5637-hook-logger-contract-shape
Aug 6, 2026
Merged

fix(objectql): hook 层用 Logger 契约形状写诊断,不再自带方言 (#5637)#5780
os-zhuang merged 2 commits into
mainfrom
claude/issue-5637-hook-logger-contract-shape

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5637

按分诊/PM 一致裁决的方向 1执行:packages/objectql 的 hook 层不再自带 logger 方言,改用 @objectstack/spec/contracts 的契约形状(PD #12)。⛔ 未改动 spec 契约(方向 2 未采纳)。

改了什么

1. 两处本地 logger 形状 → 契约的 Pick

hook-wrappers.ts / hook-binder.ts 各自手写的四方法形状删除,改为一个从契约派生的类型别名:

import type { Logger } from '@objectstack/spec/contracts';

export type HookDiagnosticsLogger = Pick< Logger, 'debug' | 'info' | 'warn' | 'error' >;

Pick 而不是整个 Logger,是因为这一层只调这四个级别(「最小可用面」);完整的 Logger 无改动即满足它,而生产上注入的两个 logger(ctx.logger / engine.logger)正是完整契约实现。hook-binder 直接复用 hook-wrappers 导出的同一个类型 —— binder 拿到的 logger 原样转交 wrapDeclarativeHook,两者的形状必须同源,不能各写各的。

2. 四个调用点改为契约参数序 error(msg, undefined, { … })

issue 正文点名三个,实际是四个 —— hook-wrappers.ts 的 condition 编译失败诊断是第四个,同缺陷同文件;改类型之后 tsc 会直接把它顶红(见下方反向验证),所以它不是扩大范围,而是这次修复的必然一环:

文件 诊断
hook-wrappers.ts [hook] condition formula failed to compile; …(issue 未列)
hook-wrappers.ts [hook] handler failed (onError=log; suppressing)
hook-wrappers.ts [hook] async handler error (fire-and-forget)
hook-binder.ts [hook-binder] failed to bind hook

第二参一律留 undefined:四个点手头的值分别是 CelFault({ kind, message },不是 Error)和 catch 绑定(类型 unknown / any,hook handler 可以 throw 任何东西),没有一个在静态上是真正的 Error。它们的 message 本来就在 meta 里,原样保留,所以这次改动不增删任何诊断字段。把 catch 值提升进 Error 位会把每个宿主渲染出来的 error 从字符串换成 { message, stack } 对象 —— 那是另一个决定,不搭这趟车。

3. debug / info / warn 与契约签名完全一致((message, meta?)),未改动。

兼容性

ObjectLogger 是今天所有宿主实际拿到的实现,自 #5575 起三种形状都认((msg, Error) / (msg, meta) / (msg, undefined, meta)),所以第二参 undefined 渲染出的记录与改动前逐字段相同 —— 对现有部署零行为变化。真正变的是契约的另外两个实现(@objectstack/observabilityConsoleLogger / JsonLogger):它们此前会把 meta 整块吞掉(落进 error 位 → error.message / error.stackundefined,meta 本身 undefined),现在如实记录。

导出类型 BindHooksOptions / WrapDeclarativeOptionslogger 字段收窄了,但全仓库(含 examples/apps)除 objectql 自身外没有第二个 bindHooksToEngine / wrapDeclarativeHook 调用点,包内既有测试传的 { debug, info, warn, error } 字面量也仍然满足新类型 —— 全量 typecheck + 全量测试可证。

测试

新增 packages/objectql/src/hook-logger-contract-shape.test.ts(5 例)。关键在于用一个忠于契约的 logger stub(三个参数分开记录、不做形状分派)去审这些调用点 —— 正是 ObjectLogger 的那份宽容让旧缺陷看不见,所以复刻宽容的 stub 什么也测不出来。

  • 4 例覆盖四个调用点:断言 error 位为空、meta 完整落在第三参(hook / object / event / error 等字段逐一 toMatchObject)。
  • 第 5 例是兼容性钉子:真实 ObjectLogger(format: 'json',捕获 stderr)必须照常输出全部字段,证明「第二参 undefined 无害」。
✓ onError: 'log' — the suppressed failure keeps its hook/object/event/error meta
✓ fire-and-forget — the async after-hook failure keeps its meta
✓ an uncompilable condition keeps the condition source in its meta
✓ a throwing registerHook is reported with the hook name and cause in meta
✓ ObjectLogger — emits the hook diagnostic fields on a contract-shaped call
Test Files 1 passed (1)  Tests 5 passed (5)

合并 origin/main(带入 #5760 / #5753 两个同包改动)后重跑全量:

pnpm --filter @objectstack/objectql typecheck   → tsc --noEmit,0 error
pnpm --filter @objectstack/objectql test        → Test Files 124 passed (124) / Tests 2042 passed (2042)

门禁(合并后重跑,全绿):check:nul-bytes(5659 files,无裸控制字节)、check:query-options-erasure(ratchet 84 sites,none new)、check:engine-double-contract(27 pinned / 65 debt / 1 exempt)、check:durability-log-levelcheck:startup-registry-verdict

反向验证(方向先判后跑)

预判三条,跑完三条全中:

  1. 恢复方言参数序 → 4 个契约 stub 用例转红。 实测:AssertionError: expected { hook: 'audit_task', …(3) } to be undefined 等 4 条 —— meta 对象落进了 error 位,metaundefined,正是忠于契约的 logger 会丢字段的那一刻。
  2. 第 5 例(真实 ObjectLogger)保持绿。 这是预期的,不是漏网:ObjectLogger 按形状分派,两种参数序都认,所以它天然不是判别器,只是兼容性钉子。照实记下,不假装它会翻转。
  3. tsc 也一起转红 —— 这是修复新增的类型级防线,改动前并不存在:
src/hook-binder.ts(251,9): error TS2353: Object literal may only specify known properties, and 'hook' does not exist in type 'Error'.
src/hook-wrappers.ts(304,11): error TS2353: …
src/hook-wrappers.ts(376,11): error TS2353: …
src/hook-wrappers.ts(430,15): error TS2353: …

第 3 条正是这单最实质的收益:方言之所以能存活,就是因为契约类型在结构上满足本地形状(参数少的一方可赋值,any 双向兼容),tsc 一句话都不说。换成契约类型之后,同一个错误再写一次会在编译期被顶回来。

范围

packages/objectql/src/hook-wrappers.tspackages/objectql/src/hook-binder.ts + 同包新增测试 + changeset(patch)。未碰 engine.ts / integrity / lifecycle / spec / observability / core,未碰 content/docs/releases/


Generated by Claude Code

claude added 2 commits August 6, 2026 04:33
…al dialect (#5637)

The `Logger` contract declares `error(message, error?: Error, meta?)` — the
`Error` slot is second, meta third. `hook-binder.ts` and `hook-wrappers.ts`
each declared their own four-method logger shape spelling `error` as
`(msg, meta?)`, and their call sites put the diagnostic in the `Error` slot
accordingly.

tsc could not see it (the contract type satisfies the local shape
structurally) and `ObjectLogger` hid it at runtime (it dispatches the second
argument by shape). The contract's other implementations —
`ConsoleLogger`/`JsonLogger` in `@objectstack/observability` — follow the
contract literally, so the meta bag landed in the `error` slot and the whole
diagnostic disappeared.

- Both option interfaces now take `HookDiagnosticsLogger =
  Pick<Logger, 'debug'|'info'|'warn'|'error'>` from `@objectstack/spec/contracts`
  (PD #12: no consumer-side dialect).
- All four `error(...)` call sites pass meta in the third parameter. The values
  in hand are a `CelFault` or a `catch` binding of type `unknown`, none of them
  statically an `Error`, so the `Error` slot stays `undefined`.
- `debug`/`info`/`warn` already matched the contract — unchanged.

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

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/objectql.

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

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/objectql)
  • content/docs/plugins/index.mdx (via @objectstack/objectql)
  • content/docs/plugins/packages.mdx (via @objectstack/objectql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql)
  • content/docs/protocol/objectql/query-syntax.mdx (via packages/objectql)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/objectql)

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

Projects

None yet

2 participants