feat(spec)!: hook 与 datasource 拒绝未知键(#4001 data 步,收尾最后两个待验证分类) - #4207
Merged
Conversation
Closes the last two ledger entries that still carried a provisional (p) classification. The ledger's own rule for these was "verify before tightening", and verification changed the answer for one of them. CLASSIFICATION. Both types are authorable on the same evidence: they sit in BUILTIN_METADATA_TYPE_SCHEMAS, so one shape backs defineStack() parsing, /api/v1/meta/types/:type, and the Studio form. But the blanket `authorable (p)` on hook.zod.ts was too wide — HookContextSchema in the same file is the RUNTIME shape the engine hands a handler. It stays tolerant, and must: strictness there would turn an engine-internal enrichment (as `provenance` was in #3712) into a breaking change for anyone parsing a context they were given. Strict now: HookSchema + retryPolicy; both hook-body branches; DatasourceSchema + pool/healthCheck/ssl/retryPolicy; ExternalDatasourceSettingsSchema + its validation block; DatasourceCapabilities; DriverDefinitionSchema. Still open, deliberately: HookContextSchema and its session/provenance/user blocks; datasource `config` and `readReplicas` (per-driver by construction — the driver's own configSchema validates them). That openness is exactly why the top level had to close: a connection key written one level too high was stripped and the datasource then connected on driver defaults rather than failing. Those keys are prescribed into `config`; a top-level `password` is pointed at `external.credentialsRef`, because relocating an inlined secret is not the fix. CORRECTS AN ASSUMPTION THE EARLIER STEPS WERE WRITTEN UNDER. Strictness does not change the published JSON Schema. build-schemas.ts converts with the default io:'output', and in output mode zod emits additionalProperties:false for a .strip() object too — the post-parse shape genuinely has no extra keys. Verified by regenerating with and without these flips: Datasource.json is byte-identical. So the JSON Schema had been advertising additionalProperties:false while the zod parse quietly accepted and discarded unknown keys. These flips align the parse with the contract already published rather than widening it. The approval note in the ledger reads as though its flip carried strictness INTO the JSON schema; it did not, and the ledger now says so. SCOPE LIMIT, since it bounds who this reaches. There is no defineHook() factory — it is referenced twice in object.zod.ts describe strings but does not exist, unlike definePosition/defineTool/defineAgent/defineApp/defineView/ defineDatasource. Authors write `const H: Hook = {…}`, a bare type annotation that never parses, so hook strictness bites at artifact/registry LOAD time, not as they type. Datasource has its factory and so rejects at authoring time. Adding defineHook() is a new API surface and belongs in its own change. The metadata-authoring-lint coverage test failed on cue: lintables fell 16 → 14 because both types graduated out of lint coverage into parse coverage. That test exists to force a human to confirm a shrink is a graduation and not a bug — so `hook` moved out of the pinned-coverage list and both moved into the strict list. Verified: full spec suite (7136) green; gen:schema clean (covers the #3746 toJSONSchema-on-strict-lazySchema hazard); authorable-surface, liveness, empty-state, variant-docs, doc-authoring and changeset gates green; both first-party example datasources parse under strict (no finding this time — unlike the app step, these were already clean). objectql has 26 pre-existing failures in protocol-data / protocol-unknown-query- param (#4164/#4134 territory, untouched here). Confirmed pre-existing by stashing these changes and re-running: identical 26 failed | 59 passed. Refs #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…block's way check:docs failed on the previous commit (inside the "TypeScript Type Check" job — tsc itself was clean). The regenerated reference page had REPLACED the whole capability-token table in content/docs/references/data/hook-body.mdx with one line: "Keys `ExpressionBodySchema` declares (drift-guarded by ...)". build-docs.ts takes the FIRST JSDoc in a module as that page's blurb. Putting the strictness constants — which carry `/** … */` per the approval.zod.ts house style — above `HookBodyCapability` handed its documentation slot to a private constant, silently deleting the `api.read` / `api.write` / `api.transaction` / `crypto.*` / `log` reference table authors actually read. Moved the block below `HookBodyCapability` and left a placement note so the next person adding a documented constant to this file does not rediscover it. Regenerating now leaves content/docs/references untouched; check:docs passes. The other two files were never at risk — their first JSDoc sits above the imports, so the inserted block could not take the slot. That is also why only this one page moved. Verified: check:docs green, full spec suite 7136 green. Refs #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
os-zhuang
marked this pull request as ready for review
July 30, 2026 15:43
This was referenced Jul 30, 2026
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
…#4232) The ledger is the campaign's map — which z.object sites are authorable (the .strict() ratchet target), which are wire, which are deliberately open. Every step reads it to pick the next move, and nothing kept it honest. It went stale twice in one week: a classification verification disproved (hook.zod.ts carried a blanket `authorable (p)` covering a runtime shape, #4207), and a "next step" that had shipped months earlier (the unknown-key warning layer, #4218), which sent the next reader off to rebuild something already in production. A map that drifts is worse than no map, because it gets followed. CHECKS. Site counts (the ledger states its own method — "z.object( occurrences per file" — so every number is verifiable); coverage (every *.zod.ts with sites in a triaged directory needs a row); section totals; and that a row claiming "strict as of" names a file that really contains .strict(). The Class column is deliberately NOT checked. Authorable vs wire vs open is a judgement about who writes the input, and this campaign's rule is verify-before-tightening — encoding it would automate exactly the call that must stay human. The gate protects the arithmetic and coverage so that judgement is always made against current code. Zero-site files are skipped: five of the six the first run flagged hold no z.object at all (enum/token modules). Not a hole — the day one grows its first z.object( it becomes undeclared and the gate says so. FIRST RUN: 11 DRIFTS, in a file being actively edited by the campaign that owns it. Six counts had moved (ui/app.zod.ts 11 → 18, ui/touch.zod.ts 4 → 7, ui/action.zod.ts 8 → 9, ui/view.zod.ts 51 → 50, ui/responsive.zod.ts 6 → 4, automation/flow.zod.ts 12 → 11), two section totals no longer summed, and automation/io-node-config.zod.ts had no row. The app.zod.ts gap was self-inflicted: the app step (#4165) added seven schemas and updated the row's prose without touching its count. Then it worked for real before merging: main landed automation/builtin-node-config.zod.ts (#4045/#4228, eight sites), and merging main turned the gate red on a branch whose own diff touches no schema. Now classified. Every pre-existing count survived that merge unchanged, so the failure was exactly as narrow as it should have been. Verified the gate bites rather than just passes: a moved count fails (plus its section total), a deleted row resurfaces as an undeclared file, and stripping .strict() from approval.zod.ts fails its "strict as of" claim. Refs #4001
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
…#4269) (#4273) Follows the defineDatasource template: input-shape config in, HookSchema.parse, resolved shape out. Exported from the package root and @objectstack/spec/data. Closes the convention-scan authoring path's zero-validation gap (the #4207 alias/guidance errors now fire at import, not bind) and converges both authoring paths on the z.output artifact shape. Also rewords the two UNKNOWN_KEY_GUIDANCE prescriptions (workflows/hooks) in object.zod.ts that referred authors to a defineHook() that did not exist — they now name a real function plus its import path — and switches the schema.mdx lifecycle-hook example to the factory with a factory-over-bare-literal note. Drift guard in hook.test.ts: factory output === HookSchema.parse output, defaults/CEL materialization, function-handler passthrough by reference, bind-time re-parse idempotence, authoring-time hard-fail with the schema's own guidance. Closes #4269 Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 31, 2026
Open
os-zhuang
added a commit
that referenced
this pull request
Jul 31, 2026
* fix(spec): stop promising validation that never runs, and let the strictness ledger see nested dirs (#4001) Two corrections to the #4001 unknown-key campaign. Both are the same defect the campaign exists to remove — a success signal covering an omission — found in the campaign's own output. 1. The `config` prescription was false. `DatasourceSchema` went strict in #4207 with `config` left open, and the rejection for a misplaced connection key prescribed: "Move it to `config: { host: … }`; the driver's own configSchema validates it there." Nothing validates it there. `DriverDefinitionSchema.configSchema` is a `z.record`; both bundled driver specs set it to `{}` (mongo's with a comment promising runtime population that no code performs); and no consumer in this repo reads it. So the message took an author who had made a recoverable mistake at a place that now catches it and directed them, with the platform's authority, at a slot where the same mistake is silent again — `config: { hostname: … }` is stripped and the datasource connects on localhost, which is #4001's original bug one level down. The guidance now names the per-driver shape to write against (`PostgresConfigSchema` / `MongoConfigSchema` / `MemoryConfigSchema`) and claims nothing about enforcement. Same false claim removed from `driver/mongo.zod.ts`, whose header advertised that the Platform validates `datasource.config` with it. Enforcement — enforce-or-remove on an inert `configSchema` — is filed as #4410. Error-message and documentation text only; no authorable key changed. 2. The ledger gate's coverage walk was one level deep. `check:strictness-ledger` promises every sited `*.zod.ts` in a triaged directory carries a classification row. It listed directories non-recursively, so `data/driver/` — three per-driver config files, nine authorable sites — was invisible to the check whose entire promise is "no undeclared surface", which printed "no undeclared schema files" and was believed. A gate that under-reports is worse than no gate: it converts "I should classify this" into "it is already classified". The walk is now recursive, the three files are classified, and the ledger records both findings. `scripts/strictness-ledger.test.ts` pins the recursion, and is not redundant with the gate: measured, with the rows now present, reverting to a non-recursive walk leaves the gate GREEN — it cannot catch its own regression. The test can, and was verified to go red before it went green. Verified: spec 283 files / 7205 tests, `tsc --noEmit` clean, all 8 generated artifacts current, and all 10 `check:*` gates green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY * fix(docs): v17 release notes repeated the false driver-configSchema claim (#4001) Fourth and last copy, and the only user-facing one. The v17 notes told readers that `config` and `readReplicas` stay open records because "the driver's own `configSchema` validates them" — the same sentence corrected in `datasource.zod.ts` (module comment + the `belongsInConfig` rejection) and `driver/mongo.zod.ts`. Found by this PR's own docs-drift advisory, which is worth recording: the bot listed 107 docs on a package-level heuristic and the correction was real in exactly one of them. Cheap to check, and it was the copy readers actually see. Now states that nothing validates inside `config`, that the per-driver schemas exist but parse nothing, and that a misspelling one level down is still silent — with the pointer to #4410. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY --------- Co-authored-by: Claude <noreply@anthropic.com>
59 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
收尾账本里最后两条还带 provisional
(p)标记的条目。账本对它们的规定是「verify before tightening」——验证确实改变了其中一条的答案。分类验证
两者都被确认为可授权,证据相同:它们位于
BUILTIN_METADATA_TYPE_SCHEMAS,因此同一个 shape 同时支撑defineStack()解析、/api/v1/meta/types/:type和 Studio 表单。但
hook.zod.ts上那个笼统的authorable (p)过宽了:同文件的HookContextSchema是引擎交给 handler 的运行时形状。它保持宽松,且必须如此——在那里收紧会把引擎内部的字段扩充(如 #3712 加入provenance)变成对所有解析该上下文者的破坏性变更。现在 strict:
HookSchema+retryPolicy;两个 hook body 分支;DatasourceSchema+pool/healthCheck/ssl/retryPolicy;ExternalDatasourceSettingsSchema+validation;DatasourceCapabilities;DriverDefinitionSchema。刻意保持开放:
HookContextSchema及其session/provenance/user;datasource 的config与readReplicas(按构造是 per-driver,由驱动自己的configSchema校验)。这份开放性恰恰是顶层必须关闭的原因:连接键写高了一层(
host写在driver旁边而不是config里)会被静默剥离,datasource 随后用驱动默认值(localhost、默认端口)连上去,而不是失败。这类键现在被明确指引进config;顶层password则指向external.credentialsRef——因为把内联密钥挪个位置并不是修复。capabilities里丢键更安静:未注册的能力读作false,引擎于是停止把该项工作下推给驱动,改在内存里重算。纠正一个前几步所依据的假设
收紧 strict 并不改变已发布的 JSON Schema。
build-schemas.ts用默认的io: 'output'转换,而 output 模式下 zod 对.strip()的 object 同样输出additionalProperties: false——解析后的形状确实没有多余键。我用带改动和不带改动各生成了一次做对比:Datasource.json逐字节相同。所以真实情况是:**JSON Schema 一直在声称
additionalProperties: false,而 zod 解析却在静默接受并丢弃未知键。**这两层此前不一致;本次是让解析对齐到早已发布的契约上,而不是扩大它。账本里 approval 那条注释读起来像是「flip 把 strict 带进了 JSON schema」——并非如此,approval 的 schema 无论如何都会是
false。它描述的注册期拒绝是真实的,但那来自已发布的 schema,不是来自 flip。账本已就此更正。覆盖面限制(会影响这次改动触达谁)
defineHook()不存在。 它在object.zod.ts的 describe 字符串里被引用了两次,但代码里没有——而definePosition/defineTool/defineAgent/defineApp/defineView/defineDatasource都真实存在。作者只能写
const H: Hook = {…}这样的裸类型标注,永不经过 parse。因此 hook 的 strict 在 artifact/注册表加载时生效,而不是在作者敲键盘时。datasource 有工厂,所以在编写时就拒绝。补
defineHook()属于新增 API 表面,应当独立决策,不在本 PR 内。lint 覆盖测试如期失败
metadata-authoring-lint的 lintables 从 16 降到 14,因为两个类型都从 lint 覆盖毕业进入 parse 覆盖。那个测试的存在意义正是强制一个人确认这次收缩是毕业而不是 bug(它的注释里就是这么写的,app在 #4165 也触发过一次)。据此:hook移出钉死的覆盖列表,两者移入 strict 列表。验证
gen:schema第一方资产核查这次没有发现问题——与 app 步抓到
ACCOUNT_APP.defaultOpen不同,这两个 datasource 本来就是干净的。如实说明一处预先存在的失败:
objectql的protocol-data/protocol-unknown-query-param有 26 个失败,属于 #4164/#4134 范围,本 PR 未触及。已通过 stash 本次改动后重跑确认为预先存在——结果完全相同(26 failed | 59 passed),重新构建 spec 后仍相同。main的Test Core在 #4177 合并时是绿的,判断为本地工作区依赖未完整构建所致,以 CI 结论为准。Refs #4001
🤖 Generated with Claude Code
https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
Generated by Claude Code