fix(spec): reject unknown keys on nested action-param literals at compile time - #12860
Conversation
…pile time ObjectSchema.create() infers its generic from the argument, so tsc's excess-property (freshness) checking never fires at any depth; the existing NoExcessObjectKeys map compensated only at the top level. A typo'd key on an actions[].params[] literal passed tsc clean and was caught only by ActionParamSchema's strict parse at module load. Mirror the same Record<excess-key, never> map over each element of each action's params array so the typo becomes a located tsc error at the authoring site. Compile-layer signal only: the strict parse at module load stays the enforcement of record; zero changes to any Zod schema's accept/reject or to parse timing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JvjTCjJQn9zSTXEhUKgT7s
📓 Docs Drift Check4 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 a4d10ae6bd4861f5185984122160051a3d751ab7 && git checkout a4d10ae6bd4861f5185984122160051a3d751ab7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin c097f7175a3fd449164c4de295a0f544f02e1047 eeddce00184b4c983e3cdd8e7e0de9c1b2d22822 && git checkout -B drift-repro c097f7175a3fd449164c4de295a0f544f02e1047 && git merge --no-ff eeddce00184b4c983e3cdd8e7e0de9c1b2d22822
node scripts/docs-audit/affected-docs.mjs --json c097f7175a3fd449164c4de295a0f544f02e1047 |
Fixes #12615
What
Triage route leg ② landed: the seam typing in
packages/spec.ObjectSchema.create()infers its generic from the argument, so TypeScript's excess-property (freshness) checking never fires at any depth; the existingNoExcessObjectKeysmap compensated only at the top level. A typo'd key on anactions[].params[]literal therefore passedtscclean and was caught only byActionParamSchema's strict parse at module load. This PR mirrors the sameRecord(excess-key, never)map over each element of each action'sparamsarray (NoExcessNestedActionParamsinobject.zod.ts), turning the typo into a locatedtscerror at the authoring site.No runtime behaviour change: zero edits to any Zod schema's accept/reject, zero changes to parse timing. The strict parse at module load stays the enforcement of record.
Measurements
carryOverX: trueon theobject_permissionsparam literal insys-permission-set.object.tsat origin/main (6f0fec3);pnpm --filter @objectstack/plugin-security typecheckwas GREEN (VERDICT command-exit 0). Plant removed, restore proven by blob equality (e726f251).@objectstack/specwith the seam —sys-permission-set.object.ts(150,79): error TS2322: Type 'true' is not assignable to type 'never'.The location (line 150, col 79) is thecarryOverXtoken itself; the single-line tsc form names the key by position, and the runtime parse (pinned in the new test) names it by spelling. Restore again proven by blob equality.pnpm typecheck(turbo, 129/129 tasks) GREEN with ZERO new errors — no legitimate call site lights up, no cast or widening added anywhere.object.test.ts— the@ts-expect-errorcompile pin (goes unused and failscheck:test-typecheckif the nested map is removed;object.test.tsis not in the test-typecheck debt ledger, so the directive is live) plus the runtime pin that the strict parse still throws namingcarryOverX, and a positive control covering an action with params (canonical spellings) and one without. Full@objectstack/specsuite at HEAD eeddce0: 436 files / 11562 tests passed.scripts/pm/dispatch-gates.mjsfrom the actual changed set, re-run post-commit at HEAD eeddce0): all 33 path-derived families plus the test-kind convention families (check:query-options-erasure,check:type-check-coverage,check:type-check-debtre-measure — "none above its recorded number",check:engine-double-contract,check:where-matcher,check:cross-package-test-inputs) GREEN. One NOT MEASURED:scripts/pm/check-half-states.mjsexits 3 "PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential" (container has no GitHub token; board-sweep gate, no reading).Changeset
@objectstack/specpatch: the published.d.tsshape ofcreate()'s parameter type changes (tightens), but no working code changes meaning — every literal the new constraint refuses was already refused at import by the strict parse. Not a value-set narrowing, so patch rather than minor.#12615 is fixed by this PR; no other issue is affected.
Generated by Claude Code
Generated by Claude Code