MOTIR-3306Done
Seed test — idempotent on sourceRef, TOTAL over the corpus with no hardcoded range, and every row carries legal axes
The seed's contract is additive and idempotent on sourceRef — a re-run inserts nothing and removes nothing. That already holds for 70 rows; this proves it at roughly 250, adds the axis assertions the backfill needs, and closes the totality hole that made the drift silent in the first place.
Four properties:
- Idempotency at the new size — apply the migration, count, apply again, assert the count is unchanged and no
sourceRefappears twice. - TOTALITY, derived rather than restated. Today
tests/lessonsBaseSeed.test.ts:83assertsseeded ∪ EXCLUDED_ENTRIES == 1..111exhaustively and then checks a hardcodedfor (const n of [156, 157, 158]). Everything above 111 that nobody typed into that list is unchecked — which is exactly how#112–#155went un-accounted for months under a green suite. Replace the hardcoded list with a check that derives the corpus's upper bound instead of restating it, so adding an entry tonotes.htmlwithout accounting for it fails a test.notes.htmllives in another repo, so if the bound cannot be derived at test time, assert an explicit, deliberately-maintained ceiling constant and make the failure message say what to do — a legible chore beats an invisible drift. Either way the test's own coverage must be total over1..<bound>, with no numeric range exempt. - Axis vocabulary — every value appearing in
kinds,typesorphasesacross all rows is legal. This is the assertion that earns its place: a typo (desing,Code,deepening) produces a row that is syntactically fine, passes every other check, and never matches any query for the rest of its life. The suite also reports the coverage split — how many rows constrain each axis, how many are unconstrained on all three — without asserting a target, so it documents the corpus rather than freezing it. - Genericity — no row contains a repo name, a
MOTIR-<n>key, or a date, mirroring the ban the shipped rule constant already enforces.
Acceptance criteria
- Applying the migration twice leaves the row count unchanged with no duplicate
sourceRef, asserted against the real test database. - The totality assertion covers
1..<bound>with no exempt range and no hardcoded list of entry numbers; adding an unaccounted entry fails, and the failure message names the entry and says what to do. - Every value in
kinds/types/phasesis in the legal vocabulary; the assertion names the offending row and value when it fails. - The suite reports the per-axis coverage split without asserting a target.
- No row matches the forbidden vocabulary; the assertion names the offending row.
- The suite runs in CI with the other lesson suites; only the changed files are run locally.
Context refs
motir-aitests/lessonsBaseSeed.test.ts— the existing idempotency suite, and (line 83) the totality hole this card closes.motir-aisrc/seed/seedBaseLessons.ts— the upsert keyed onsourceRef.motir-aitests/planningRulePacks.test.ts— the genericity ban this mirrors.- MOTIR-1948 — retired; its scope item 3 is the totality fix this card absorbs, and its body carries the original diagnosis.
- MOTIR-3299 — the three axes and their legal vocabularies.