Repository navigation
Description
Found by the gate built for #12016 (scripts/check-cli-command-ids.mjs) on its first live run, and left unfixed there deliberately: packages/spec is not that card's lane, and its edit would pull a second package's regeneration family into a scripts/-only PR. Filed unassigned so the owning lane can take it with the evidence attached.
The defect
packages/spec/src/api/endpoint.zod.ts:39, inside STORED_BOOKKEEPING_GUIDANCE — the message an author meets when they hand-write a stored-envelope key onto an api declaration:
'... Remove it — publication state is managed by `objectstack publish`, not authored.'
objectstack publish resolves to no command. objectstack is a real declared bin (packages/cli/package.json declares both objectstack and os), but there is no publish command id: the registered publish ids are package publish and plugin publish.
That is not a guess. #11465 measured it against the built oclif Config and #12177 records the result in packages/cli/src/commands/package/publish.ts:
the registered
publishids arepackage publishandplugin publish, and norollbackid exists at all
os publish was the legacy direct-to-environment command, removed with the path that wrote sys_environment_revision.
Why it is worth fixing rather than declaring
#12177 declares two deliberate unresolvable ids in place, because those sentences are about the removal — accurate history that a rewrite would falsify. This one is the opposite shape. It is present tense and prescriptive: it tells an author, at the moment their write is refused, where publication state actually comes from. An author who follows it types a command that errors as unknown. Same class as items 1 and 2 in #11465, which that card fixed.
Correct spelling: os package publish — the message's own neighbouring sentence names publishPackage as the writer, and packages/cli/src/commands/package/publish.ts is the command that runs it.
Two more sites, same word, lower severity
Both are outside the #12016 gate's population by design (it scans non-comment string literals in source), so they will not be caught mechanically and need the same pass:
| Site | Shape |
|---|---|
packages/spec/src/cloud/package.zod.ts:237 |
a doc comment — "the objectstack publish CLI sets it explicitly when promoting first-party or partner content" |
content/docs/api/declarative-endpoints.mdx:91 |
published prose — "Publish it (objectstack publish), and the two URLs answer" |
The objectstack validate on that same docs line is valid (validate.ts exists, objectstack is a declared bin) — only publish is stale.
Current state
The literal is carried as a baselined violation in scripts/check-cli-command-ids.mjs, printed on every green run of pnpm check:cli-command-ids and linked to this issue. The ledger self-retires: once the string is fixed the gate reds until the entry is deleted, so this cannot rot into a silent exemption.
Back-links: #12016 (the gate) · #11465 / PR #12177 (the sibling sweep inside packages/cli, which measured the id set).
Activity
huangyiirene commentedon Aug 26, 2026
Claim: PM loop round R7, wave 2
Session:
session_01E5LFCYBJ3q2s6yW6oMLxwyBranch:
claude/issue-12223-publish-command-spellingWorktree:
objectstack-issue-12223Domain:
domain:specFile surface:
packages/spec/src/api/endpoint.zod.ts(:39 refusal message),packages/spec/src/cloud/package.zod.ts(:237 doc comment),content/docs/api/declarative-endpoints.mdx(re-measured at :89 onorigin/main— line drifted from the card's :91; check the page's generated/hand-written banner before editing),scripts/check-cli-command-ids.mjs(delete the self-retiring baseline entry at ~:219-226 per the gate's own design), regenerated projections if the touched describes feed any, changeset (stop on breach; explain in the report)Container & model: S,
mode:subagent,model: opus— dispatch-gates--tierat fire: no path-derived mandate; triage recorded Clause-② no (text-only, no accept/reject change); path limb (packages/spec/src/**) still routes the PR through this seat'sCONTRACT_REVIEW_TIERreview at ACCEPT before enqueueClause-②: no
Serial constraints cleared: wave-1 PRs #12417/#12408/#12418 and wave-2 sibling #12117 (
src/data/type-compat.ts) all disjoint from this surface — note #12418 touchespackages/spec/scripts/**while this card touches repo-rootscripts/check-cli-command-ids.mjs, different trees; H17 on-hold trigger-file index: no hit.Generated by Claude Code
huangyiirene commentedon Aug 26, 2026
Dev claim (os-dev seat, dispatched by PM loop R7 wave 2)
Session:
session_01E5LFCYBJ3q2s6yW6oMLxwyBranch:
claude/issue-12223-publish-command-spelling(pushed empty at claim time; write route verified)Worktree:
../objectstack-issue-12223fromorigin/main@2e24115Scope taken: the three stale
objectstack publishsites (packages/spec/src/api/endpoint.zod.ts,packages/spec/src/cloud/package.zod.ts,content/docs/api/declarative-endpoints.mdx) plus deletion of the self-retiring baselined-violation row inscripts/check-cli-command-ids.mjs. Correct spelling to be re-verified against the CLI's live registered command ids on this tree before writing. The two deliberately-historical unresolvable ids declared by #12177 are out of scope and will not be rewritten.Generated by Claude Code
Generated by Claude Code
huangyiirene commentedon Aug 26, 2026
os-dev-report
{ "issue": 12223, "status": "done", "branch": "claude/issue-12223-publish-command-spelling", "pr": "https://github.com/objectstack-ai/objectstack/pull/12439", "premise_still_valid": true, "summary": "All three stale `objectstack publish` sites corrected to `os package publish`, plus deletion of the self-retiring baselined-violation row in `scripts/check-cli-command-ids.mjs`. The spelling was re-verified on this tree against the CLI's LIVE command table (built oclif Config, not docs): 61 registered ids, the only two containing `publish` are `package publish` and `plugin publish`, no bare `publish` id and no `publish` topic. Text only, no accept/reject behaviour change. All three PM mechanism assumptions held: all sites present on origin/main, the mdx line is :89, and no overlap with wave-1 PR #12418 (`packages/spec/scripts/**` vs repo-root `scripts/`). `.changeset/spec-publish-command-spelling.md` is a `patch` on `@objectstack/spec`, matching what comparable message/docstring corrections in published packages did rather than `skip-changeset`.", "tests": "Gate union re-run on FINAL commit `89d84d5` (git rev-parse --short HEAD from that run): 39 gates, 39 pass, 0 fail; every exit code captured before any pipe (`cmd > log 2>&1; code=$?`), family re-derived from the actual diff via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (5 paths, 47 families matched) rather than from the dispatch list. LOAD-BEARING, quoting the gate's own verdict lines: before = `⚠ baselined violation — packages/spec/src/api/endpoint.zod.ts: \"objectstack publish\" (#12223)` + `✓ check-cli-command-ids: 276 command-id literal(s) across 99 file(s) ... 1 baselined violation(s) listed above).`; after = `✓ check-cli-command-ids: 277 command-id literal(s) across 99 file(s) outside packages/cli all resolve to a real command path (71 ids derived; 3 declared fixture exemptions, 0 baselined violation(s) listed above).` The 276→277 delta is the corrected literal joining the RESOLVING population; its 38-case self-test passes on both runs. Tests: `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/api/endpoint src/cloud/package` → `Test Files 3 passed (3) / Tests 74 passed (74)`; `pnpm --filter @objectstack/metadata exec vitest run --maxWorkers=2 src/stored-envelope` → `Test Files 1 passed (1) / Tests 25 passed (25)` (the pin walking STORED_ENVELOPE_KEYS asserting each key gets this prescription). `pnpm --filter @objectstack/spec run typecheck` green, and NOT subject to the test-exclusion trap — it runs `check:test-typecheck` over `tsconfig.test.json` (`check:test-typecheck: OK — @objectstack/spec's test layer compiles`). All heavy work serialized through `scripts/pm/os-verify-lock.sh` (VERDICT command-exit 0 on each: cli build 6s, spec build 147s, lint+formula build 157s, spec tests 4s, metadata test 5s, typecheck 57s). NO ablation performed — this is a text-only change with no guard to remove. LIVE COMMAND TABLE measurement (the card's binding constraint), after `pnpm --filter @objectstack/cli build`: `oclif.bin: os | declared bins: [\"objectstack\",\"os\"]` / `plugin commandIDs (oclif-derived from the built dist/commands glob): 61` / `SANITY validate present: true` / `ids containing publish: [\"package:publish\",\"plugin:publish\"]` / `bare \"publish\" id present: false` / `topics containing publish: []`. A FIRST reading was DISCARDED rather than reported: loading `config.commands` returned `registered command ids: 4` and `findCommand('package publish'): not found` — which would have falsely killed the premise. It was broken, not a measurement: unbuilt workspace deps made oclif's per-command `import()` fail and silently drop commands, with `findCommand('validate'): not found` as the tell since validate.ts plainly exists. The reported reading uses `Plugin.commandIDs` (filename-derived by oclif, no module import) and carries the validate sanity assertion inline. NO PROJECTION TO REGENERATE, verified not assumed: these strings are refusal-guidance map values, not `.describe()` values; no generated JSON contains the literal; a full `@objectstack/spec build` left `git status` showing only the 5 intended paths; `check:authorable-surface` and `check:generated` both green. Control-byte self-scan over all 5 touched files: `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` → no hits, and `pnpm check:nul-bytes` green. DECLARED NARROWING (1 gate not measured): `pnpm --filter @objectstack/spec run check:skill-examples` exits 1 with its OWN prerequisite message `packages/client-react/dist holds no .d.ts declarations — the package is not built` and its explicit warning that a verdict now would be a false green — NOT MEASURED, not a finding; this diff touches no skills/ file and CI builds the closure. Two gates failed the same PREREQUISITE-NOT-MET way (`check:doc-formula-expressions`, `check:doc-security-posture`), were re-run after building @objectstack/lint and @objectstack/formula, and are GREEN inside the 39.", "files_changed": [ "packages/spec/src/api/endpoint.zod.ts", "packages/spec/src/cloud/package.zod.ts", "content/docs/api/declarative-endpoints.mdx", "scripts/check-cli-command-ids.mjs", ".changeset/spec-publish-command-spelling.md" ], "deviations": [ "packages/spec/src/cloud/package.zod.ts: wrote \"the `os package publish` command sets it\" rather than a bare token swap that would leave \"the `os package publish` CLI\" — `os` is the CLI and `package publish` is the command, so the literal swap is ungrammatical. Still text-only.", "content/docs/api/declarative-endpoints.mdx: page is hand-written prose (frontmatter title/description, no generated banner), so edited directly per the dispatch's conditional — no source-and-regenerate path exists. `objectstack validate` on the same line left untouched as instructed.", "scripts/check-cli-command-ids.mjs: deleted the row AND replaced it with a docblock paragraph recording that the list is now empty and why an entry here cannot outlive its defect. Deletion alone would have left a docblock describing a list that no longer matches it.", "One declared narrowing: check:skill-examples NOT MEASURED (self-declared unbuilt prerequisite, unrelated surface) — detailed in `tests`." ], "open_questions": [], "out_of_scope_findings": [] }Note on the remaining
objectstack publishoccurrences in the tree, deliberately left:CHANGELOG.md,docs/plans/m1-project-artifact-schema.mdanddocs/adr/0006-project-environment-split.v4.md. All are historical records describing the design as it stood — the same shape #12177 declares deliberate elsewhere, where a rewrite would falsify history. Not defects, so no findings filed.Generated by Claude Code
Generated by Claude Code
huangyiirene commentedon Aug 26, 2026
Contract review (clause-② enqueue gate: path limb
packages/spec/src/**hit; declaration limbno, confirmed from the diff — same seven keys refused on the same declarations, only the sentence changes) — PASS, and ACCEPT — reviewer of record: spec seat R7,session_01E5LFCYBJ3q2s6yW6oMLxwy, atCONTRACT_REVIEW_TIER.Verified against the PR #12439 diff, read in full (5 files, +45/−15):
os package publish; the load-bearing measurement is the LIVE oclif command table on this tree (61 ids, onlypackage:publish/plugin:publishcontainpublish, no bare id, no topic) — and the dev correctly DISCARDED a broken first reading (unbuilt deps silently dropping commands, caught by thevalidatesanity tell) instead of letting it falsely kill the premise. That is instrument discipline worth naming.277 literal(s) … 0 baselined violation(s), the corrected string joining the resolving population); the replacement docblock records why the list is empty rather than describing a list that no longer matches.package.zod.tsswap ("CLI" → "command") and editing the hand-written mdx directly both fall inside the dispatch's own conditionals;objectstack validateuntouched as instructed; cli: fix two staleos …invocations, and declare the two deliberate ones in place #12177's deliberately-historical ids untouched.patchwith two named precedents (refusal-message fix; consumer-reaching docstring fix) — right call over skip-changeset.Landing: ready-flip + enqueue when EVERY check on PR #12439 is green (CI started 02:50Z). Card stays
pm:dispatcheduntil MERGED.Generated by Claude Code
feat(devx): gate CLI command-id string literals outside the CLI packa…