Skip to content

feat(spec): check:generated — report every stale artifact in one run - #4183

Merged
os-zhuang merged 1 commit into
mainfrom
claude/console-screen-flow-submit-jfpjy4
Jul 30, 2026
Merged

feat(spec): check:generated — report every stale artifact in one run#4183
os-zhuang merged 1 commit into
mainfrom
claude/console-screen-flow-submit-jfpjy4

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

问题

packages/spec 有八个签入的生成产物,各有闸门,分在两个 CI job 里而且顺序执行。于是第一个过期的产物会挡住后面全部:你修好它、推上去、下一轮才知道还有一个。

实际代价:#4040 两次(check:docscheck:api-surface),#4161 又两次(check:spec-changescheck:upgrade-guide)。四个来回,换来的是一次本地运行就能一次说完的信息。

做法

pnpm --filter @objectstack/spec build             # 必须先构建 —— 见下方 dist 陷阱
pnpm --filter @objectstack/spec check:generated   # 全部闸门;失败不中断后续
pnpm --filter @objectstack/spec check:generated --fix   # 只重生成本次证实过期的那些

--fix 是刻意做窄的

不是「一键全量重生成」。全量重生成会毁掉信号 —— 它会改写那些你根本没注意到已经过期的产物,于是一个真实的语义变更就悄悄躲进了一堆机械 diff 里。先让检查告诉你哪些过期,再只重生成那些。

账本每次运行都对账,而不是藏在 --self-test 后面

gate → generator 的账本会在每次运行时与 package.json 双向对账。一个没被归类的新 check:/gen: 脚本会让本次运行失败,而不是静默地退出覆盖范围 —— 否则汇总仍会说「全部产物都是最新的」,同时实际检查得更少,而这正是这个脚本要消除的那类谎言

它当场证明了自己:第一次运行就拒绝了它自己的 package.json 注册项(未归类),一个产物都还没检查。

两件它拒绝沉默的事

  1. check:api-surface 的过期-dist 陷阱:它读的是构建产物 .d.ts,所以未构建的树会把新增导出报成「breaking removal」。现在当这个闸门失败时会就地打印这条说明,而不是让下一位读者去追幽灵。写这个 PR 时它就当场救了我一次 —— 干净树上报 AuthoringKeySurface / FIELD_KEY_GUIDANCE 被删除,实为我并入 main 后没重建。
  2. 它不跑的四个check:liveness / check:empty-state / check:react-conformance / check:skill-examples,都是无产物的源码审计)会被列出来,所以「全部最新」永远不会被读成「全部通过」。

顺带曝出的一个既有缺口

gen:openapigen:sbom 没有任何闸门 —— 它们产出的东西无人验证是否最新。脚本每次运行都会报这一条,而不是装作不存在。

验证

两个方向都做实了:

结果
干净且已构建的树 8 项全过,退出码 0
往迁移步骤的 rationale 注入一处改动 check:upgrade-guide 失败,--fix 重生成了 docs/protocol-upgrade-guide.md,其余七个未被触碰(git status 确认)
未构建的树 check:api-surface 失败并打印 dist 陷阱说明;构建后消失

这轮验证还纠正了我自己先前的一个误读:我原以为改 rationale 会让两个 ADR-0087 闸门一起失败。实测只有 check:upgrade-guide 失败 —— 两者输入不同:转换注册表驱动 spec-changes.json,rationale 散文驱动 protocol-upgrade-guide.md。它们在 #4161 上「一起失败」,是因为那个 PR 恰好两样都改了。

另一条实测spec-changes.json 只覆盖 major 11–17(到当前 PROTOCOL_MAJOR 为止),所以 step 18 的 rationale 根本不进生成物 —— 第一次负控就是因为改了 step18 而没咬,这才查出来。

根级 8 道闸门、tsc --noEmit(CI 的 typecheck 门禁)均通过。

AGENTS.md

那段手写的 for 循环被这条命令取代。它硬编码了八个闸门名 —— 第九个产物出现时会静默漏掉,正是 #3786 排查的那个「手抄清单没有闸门」形状。脚本的账本不会漂,因为它对着 package.json 对账。

🤖 Generated with Claude Code

https://claude.ai/code/session_01QoA8AV99Ss1RRkLLAYmDzq


Generated by Claude Code

`packages/spec` has eight checked-in generated artifacts, each with its own gate,
split across two CI jobs that run their gates SEQUENTIALLY. The first stale
artifact therefore masks every one behind it: you fix it, push, and learn about
the next on the following run. Two pushes on #4040 (`check:docs`, then
`check:api-surface`), two more on #4161 (`check:spec-changes`, then
`check:upgrade-guide`) — four round trips spent discovering something one local
run could have said at once.

