Skip to content

fix(spec): route the stored-envelope refusal to os package publish, a command that exists - #12439

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-12223-publish-command-spelling
Aug 26, 2026
Merged

fix(spec): route the stored-envelope refusal to os package publish, a command that exists#12439
huangyiirene merged 1 commit into
mainfrom
claude/issue-12223-publish-command-spelling

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes#12223

An author who hand-writes one of the seven STORED_ENVELOPE_KEYS onto an api declaration is refused, and the refusal tells them where publication state actually comes from. It named a command that resolves to nothing.

before: Remove it — publication state is managed by `objectstack publish`, not authored.
after: Remove it — publication state is managed by `os package publish`, not authored.

Text only. No accept/reject behaviour change: the same seven keys are refused on the same declarations through the same unrecognized_keys upgrade 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 oclif Config, not against docs. Done on this branch, 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: []

61 ids matches the count this gate's own header recorded from command files. With topicSeparator: " ", package:publish is spelled os package publish at the prompt. There is no bare publish id and no publishtopic, 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.commands and got registered command ids: 4 with findCommand('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 module import() fail, silently dropping commands from the table. findCommand('validate') → not found was the tell, since validate.ts plainly exists. The reading above uses Plugin.commandIDs, which oclif derives from the dist/commands glob without importing the modules, and carries the validate sanity assertion inline.

Sites

SiteShape
packages/spec/src/api/endpoint.zod.ts :39the load-bearing one — a present-tense, prescriptive refusal message, i.e. the text an AI author obeys at the moment its write is refused
packages/spec/src/cloud/package.zod.ts :237doc comment on publisher; ships to consumers in the package's type declarations. the `objectstack publish` CLI sets itthe `os package publish` command sets it — "CLI" became "command" to keep the sentence grammatical, since os is the CLI and package publish is the command
content/docs/api/declarative-endpoints.mdx :89hand-written prose (frontmatter is title/description, no generated banner), so edited directly — no regeneration path. The objectstack validate on the same line is valid and is untouched
scripts/check-cli-command-ids.mjsthe baselined-violation row, deleted

The 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_VIOLATIONS is 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-ids until 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:

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).

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 with node 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 passed
  • pnpm --filter @objectstack/metadata exec vitest run src/stored-envelope — 1 file, 25 tests passed (the pin that walks STORED_ENVELOPE_KEYS and asserts each key gets this prescription)
  • pnpm --filter @objectstack/spec run typecheck — green, and it covers the test layer too via tsconfig.test.json, so no silent test-file exclusion
  • No projection to regenerate. These strings are refusal-guidance values, not .describe() values; no generated JSON contains the literal, and a full @objectstack/spec build left git status showing only the five intended paths. check:authorable-surface and check:generated both green.

Declared narrowing — one gate not measured:pnpm --filter @objectstack/spec run check:skill-examples exits 1 here with its own packages/client-react/dist holds no .d.ts declarations — the package is not built message and the explicit note that a verdict now would be a false green. Nothing was measured; it is not a finding. This diff touches no skills/ file. CI builds the closure and will run it for real. Two other gates (check:doc-formula-expressions, check:doc-security-posture) failed the same PREREQUISITE NOT MET way, were re-run after building @objectstack/lint and @objectstack/formula, and are green in the 39.

Changeset

patch on @objectstack/spec, not skip-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

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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot 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
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 126 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 4045b954d30f54675a19d634859380db196b6b37packageMentionDocs.

Which tree this was computed on

This run read content/docs from df2f92af33b5069bb327e9d61e155f1dcc84375d — the merge of head 89d84d573f73cfe18e2367538d7043a75405ec71 into base 4045b954d30f54675a19d634859380db196b6b37, 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 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

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

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/stooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

packages/spec: an author-facing refusal message routes to objectstack publish, a command retired with the direct-to-environment path

2 participants

@huangyiirene@claude