Skip to content

fix(cli): os init and os generate object declare the scaffolded object with ObjectSchema.create - #20195

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-19722-scaffold-object-factory
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-19722-scaffold-object-factory

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #19722

Clause-②: no

Ruling 5644350230 (director seat, decision batch #122 item 1, maintainer 「同意」 2026-09-12), item 1: 「packages/cli/src/commands/init.ts TEMPLATES emit the factory shape; content/docs/deployment/cli.mdx:1323 describes it.」 Item 3: 「the changeset states how a user converts theirs (one mechanical rewrite: wrap the literal).」 This is the domain:cli half; the spec/scripts half landed as PR #19720 (42339e2f). #17418 remains open (it carries Blocked-by on this card and is the spec lane's to move). #19098 remains open (the other generate.ts card, serial behind this one).

What changed

Both doors that write a *.object.ts now emit the one authorised shape, ObjectSchema.create({ … }):

door before after
os init -t app / -t plugin (TEMPLATES[…].srcFiles) import * as Data … + const myAppItem: Data.ServiceObject = { … }; import { ObjectSchema } … + const myAppItem = ObjectSchema.create({ … });
os generate object (GENERATORS.object) const orderLine: Data.ServiceObject = { … }; const orderLine = ObjectSchema.create({ … });
  • The emitted shape is exactly what the changeset's user rewrite produces from the old one (wrap the literal, drop the annotation, import the factory), so a scaffold and a converted file look the same.
  • ObjectSchema is a value import: import type is erased at compile time and the module would throw on first evaluation.
  • The binding stays the file's default export. Both barrels (os init's src/objects/index.ts and the line os generate appends for all seven generators) re-export default, so no barrel spelling moves and no user barrel needs touching.
  • The authored OWD comment block is unchanged byte for byte in all three emitters (only the closing }; became });); init-template-comments-self-contained.test.ts is green.
  • generate.ts's docblock states that the init/generate parity now covers the declaration shape as well as the sharingModel value, and names the pin that holds it.

Premise check (on origin/main, sites located by symbol)

  • TEMPLATES (both object-bearing entries) and GENERATORS.object.generate emitted the annotated literal: confirmed.
  • create-objectstack's bundled blank/src/objects/note.object.ts is already export const Note = ObjectSchema.create({ … }): confirmed.
  • scripts/sync-scaffold-emission-policy.mjs syncs the pnpm/TypeScript ranges only and reads no declaration shape; pnpm check:scaffold-emission-policy was run (read-only --check) and is green.
  • The ruling's cli.mdx:1323 anchor has drifted with later edits. The page's only description of the scaffolded object shape was the os generate "What it does" line (it named Data.ServiceObject); that line now describes the factory (and names defineSkill({ … }) for skill, the one non-object type that is not a typed literal), and the os init section gains a short paragraph naming the shape and the one mechanical rewrite for older projects.

Measured: does the #19720 gate reach a scaffold? Before and after

Built @objectstack/cli at the base and at this branch, ran os init my-app -t app and os init my-plugin -t plugin (--no-install, under packages/cli/node_modules so @objectstack/spec is found by the upward walk), then os g object my_app_order_line in each, then the project's own gates. The repo gate was driven through its exported sweep() over a tree holding the four scaffolded object files (plus the driver file it reads its text family from).

reading before (base 3bd28e2b) after (this branch)
os validate / os compile / tsc --noEmit, init only exit 0 / 0 / 0 (both templates) exit 0 / 0 / 0
same, after os g object exit 0 / 0 / 0 exit 0 / 0 / 0
check-keyed-text-bounds sweep() over the 4 scaffolded files 0 objects parsed, 4 shape violations (… is declared as a plain object literal — use ObjectSchema.create) 4 objects parsed, 0 shape violations, 0 refusals
compiled dist/objectstack.json sha256 3981f1ab… (app), e6d2c61d… (plugin) byte-identical (cmp equal)

So the platform's own shape gate refused every scaffold before this change, but only as a repo script: a user project carries no scripts/, and os validate / os compile never judged the shape. After it, the gate parses all four. The compiled artifact is byte-identical, which is the measured basis for Clause-②: no (no published payload changes).

Pins

  • New: packages/cli/test/scaffold-object-declaration-shape.test.ts reads every emitter's bytes with the TypeScript parser (roster derived from TEMPLATES and GENERATOR_SCAFFOLD_TARGETS) and asserts: value import of ObjectSchema from @objectstack/spec/data; exactly one top-level declaration, initialised by ObjectSchema.create({…}), with no annotation; the default export is that binding; and one signature across os init and os generate object, which is the parity the docblock claims. Two controls prove the reader can refuse each half (the pre-ruling annotated literal; a type-only factory import).
  • Repointed (they asserted the refused spelling, per the domain:services pointer 5788276757): generate-emission-parses.test.ts (:148 and the class discriminator, which asserted const class:), generate-refuses-unparseable-name.test.ts:255, and the worked examples in emitted-source-parses.ts, generate-emission-parses.test.ts and the generate.ts refusal comment. Docblock-only: scaffold-emission-typechecks.test.ts (why the pin still stands after the annotation is gone) and generate-refuses-name-outside-charset.test.ts (const class: → const class =).
  • Unchanged and still covering it: scaffold-emission-typechecks.test.ts (tsc over every emitted scaffold), generate-scaffold-validates.test.ts and init-scaffold-authoring-rules.test.ts (runtime loads, which now execute the factory), init.test.ts (its assertions are name and barrel, not shape).

Ablation (the new pin can fail)

Committed first, then node scripts/ablation-replace.mjs swapped the os generate object emitter's import { ObjectSchema } for import type { ObjectSchema } and ran the pin: 2 failed / 5 passed. The failures were 'os generate object order_line' (is not value-imported … (type-only)) and one signature across every door (the generate door's signature diverged). Restore proven by the tool: blob 03b8006959dc == HEAD and git diff HEAD empty. The direction observed was red, as expected.

Verification (head 3082b024, after merging origin/main 836aad2a; round 1 at 468000c4 below)

main moved under this branch with PR #20164 (same package), so the suite was re-run after the merge:

  • @objectstack/cli unit tier, vitest run --project unit --maxWorkers=2 --shard=N/4 × 4: 226 files / 3199 tests passed (949 + 780 + 723 + 747).
  • @objectstack/cli integration tier, run locally because the diff touches two integration-tier files: generate-refuses-unparseable-name + generate-refuses-name-outside-charset, 2 files / 25 tests passed. The rest of the integration tier is declared to CI.
  • pnpm --filter @objectstack/cli typecheck (tsc + check:test-typecheck): exit 0; the new test file is in the test program (tsc -p tsconfig.test.json --listFilesOnly counts it).
  • pnpm lint (full, eslint . --no-inline-config): exit 0.
  • Gates derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (94): all 94 exit 0; --ran verdict: 94 derived famil(ies) accounted for — 94 run, 0 NOT-MEASURED.
  • Before the merge (head 20526f3d): unit tier 225 files / 3163 tests passed, the same two integration files 25/25, typecheck exit 0.
  • Round 1 (head 468000c4: origin/main d7c02413 merged as e5499d52, then the one-sentence cli.mdx correction naming defineSkill for skill): the 41 docs-scoped gates (dispatch-gates --commands content/docs/deployment/cli.mdx) all exit 0, --ran 41 of 41 accounted for, 0 NOT-MEASURED; pnpm lint exit 0; node scripts/check-issue-citations.mjs answered no issue citations added against d7c024133 (3 file(s) read). The cli test tiers were not re-run locally on this head; CI runs them.

Acceptance notes

  • scripts/check-keyed-text-bounds.mjs's refusal text says 「the os init shape imports only * as Data」. After this change that describes the shape older os init releases emitted, not the current one; it is still the right advice for a converted file. scripts/** is read-only for this lane. Carrier: the spec lane when it next touches that gate (for example when Two official scaffolders and two published docs disagree on how a .object.ts may be written — ObjectSchema.create() factory vs plain annotated literal #17418 is unblocked). Noted, not filed.
  • Reported to the seat, not addressed here: in an os init project, os g object order_line writes name: 'order_line', and the project's own os validate then refuses it (Object 'order_line' is missing the package namespace prefix). Measured at the base; this PR does not change it.
  • Local tooling observation: pnpm check:type-check-debt (--re-measure) runs a whole-workspace turbo run build before tsc. A local timeout that kills it mid-build leaves some packages' dist/ without declarations, and check:dual-build-cjs-loads then flags them. Rebuilding the two packages cleared it; CI builds fresh.

Generated by Claude Code

…t with ObjectSchema.create

