Skip to content

feat(cli,create-objectstack): os generate picklist, the src/picklists starter barrel, and a Picklists count in the metadata summary - #21167

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-21018-generate-picklist
Oct 2, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-21018-generate-picklist

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Part of #21018
Clause-②: yes (widening)

This PR is the code half of #21018. It covers scope items 1, 2, 3 and 5 of the card body. Item 4, the "seven generator barrels" line in skills/objectstack-platform/SKILL.md, is on a Tier H governed path. It follows in its own PR under a second claim, so #21018 remains open for it.

Dispatched by the domain:cli seat's PM under claim 5929373213. Dev session session_01VvcEokUG1tvVxkceYfR5XB.

Premise, checked on origin/main

The card waited on the runtime reader. That reader is on main as 88b484e (#21047), and these are the parts this PR relies on:

  • picklists and picklistExtensions are in METADATA_ARRAY_KEYS (packages/objectql/src/engine.ts).
  • The registry fold resolves a field's picklist into served options (picklist-resolution.ts, resolvePicklistFieldsOnto).
  • The write door judges a write against the resolved options.
  • The boot refuses an unresolved name (packages/objectql/src/plugin.ts).

The repro below measures all of this end to end on a scaffolded project.

What changed

Item 1: os generate picklist NAME (packages/cli/src/commands/generate.ts)

Item 2: scaffold wiring

  • os init derives its wiring from the roster (SCAFFOLD_WIRED_BARRELS), so the app and plugin templates wire src/picklists with no edit to init.ts.
  • The blank starter in create-objectstack gains src/picklists/index.ts, byte-identical to the empty barrel os init writes. Its objectstack.config.ts gains import * as picklists from './src/picklists'; and picklists: exportsOf(picklists),.
  • create-objectstack-wiring-parity.test.ts holds the two scaffolders equal.

Item 3: docs

  • content/docs/deployment/cli.mdx gets the example line, the table row (picklist, src/picklists/, NAME.picklist.ts, picklists) and the "What it does" clause.
  • packages/cli/README.md gets the type list and a short paragraph.

Item 5: the metadata summary (packages/cli/src/utils/format.ts)

  • MetadataStats gains picklists, and collectMetadataStats counts it through the existing authoringRuleUnionStack fold, so both ADR-0130 D4 shapes are covered.
  • printMetadataStats renders it in the Data: row, the kind's own domain (domain: 'data' in the registry).
  • A stack with no picklists prints the row it printed before. A picklistExtensions entry is not counted as a list.
  • The two existing test fixtures that spell out every MetadataStats member gain picklists: 0: format.metadata-stats-package-fold.test.ts and print-metadata-stats-zero-row.test.ts.

Changesets

  • @objectstack/cli: minor. A new generator kind, and stats.picklists is added to the --json output of os validate, os build and os info.
  • create-objectstack: minor. The starter gains a wired barrel.
  • Both carry Clause-②: yes (widening). Nothing is narrowed and nothing is renamed, so there is no ADR-0087 marker.

Repro, before and after

Before, on origin/main 58a77db:

os generate picklist industry      exit 1   ✗ Unknown type: picklist   (roster lists 7 types)
npm create objectstack (blank)     src/ = actions apps dashboards flows objects skills views
hand-wired picklist + select field
  os validate                      exit 0   Data: 1 Objects  3 Fields

After, on this branch:

npm create objectstack (blank)     src/ = actions apps dashboards flows objects picklists skills views
os generate picklist industry      exit 0   ✓ Reaches the stack: objectstack.config.ts carries it in `picklists` as 'industry'
note.object.ts gains  industry: Field.select({ picklist: 'industry', label: 'Industry' })
  os validate                      exit 0   Data: 1 Objects  3 Fields  1 Picklists
  os build                         exit 0   Data: 1 Objects  3 Fields  1 Picklists   (artifact carries `picklists` and the field's `picklist`)
  os info                          exit 0   Data: 1 Objects  3 Fields  1 Picklists
  os info --json                   stats.picklists = 1
os dev --fresh -p RANDOM
  GET  /api/v1/meta/object/my_app_note   200   fields.industry = { picklist: 'industry', options: [option_a, option_b], … }
  GET  /api/v1/meta/picklist/industry    200
  POST /api/v1/data/my_app_note { industry: 'option_a' }   201
  POST /api/v1/data/my_app_note { industry: 'option_z' }   400 VALIDATION_FAILED · invalid_option · names picklist "industry"

On a project scaffolded before this change (its config wires no src/picklists), os g picklist region exits 0. It reports Not wired and prints the import line and the defineStack key to add.

Tests

The pins are:

  • packages/cli/src/commands/generate-picklist.pin.test.ts:

    • the roster row (src/picklists, stack key picklists, namesObject: false, no requires) and the NAME.picklist.ts file name;
    • the scaffold loaded through the loader os validate uses (bundle-require, BUNDLE_REQUIRE_EXTERNALS), parsed by PicklistSchema, under the item name os g reports;
    • that scaffold registered through ObjectQL.registerApp under picklists, serving its options on a Field.select({ picklist }) field, and passing PicklistServedFieldSchema;
    • a control: the same field with no list serves no options.

    It constructs ObjectQL, so it lands in the integration tier.

  • packages/cli/src/utils/format.metadata-stats-picklists.test.ts:

    • the count, top level and option-B;
    • that an extension is not a list;
    • the zero case;
    • the rendered row Data: 1 Objects 2 Fields 1 Picklists;
    • a control: a stack with no picklists prints Data: 1 Objects 2 Fields.

The runs, all on HEAD 5a53515 except where noted:

Run Files Tests Result
pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 241 + 2 3427 + 29 All passed, 29 skipped. The 2 files first failed with packages/cli is not built and passed after pnpm --filter @objectstack/cli build.
--project integration, the 8 integration files among the related tests 8 66 Passed: generate-picklist.pin, create-objectstack-stack-reach, generate-stack-reach, info-detail-package-fold and 4 more
OS_TEST_TIERS=nightly, the related *.e2e files 6 42 Passed. Includes generate-scaffolds-reach-stack.e2e: "os g picklist exits 0 and reports no wiring to add"
pnpm --filter create-objectstack exec vitest run 16 249 Passed
pnpm --filter @objectstack/cli typecheck (tsc --noEmit && check:test-typecheck) Exit 0. The test-typecheck ledger is unchanged (3 files / 28 errors)
pnpm --filter create-objectstack typecheck Exit 0

Ablation

The ablations ran on the committed fix (e63b1db). Each mutation went through scripts/ablation-replace.mjs with the anchor counted on disk. Each restore was proven: the blob equals HEAD and git diff HEAD is empty.

Leg Mutation Red Control (green)
generator The picklist row deleted from GENERATORS (anchor x1 → x0, 53 lines) generate-picklist.pin 3 of 5. wiring-parity: imports and stack keys (2) generate-scaffold-validates 19/19; the pin's file-name case and its no-list control
template picklists: exportsOf(picklists), deleted from the blank config wiring-parity: "hands every wired barrel to its stack key" (1 of 22) the other 21
collect picklists: count(stack.picklists), deleted metadata-stats-picklists 5 and package-fold 4 (its ZEROES comparisons) the pin's no-picklists row; print-metadata-stats-zero-row
print ['Picklists', stats.picklists], deleted metadata-stats-picklists "Data: row", and print-metadata-stats-zero-row "every metric collectMetadataStats counts is rendered" the other 27

Gates

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run with no paths, derived 96 families. All 96 were run, each with its exit code recorded before any pipe.
  • Five of them first refused with PREREQUISITE NOT MET (exit 3) because their build inputs were missing: check:skill-examples, check:dual-build-cjs-loads, check:i18n, check:i18n-coverage and check:i18n-walk-parity. All five passed once those inputs were built.
  • --ran reconciliation: ✓ dispatch-gates --ran: 96 derived famil(ies) accounted for — 96 run, 0 NOT-MEASURED.

pnpm lint, as a proven narrowing (on HEAD 5a53515):

  1. Population, read from eslint's own config (isPathIgnored / calculateConfigForFile): all 8 touched .ts files are linted. The 4 touched .md / .mdx files are outside its population.
  2. Count, from --format json: 8 files, 0 errors, 0 warnings, with --no-inline-config.
  3. Invariance: no resolved config for these files is type-aware (no parserOptions.project, no projectService, as eslint.config.mjs states). This diff touches no lint config or baseline. So it cannot move the verdict on any file it does not touch.

Not in this PR

Acceptance notes

  • The two starter README listings now name src/picklists (commit 90d55b1e13, added on contract review 5930680827):

    • the Layout bullet in packages/create-objectstack/src/templates/blank/README.md, which ships into every new project;
    • the tree in packages/create-objectstack/README.md.

    They ride this PR, not the Tier H item-4 PR, so two non-governed lines do not wait on a human approval.

  • Scaffold with repo dist is red on this PR, and the PR is held until the next release publishes. This note was added by the seat.

    • Cause: the job scaffolds with the repo-built create-objectstack, then installs the project's framework from the npm registry. The template's ^17.0.0 range resolves to the published @objectstack/spec 17.5.0, which predates the picklists stack key (addbbf0, .changeset/19518-picklist-kind.md, still unreleased). So the generated project's defineStack refuses the key as unrecognized at the validate step.
    • Scope of the measurement: the repro table's npm create objectstack rows and the end-to-end chain were measured on the repo build, not against the registry.
    • It is not this PR's code to fix. Its gate checks the template against the real registry, and the template now runs one release ahead of it.
      • The check is not required. It runs only on pull_request under its path filter (packages/create-objectstack/**, docker/** and its own workflow file), and it is not red on main.
      • Landing now would leave it red on every later create-objectstack PR until that release.
    • The hold: the release that carries the key is the open Version Packages PR chore: version packages #20639; its @objectstack/spec changelog already lists addbbf0. Once it merges and publishes, a re-run of this job reads the new spec and the template is coherent with it.
    • Until then, the PR stays draft and its card is pm:blocked on chore: version packages #20639.
  • On a project scaffolded by an earlier release, the not-wired hint prints picklists: Object.values(picklists),, not the starter's exportsOf(...) spelling. Both type-check once the barrel exports a list, which is the only state the hint is printed in.


Generated by Claude Code

claude added 4 commits October 1, 2026 10:34
… starter barrel, and a Picklists count in the metadata summary

os generate picklist NAME writes NAME.picklist.ts through definePicklist into
src/picklists, collected under the picklists stack key. os init derives its
wiring from the roster; the blank starter of create-objectstack gains the same
empty barrel and config lines. collectMetadataStats counts picklists, printed
in the Data: row of os validate, os build and os info.

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
…, with changesets

Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB
Co-authored-by: Claude <noreply@anthropic.com>
…project sees, as measured

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

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, create-objectstack, objectstack-blank, touching 7 documentable anchor(s). ⚠️ 5 changed file(s) yielded no anchor (packages/cli/README.md, packages/create-objectstack/README.md, packages/create-objectstack/src/templates/blank/README.md, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/deployment/cli.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts))
  • content/docs/getting-started/your-first-project.mdx (via 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))

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

  • content/docs/releases/v17/17-0.mdx (via MetadataStats (symbol, a top-level interface))
  • content/docs/releases/v17/17-4.mdx (via os generate (command, read off packages/cli/src/commands/generate.ts))
  • content/docs/releases/v17/17-5.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
  • 5 changed file(s) yielded no anchor (packages/cli/README.md, packages/create-objectstack/README.md, packages/create-objectstack/src/templates/blank/README.md, …) — 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 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 — 32 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 1371dc980cdf0d3128bee4a5441f2c6bec18f008 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 1371dc980cdf0d3128bee4a5441f2c6bec18f008

⚠️ 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 1371dc980cdf0d3128bee4a5441f2c6bec18f008 → 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: 5a535158c74d2b9e87e5adf7f03a5d87f477c54a
Local-runs: none

Inputs read: card #21018 (body, claim 5929373213, os-dev-report 5930446033); #20825 (answer B, dev report 5922744843, addendum 5922965902); #19519 and PR #21047 (88b484e00c, an ancestor of the head); the reverted row 6ef78d3f29 → e16359a9fa; PR #21167 body, file list (12 files, +329 / −5, matches) and git diff 70dae533c5 refs/review/pr-21167; the head's check-runs, polled to convergence; the head's sources by git show.

Checks on the head, collapsed latest-per-name, converged at 11:44Z: 37 names, 33 success, 3 skipped (Console Pin Gate, Packed-tarball smoke opt-in, Registry canary), 1 failure: Scaffold with repo dist (.github/workflows/scaffold-e2e.yml, job 110347683090, step 11 "Validate and build the generated project"). The required seven (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard) are all success. The red is NOT red on origin/main: that job runs only on pull_request under a packages/create-objectstack/** path filter (the nightly schedule runs the published-registry canary only), origin/main bafb8c9498 carries no check-run of that name, and the previous run of the same job, PR #21162 at a2d5d53c4e (11:06Z), was green. The red is this diff's; see ① (b).

Mergeability: git merge-tree --write-tree of the head onto a freshly fetched origin/main (bafb8c9498, fetched into refs/review/main-21167) exits 0 with no conflicted path. None of the 12 paths is routed to the merge=os-regen driver, so the local answer is GitHub's answer. Merge-base is 70dae533c5 (one main merge, as declared); main's 7 commits since touch none of the 12 paths.

① Derived judgments

(a) Item 1, the generator. The emitted file is definePicklist({ name: snake(NAME), label: Title(NAME), options: [two label/value entries] }), which is valid against PicklistSchema at the head (packages/spec/src/data/picklist.zod.ts: strict object, name SnakeCaseIdentifier, label string, options min 1 SelectOptionSchema; definePicklist parses at module load). The pin loads the scaffold through bundleRequire with BUNDLE_REQUIRE_EXTERNALS and parses it with PicklistSchema; green in Test Core. Conventions hold: the filename comes from metadataFileName('picklist', …), i.e. the registry row (kernel/metadata-plugin.zod.ts:825, filePatterns[0] **/*.picklist.ts, domain: 'data', loadOrder: 8), no override; the charset gate (nameCharsetRefusal, asking ObjectSchema.shape.name) runs for every type before anything is derived; namesObject: false is right (a picklist name is judged by no namespace gate, so neither the prefix nor the load-failed refusal applies); requires: [] is right (no capability token); the retired ledger is untouched; the reach report (measureStackReach / wiringLines) is per-generator generic, so "Reaches the stack" / "Not wired" come for free, and the e2e generate-scaffolds-reach-stack iterates the roster. The stack key is singularToPlural('picklist') = picklists through the SINGULAR_TO_PLURAL row (meta-spelling/manifest-collection-spelling.ts:93), the same key METADATA_ARRAY_KEYS reads first (packages/objectql/src/engine.ts:2962) and registerApp registers; the pin proves it end to end (served options equal the list's, PicklistServedFieldSchema passes, control with no list serves none). Enumerations of generator kinds: the oclif help text is Object.keys(GENERATORS) (generate.ts:3518), the Unknown type: listing iterates GENERATORS, GENERATOR_SCAFFOLD_TARGETS → SCAFFOLD_WIRED_BARRELS are derived, and the 14 roster-reading pins extend by themselves; generate-emission-parses.test.ts asserts containment and length ≥ 7 (its title still says "seven", harmless); the two prose counts in generate.ts are reworded; the cli README types list and the cli.mdx example, table row and "What it does" clause moved. metadata-file-name.ts:93 "across the seven generators" is a historical measurement and is correctly left. The roster counts still false at the head are the Tier H skills/objectstack-platform/SKILL.md:194-195 (item 4, owed) and the two create-objectstack README listings (③).

(b) Item 2, the starter. Structural half holds. The blank config gains the import and picklists: exportsOf(picklists),; src/picklists/index.ts is byte-identical to renderEmptyWiredBarrel('picklists', 'picklist') (init.ts:671-677). os init derives both the app and plugin templates from SCAFFOLD_WIRED_BARRELS = GENERATOR_SCAFFOLD_TARGETS.map(…) (init.ts:602-603; renderWiredImports / renderWiredStackKeys / withWiredBarrels at 752/778/781 and 846/873/876): one list, no second. create-objectstack-wiring-parity.test.ts reads that roster and TEMPLATES.app's render and compares the import lines, the helper, the stack-key lines, the requires tokens and each empty barrel's bytes against the template, so it holds the two together in both directions (the dev's row-absent and key-absent legs red 2 and 1 respectively, as its structure predicts).

Behavioural half: against the REPO build a fresh project validates and builds — create-objectstack-stack-reach.test.ts (integration: real bin/ scaffold → os g → os validate on workspace copies) and the dev's chain. Against the PUBLISHED framework it does NOT, and the head's own check says so: Scaffold with repo dist scaffolds with the repo-built scaffolder, npm installs the template's ^17.0.0 pins from the registry (resolves to 17.5.0, published 2026-09-29T08:09Z, no fallback taken, 446 packages), and npm run validate exits 1 with ✗ (root): Unrecognized key(s) on this stack definition: picklists from the published defineStack. The key entered ObjectStackDefinitionSchema at addbbf02ab (2026-09-30, #20823) and its changeset .changeset/19518-picklist-kind.md is still pending at the head, so no published spec accepts it. That job's contract, recorded at d51106d4dc (#9374), is the template against the real registry; the template now runs ahead of it. Consequence: the check stays red on every PR touching packages/create-objectstack/** until the lockstep release that carries that changeset publishes @objectstack/spec with the key; after that release the first-run path is coherent by construction (one changeset publish of the 69-package fixed group; the only residual is the publish-order window inside a release, 17.5.0 published create-objectstack 14 minutes before spec, which is a release-process property, not this PR's). Nothing inside this claim's surface can green it earlier: holding the wiring is what the card's own measurement ("4 red with the row alone") rules out, and the e2e workflow is outside the surface. What the PR owes and does not carry is the disclosure: the body says nothing about the red; its "After" table reads npm create objectstack (blank) … os validate exit 0 … 1 Picklists without saying it was measured on the repo build, and the os-dev-report reads CI as "22 in_progress (not waited on)" although the job had completed red at 11:31:15Z, 92 seconds before the report. An advisory red that lands undisclosed rides every later create-objectstack PR until stanched. This is the FAIL.

(c) Item 3, docs. content/docs/deployment/cli.mdx: the example line, the table row (picklist / src/picklists/ / NAME.picklist.ts / picklists / description) and the "What it does" item 2 clause match the roster, the registry and #21047's served behaviour; item 1's object-naming list correctly omits picklist. packages/cli/README.md: the types list and the three-line paragraph are accurate. No content/docs/releases/** and no CHANGELOG.md in the diff. Check Documentation Links green.

(d) Item 5, the stats row. collectMetadataStats counts count(stack.picklists) over authoringRuleUnionStack(config): the number of picklist DECLARATIONS in the union stack (top level, or each packages[] body, the same fold every other member uses), never references, and picklistExtensions is a different key so an extension is not a list (pinned). printMetadataStats adds ['Picklists', stats.picklists] to Data: between Fields and Extensions; the loop keeps items.filter(v > 0) and zeroFallback: ['Objects'] is unchanged, so a stack with no lists prints the row it printed before (pinned: Data: 1 Objects 2 Fields), and the zero-row pin's "every metric collectMetadataStats counts is rendered" plus the MetadataStats interface enforce from both ends. --json: stats is spread whole into three emitJson object literals (validate.ts:859, compile.ts:1144, info.ts:75); there is no declared Zod or TS payload type; build-json-advisory-parity.e2e.test.ts:306-322 pins the TOP-LEVEL key set only, with stats as one key, so stats.picklists is additive inside an open shape and the three payloads stay mutually consistent through the one interface.

(e) Pins and ablations. generate-picklist.pin.test.ts (5 cases) and format.metadata-stats-picklists.test.ts (6 cases) read as described. The four legs are consistent with the pins' structure: row deleted → PICKLIST undefined reds the three PICKLIST! cases, the filename case and the no-list control stay green (3/5), and parity's imports and stack-keys cases red (2); key deleted → parity's stack-keys case alone (1/22); collect line deleted → 5 of 6 in the new file (the control stays green because undefined > 0 is false) plus the 4 ZEROES comparisons; print row deleted → the Data-row case and the every-metric pin. The two fixture edits (picklists: 0) are forced by the interface (toEqual and typecheck). The reported chain is consistent with the code: served options and the 400 invalid_option naming the list are #21047's registry fold and write door; GET /meta/picklist/industry 200 is #21047's artifact-door mapping; os validate refusing an unknown reference is #21003's utils/picklist-references.ts, present at the head. One caveat on (e) as reported: the chain was run on the repo build only; see (b).

② Semver level

@objectstack/cli minor: a new accepted generator kind and a new stats.picklists key on three published --json payloads are "a new accepted key or value" under WHICH LEVEL (pr-automation.yml:754-793), so at least minor; feat( does not lower it. create-objectstack minor: the published package's output gains a file and a wired key, purely additive, under a feat(; minor is never below what the act requires, and the fixed group of 69 (both packages in it) versions in lockstep regardless. Both changesets carry Clause-②: yes (widening), so check-changeset-no-major.mjs's level axis (declared yes → at least one moved package ≥ minor) is satisfied; no major; nothing removed or renamed, so no BREAKING banner and no ADR-0087 marker is owed. Check Changeset green. Right.

③ Boundary flags

  • Deviations, each confirmed: the turbo-written AGENTS.md block is not in the diff (no governed path among the 12; fde553c509 sits on main after the merge-base, as stated); the two fixture edits are inside item 5's direct fixtures and forced by the interface; the item-2 measurement shape (row-absent 2 red, key-absent 1 red) is sound and matches the parity pin's structure; exactly one main merge, 70dae533c5 is the merge-base, not re-merged, main's 7 later commits are disjoint, merge-tree clean; the lint narrowing is superseded by Lint & Repo Gates green on this head; the three authored commits end with the model-free pair (Claude-Session, Co-authored-by: Claude), the merge commit 5a535158c7 carries no trailer at all (an observation, not a flag); Clause ② re-read matches the diff.
  • Out-of-scope finding, real at the head: packages/create-objectstack/src/templates/blank/README.md:70-76 ("Layout") and packages/create-objectstack/README.md:79-88 ("What Gets Generated") both omit src/picklists. The blank README ships into every new project and sits INSIDE the directory the claim names for item 2, so it belongs in this PR; the package README is one line in the same non-governed package and may ride with it. Neither belongs on the Tier H item-4 PR, which would make two non-governed lines wait on the maintainer's word.
  • Part of #21018 is the first line; no closing keyword anywhere in the body; Part-of PR must not also close its card green. Right, since item 4 remains.
  • No governed path in the diff: nothing under skills/**, docs/adr/**, .claude/**, docs/NORTH-STAR.md, AGENTS.md, CLAUDE.md; Governed Surface Queue Guard green. The ADR-0063 anchor on generate.ts is unaffected (the agent row stays in RETIRED_GENERATORS; check:adr-anchors green inside Lint & Repo Gates).
  • Item-4 line handed over: "the eight generator barrels (objects, views, actions, flows, dashboards, apps, skills, picklists)" is accurate to the roster at the head (8 rows, keys by singularToPlural), and SKILL.md:194-195 at the head still reads the db48028f1a text the dev quotes.

Remedy for the FAIL (no code change needed; a body edit leaves this head-sha standing): (1) add an Acceptance note naming the Scaffold with repo dist red, its cause (the template wires picklists; the scaffolded project installs the published spec 17.5.0, which predates the key; .changeset/19518-picklist-kind.md pending), its reach (every packages/create-objectstack/** PR until the release that carries that changeset) and that the repro chain was measured on the repo build; (2) the owning seat records on the PR whether it lands now with the advisory red or holds until that release — a needs-user-decision if the seat cannot; (3) carry the blank README "Layout" line here (and the package README line with it), not on the Tier H PR.

Implemented-by: claude/issue-21018-generate-picklist
Reviewed-by: session_01VvcEokUG1tvVxkceYfR5XB

VERDICT: FAIL

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Scaffold with repo dist is red on 90d55b1e13, as it was on 5a535158c7, and this PR is held. It is not readied or queued.

domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T12:07Z · follows the contract review 5930680827

  • What fails: the job scaffolds with this branch's create-objectstack, installs the framework from npm, and npm run validate refuses the template's picklists stack key as unrecognized. The registry's latest @objectstack/spec (17.5.0) predates that key.
  • Why it is not fixable here: the key's changeset (addbbf0, .changeset/19518-picklist-kind.md) is unreleased. The check is not required, but landing with it red would leave it red on every later create-objectstack PR. It is not red-by-design in its workflow's own terms, so the queue rule does not let this PR carry it.
  • What unblocks it: the release in the Version Packages PR chore: version packages #20639, whose @objectstack/spec changelog already lists addbbf0. After it publishes, the seat re-runs this job once and lands the PR if it is green.
  • No re-run now: the failure is deterministic on the published spec, so a re-run would fail identically.
  • The PR body's Acceptance notes carry the same reading.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 90d55b1e13821e4f8018776b809ebc8917b427aa
Local-runs: none

Re-review of PR #21167 after the FAIL record 5930680827 on 5a535158c7. Read: git diff 5a535158c7 refs/review/pr-21167 (re-fetched from refs/pull/21167/head), the PR body's Acceptance notes from the API, the hold comment 5931049852, the head's check-runs (converged), .changeset/19518-picklist-kind.md at the head, and #20639's head 718e74c744 through git show (fetched into refs/review/pr-20639). Unchanged detail is not restated; 5930680827 carries it.

What moved. One commit, 90d55b1e13 ("docs(create-objectstack): both starter README listings name the src/picklists barrel", model-free trailer pair present), touching exactly packages/create-objectstack/src/templates/blank/README.md (the Layout bullet gains src/picklists/, in the "empty to start" list, same sentence otherwise) and packages/create-objectstack/README.md (the "What Gets Generated" tree gains picklists/index.ts as the last leaf, skills re-drawn as a branch). Both lines are accurate to the roster at the head (eight generators, insertion order ending in picklist) and to the starter (an export {}; barrel wired under picklists through exportsOf). Every other blob of the 14-file diff against the merge-base 70dae533c5 is byte-identical to 5a535158c7 (checked per path by blob id: the ten code, test, doc and changeset files and the two template files). Total now 14 files, +336 / −11, matching the API.

Checks on 90d55b1e13, collapsed latest-per-name, converged (0 pending): 37 names, 31 success, 5 skipped (Auto Label, Check PR Size, Console Pin Gate, Packed-tarball smoke opt-in, Registry canary), 1 failure: Scaffold with repo dist (job 110359370992), red at the same step 11 "Validate and build the generated project" with the same message, ✗ (root): Unrecognized key(s) on this stack definition: picklists, after npm install resolved the template's ^17.0.0 to the registry's 17.5.0. The required seven (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard) are all success. Still not red on origin/main: e35c40a525 carries no check-run of that name and no failure at all, the job runs only on pull_request under its path filter, and the next run of the same job after this one, PR #21162 at 0f87e84889 (12:12Z), was green. The red remains this diff's, deterministic on the published spec.

Mergeability. git merge-tree --write-tree onto a freshly fetched origin/main (e35c40a525, into refs/review/main-21167) exits 0 with no conflicted path; none of the 14 paths is driver-routed; merge-base unchanged at 70dae533c5; main's commits since touch none of the 14 paths. No governed path in the diff.

① Derived judgments

(a), (c), (d) and (e) carry from 5930680827 unchanged: the code and test blobs are identical, and Test Core, Lint & Repo Gates and TypeScript Type Check are green again on this head.

(b) carries with its FAIL cause removed. The structural half is as before. The behavioural half is now stated correctly in the PR: the Acceptance notes name the Scaffold with repo dist red, its cause (the scaffolded project installs the published @objectstack/spec 17.5.0, which predates the picklists stack key; addbbf0; .changeset/19518-picklist-kind.md unreleased), the repo-build scope of the repro table and chain, the reach (every later create-objectstack PR until the release), and the hold. Each of those claims checks out at the head: .changeset/19518-picklist-kind.md is still present (pending) on 90d55b1e13; #20639's head 718e74c744 (regenerated 12:17Z) has addbbf02ab as an ancestor, its packages/spec/CHANGELOG.md opens with ## 17.6.0 and lists addbbf0: feat(spec): the picklist metadata kind … (#19518) at line 14, and the changeset is consumed there (absent). One immaterial imprecision: the note says the check runs "only … under packages/create-objectstack/**"; the workflow's filter also names docker/** and itself. The out-of-scope README finding from 5930680827 is closed by 90d55b1e13, inside this PR and not on the Tier H item-4 PR, as the remedy asked.

② Semver level

Carries from 5930680827: @objectstack/cli minor, create-objectstack minor, both Clause-②: yes (widening), no major, no BREAKING, no ADR-0087 marker owed; the changeset files are byte-identical and Check Changeset is green on this head. The README commit publishes nothing new that moves the level.

③ Boundary flags

  • Remedy (1), the disclosure: met as written; the Acceptance note is accurate (checked above).
  • Remedy (2), the landing decision: met on the PR. Comment 5931049852 (12:08Z) records the hold — not readied, not queued, no re-run now, until chore: version packages #20639 merges and publishes, then one re-run and a landing if green; the PR is draft: true with auto_merge: null, consistent with it. The card side of that note is not yet reflected at read time: cli + create-objectstack: os generate picklist and the src/picklists scaffold wiring (os init / npm create), after the picklist runtime reader #19519 lands (split from #20825 item 1) #21018 still carries pm:dispatched (no pm:blocked), its body still reads Blocked-by: #19519, and no comment follows the os-dev-report 5930446033. That is the seat's bookkeeping, not the contract; it is flagged so the card and the PR agree.
  • Remedy (3): met by 90d55b1e13, both listings, non-governed, in this PR.
  • The earlier deviations carry (one main merge at 70dae533c5, not re-merged; AGENTS.md not in the diff; the two forced fixture edits; trailers model-free on all four authored commits, the merge commit 5a535158c7 still carrying none). Part of #21018 is the first line, no closing keyword in the body, Part-of PR must not also close its card green. The item-4 line handed over is still accurate to the roster at the head, and skills/objectstack-platform/SKILL.md:194-195 is still the db48028f1a text, owed by the Tier H follow-up.

Landability. The contract is met on this head. The one red is an advisory, non-required check, disclosed in the body and held by the seat, that cannot turn green before the release in #20639 publishes @objectstack/spec 17.6.0 with the picklists key. Once that release publishes and a re-run of Scaffold with repo dist on this head is green, the PR is landable as it stands: required seven green, merge-tree clean against current main, semver and card linkage right, no governed path. The hold is the seat's and is not a verdict input.

Implemented-by: claude/issue-21018-generate-picklist
Reviewed-by: session_01VvcEokUG1tvVxkceYfR5XB

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 2, 2026 04:28
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 2, 2026 04:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit bcd68a2 Oct 2, 2026
39 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21018-generate-picklist branch October 2, 2026 04:54
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

Development

Successfully merging this pull request may close these issues.

2 participants