test(cli): sweep every os explain catalog entry against its spec schema - #15197
Merged
Merged
Conversation
…hema The `SCHEMAS` catalog in `packages/cli/src/commands/explain.ts` is hand-maintained and derives from nothing; until now one field of one entry was guarded by an exact-token assertion, and one entry's example was pinned by parsing it against the real schema. This generalises that parse technique across the whole catalog, deriving the entry set from `SCHEMAS` itself so a future entry cannot be added without being classified. Entries whose example does not parse today land as `it.fails` xfails naming the card filed for each; rewriting a catalog entry is operator-facing output and a separate review question, so nothing is fixed here. The two entries with no schema to parse against assert that reason rather than being silently absent. Also corrects a stale claim in this file's own comments: it is no longer outside every tsc program. `packages/cli/tsconfig.test.json` includes `test/**/*`, and this file carries no row in `test-typecheck-debt.json`, so a diagnostic it gains is red on arrival. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-litant
marked this pull request as ready for review
September 4, 2026 06:05
os-litant
enabled auto-merge
September 4, 2026 06:05
os-litant
deleted the
claude/issue-14811-explain-catalog-schema-guard
branch
September 4, 2026 06:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #14811
Generalises the parse-the-example guard from one catalog entry to the whole
os explaincatalog, and reports what that turns up rather than repairing it.The catalog is 11 entries, not 12
Triage refused to publish a count and required it be derived programmatically,
which was the right call: iterating
SCHEMASyields 11 keys —object,field,view,flow,agent,app,query,dashboard,action,workflow,trigger.The card says 12 and lists
fieldsalongsidefield. There is nofieldsentry in the catalog. Triage's step 2 named
fieldsas one of the two entrieswith nothing to parse against — that half of the instruction is about an entry
that does not exist, and the two real unbound entries turned out to be
workflowandtriggerinstead. Nothing was hand-counted to establish this:the guard's own classification test compares its table against
Object.keys(SCHEMAS)and goes red on any disagreement.What the sweep does
Each entry's
exampleis evaluated and parsed against the schema it names,resolved by name from the spec. Measured while wiring it up: the package
root
@objectstack/specexports none of these Zod schemas (129 rootexports, no
ObjectSchema/FieldSchema/ … among them), so every bindingnames its subpath —
/data,/ui,/ai,/automation.Three properties the block is built to have:
Object.keys(SCHEMAS), soa catalog entry added tomorrow makes this red until it is classified. A
filter would have been the exact shape of the defect being closed.
that assert their reason instead of being absent.
it.failscannot go green by accident. Vitest's xfail passes on anyfailure, so a broken subpath export or an unevaluable example would keep six
xfails green while measuring nothing. A separate assertion resolves every
bound schema to a real
safeParseand every bound example to an object —that one is a plain
it, and it is what makes the six xfails load-bearing.Results — 3 parse, 6 do not, 2 have nothing to parse against
fieldFieldSchemaflowFlowSchemaqueryQuerySchemaobjectObjectSchemaviewViewSchemaagentAgentSchemaappAppSchemadashboardDashboardSchemaactionActionSchemaworkflowtriggerTriggerSchemaat all — see #15176⛔ No catalog entry is repaired here. Rewriting one rewrites operator-facing
output and is a separate change with a separate review question; the ruling on
#14811 put that out of scope. Each broken entry is an
it.failsnaming its owncard, and the day one is corrected its xfail reports "Expect test to fail" —
that is the prompt to promote it to a plain assertion.
The sharpest of the six is
agent: it still documents and demonstratestools, a key removed in@objectstack/spec17 whose own rejectionmessage says there is no key the value moves to (ADR-0064). The
triggerentry is the odd one out and arguably worse than a broken example — it
documents a metadata type the spec does not have, and its sample is not a
Hookeither, so the guard rules that candidate out explicitly rather thanassuming it.
One correction outside the new block, named here
This file's own comment claimed it "sits outside every tsc program — see the
TEST_DEBT ledger". That is no longer true and the new code's safety argument
depends on the opposite, so it is corrected in place rather than left to
mislead the next editor. Evidence: #14710 landed
packages/cli/tsconfig.test.jsonwith
include: ["test/**/*"];tsc --noEmit --listFiles -p tsconfig.test.jsonresolves
packages/cli/test/commands.test.ts(1 hit), and the file carries norow in
test-typecheck-debt.json, so any diagnostic it gains is red on arrival.Confirmed by
pnpm --filter @objectstack/cli typecheckpassing with the ledgerheld at 3 files / 28 errors / 6 pinned signatures, unchanged.
⛔ The
object.ownershipexact-token assertion (#3244, widened at #5678) isuntouched — it guards a different axis and this parse check does not subsume it.
packages/cli/src/commands/explain.tsis unmodified.Reverse verification
Four ablations, each mutating the tree, proving the mutation landed on disk by
a marker that was absent before, running the suite, restoring via
git checkout HEAD -- <abs path>, and proving the restore by comparinggit hash-objectagainst the HEAD blob (an empty hash read as failure, not as"nothing to compare"). No rebuild leg is involved or needed: the test imports
../src/commands/explainas source through vitest, not through adist/export.
fieldexample (type: 'not_a_field_type')AssertionError: os explain field: its example must parse as FieldSchemabrand_new_entrytoSCHEMASexpected [ …(8) ] to deeply equal [ …(9) ]Error: Expect test to failobject's xfail markerAssertionError: os explain object: its example must parse as ObjectSchemaBoth restore legs verified clean after every case (
git statusempty, blobhashes equal to HEAD).
Verification — all at
e5a680b6, the final commitpnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/commands.test.ts—
Test Files 1 passed (1)·Tests 25 passed | 6 expected fail (31).pnpm --filter @objectstack/cli typecheck— green, both legs. The test-layerprogram reports
3 file(s) / 28 error(s) / 6 pinned signature(s) held in test-typecheck-debt.json, i.e. unchanged: this file is in that program andgained nothing.
Gate union derived, never hand-listed:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandson a clean tree — 35 commands from 1 changed path. Each exit code captured
before any pipe. 34 pass, 1 NOT MEASURED:
pnpm check:dual-build-cjs-loads—PREREQUISITE NOT MET … ⛔ This is NOT a pass: nothing was measured(exit 3). It reads built output and 12 packageshave no
dist/in this worktree, which built only the CLI's dependencyclosure. Not a red and not a green; CI builds everything and runs it. A
test-only diff ships nothing into any
dist/.node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 1 withcannot read the positive control at 621a4876…— the shallow-cloneprerequisite, not a red. After
git fetch --depth=1 origin 621a4876…(theobject only, so the shared
origin/mainpointer was not moved) it passes:✓ … 47 cases pass.Repo-wide
pnpm lintwas narrowed, and the narrowing is declared rather thansilent. Run:
npx eslint --no-inline-config --format jsonover the changedpath —
0 errors, 0 warnings, and the JSON reports 1 file linted, so thefile was really processed rather than skipped by an ignore rule. Why 1 is the
whole population that can move: the changed-path set is derived from git by
dispatch-gates.mjs(1 path vs merge base25a59bd10), not hand-written; andthis repo runs one
eslint.config.mjswhich "never enables type-aware linting(no
parserOptions.project, no typed@typescript-eslintrules) for ANY file"(its own measured note,
eslint.config.mjs), so no untouched file's verdictcan move because of this diff. CI runs the full scan regardless.
pnpm --filter @objectstack/cli exec vitest run(the whole package) isNOT MEASURED. It was started, held the shared verify lock for 745s while
writing nothing for the last 6.5 minutes of that, with a sibling agent queued
behind it for 500s — so it was stopped to free the lock rather than left to
hog it. Recorded as not measured rather than quietly dropped. The signal it
would have added is thin: this diff changes one test file and no source, so no
other test's behaviour can move; CI runs the suite.
Generated by Claude Code