Skip to content

feat(spec): 让 #4001 严格性账本接受机器校验(首次运行抓到 11 处漂移) - #4232

Merged
os-zhuang merged 3 commits into
mainfrom
claude/unknown-key-stripping-strictness-h3a3zt
Jul 31, 2026
Merged

feat(spec): 让 #4001 严格性账本接受机器校验(首次运行抓到 11 处漂移)#4232
os-zhuang merged 3 commits into
mainfrom
claude/unknown-key-stripping-strictness-h3a3zt

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

账本是这场战役的地图——哪些 z.object 站点是 authorable(.strict() 棘轮的目标)、哪些是 wire、哪些刻意保持开放。每一步都靠读它来决定下一步做什么,而没有任何东西保证它是真的

它在一周内失效了两次:

会漂移的地图比没有地图更糟,因为它会被照着走。

闸门校验什么

只校验机器能判定的部分:

  • 站点计数——账本自己写明了方法(「site counts are z.object( occurrences per file」),所以每个数字都可验证。计数对不上,意味着有人在某个没人重新审视过的 Class 判定下增删了 schema。这就是棘轮:动了文件就必须回到账本。
  • 覆盖——被分类目录下每个有站点的 *.zod.ts 都必须有一行。
  • 小节合计要对得上,且声称 "strict as of" 的行必须指向真的含 .strict() 的文件。

刻意不校验 Class

authorable / wire / open 是关于「谁写这个 schema 的输入」的人类判断,而这场战役的规则是 verify-before-tightening。把它编码进闸门,等于自动化掉那个恰恰必须由人做的判断。

闸门守住算术覆盖,好让那个判断永远是对着当前代码做出的。

0 站点文件跳过

首次运行标为「未申报」的 6 个文件里,有 5 个根本没有 z.object(date-macroscontext-tokensfeedaction-paramstime-relative-trigger 都是枚举/token 模块)。账本分类的是站点,给它们加行只是噪音。

这不是漏洞:这类文件长出第一个 z.object( 的那天,它就变成未申报文件,闸门会立刻说话。

首次运行:11 处漂移

而且是在一个正被拥有它的战役主动编辑的文件里:

类别 内容
计数移动(6) ui/app.zod.ts 11→18ui/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
小节合计不符(2) ui/ 192→197、data/ 163→154
真·未申报(1) automation/io-node-config.zod.ts(2 站点),已分类

app.zod.ts 那处是我自己造成的:app 步(#4165)往那个文件加了七个 schema,更新了该行的叙述文字,却没动它的计数。

一句话概括这道闸门的理由:最熟悉这份账本、并且在同一周里编辑过它的人,仍然在里面留下了十一处漂移。

验证它会咬人,而不只是会通过

场景 结果
改动一个计数 失败(该行 + 它的小节合计,共 2 处)
删掉一行 该文件重新浮现为「未申报」
approval.zod.ts 剥掉 .strict() 它的 "strict as of" 声称失败
全部恢复 通过

接入与影响

Refs #4001

🤖 Generated with Claude Code

https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4


Generated by Claude Code

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)` that covered 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.

WHAT THE GATE CHECKS. Only what is mechanically checkable:
  - Site counts. The ledger states its own method — "z.object( occurrences per
    file" — so every number in it is verifiable. A stale count means schemas
    were added or removed under a Class verdict nobody re-examined. This is the
    ratchet: touching a file forces you back through the ledger.
  - Coverage. Every *.zod.ts with sites in a triaged directory must have a row.
  - Section totals sum, and 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 the coverage so that
judgement is always made against current code.

ZERO-SITE FILES ARE SKIPPED. Five of the six files the first run flagged as
undeclared (date-macros, context-tokens, feed, action-params,
time-relative-trigger) hold no z.object at all — they are enum/token modules,
and the ledger classifies sites. Requiring rows for them would be noise. Not a
hole: the day such a file 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 (now classified: the sibling
contracts validating the open `config` slot on flow notify/http nodes).

The app.zod.ts gap was self-inflicted — the app step (#4165) added seven schemas
to that file and updated the row's prose without touching its count. That is the
argument for this gate in one sentence: the people most familiar with this
ledger, editing it the same week, still left eleven drifts in it.

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. Restored, green.

check:docs green and content/docs/references unchanged (the #4207 lesson).

Refs #4001

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
@vercel

vercel Bot commented Jul 30, 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 Jul 30, 2026 6:55pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 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 packages/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 packages/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/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/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/kernel/runtime-capabilities.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/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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file tooling labels Jul 30, 2026
claude added 2 commits July 30, 2026 18:52
The ledger gate did its job before it merged. `main` landed
automation/builtin-node-config.zod.ts (#4045/#4228) — eight new sites, sibling
to io-node-config.zod.ts — and merging main turned the gate red on a branch
whose own diff touches no schema at all. That is the intended behaviour: a new
authorable surface arrived, so somebody has to classify it rather than let it
sit in the ratchet's blind spot.

Classified `authorable`, same family as io-node-config: the CRUD quartet,
`screen` and `map` config contracts. Worth noting on the row — these were
written from what the executors actually read rather than from the descriptors'
configSchema literals, and `builtin-node-form-zod-ledger.test.ts` reconciles the
two bidirectionally, so this row already carries a drift check of its own.

Every pre-existing count survived the merge unchanged, so the failure was
exactly as narrow as it should have been: one new file, one new row, one section
total (automation 81 → 89).

Verified after merging: spec suite 7163 green, tsc clean, and the
strictness-ledger / liveness / variant-docs / docs gates all green with
content/docs/references unchanged.

Refs #4001

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
@os-zhuang
os-zhuang marked this pull request as ready for review July 31, 2026 00:27
@os-zhuang
os-zhuang merged commit 19365b7 into main Jul 31, 2026
19 checks passed
@os-zhuang
os-zhuang deleted the claude/unknown-key-stripping-strictness-h3a3zt branch July 31, 2026 00:27
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…d omission fails instead of shipping (#3786) (#4252)

A recursive Zod schema carries a hand-written `z.ZodType<...>` annotation.
`z.ZodType` takes <Output, Input> and Input DEFAULTS TO `unknown`, so naming only
the first compiles, validates correctly at runtime, and silently un-types every
authoring path through the schema.

This package has made that mistake twice. #4171 fixed the output half of the nav
annotation, and check-exported-any.ts — built to hold that fix — reads output
only, so it reported green over the half still broken. #4221 found the
consequence (`defineApp` compiled `navigation: [{ totally: 'made up' }, 42,
'nonsense']`) and #4227 named both parameters on the six remaining recursive
schemas. Both fixes are correct; neither was pinned.

#4227 declined a .d.ts scanner for a good reason — separating a deliberate
single-parameter `z.ZodType<T>` (contracts/llm-adapter.ts takes a caller-supplied
schema) from an omission needs heuristics on emitted type names, against
check-exported-any.ts's zero-false-positive rule — and nominated #4221's
assertion pattern instead. This applies it to the eight schemas #4221 did not
cover: Query, JoinNode, FieldNode, FilterCondition, NormalizedFilter, StateNode,
ValidationRule, FormField.

Each gets a positive probe (the authoring shape still compiles, guarding an input
type drawn too tight) and a negative probe reached through
`z.input<typeof Schema>` — the way a consumer gets there, so the pin covers the
WIRING and not just the type alias. Dropping a type parameter turns the
suppression unused and tsc fails by name.

Mutation-tested in both shapes a regression can take:
- QuerySchema back to one parameter → the pin fires AND JoinNodeSchema cascades,
  since JoinNodeInput.subquery is QueryInput.
- StateNodeSchema back to one parameter → ONLY the pin fires. Nothing else
  references its input, so without this file that regression is silent.

No runtime change and no new public export; the module is referenced by no tsup
entry and re-exported by no barrel.

Also classifies check:strictness-ledger in check-generated.ts's ledger — it
landed in #4232 with no entry, so check:generated was failing on main itself,
the same cross-PR race the check:variant-docs entry above it documents.
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…4255) (#4257)

The aggregate reconciles its GATED/NO_GENERATOR ledger against package.json
on every run, in both directions — but only where the aggregate runs, which
was locally. CI runs the gates as individual steps, so a PR adding a
check:/gen: script without classifying it kept every CI gate green while the
wrapper AGENTS.md prescribes exited red on main at the reconcile stage,
running zero gates.

Twice in three days. #4177 added check:variant-docs and main stayed red for
local wrappers until #4194 collided with the same wall (#4203). Then #4232
added check:strictness-ledger — found unclassified while wiring this fix, so
the wrapper was red on main again at the moment of writing. Classified here
as NO_GENERATOR: it audits the hand-written strictness ledger (docs/audits/)
against the code it describes; there is no generator to name.

The gate is a --reconcile-only mode (reconcile, report, exit — no gates;
reads package.json only, no build, sub-second) wired into lint.yml's
"TypeScript Type Check" job. Deliberately not ci.yml's check-generated job:
its `generated` paths filter does not watch packages/spec/package.json — the
one file every offending PR must touch — and both offenders' changed files
missed every entry in that filter, so both skipped the job entirely. The
typecheck job is unfiltered and required, so the meta-gate cannot go
dormant — the reasoning that already placed check:docs, check:skill-refs and
check:react-blocks there.

Verified: --reconcile-only exits 0 on this tree (16 check: + 10 gen:, all
classified) after catching #4232's script pre-classification; an injected
unclassified script and a deleted ledger-named script both fail with the
existing diagnostics; the default mode still reconciles and then runs the
gates. AGENTS.md's wrapper section now states the CI backstop.

Closes #4255
Refs #4203, #4232

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QCrKazC5o5a77yXd7ykaZg
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants