fix(spec): route the stored-envelope refusal to os package publish, a command that exists - #12439
Conversation
The refusal an author meets when hand-writing a stored-envelope key onto an `api` declaration named `objectstack publish`, which resolves to no command — `os publish` was retired with the direct-to-environment path. Re-measured against the BUILT oclif Config on this tree (not against docs): 61 registered ids, the only two containing `publish` are `package publish` and `plugin publish`, no bare `publish` id and no `publish` topic. Same stale spelling corrected in the `publisher` doc comment of `cloud/package.zod.ts` and in `content/docs/api/declarative-endpoints.mdx` (the `objectstack validate` on that line is valid and is left alone). The gate's baselined-violation entry is self-retiring by design: fixing the string makes it stop reproducing and reds `check:cli-command-ids` until the row is deleted, so it goes in the same commit. Text only — no accept/reject behaviour change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E5LFCYBJ3q2s6yW6oMLxwy
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin df2f92af33b5069bb327e9d61e155f1dcc84375d && git checkout df2f92af33b5069bb327e9d61e155f1dcc84375d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4045b954d30f54675a19d634859380db196b6b37 89d84d573f73cfe18e2367538d7043a75405ec71 && git checkout -B drift-repro 4045b954d30f54675a19d634859380db196b6b37 && git merge --no-ff 89d84d573f73cfe18e2367538d7043a75405ec71
node scripts/docs-audit/affected-docs.mjs --json 4045b954d30f54675a19d634859380db196b6b37 |
Fixes #12223
An author who hand-writes one of the seven
STORED_ENVELOPE_KEYSonto anapideclaration is refused, and the refusal tells them where publication state actually comes from. It named a command that resolves to nothing.Text only. No accept/reject behaviour change: the same seven keys are refused on the same declarations through the same
unrecognized_keysupgrade path — only the sentence an author reads changes.The spelling, re-measured on this tree against the LIVE command table
The card's answer was
os package publish; the constraint was to re-verify it against the CLI's built oclifConfig, not against docs. Done on this branch, afterpnpm --filter @objectstack/cli build:61 ids matches the count this gate's own header recorded from command files. With
topicSeparator: " ",package:publishis spelledos package publishat the prompt. There is no barepublishid and nopublishtopic, so the old spelling exits as an unknown command rather than falling through to topic help.One reading was discarded rather than reported. The first attempt loaded
config.commandsand gotregistered command ids: 4withfindCommand('package publish') → not found— which would have "confirmed" the fix was pointless. It was broken, not a measurement: unbuilt workspace deps made oclif's per-command moduleimport()fail, silently dropping commands from the table.findCommand('validate') → not foundwas the tell, sincevalidate.tsplainly exists. The reading above usesPlugin.commandIDs, which oclif derives from thedist/commandsglob without importing the modules, and carries thevalidatesanity assertion inline.Sites
packages/spec/src/api/endpoint.zod.ts:39packages/spec/src/cloud/package.zod.ts:237publisher; ships to consumers in the package's type declarations.the `objectstack publish` CLI sets it→the `os package publish` command sets it— "CLI" became "command" to keep the sentence grammatical, sinceosis the CLI andpackage publishis the commandcontent/docs/api/declarative-endpoints.mdx:89title/description, no generated banner), so edited directly — no regeneration path. Theobjectstack validateon the same line is valid and is untouchedscripts/check-cli-command-ids.mjsThe two deliberately-historical unresolvable ids #12177 declares elsewhere are not rewritten — those sentences are about the removal and are correct as history. This one is the opposite shape.
The ledger row is self-retiring, and that is why it is in this commit
BASELINED_VIOLATIONSis checked for entries that no longer reproduce (const stale = LEDGER().filter((e) => !seen.has(...))). Fixing the string makes the row stop reproducing, which redscheck:cli-command-idsuntil the row is deleted — the design working, not a second defect. The list is now empty, and the docblock records why an entry here can never outlive its defect.The gate's own verdict line, before and after:
276 → 277 is the corrected string joining the resolving population — it now resolves to a real command path instead of being carried as an exemption. Its 38-case self-test passes on both runs.
Verification
Gate union re-run on the final commit
89d84d5: 39 gates, 39 pass, 0 fail, each exit code captured before any pipe. The family was re-derived from the actual diff withnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(5 paths, 47 families matched) rather than taken from the dispatch list.pnpm check:cli-command-ids— green, verdict line above (load-bearing)pnpm --filter @objectstack/spec exec vitest run src/api/endpoint src/cloud/package— 3 files, 74 tests passedpnpm --filter @objectstack/metadata exec vitest run src/stored-envelope— 1 file, 25 tests passed (the pin that walksSTORED_ENVELOPE_KEYSand asserts each key gets this prescription)pnpm --filter @objectstack/spec run typecheck— green, and it covers the test layer too viatsconfig.test.json, so no silent test-file exclusion.describe()values; no generated JSON contains the literal, and a full@objectstack/spec buildleftgit statusshowing only the five intended paths.check:authorable-surfaceandcheck:generatedboth green.Declared narrowing — one gate not measured:
pnpm --filter @objectstack/spec run check:skill-examplesexits 1 here with its ownpackages/client-react/dist holds no .d.ts declarations — the package is not builtmessage and the explicit note that a verdict now would be a false green. Nothing was measured; it is not a finding. This diff touches noskills/file. CI builds the closure and will run it for real. Two other gates (check:doc-formula-expressions,check:doc-security-posture) failed the samePREREQUISITE NOT METway, were re-run after building@objectstack/lintand@objectstack/formula, and are green in the 39.Changeset
patchon@objectstack/spec, notskip-changeset— following what comparable message corrections in published packages did (automation-toggle-deny-message.md, a refusal-message fix;approval-snapshot-docstring-audit-evidence.md, a docstring-only correction that still shipped a patch because the docstring reaches consumers through the type declarations). Both sites here are in a published package and both are author-facing.Generated by Claude Code