fix(cli): os generate schema falls back like every other toJSONSchema call site, so the IDE schema it exists to write is written - #17903
Conversation
… call site `runSchemaGeneration` was the one `z.toJSONSchema` call site in this repo that neither fell back nor used the `unrepresentable` convention. Its bare two-argument call throws in BOTH io directions on today's tree — a transform in output mode, a function type in input mode — so the `catch` below it printed and exited 1 and the command could never reach its own `fs.writeFileSync`. Adopt the ladder `packages/spec/scripts/build-schemas.ts` already runs, with `packages/metadata-protocol/src/protocol.ts`'s `unrepresentable: 'any'` as the third tier. Each tier re-raises anything the known-unsupported predicate does not recognise. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
Asserts the written file exists (listed, not assumed), its bytes parse, the document is a JSON Schema of the declared draft, and each of the four members #17873 required declaring carries the fragment that declaration names. A fifth, derived assertion pins the authoring derivation: no object schema anywhere lists a defaulted property as required — 752 do in the output derivation, each one an IDE reporting a valid config as missing a key nobody had to write. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 24 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9ca24545986759e0b7fcd9b58584b069975e1336 && git checkout 9ca24545986759e0b7fcd9b58584b069975e1336
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 134b410a90c6e5e7311c840076dcd5d486841f1d 30361ba9b45626203f1e95267f0fb0baa22a9f0f && git checkout -B drift-repro 134b410a90c6e5e7311c840076dcd5d486841f1d && git merge --no-ff 30361ba9b45626203f1e95267f0fb0baa22a9f0f
node scripts/docs-audit/affected-docs.mjs --json 134b410a90c6e5e7311c840076dcd5d486841f1d
|
Contract review — PR #17903 (card #17873)Head reviewed:
① Derived judgments — each measured here, ⛔ none ratified from the report1. ⛔ The half triage refused to rule is DECLARED, not defaulted — and this seat did not answer it either. The 2. ⭐ The delivery CORRECTED the card and triage's shared wording, and the correction is right. Both said 3. ⭐ The third tier COULD NOT have been spec's, and that is forced by the ruling rather than chosen. 4. No published surface moves, and the level follows — re-measured on this head. The per-file matcher over the added lines of 5. The pin asserts the ARTEFACT, ⛔ never 「it did not throw」. Five groups: (a) exit 0 and the output file written by name; (b) the bytes parse as JSON; (c) 6. ⭐ And row (e) was VACUOUS on the first ablation — the dev found that itself and strengthened it BEFORE reporting. An empty document has no offenders either, so 7. The ablation is executed, and its restore is proved by STATE rather than by an exit code. Mutated: 8 of 8 rows red, command exit 1, no file written. On-disk proof taken before any verdict was read — injected marker ×1, ② semver level —
|
| page | what it says | falsified? |
|---|---|---|
content/docs/api/data-flow.mdx:200 |
the only os generate schema mention in the four: the JSON Schema gives VS Code / IntelliJ autocomplete and validation for objectstack.config.ts |
no — ⭐ and it was describing a capability that could not run; this PR makes the page true |
content/docs/deployment/cli.mdx |
os generate schema ×0; its seven mentions are the metadata-scaffold types and the os generate <type> <name> table row |
no |
content/docs/protocol/kernel/lifecycle.mdx |
os generate schema ×0; all six are os generate migration |
no |
content/docs/protocol/objectql/types.mdx |
os generate schema ×0; all three are os generate migration |
no |
content/docs/releases/v17/17-4.mdx is release-owned: 0 paths in the diff, ⛔ not read for editing.
d. Gates. 63 families derived, 63 run, all exit 0. ⭐ Four returned exit 3 = PREREQUISITE NOT MET on the first sweep (check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity), all for the same missing packages/cli/dist; re-run after the build, all four returned 0, and neither reading is reported as the other. pnpm lint exit 0 at this head. Package legs: typecheck 0; vitest run 249 files / 3332 tests passed. Heavy runs went through os-verify-lock.sh with verdicts read from its VERDICT line.
e. ③ is already satisfied on this head: 34 check names, all terminal — 29 success / 5 skipped / zero failures.
Independence pair
Implemented-by: claude/issue-17873-generate-schema-unrepresentable (mode:subagent)
Reviewed-by: os-sales — domain:cli execution seat, issue #6024, session_01TSf4DV7ziu4V5j73e46b7c
Independence: SELF-REVIEW — the implementer is a subagent of the reviewing seat's own session
Tier: default judgment — 「余席条款②复核 = 默认判断档自审加门禁」.
Verdict
PASS. ⛔ No carrier to clear: the claim declared the axis no, the measurement on this head agrees, and check-clause2-carriers --pair 17903 returns exit 0 (both carriers agree, no widening tell). ⇒ the landing pre-check is ③ every check green, which this head already meets. git revert of a module-private function body.
Generated by Claude Code
Fixes #17873
Clause-②: no
runSchemaGenerationinpackages/cli/src/commands/generate.tswas the onez.toJSONSchemacall site in this repository that neither fell back nor used theunrepresentableconvention. Its bare two-argument call has no JSON form in either io direction on this tree, so thecatchbelow it didprintError(...)+process.exit(1)and the command could never reach its ownfs.writeFileSync— for any repository, for any flags.It now runs the three-tier ladder
packages/spec/scripts/build-schemas.tsalready runs for every schema it publishes, withpackages/metadata-protocol/src/protocol.ts'sunrepresentable: 'any'as the third tier. Each tier re-raises anything the known-unsupported predicate does not recognise, so a real conversion failure is still loud. The handling is entirely inside the module-private function: no new flag, no new accepted key, no new exported symbol.Reproduction, with a lit control
Taken on this branch's merge base, through the built
@objectstack/spec, with the card's own control:Per member, in the strict authoring direction, 4 of 44 have no JSON form — exactly the four the card named:
After the change, a real run writes the artefact:
维护者速读(草稿)
改了什么。
os generate schema过去 100% 失败——它是全仓唯一一处既不回退、也不用unrepresentable约定的toJSONSchema调用点。现在它按build-schemas.ts已有的三层阶梯降级,第三层用metadata-protocol已有的unrepresentable: 'any'。⛔ 没发明第四种机制,⛔ 没加 flag,⛔ 没加导出符号。为什么改。 分诊已裁:「命令应当像本仓其他调用点那样处理不可表达成员(回退 +
unrepresentable: 'any'),⛔ 不应当裸调用后直接process.exit(1)。这是往本仓已确立的约定上收,⛔ 不是新决策。」⭐ 这份 IDE schema 现在对那四个成员承诺什么——逐条读自产物本身,不是读自源码。
产物落在第三层(authoring 方向 +
unrepresentable: 'any'),$schema=https://json-schema.org/draft/2020-12/schema,顶层type: object、44 个properties、additionalProperties: false。onEnable{"description": "…"}——⛔ 无type、无任何约束hookstype: "array",items是完整对象(required: ["name","object","events"]、additionalProperties: false);handler={"anyOf": [{"type":"string"}, {}]}handler的「内联函数」那一支不受约束functions{"description": "…", "anyOf": [映射形式, 数组形式]},两支结构均保留;handler位置为{}handler的值不受约束packagestype: "array",items完整;不受约束的点在manifest.hooks[].handler与manifest.functions的handler上unrepresentable: 'any'并没有把这四个成员整体变成{}。整体变成不受约束的只有onEnable一个;另外三个成员的结构完整保留,被放宽的是它们内部的可调用叶子节点。另一半读数同样重要:产物里绝大多数{}与本次改动无关——只能严格转换、⛔ 根本不经过第三层的成员(objects/apps/views)本身就各带 40 / 11 / 41 个{},它们来自 spec 里原有的z.unknown()/z.any()。真正因本次放宽而产生的节点,全文档只有 5 个(4 个在packages下的 handler 上,1 个是onEnable本身)。另外,已退役的键(如
hook.timeout)在产物里是{"description": "[REMOVED] …", "not": {}}——not: {}是拒收,不是放宽:墓碑照常拒绝该键,并把迁移说明带给 IDE 用户。⛔ 没有采纳的三条出路,各自会承诺什么。
packages/hooks/functions/onEnable当作未声明的键——在additionalProperties: false之下它们会被报成错误,即一份合法的objectstack.config.ts被标红。unrepresentable: 'any'走 output 方向:同样能产出文件,但 output 派生会把 752 处带default的属性升级为required,于是一份合法配置被报成「缺 752 个键」。本 PR 因此取 authoring 方向,并把这一点写成一条断言。content/docs/api/data-flow.mdx承诺的那条 IDE 自动补全通路(以及 VS Code 扩展的jsonValidation指向一个不存在的文件 ——schemas/objectstack.schema.json从未存在过,也没有任何脚本生成它 #5028 想要的那个文件)将永久没有生产者。⛔ 退役属维护者底线,本席无权执行。风险与代价(含回滚)。 风险集中在一句话:这份 IDE schema 从「不存在」变成「存在但对上述可调用叶子不设限」。回滚成本极低——改动全在一个模块私有函数体内,
git revert即回到今天的「命令必失败」状态,⛔ 不影响运行时、⛔ 不影响任何已发布的导出面。席位意见。 ⛔ 本席不答这一问,也不代答。 这四个成员对外承诺什么是对外承诺的改变,分诊明写「⛔ 不可裁(确属产品)」,交付席与本席都无权替你选;上面那一问就是原样交到你手上的。本席只提供三件可核的事实,⛔ 不含推荐:
## Contract review评论),并确认卡与分诊共同的措辞「四个成员整体变成{}」只对onEnable成立——这是交付方主动更正上游措辞,⛔ 不是本席事后补的。git revert,⛔ 不动任何已发布的导出面、⛔ 不改运行时行为。你答 no 不会让已落地的东西变成半成品。⭐ 你要做的(一个 token 即可)。 上表就是本 PR 交付的产物对那四个成员的承诺,现在把它交给你否决:接受吗?yes / no。
Acceptance notes
Measured on the way through; none of it belongs to this card.
.e2e.test.tsname this repo's name-based switch (scripts/nightly-tiers.mjs) uses to move e2e off per-PR and merge-queue runs.runSchemaGenerationis module-private and §0 forbids exporting it, so spawning is the only way to run the command — there is no cheaper queue-tier pin available without a new export. Naming it out of the.e2etier purely to buy queue coverage would be gaming that switch, so it is named for what it is and the consequence is declared here instead. Noted, not filed.hooks.timeoutis published as a tombstone, and that is the tombstone working.{"description": "[REMOVED]hook.timeoutwas removed in @objectstack/spec 17 …", "not": {}}—not: {}refuses every value, so an IDE rejects the retired key and shows the migration line. Noted, not filed.content/docs/**that mentions this command iscontent/docs/api/data-flow.mdx:200— "JSON Schema | VS Code, IntelliJ | Autocomplete and validation forobjectstack.config.ts(viaos generate schema)". It states that the command produces such a schema and says nothing about what it promises per member, so it is true of the artefact this PR now produces and needs no edit.content/docs/deployment/cli.mdx'sos generatesection documents only the metadata-scaffold types and never mentions theschemasub-command at all. Control for the grep:os generatelights 5 pages in the same sweep. Noted, not filed.build-schemas.ts's, and it has one latent consequence worth stating. Tiers 1 and 2 both throw today, so the landing tier is 3 regardless of order. If a future spec removed the transform, tier 1 (output, strict) would land and the document would flip to the output derivation — the 752-defaulted-keys shape above. Assertion (e) of the pin is what would catch that. Noted, not filed: nothing reproduces it today.jsonValidation指向一个不存在的文件 ——schemas/objectstack.schema.json从未存在过,也没有任何脚本生成它 #5028's half is untouched — nojsonValidationpointer is added or moved here.Verification
Test Files 1 passed (1)·Tests 8 passed (8)Test Files 1 failed (1)·Tests 8 failed (8)widenedUnrepresentable= 0 ·unrepresentable: 'any',code lines = 0 · mutated blob6d2865d0≠ HEAD blob1fccbeffgit diff HEADempty ·git hash-object=1fccbeffad798a50a13b3f8a97859ca574e16a23= the HEAD blobTest Files 1 passed (1)·Tests 8 passed (8)The ablation script carried
trap restore EXIT INT TERMwith an absoluteREPO_ROOT, restored withgit checkout HEAD -- <path>(never the bare form, which restores from the index), and refused to report a reading at all unless the mutation was proven present on disk — it fired once, correctly, on a first attempt whose expectation had miscounted comment text.🤖 Generated with Claude Code
https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
Generated by Claude Code
Generated by Claude Code