Every gate runs; a failure does not stop the rest. The summary lists all stale
artifacts with the exact `gen:` command for each.

`--fix` regenerates ONLY what this run proved stale. Deliberately not a
regenerate-everything button: blanket regeneration rewrites artifacts whose
staleness you never saw, which is how a real semantic change lands silently
inside a mechanical diff.

The gate -> generator ledger reconciles against `package.json` on EVERY run, in
both directions, rather than behind a `--self-test` flag. An unclassified
`check:`/`gen:` script fails the run instead of quietly dropping out of coverage
— otherwise the summary would still say "all artifacts up to date" while checking
fewer, the exact class of lie this exists to remove. It proved itself by
rejecting its own `package.json` entry on the first run.

Two things it refuses to be silent about: the `check:api-surface` stale-`dist`
trap is printed inline when that gate is the one failing (it reads the built
`.d.ts`, so an unbuilt tree reports newly-added exports as breaking removals),
and the four source audits it does NOT run are named, so "all up to date" never
reads as "everything passed". It also reports that `gen:openapi` and `gen:sbom`
have no gate at all.

Verified both directions: clean built tree -> all 8 pass; a change injected into
a migration step's `rationale` -> `check:upgrade-guide` fails and `--fix`
regenerates that artifact and no other. That run also corrected an earlier
reading of mine: the two ADR-0087 gates have different inputs — the conversion
registry drives `spec-changes.json`, the rationale prose drives
`protocol-upgrade-guide.md` — they had only appeared to fail together because
#4161 changed both.

AGENTS.md's hand-rolled loop over eight hardcoded gate names is replaced by the
command; that list could not survive a ninth artifact, and the ledger can.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoA8AV99Ss1RRkLLAYmDzq
@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 2:22pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tooling size/m labels Jul 30, 2026
@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.

@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 14:38
@os-zhuang
os-zhuang merged commit 8621cdd into main Jul 30, 2026
19 checks passed
@os-zhuang
os-zhuang deleted the claude/console-screen-flow-submit-jfpjy4 branch July 30, 2026 14:38
os-zhuang added a commit that referenced this pull request Jul 30, 2026
…ial-cased regex (#4162) (#4206)

The drift mapper special-cased only `packages/plugins/*`, so the 30
packages nested under the other six container dirs collapsed into their
container — whose missing package.json killed the npm-name matching arm.
A doc naming `@objectstack/service-automation` but never the repo path
was a guaranteed miss (`automation/flows.mdx` on a service-automation-
only change). The root is now the deepest ancestor with a package.json,
with no hardcoded container list to drift out of sync.

Also from the issue thread: `<packageRoot>/scripts/**` is build tooling,
not documented behaviour, and no longer counts as an implementation
change (#4183); `package.json` and `src/scripts/**` stay counted. Both
the derivation and the classifiers are now pinned by `--self-test`.


Claude-Session: https://claude.ai/code/session_01J2x8Tie9WT1VgWFifqMKrR

Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
Two conflicts, both in code #4165 ("reject unknown keys across the app shell
and navigation tree") rewrote underneath this branch:

- `packages/spec/src/ui/app.zod.ts` — main turned the nav union into a
  `z.discriminatedUnion('type', …)` with `.strict()` members and kept the
  `z.ZodType<any>` annotation; this branch replaced that annotation with the
  real `NavigationItem`. Both are kept. Reconciling them needed one addition:
  main widens the member array (`as unknown as readonly [ZodObject<ZodRawShape>,
  …]`) because the members are lazySchema Proxies and a superRefine-wrapped
  variant, and `discriminatedUnion` then reports the union's output as
  `Record<string, unknown>` — which fits `z.ZodType<any>` and nothing sharper.
  The union therefore takes a matching cast back to `z.ZodType<NavigationItem>`,
  documented at the declaration: every branch of the type is still
  `z.infer<typeof XNavItemSchema>`, so only the MEMBERSHIP of the list is
  unchecked, and app.test.ts parses all nine variants.

- `AGENTS.md` — main added the `check:generated` aggregate section; this branch
  added `check:exported-any` to the "pure checks with no generator" list. Both
  are kept.

Also registers `check:exported-any` in `check-generated.ts`'s ledger, which
main added and which reconciles itself against package.json on every run.

Registers `check:variant-docs` in the same ledger while here: #4177 added the
script and #4183 added the reconciliation, neither PR could see the other, so
`check:generated` was already failing on `main` itself. One line, in a file this
branch was editing anyway.

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

Labels

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