Both doors that write a *.object.ts now emit the one authorised shape
(ruling 5644350230, decision batch #122 item 1) instead of a
Data.ServiceObject-annotated literal: a value import of ObjectSchema from
@objectstack/spec/data, const X = ObjectSchema.create({ ... }), and the
unchanged default export the barrels re-export.

Repoints the pins that asserted the refused spelling, adds a parity pin
over both emitters, updates the cli docs page, and carries the
@objectstack/cli changeset with the one mechanical user rewrite.

Claude-Session: https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m 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 3 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/cli/src/utils/emitted-source-parses.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/api/data-flow.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts))
  • content/docs/deployment/cli.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/getting-started/examples.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/getting-started/your-first-project.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/plugins/index.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/protocol/kernel/index.mdx (via os init (command, read off packages/cli/src/commands/init.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))

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

  • content/docs/releases/v17/17-1.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/releases/v17/17-4.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts), os init (command, read off packages/cli/src/commands/init.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
  • 1 changed file(s) yielded no anchor (packages/cli/src/utils/emitted-source-parses.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 207 client-bound route-ledger rows — the other 153 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 153: 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; 98 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 d7c024133e77f69aa0f26af359391c6a4b0142e4 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 5575e5a3039849e34164ca61d00cb483ca9d6e63 — the merge of head 468000c4d33c114821ce729f204f2286965f4673 into base d7c024133e77f69aa0f26af359391c6a4b0142e4, 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 5575e5a3039849e34164ca61d00cb483ca9d6e63 && git checkout 5575e5a3039849e34164ca61d00cb483ca9d6e63
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d7c024133e77f69aa0f26af359391c6a4b0142e4 468000c4d33c114821ce729f204f2286965f4673 && git checkout -B drift-repro d7c024133e77f69aa0f26af359391c6a4b0142e4 && git merge --no-ff 468000c4d33c114821ce729f204f2286965f4673

node scripts/docs-audit/affected-docs.mjs --json d7c024133e77f69aa0f26af359391c6a4b0142e4

⚠️ 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 d7c024133e77f69aa0f26af359391c6a4b0142e4 → 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: 3082b024c60d6bd0e137882dbbc7182a120ac757

① Derived judgments

  • Both emitters emit the ruled factory shape, with a value import from a published subpath — correct. packages/cli/src/commands/init.ts TEMPLATES.app.srcFiles['src/objects/__name___item.object.ts'] (:649) and TEMPLATES.plugin (:744), and packages/cli/src/commands/generate.ts GENERATORS.object.generate (:101–137) all emit import { ObjectSchema } from '@objectstack/spec/data'; (no type), one const X = ObjectSchema.create({ … });, export default X;. packages/spec/package.json exports['./data'] maps to dist/data/index.* and files ships dist; packages/spec/src/data/index.ts:124 export * from './object.zod' carries ObjectSchema with its create() (object.zod.ts ~:2498–2514). The two doors agree by construction and by pin (scaffold-object-declaration-shape.test.ts "one signature across every door"). Parity with create-objectstack is on the factory and the same value import from the same subpath (packages/create-objectstack/src/templates/blank/src/objects/note.object.ts export const Note = ObjectSchema.create({…})); export style (named there, default here) and Field.* helpers there vs plain field literals here differ, which the ruling does not govern.
  • Emitted file typechecks, parses and the gate accepts it — correct. CI evidence on the head: scaffold-emission-typechecks.test.ts spawns tsc --noEmit over every TEMPLATES and GENERATOR_SCAFFOLD_TARGETS emission; generate-scaffold-validates.test.ts bundleRequires the os generate object scaffold (so create() executes); init-scaffold-authoring-rules.test.ts writes both init templates and runs validateScaffold (schemaError must be null). None is a .e2e/.live nightly-tier file, so all three are in the queue population Test Core ran (success). Gate: scripts/check-keyed-text-bounds.mjs literalShapeDeclarations (:723–777): BINDING_HEAD matches const myAppItem, its initializer matches CREATE_INITIALIZER and is skipped; DEFAULT_EXPORT_HEAD reaches myAppItem;, not {, so consider returns; CREATE_CALL (:671) counts the one declaration. The gate's own self-test (:1642–1644) names export default ObjectSchema.create({…}) GOOD. The gate's walk never reaches a scaffold (templates are string literals; scaffolds land in tmpdir), so the new pin, not the gate, is what CI holds on this output.
  • Authored OWD comment block preserved — correct. init.ts diff is 6/6 lines: the import line, the const … = ObjectSchema.create({ line and }; → }); in each template; the seven comment lines are unchanged context. init-template-comments-self-contained.test.ts untouched and green.
  • Pins genuine — correct. New packages/cli/test/scaffold-object-declaration-shape.test.ts: roster derived from TEMPLATES and GENERATOR_SCAFFOLD_TARGETS (exported at generate.ts:430); on the old bytes shapeFindings yields absent import, ObjectLiteralExpression initializer and carries a type annotation, which its first control asserts verbatim; second control refuses import type. Repointed generate-refuses-unparseable-name.test.ts:255 and generate-emission-parses.test.ts:148 fail on the old shape; :167 const class = is still a parse failure (reserved word). The remaining edits (scaffold-emission-typechecks.test.ts, generate-refuses-name-outside-charset.test.ts, emitted-source-parses.ts) are docblock-only, as the PR says.
  • Changeset sentences — correct, and the ruling item 3 rewrite is stated. .changeset/19722-scaffold-object-factory.md (@objectstack/cli: patch): the before/after block is byte-shape-identical to the old and new init.ts template ("wrap the literal, drop the annotation, import the factory"). "Projects you already scaffolded keep working" — no reader changed in the diff; ObjectSchema is .strict() at the schema (object.zod.ts:1389), so defineStack parses both shapes alike; PR feat(scripts): refuse a non-factory *.object.ts declaration by name (ruling item 2 + census) #19720's sole file is scripts/check-keyed-text-bounds.mjs, so no published command refuses the old shape. "unknown top-level key … the message names it" — object.zod.ts:2514. Barrel lines unchanged (init.ts:647, generate.ts:970).
  • cli.mdx — one sentence wrong. The new os init paragraph (:144–151) is true on every clause. But the replacement at :1450, "the other types as typed literals (UI.View, Automation.Flow, etc.)", is false for skill: generate.ts:379 emits const …Skill = defineSkill({, its docblock (:375) says "Authored through defineSkill rather than as a bare typed literal", and the same page's callout ~20 lines above says skills are authored "with defineSkill". Five of six other generators are typed literals; the sentence asserts all. Wrong — a false statement introduced on a published page.
  • No other doc page states the old shape as current — correct. concepts/metadata-driven.mdx:363 shows const Account: ServiceObject = { under a "❌ Deprecated" heading; getting-started/quick-reference.mdx:298 is an import type listing; plugins/packages.mdx:34 is an import example; your-first-project.mdx:176 and schema-design.mdx:14–19 show the factory.

② Semver level

patch is right. AGENTS.md Post-Task Checklist 3: a bug fix in a released package takes patch; Clause-②: no is the ruling's own item 4 and holds on the diff — only emitters, docblocks, tests and docs change; no reader, accept-set or published payload moves (the PR's byte-identical dist/objectstack.json is consistent with that). Nothing an author can write is removed or renamed; prior output keeps loading through the same .strict() schema; the only refusal of the old shape is a repo script, not a published command. So users of prior output are not required to convert — the changeset says so and still states the mechanical rewrite as ruling item 3 demands.

③ Boundary flags

  • const X = ObjectSchema.create(…) + export default X rather than export const — sound. Both barrels re-export default (init.ts:647, generate.ts:970) and the init config does import * as objects from './src/objects' + Object.values(objects), so no barrel or config spelling moves; keeping default is what makes the changeset's rewrite exactly the emitted file.
  • Held files untouched — yes. The PR's 10 files contain none of packages/cli/src/utils/scaffold-validate.ts, packages/cli/src/commands/{validate,compile,lint}.ts, packages/spec/**, scripts/**; merge commit 3082b024 equals the parents' auto-merge tree (no evil merge); its diff against 836aad2a is exactly the 10 PR files.
  • CI on the head: 36 check-runs, 34 success, 2 skipped (Console Pin Gate, Packed-tarball smoke (opt-in)); all seven required contexts success. PR is draft, no governed path in the file list.
  • Attribution: both commits carry the model-free trailer pair; PR body footer is the session-URL form.
  • End-to-end os init is not exercised by CI; the emitted-file evidence in CI is the three cli pins named above, which is adequate.
  • Residual for the spec lane (correctly not touched here): scripts/check-keyed-text-bounds.mjs:787 refusal text "the os init shape imports only * as Data" now describes pre-change output only; noted in the PR's acceptance notes.

Implemented-by: claude/issue-19722-scaffold-object-factory
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: FAIL

  • content/docs/deployment/cli.mdx:1450 ("What it does", item 1): correct the clause "the other types as typed literals (UI.View, Automation.Flow, etc.)" so it does not claim os g skill writes a typed literal — generate.ts:379 emits defineSkill({ … }) and the page's own callout above says so. Everything else in the record stands; on that one edit the verdict is PASS.

Generated by Claude Code

… type

The item-1 sentence claimed every non-object type is written as a typed
literal; `os g skill` writes `defineSkill({ ... })`. The clause now names
the skill exception and lists the five typed-literal types exactly.

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: 468000c4d33c114821ce729f204f2286965f4673

Delta of: 5853875973 (FAIL on 3082b024)

① Derived judgments

  • Required change met — correct. The delta on the PR's own files is one line: content/docs/deployment/cli.mdx:1450 (delta stat restricted to the 10 PR files: cli.mdx | 2 +-, nothing else). The clause now reads "an object declared with ObjectSchema.create({ … }), the same shape the os init templates write; a skill declared with defineSkill({ … }); the other types as typed literals (UI.View, UI.Action, Automation.Flow, UI.Dashboard, UI.App)". The "etc." is gone and the list is closed.
  • Every claim in that sentence against GENERATORS at head — correct for all seven keys, none omitted. packages/cli/src/commands/generate.ts: object :111 const X = ObjectSchema.create({ with :106 import { ObjectSchema } from '@objectstack/spec/data'; view :166 const XViews: UI.View = {; action :208 UI.Action; flow :251 Automation.Flow; dashboard :295 UI.Dashboard; app :326 UI.App; skill :379 const XSkill = defineSkill({ (:366, from @objectstack/spec/ai). GENERATORS holds exactly those seven entries (:77–:416); runMetadataGeneration does a direct GENERATORS[type] lookup (:859), no alias or case-folding, so the accepted roster equals the sentence's roster. agent is in RETIRED_GENERATORS (:472) and exits 1 (:848–:857); the page's callout at :1431 says so. types / client / migration / schema are routed before metadata generation (:3154–:3175) and are not in the "Available types" table, so a reader cannot misread them into item 1.
  • "the same shape the os init templates write" — correct. init.ts :649–:651 (app) and :744–:746 (plugin): same value import, const XItem = ObjectSchema.create({, });, export default XItem;. empty has srcFiles: {} (:816), and the os init paragraph names only app and plugin. scaffold-object-declaration-shape.test.ts is byte-identical to 3082b024; its roster is derived from TEMPLATES + GENERATOR_SCAFFOLD_TARGETS (:53–:54, :138, :144) and it asserts "one signature across every door" (:166).
  • The os init paragraph (cli.mdx:144–151) — correct on every clause at head. Factory in both templates; "validates … when the file is evaluated" is object.zod.ts:2873–2915 create(), which throws on unknown keys before parsing and then runs ObjectSchemaBase.parse(withDefaults); "an earlier release carries a Data.ServiceObject-annotated object literal" is the pre-diff template (init.ts diff: -const … : Data.ServiceObject = {); the stated rewrite matches the changeset's.
  • No other sentence on the page misdescribes the emitters — correct. At head the page's only ServiceObject mention is :149 (the paragraph above). The "Available types" table (:1404–:1412) matches each entry's description / defaultDir. At the base d7c02413 the page's only mention was :1441 (the replaced line) and :1323 is a sys_metadata index table, so the PR body's "the ruling's anchor drifted" is true.
  • No other doc page states the old shape as current — still correct after the merge. Tree-wide at head, the only const X: ServiceObject = { under content/docs is concepts/metadata-driven.mdx:363, under "❌ Deprecated".
  • Changeset (.changeset/19722-scaffold-object-factory.md, unchanged in the delta) re-judged at head — every claim holds. "now declare … with ObjectSchema.create": init.ts:651 / :746, generate.ts:111. "parses … when the file is evaluated": create() above. "create-objectstack's starter … already used the factory": packages/create-objectstack/src/templates/blank/src/objects/note.object.ts:5 export const Note = ObjectSchema.create({. "the data-modeling docs": schema-design.mdx:14–19. "every object file in this repository": 110 tracked *.object.ts at head, every one contains ObjectSchema.create, none carries a : ServiceObject annotation (git grep). "the two CLI doors were the outliers": the datasource emitter (os datasource introspect emits the annotated-literal .object.ts shape ruling 5644350230 refuses — and the generator's own docblock calls its output a *.object.ts #19723, closed 2026-09-23) writes export const X = ObjectSchema.create({ at head (packages/services/service-datasource/src/external-datasource-service.ts:956–958); TypeScriptSerializer emits an annotated object literal although packages/metadata's own README says it is for ObjectSchema.create() #19724's TypeScriptSerializer still emits export const metadata: ServiceObject = … (packages/metadata/src/serializers/typescript-serializer.ts:97–99) but that card closed on the measured finding that no caller writes it to a *.object.ts (ACCEPT comment 5793838396, "A1 = NO"), so it is not a *.object.ts door and the claim stands. "barrel lines … unchanged": init.ts:647, generate.ts:970 export { default as X } from …. "the unchanged export default myAppItem;": init.ts:684. "keep working … nothing reads the old file differently": no reader in the PR surface; ObjectSchema is .strict() (object.zod.ts:1389) for both shapes. "the message names it": object.zod.ts:2514.
  • PR body verification section — correct; nothing claimed that the head does not support. Line 27's addition "names defineSkill({ … }) for skill, the one non-object type that is not a typed literal" is true (five typed literals, one factory, one defineSkill). Line 62 "origin/main d7c02413 merged as e5499d52, then the one-sentence cli.mdx correction" matches git log (e5499d52 parents 3082b024 + d7c02413; 468000c4 parent e5499d52, 1 file / 1 line). It says plainly the cli test tiers were not re-run locally; CI ran them (below). The local "41 docs-scoped gates" count is not independently verifiable; it is consistent with Lint & Repo Gates, Build Docs and Check Documentation Links all success on the head.

② Semver level

Unchanged by the delta: patch + Clause-②: no stands. The delta on PR files is one documentation sentence, and the merge changed no PR file: diff d7c02413..e5499d52 is exactly the 10 PR files, and diff 3082b024..e5499d52 is byte-identical to diff 836aad2a..d7c02413 (no evil merge). Main's only packages/spec/src/data change in the merged range is field.zod.ts .describe() strings and docblocks (three hunks, no acceptance change), and its only packages/cli change is a new test file (validate-field-predicate-traversal.test.ts), so neither the scaffolds' field shapes nor the pins move. No reader, accept-set or published payload changes anywhere in the PR surface.

③ Boundary flags

  • Merge e5499d52 is clean. Name list and diff content identical to what main carried in 836aad2a..d7c02413; no PR file touched by it. Held files untouched: packages/spec/**, scripts/**, packages/cli/src/utils/scaffold-validate.ts and packages/cli/src/commands/{validate,compile,lint}.ts are all absent from the 10-file surface.
  • Main since the base: one commit (560b724c: packages/formula, packages/lint tests, packages/spec/src/migrations); touches neither packages/cli nor cli.mdx; PR mergeable_state: clean.
  • CI on 468000c4 (check-runs API): 43 check-runs, 39 success, 4 skipped. The seven required contexts (rulesets API for main: TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Lint & Repo Gates, Governed Surface Queue Guard) are all success. Skips: Console Pin Gate and Packed-tarball smoke (opt-in) (same two as on 3082b024), plus the Auto Label and Check PR Size jobs of the 07:58 PR Automation re-run on the PR-edit event, whose 07:40 runs on the same sha are success. PR is draft; no governed path in the file list.
  • Attribution: both round-1 commits (e5499d52, 468000c4) carry the session trailer and a model-free co-author line; the PR body footer is the session-URL form.
  • Pins: none changed in the delta; Test Core success on the head covers scaffold-emission-typechecks, generate-scaffold-validates, init-scaffold-authoring-rules and the new declaration-shape pin.
  • Residual, not this card's and not a page falsity: generate.ts:3137 Args.type description lists six types and omits skill (pre-existing help string, outside the ruled surface). Still residual for the spec lane: scripts/check-keyed-text-bounds.mjs refusal text describes the pre-change os init shape (in the PR's Acceptance notes). The namespace-prefix finding is filed as [finding] os generate object NAME in an os init -t app project writes name: 'NAME' with no namespace prefix, so the next os validate refuses the object the CLI just generated #20197.

Implemented-by: claude/issue-19722-scaffold-object-factory
Reviewed-by: session_01UYBdGBzWSrAMzpW8ah3GbP

Independence: INDEPENDENT AGENT (fed the card, 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 08:10
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 0bd1126 Sep 27, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-19722-scaffold-object-factory branch September 27, 2026 08:28
os-litant pushed a commit that referenced this pull request Sep 27, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants