Skip to content

fix(cli)!: retire os generate schema — it refuses, and points at os validate and the published per-type schemas (#19098) - #20266

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-19098-retire-generate-schema
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-19098-retire-generate-schema

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #19098
Fixes #20270
Clause-②: no (narrowing)

Executes maintainer ruling 5856790152 on #19098 (batch #227 item 5, letter C, maintainer 「同意」, confirmed 5856865990): os generate schema is retired, not repaired. No file is generated in its place, and nothing in packages/spec moves (ruling item 4).

What changed

packages/cli/src/commands/generate.ts

  • RETIRED_GENERATORS gains schema in the os g agent shape. The refusal reads "os g schema was retired" and cites the maintainer ruling. It names os validate (the real parse: ObjectStackDefinitionSchema.safeParse), and names the per-type schemas @objectstack/spec publishes at node_modules/@objectstack/spec/json-schema/CATEGORY/TYPE.json, which carry the published projection plus x-dropped-refinements. It exits 1.
    • The ruling's id lives in the ledger's code comment, not in the refusal. Text an author is shown carries no tracker number (AGENTS.md; check:doc-authoring's cross-package prose-id leg ratchets #NNN tokens in packages/** strings, and generate.ts has no ledger row).
  • Deleted, not left dead: runSchemaGeneration, with its three z.toJSONSchema(ObjectStackDefinitionSchema…) rungs and both ladder notices; KNOWN_UNSUPPORTED_JSON_SCHEMA_PATTERNS; isKnownUnsupportedJsonSchema; and the case 'schema' route.
    • Nothing else was schema-only. -o, --dry-run and --format serve types, client and migration. z and ObjectStackDefinitionSchema were dynamic imports inside the deleted function. zod stays a dependency, used by compile, validate and format. The type argument's help text derives from GENERATORS and never listed schema.
  • The ledger door moved (PM mechanism assumption 2, measured and falsified). The lookup sat inside runMetadataGeneration, which runs only after two earlier steps: the sub-command switch (where schema returned first) and the NAME requirement (where a nameless call stopped). So a schema entry alone could never be reached from os generate schema.
    • BEFORE, at BASE 0d3ec4713 on the dev runner, os g agent with no name printed Missing required argument and exited 1.
    • The lookup now runs first in Generate.run, through refuseRetiredGenerator, as an own-key read (the package's Object.prototype.hasOwnProperty.call idiom).
    • Consequences, both pinned. os g agent with no name now prints the agent retirement. os g constructor NAME used to print "os g constructor was retired — undefined" and crash with TypeError: retired.detail is not iterable. It now falls through to the ordinary checks: exit 1, "No file naming convention is declared for type: constructor".

Tests

  • packages/cli/test/generate-schema-retired.e2e.test.ts (new) is the retirement pin, in the agent precedent's shape: a real child process through bin/run-dev.js plus tsx, with assertions on stdout content and the exit status. It covers:
    • os generate schema (no name) and os g schema -o custom.schema.json both exit 1 and write nothing (directory listing []);
    • "was retired", with neither "Unknown type:" nor "Missing required argument";
    • "maintainer ruling", os validate, and @objectstack/spec/json-schema/;
    • the alias output is byte-equal to the documented spelling's;
    • os g agent (no name) answers the agent retirement;
    • os g constructor thing is not taken for a retired type;
    • control: os g object customer --dry-run still previews.
  • packages/cli/test/generate-schema-writes-json-schema.e2e.test.ts (the cli: os generate schema can never succeed — z.toJSONSchema(ObjectStackDefinitionSchema) throws in BOTH io directions, so the published IDE schema it exists to write is never written #17873 pin, groups (a) to (e)) is deleted. Every assertion in it was about the document the ruling withdrew: the file exists, parses, declares its draft, the four members' fragments, and the input direction. Nothing in it survives the retirement.
  • Other tests that exercised the command: none. git grep for generate schema, runSchemaGeneration, objectstack.schema.json and isKnownUnsupportedJsonSchema over packages/cli/src and packages/cli/test finds only the deleted file. The ladder PR (fix(cli): os generate schema falls back like every other toJSONSchema call site, so the IDE schema it exists to write is written #17903) added no pin of its own beyond that file.
  • packages/cli/test/generate-refuses-retired-generator.test.ts (new, patch round 1) is the per-PR guard for the door. It spawns the CLI and is not named .e2e, so it runs in the queue's integration project. It pins:
    • os generate schema and os g schema -o custom.schema.json: exit 1, nothing written, the retirement answered (neither "Missing required argument" nor "Unknown type:"), naming the ruling, os validate and @objectstack/spec/json-schema/;
    • os g constructor thing: an own-key miss (exit 1, not "was retired", no TypeError, nothing written);
    • control: os g object customer --dry-run exits 0 and previews exactly what the exported object template emits.
  • packages/cli/test/generate-agent-retired.e2e.test.ts (cli nightly e2e: generate-agent-retired.e2e.test.ts expects the pre-#20195 object template import (import * as Data) and fails on main #20270, its own commit a0cec66e9): the live-generator control asserted import * as Data from '@objectstack/spec/data', which the object template stopped emitting in 0bd11261e, so this nightly case was red on main. It now asserts the template's current shape, ObjectSchema among the named imports from @objectstack/spec/data plus the ObjectSchema.create({ call, never one exact import line. The case is neither skipped nor quarantined.

Docs (ruling item 2). The sweep is git grep -n "generate schema" -- content/docs, run at BASE:

hit disposition
content/docs/api/data-flow.mdx:200, the JSON Schema row ("Autocomplete and validation for objectstack.config.ts (via os generate schema)") removed
content/docs/references/api/plugin-rest-api.mdx:107 kept: a substring of "Auto-generate schemas", the REST plugin's generateSchemas option, in an auto-generated reference; it is not the command
content/docs/references/api/plugin-rest-api.mdx:282 kept: same
  • One docs edit goes beyond the grep-found set, declared here. The Mermaid node D[JSON Schema] and its two edges, in the same section directly above the row, pictured the removed row's output ("Build Time: generate, then JSON Schema, then feed IDE Autocomplete"). They are removed, and TypeScript Types now feeds IDE Autocomplete alone.
  • The page's front matter is untouched. docs(content): apply the approved search-intent title rule to 169 authored pages, short nav labels kept via navTitle #20170 landed its title-rule hunk on it, and the merge was clean.
  • content/docs/deployment/cli.mdx (patch round 1) gains a warn-type Callout, "os generate schema is retired", in the os generate section beside the os g agent one, the same shape as the two prior CLI retirements. It points at os validate (linked to the page's own os validate heading) and at the per-type schemas @objectstack/spec publishes. The refusal's Docs: link lands on this page. The page never listed a schema sub-type, so nothing else on it changes.
  • Also swept, with nothing to change:
    • packages/cli/README.md carries only the generic os generate row;
    • skills/ has 0 hits;
    • content/docs/protocol/diagram.mdx:243-255 (see Acceptance notes).

Changeset .changeset/19098-retire-generate-schema.md:

  • @objectstack/cli: minor, with a BREAKING banner and Clause-②: no (narrowing);
  • the ADR-0087 marker in the os g agent wording (not-required (no-migration-prescription));
  • reach outside the repository stated as NOT MEASURED (no telemetry);
  • the refusal's pointer given as the migration: delete the call and any editor mapping of objectstack.schema.json, run os validate, and point editors at the per-type schemas.

Verification, patch round 1 (at HEAD 2cb9e9613 unless stated)

  • Merge. origin/main 17bd31877 merged by merge as 9f702ee94 (scripts/pm/os-regen-merge.sh). No incoming file is one of this PR's.
  • Derived gates. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 93 commands: round 0's 92 plus pnpm check:cli-examples-parity, from the cli.mdx edit. All 93 exited 0 at 2cb9e9613. --ran with the recorded exit codes reads "93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero).
  • Named gates:
    • pnpm lint: exit 0 (118 s);
    • node scripts/check-issue-citations.mjs --base origin/main (ae8e3ca01): exit 0;
    • pnpm check:docs-audit-scope and node scripts/docs-audit/check-affected-docs.mjs: exit 0 each;
    • pnpm check:docs-transcript-drift and pnpm check:cli-examples-parity: exit 0 each;
    • pnpm --filter @objectstack/cli typecheck: VERDICT command-exit 0, with the new pin inside the tsconfig.test.json program (--listFilesOnly: 1 hit).
  • Per-PR pin. pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/generate-refuses-retired-generator.test.ts: 7/7. It sits in the queue's integration population (54 to 55 files) and is absent from the nightly population. The partition pin test/vitest-tiers-partition.test.ts passes 22/22.
  • Nightly. OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/generate-schema-retired.e2e.test.ts test/generate-agent-retired.e2e.test.ts: 2 files, 19/19. That includes "the generators that were not retired still work", the cli nightly e2e: generate-agent-retired.e2e.test.ts expects the pre-#20195 object template import (import * as Data) and fails on main #20270 case.
  • Shape proof for cli nightly e2e: generate-agent-retired.e2e.test.ts expects the pre-#20195 object template import (import * as Data) and fails on main #20270. The two new assertions accept the current emission: through the exported template, the ObjectSchema import matches and the create call matches. They reject the pre-fix(cli): os init and os generate object declare the scaffolded object with ObjectSchema.create #20195 emission taken from 0bd11261e^: neither matches.
  • Ablations on the per-PR file. These ran on committed state a2dda36c2. generate.ts and the per-PR file are byte-identical at 2cb9e9613.
leg mutation on-disk proof per-PR pin result restore
A schema ledger key renamed to schema_ablated anchor 1 to 0; blob 62cb7ec8b8ed to 7a1f27dd8cfb; grep schema-entry=0 ablated-entry=1 2 failed, 5 passed: "is the retirement, not a missing name and not an unknown type"; "names the ruling, os validate and the per-type schemas" blob equals HEAD 62cb7ec8b8ed, git diff HEAD empty
B own-key read reverted to RETIRED_GENERATORS[args.type] anchor 1 to 0; blob to 26ec87322f84; grep own-key=0 bracket=1 1 failed, 6 passed: the os g constructor thing case same

After both legs, the restored per-PR run passed 7/7.

Verification, round 0 (at HEAD c1036ebb9 unless stated)

  • Derived gates. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 92 commands, and all 92 exited 0 at c1036ebb9. --ran with the recorded exit codes reads "92 derived, 92 run, 0 NOT-MEASURED, 0 UNRUN" (a derived zero).
    • check-adr-0087-registration --base origin/main reads [BREAKING+clause-②-narrowing] not-required (no-migration-prescription).
    • check-changeset-no-major reads "no major bump". Its level axis reads the PR body, so it is judged in CI.
  • pnpm lint (repo-wide, eslint . --no-inline-config): exit 0, 130 s.
  • pnpm --filter @objectstack/cli typecheck: VERDICT command-exit 0. That is tsc --noEmit plus check:test-typecheck, and the new pin is inside the tsconfig.test.json program (--listFilesOnly: 1 hit).
  • Board check. node scripts/check-issue-citations.mjs --base origin/main (a9fb83ef0) exits 0: 73 citations judged, 71 resolve, 2 cross-repo.
  • unit layer, run at merge commit 5c7c5bee3:
    • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 ran 229 files: 227 passed and 2 failed as prerequisite refusals ("packages/cli is not built").
    • After the CLI build, those 2 files passed (29 tests).
    • c1036ebb9 differs from 5c7c5bee3 only in the new pin, which is not in the unit population.
  • Integration tier. The retirement pin is *.e2e.test.ts like the agent precedent, so it is nightly-tier: the per-PR queue run does not select it.
    • Run locally as OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 over the new pin plus generate-agent-retired, generate-skill and generate-object-namespace-prefix.
    • Result: 38 passed and 1 failed. The failure is the pre-existing stale control in generate-agent-retired.e2e.test.ts (see Acceptance notes); the new pin is 10/10.
  • Reach checks on the dev runner, BEFORE at BASE and AFTER at HEAD:
    • os generate schema -o out.json went from exit 0 and a 3,335,734-byte file written to exit 1, the refusal, and nothing written;
    • os g agent went from "Missing required argument" to the agent retirement;
    • os g constructor foo went from a TypeError crash to the ordinary refusal.

Ablation. This is a one-shot proof, run against the committed state c1036ebb9 through scripts/ablation-replace.mjs under a script-level trap restore. The subject resolves from src/ via tsx, so no dist is involved.

leg mutation on-disk proof pin result restore
A schema ledger key renamed to schema_ablated (the lookup misses it) anchor 1 to 0; blob 62cb7ec8b8ed to 7a1f27dd8cfb; grep schema-entry=0 ablated-entry=1 4 failed, 6 passed: "was RETIRED", "names the ruling", os validate, per-type schemas blob equals HEAD 62cb7ec8b8ed; git diff HEAD empty
B own-key read reverted to RETIRED_GENERATORS[args.type] anchor 1 to 0; blob to 26ec87322f84; grep own-key=0 bracket=1 1 failed, 9 passed: the constructor own-keys assertion same

In leg A the exit-1 and writes-nothing assertions stayed green, because the missing-name path also exits 1 without writing. That is exactly why the pin asserts the refusal's content, as the agent precedent does.

Acceptance notes

  1. Now repaired here (cli nightly e2e: generate-agent-retired.e2e.test.ts expects the pre-#20195 object template import (import * as Data) and fails on main #20270, commit a0cec66e9): the live-generator control in packages/cli/test/generate-agent-retired.e2e.test.ts asserted import * as Data from '@objectstack/spec/data', which the object template stopped emitting in 0bd11261e.
    • Triage's note-2 sweep covered all 73 nightly-tier files under packages/cli for pre-fix(cli): os init and os generate object declare the scaffolded object with ObjectSchema.create #20195 template text (import * as Data from '@objectstack/spec/data', Data.ServiceObject). That line was the only nightly hit; the control, the same sweep at 9f702ee94, finds it.
    • Two per-PR files carry the old shape on purpose and are not changed: scaffold-emission-typechecks.test.ts (a tsc canary fixture) and scaffold-object-declaration-shape.test.ts (it refuses the pre-ruling literal).
  2. .changeset/sour-moons-smile.md, pending and unreleased, records the fix(cli): os generate schema falls back like every other toJSONSchema call site, so the IDE schema it exists to write is written #17903 ladder repair this PR deletes. It is left unchanged.
  3. The docblock at packages/spec/scripts/lib/refinement-projection.ts (the "Producers outside packages/spec's own artifacts" bullet) still names the CLI's os generate as a direct z.toJSONSchema producer. It goes stale once this lands, but packages/spec is untouched by ruling item 4.
  4. The GENERATORS[type] lookup in runMetadataGeneration is still an inherited-key read. So os g constructor NAME answers "No file naming convention is declared for type: constructor" rather than "Unknown type". The answer is misleading but does not crash, and it is not changed here.
  5. Reach outside this repository is NOT MEASURED (no telemetry). Inside it there is no reader and no editor mapping of objectstack.schema.json.
  6. content/docs/protocol/diagram.mdx:243-255 carries a D[JSON Schema] node feeding IDE autocomplete, and a row "Generated JSON Schema files for IDE validation". It does not name the command, and the plural "files" reads as the per-type schemas @objectstack/spec still publishes, so it is left unchanged.

Generated by Claude Code

…s validate` and the published per-type schemas

Maintainer ruling on #19098 (comment 5856790152, letter C, confirmed
5856865990). `schema` joins RETIRED_GENERATORS in the `os g agent` shape.
The retirement ledger is now read in Generate.run before sub-command
routing and before the <name> requirement, as an own-key read; before
this, neither `os generate schema` (routed, nameless) nor `os g agent`
without a name could reach it. The three z.toJSONSchema rungs over
ObjectStackDefinitionSchema, their ladder notices and the
known-unsupported predicate are deleted with runSchemaGeneration.

The e2e pin of the written file is replaced by the retirement pin.
The data-flow docs row that advertised the command, and its diagram
node, are removed.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
…he current object template

The control copied the agent pin's `import * as Data` assertion, which
the object template stopped emitting in 0bd1126. It now asserts the
previewed file path and the `@objectstack/spec/data` import source.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 27, 2026
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 8 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via objectstack.schema.json (literal, a string literal in Generate), os generate (command, read off packages/cli/src/commands/generate.ts))
  • content/docs/protocol/kernel/lifecycle.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts))
  • content/docs/protocol/objectql/types.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-4.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 25 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 3f86dc52f22668dd92de00f604148e04d3d048da → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 39a73531da06c15ef3a1ca8e11fd2aa1239f1435 — the merge of head 2cb9e9613fb244c880a81303a252176ce2e67e6b into base 3f86dc52f22668dd92de00f604148e04d3d048da, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 39a73531da06c15ef3a1ca8e11fd2aa1239f1435 && git checkout 39a73531da06c15ef3a1ca8e11fd2aa1239f1435
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3f86dc52f22668dd92de00f604148e04d3d048da 2cb9e9613fb244c880a81303a252176ce2e67e6b && git checkout -B drift-repro 3f86dc52f22668dd92de00f604148e04d3d048da && git merge --no-ff 2cb9e9613fb244c880a81303a252176ce2e67e6b

node scripts/docs-audit/affected-docs.mjs --json 3f86dc52f22668dd92de00f604148e04d3d048da

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 3f86dc52f22668dd92de00f604148e04d3d048da → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c1036ebb9ac39465612299dd67d212806a075975

① Derived judgments

  • Ruling items 1–5 are executed, and nothing forbidden is touched. Correct.
    • Item 1: schema is an entry in RETIRED_GENERATORS (packages/cli/src/commands/generate.ts:567-592 at head). Deleted: runSchemaGeneration, the three z.toJSONSchema(ObjectStackDefinitionSchema…) rungs, both printInfo ladder notices, KNOWN_UNSUPPORTED_JSON_SCHEMA_PATTERNS, isKnownUnsupportedJsonSchema and case 'schema' (hunks @@ -3146,132 and @@ -3318,11).
    • Item 2: the content/docs/api/data-flow.mdx:200 row is removed.
    • Item 3: the changeset shape is judged in ② below.
    • Item 4: the diff stat is 5 files, none under packages/spec/.
    • Item 5: Fixes #19098 is line 1 of the body.
  • Every spelling is refused with exit 1, and nothing is written. Correct. The own-key guard at Generate.run (generate.ts:3240) runs before the types/client/migration switch and before the name check. So each of these reaches refuseRetiredGenerator (:603), which only prints and calls process.exit(1):
    • os generate schema and os g schema;
    • with -o, with --dry-run, or with a stray name.
      git grep 'toJSONSchema(ObjectStackDefinitionSchema' at head gives 0 code hits; the only hit is prose in .changeset/sour-moons-smile.md.
  • The moved lookup, enumerated. Correct.
    • os g agent with no name: BASE printed Missing required argument, because the name check came before the ledger inside runMetadataGeneration. HEAD prints the agent refusal. This is declared in the PR body and the changeset, and it is pinned.
    • os g agent NAME: the same text as BASE (printHeader('Generate'), then printError). Unchanged.
    • os g constructor NAME:
      • BASE read RETIRED_GENERATORS['constructor'], which is Object.prototype.constructor (truthy). retired.detail was then undefined, and the for…of threw a TypeError (verified in the removed hunk).
      • HEAD's own-key read is false, so the input falls through to GENERATORS['constructor'] (:998, still an inherited-key read) and exits 1 with "No file naming convention…".
      • The change is declared and pinned, and the residual is acknowledged in acceptance note 4.
    • os g toString and os g __proto__: without a name, the missing-name exit 1, unchanged. With a name, the same GENERATORS inherited-key path as BASE, unchanged.
    • Unknown type: unchanged.
    • Live generators: types/client/migration go through the switch; object/view/action/flow/dashboard/app/skill go through runMetadataGeneration. No path changes; there is one extra hasOwnProperty read.
  • Dead code: none left. Correct.
    • A head grep for runSchemaGeneration, KNOWN_UNSUPPORTED_JSON_SCHEMA_PATTERNS, isKnownUnsupportedJsonSchema, schema.objectstack.io/objectstack.config, objectstack.schema.json and Generate Schema gives 0 code hits.
    • The imports the deleted function used remain live elsewhere: createTimer 3, printInfo 8, printStep 3, printSuccess 7, fs. 17, path. 15.
    • Help text comes from Object.keys(GENERATORS), which never listed schema. There are no static examples. Neither the README roster (:61) nor cli.mdx:1409-1418 ever listed it, and skills/ has 0 hits.
    • One residual lies outside the ruling's grep radius. content/docs/protocol/diagram.mdx:243-255 carries the same C -->|"generate"| D[JSON Schema] -->|"feed"| E[IDE Autocomplete] node, plus a row "Generated JSON Schema files for IDE validation". This PR removed the matching node from data-flow.mdx, on its own reasoning that the node pictured the removed row's output. The diagram.mdx node does not name the command, and the plural "files" reads as the per-type schemas. Flag, not required.
  • The pin binds the refusal. Correct. generate-schema-retired.e2e.test.ts has 10 tests:
    • exit 1 for both spellings;
    • readdirSync(dir) equals [] right after the two schema runs (the default target and -o custom.schema.json);
    • the output says "was retired", and says neither "Unknown type:" nor "Missing required argument";
    • it names "maintainer ruling", "os validate" and "@objectstack/spec/json-schema/";
    • the alias output is byte-equal;
    • os g agent with no name;
    • constructor read as an own key, with no TypeError;
    • a live control.
  • The pin runs only nightly. Judgment: follow-up, not a required change.
    • scripts/nightly-tiers.mjs has NIGHTLY_TIERS=['e2e','live'].
    • git grep 'RETIRED_GENERATORS|was retired|refuseRetiredGenerator' -- packages/cli/test finds only the two e2e files. RETIRED_GENERATORS is not exported, so no unit test can read it.
    • The agent pin was per-PR when it landed (15b63e85a, 2026-08-22). The tier switch f48f3f1b2 (2026-09-07) made it nightly-only.
    • create.ts exports RETIRED_TEMPLATES, which has a per-PR unit pin (create-example-retired-docs-parity.test.ts).
    • This matches the ruling's literal "agent precedent's shape" and the repo's current tier policy, so the ruling does not owe a per-PR pin of the ledger door. It is recommended as a follow-up.
  • The docs edit is complete, and the page is coherent. Correct.
    • The row, the Mermaid D node and its two edges are everything the ruling's git grep "generate schema" finds.
    • plugin-rest-api.mdx:107,282 are the generateSchemas option (plugin-rest-api.zod.ts:606), not the command.
    • Build Time now reads C → E[TypeScript Types] → F[IDE Autocomplete]. The table has 3 rows, and the section title still holds through the TS types.
    • troubleshooting.mdx:437 names the per-type schemas, which is consistent with the refusal.
    • A precedent gap lies outside the claimed surface. Both prior CLI retirements carry a cli.mdx callout (:1450 for agent, :1488 for create example), and none is added here. The refusal's Docs: link points at cli.mdx, which says nothing about this retirement; it does document os validate at :642. Follow-up.
  • The ruling-id conflict: correct reading, and declared.
    • AGENTS.md :16-17 says runtime strings carry no tracker number. A comment id is a tracker number; an ADR citation, which the agent precedent uses, is not.
    • "By maintainer ruling it is retired, not repaired" names the ruling in substance. The id sits in the ledger comment (generate.ts:524), which check-doc-authoring names as the sanctioned home.
  • sour-moons-smile.md: adequate, not a required change.

② Semver level

@objectstack/cli: minor + Clause-②: no (narrowing) + **BREAKING** + adr-0087: not-required (no-migration-prescription) is correct:

  • AGENTS.md Post-Task item 3: "(narrowing) is BREAKING".
  • scripts/pm/clause2-line.mjs defines no (narrowing) as "NOT a widening, but breaking".
  • check-adr-0087-registration.mjs reads BREAKING from the banner and from the narrowing arm (:632,:640).
  • check-changeset-no-major applies its launch-window rule.

It is the same triple and marker category as .changeset/19120-install-door-parses-manifest-version.md, .changeset/19417-* and the agent precedent .changeset/retire-agent-generator.md (minor + BREAKING, with identical marker wording). PR body line 2, Clause-②: no (narrowing), is line-initial and matches the changeset.

③ Boundary flags

  • Files: exactly generate.ts, content/docs/api/data-flow.mdx, the new and deleted pins in packages/cli/test/, and .changeset/19098-retire-generate-schema.md. All are inside claim 5856964128's surface, and packages/spec/** is untouched.
  • PR body vs diff: every claim checked holds: the deleted symbols, the dynamic imports of z/ObjectStackDefinitionSchema, the help-text derivation, the 10-test pin and the docs table. One omission: the "also swept" list does not include content/docs/protocol/diagram.mdx (see ①).
  • Out-of-scope finding: CONFIRMED at source.
  • The declared stale docblock in packages/spec/scripts/lib/refinement-projection.ts: leaving it is correct under ruling item 4.
  • Precedent source: the PR fix(cli): retire the agent generator — os g agent now names ADR-0063 and points at skills #11028 API returned 404, so the precedent was read from merge commit 15b63e85a (same files, changeset and pin).
  • CI on c1036ebb9: 33 check-runs: 27 success, 2 skipped, 4 in progress (Lint & Repo Gates; Type Check · workspace; Test Core 4/6 and 5/6). 0 failures at read time. mergeable_state: blocked (draft, checks pending).

Implemented-by: claude/issue-19098-retire-generate-schema
Reviewed-by: session_01UYBdGBzWSrAMzpW8ah3GbP

Independence: INDEPENDENT AGENT (fed the card, the ruling and the PR only; not the dispatch order or the seat's conclusions)

VERDICT: PASS

…control reads the template; cli.mdx retirement callout

The retirement pin was nightly-only (`.e2e`). generate-refuses-retired-
generator.test.ts is the per-PR spawn guard: `os generate schema` and
`os g schema -o FILE` exit 1, write nothing and answer the retirement
(not a missing name, not an unknown type); `os g constructor NAME` is an
own-key miss with no TypeError; `os g object --dry-run` is the control.

The live-generator control in generate-agent-retired.e2e.test.ts asserted
a copied `import * as Data` line the object template stopped emitting in
0bd1126. Both controls now assert the preview equals what the exported
object template emits.

content/docs/deployment/cli.mdx gains the `os generate schema` callout
beside the `os g agent` one, pointing at `os validate` and the per-type
schemas.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
…tirement callout

The retirement pin was nightly-only (`.e2e`). generate-refuses-retired-
generator.test.ts is the per-PR spawn guard: `os generate schema` and
`os g schema -o FILE` exit 1, write nothing and answer the retirement
(not a missing name, not an unknown type); `os g constructor NAME` is an
own-key miss with no TypeError; `os g object --dry-run` is the control,
asserting the preview equals what the exported object template emits.

content/docs/deployment/cli.mdx gains the `os generate schema` callout
beside the `os g agent` one, pointing at `os validate` and the per-type
schemas.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
…the ObjectSchema.create shape (#20270)

`generate-agent-retired.e2e.test.ts` asserted the pre-#20195 object
template line `import * as Data from '@objectstack/spec/data'`, which
the template stopped emitting in 0bd1126, so the nightly e2e tier was
red on main. The control now asserts the template's current shape:
`ObjectSchema` among the named imports from `@objectstack/spec/data`,
and the `ObjectSchema.create({` call, not one exact import line. The
case is neither skipped nor quarantined.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
The pushed head a2dda36 carried the per-PR pin, the cli.mdx callout
and a first version of the stale-control fix in one commit. The line
merged here carries them split, with the stale-control fix as its own
commit a0cec66 (#20270). Conflict in generate-agent-retired.e2e.test.ts
resolved to that commit's ObjectSchema.create shape assertion; the merged
tree equals a0cec66's tree.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2cb9e9613fb244c880a81303a252176ce2e67e6b

Delta of: 5858240745 (PASS on c1036ebb), after REWORK 5858256961

① Derived judgments

  • The per-PR pin runs per PR. Correct.
    • packages/cli/test/generate-refuses-retired-generator.test.ts is not .e2e./.live., so scripts/nightly-tiers.mjs selectTierFiles (NIGHTLY_TIER_FILE_RE, with readTierMode unset → queue) keeps it in the queue population.
    • It imports execFile from node:child_process and names run-dev.js and .bin/tsx, so vitest-tiers.ts isIntegration (childProcess && (entryBasename || tsxBin)) places it in the integration project. ci.yml Test Core runs that project under OS_TEST_TIERS: queue (ci.yml:663).
    • Test Core 1/6–6/6 are green on the head.
  • The pin binds all three facts. Correct.
    • Exit 1 for generate schema and for g schema -o custom.schema.json (:121-124).
    • readdirSync(dir) equals [] after both runs, read before the control (:110,:127).
    • The text: toContain('os g schema was retired'), not.toContain('Missing required argument') and not.toContain('Unknown type:') (:130-136), plus maintainer ruling, os validate and @objectstack/spec/json-schema/ (:138-142).
    • errorLine is chalk.red(' ✗ ' + msg) (format.ts:365), so the backticked substring survives under NO_COLOR=1.
  • Ablations A and B redden it. Correct by trace.
    • A: without an own schema key, Generate.run (generate.ts:3240) falls through to the switch and then to Missing required argument (:3266), with exit 1 and nothing written. So exactly the two text cases fail.
    • B: RETIRED_GENERATORS['constructor'] is Object.prototype.constructor, which is truthy, so refuseRetiredGenerator prints "was retired — undefined" and throws on retired.detail (:607). The constructor case fails on both not.toContain('was retired') and not.toContain('TypeError') (:146-150).
    • This matches the reported 2/7 and 1/7.
  • The control is real. Correct. It uses the same directory and the same spawn path, and asserts exit 0, Dry run, and toContain of the exported GENERATOR_SCAFFOLD_TARGETS object template with two-space indentation (:158-166). That is exactly what the dry-run branch prints (generate.ts:1337-1345), so a door that refused everything fails it. A tmpdir with no config reads as kind: 'no-config' (project-namespace.ts:40), so there is no namespace, as the pin assumes.
  • Flakiness. Correct.
    • Four sequential cold starts run under an explicit beforeAll timeout of 240 s, the same as the convention file's five, and the it blocks are synchronous.
    • The child env is childEnv({ NO_COLOR: '1' }), with no bulk process.env reference, so check:cli-test-child-env has nothing to flag.
    • The ../../../node_modules/.bin/tsx escape uses the same resolve(HERE, …) spelling as generate-refuses-namespace-prefix.test.ts:62.
  • cli nightly e2e: generate-agent-retired.e2e.test.ts expects the pre-#20195 object template import (import * as Data) and fails on main #20270: the new assertion accepts today's template and rejects the old one. Correct.
    • generate-agent-retired.e2e.test.ts:156-157 asserts /import \{[^}]*\bObjectSchema\b[^}]*\} from '@objectstack\/spec\/data';/ and /const customer = ObjectSchema\.create\(\{/.
    • The template emits import { ObjectSchema } from '@objectstack/spec/data'; and const ${toCamelCase(name)} = ObjectSchema.create({ (generate.ts:139,:144). The pre-0bd11261e import * as Data / const customer: Data.ServiceObject = { matches neither.
    • This is the shape triage note 1 names, not one exact import line. Nothing is skipped or quarantined (note 3).
    • The earlier pushed version (a2dda36c2) anchored to the exported template; the merge resolved to the regex shape, as the merge message declares. Either satisfies the card. Flag, not required.
  • The note-2 sweep. Correct.
    • git grep at head over packages/cli for import \* as Data|Data\.ServiceObject|Data\.Object|ServiceObject: among the 73 nightly-tier files, the only hit is the new comment in the agent pin.
    • The two other test hits are per-PR files that carry the old shape on purpose: scaffold-emission-typechecks.test.ts:185 (a tsc canary) and scaffold-object-declaration-shape.test.ts:175-177 (a refusal fixture).
    • The remaining hits are CHANGELOG.md and a type import in secret-reference-union.ts.
  • The cli.mdx callout. Correct.
    • It is inserted at :1464, directly after the os g agent callout (:1450), inside #### \os generate` (:1379). It has the same warn+ title shape as:1450and:1506`.
    • [\os validate`](#os-validate)resolves to#### `os validate` (:642), the same slug pattern the os create examplecallout uses for#os-init (:80`).
    • These all match the refusal (generate.ts:567-592): "fails and writes nothing, not even objectstack.schema.json"; "retired by maintainer ruling"; os validate; the per-type schemas; "no replacement file".
    • The path node_modules/@objectstack/spec/json-schema/CATEGORY/TYPE.json matches the manifest keys (json-schema.manifest/data.json: data/Address, …) and the gitignored build output (.gitignore:63), which packages/spec/package.json publishes via files. x-dropped-refinements is emitted at build-schemas.ts:599.
  • Nothing previously judged correct moved. Correct. git diff c1036ebb 2cb9e961 -- generate.ts .changeset/19098-retire-generate-schema.md data-flow.mdx generate-schema-retired.e2e.test.ts generate-schema-writes-json-schema.e2e.test.ts is empty, and the origin/main merge 9f702ee94 touched none of the PR's files.
  • The PR body. Correct.
    • Lines 1–3 are Fixes #19098, Fixes #20270 and Clause-②: no (narrowing), all line-initial.
    • The checked claims hold:
      • the pin's seven cases and their ablation counts;
      • a0cec66e9 is its own commit, touching only the agent pin (+8/−1);
      • cb9f98c27 is the pin plus cli.mdx only;
      • generate.ts and the pin are byte-identical between a2dda36c2 and head;
      • the nightly-tier count is 73;
      • the two per-PR old-shape files are named in acceptance note 1;
      • the sour-moons-smile.md note now cites the DELIBERATE-CORRECTION class.

② Semver level

@objectstack/cli: minor + Clause-②: no (narrowing) + BREAKING + adr-0087: not-required (no-migration-prescription) stands. The changeset is byte-identical to the one PASSed at c1036ebb, and the delta adds no product change: a test file, a test assertion and a docs callout.

The #20270 rider is test-only, in packages/cli/test/. A changeset describes published behaviour, and none changes. No gate ties a changeset to each Fixes line:

  • check-empty-changeset.mjs asks only that the PR carry one, which it does;
  • check-closing-target-claim.mjs asks that each closed card's newest Claim: name the head branch, which both 5856964128 and 5858595032 do.

Check Changeset is green on the head. Confirmed: the rider owes no changeset of its own.

③ Boundary flags

  • Files: the whole PR (17bd31877...2cb9e961) is 8 files: generate.ts, .changeset/19098-retire-generate-schema.md, data-flow.mdx, cli.mdx, and four files under packages/cli/test/ (two new pins, one deleted pin, the agent pin). All are inside claim 5856964128 as amended (adding cli.mdx and the agent e2e file) or claim 5858595032 (test files only). packages/spec/** is untouched.
  • History:
    • 2cb9e9613 is a merge of a0cec66e9 and a2dda36c2. Both, and c1036ebb, are ancestors of the head, so nothing pushed is lost.
    • 2cb9e9613^{tree} equals a0cec66e9^{tree} (0a04af3f…). The only content difference between a2dda36c2 and head is the agent pin's assertion style (template-anchored → regex shape), as the merge message states.
    • Commit subjects carry (#20270), not a closing keyword. check-commit-card-trailers looks only for the spellings Fixes, Closes, Resolves, Part of and Refs.
  • Docblock tracker ids: the new pin's header names comment 5856790152. Test files are outside check-doc-authoring's scan (:634,:710), and it is not a runtime string. Not a finding.
  • CI on 2cb9e9613: 42 check-runs: 38 success, 4 skipped, 0 failure, 0 in progress at read time. Lint & Repo Gates (19:11Z) and the closing-claim and changeset checks re-ran after the 19:05Z body rewrite and are green. mergeable_state: unknown (draft).

Implemented-by: claude/issue-19098-retire-generate-schema
Reviewed-by: session_01UYBdGBzWSrAMzpW8ah3GbP

Independence: INDEPENDENT AGENT (fed the cards, the prior review and the PR only; not the dispatch order or the seat's conclusions)

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 19:17
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit f289f2b Sep 27, 2026
43 of 44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-19098-retire-generate-schema branch September 27, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment