No lesson CORRECTION can ever ship — the generator emits an UPDATE that its own delivery guard rejects as destructive
Found while running MOTIR-3391 (the kind-leaf / kind-bug mirror card), which is the first card in the corpus's history to try to CORRECT an existing BASE_LESSONS row rather than add one. It cannot.
What happens
scripts/generate-lessons-migration.ts has a fully-built, fully-documented CORRECTION path. Change any field of an existing row and re-run it:
[lessons:migration] wrote prisma/migrations/<ts>_lessons_base_data/migration.sql
— 0 new row(s) (0 global, 0 tenant), 1 correction(s), 1 of which re-embed.
The emitted SQL is an UPDATE "Lesson" SET … WHERE "sourceRef" = '…', ending in "embedding" = NULL so the release's embedding sweep re-ranks the row. The generated header describes exactly that, and calls itself a forward-only data migration.
tests/lessonsMigrationDelivery.test.ts then fails it:
× every insert is ON CONFLICT ("sourceRef") DO NOTHING, and none computes an embedding
a lesson data migration must only INSERT: UPDATE "Lesson" SET
The guard filters statement-initial /^\s*(DELETE|TRUNCATE|DROP|ALTER|UPDATE)\b/i and asserts the list is empty. UPDATE is in that list, and the correction path emits nothing else.
Why it has never fired
No correction had ever been generated. Re-running the generator on origin/main reports "nothing to emit — all 182 curated rows are already carried by a generated migration, at their current content", so the drifted branch had never produced a statement for the guard to see. Every migration in the directory is inserts only.
The two mechanisms disagree, and the guard is the one that is wrong
- The generator's header: "corrected rows are handed back to the release's embedding sweep, whose work list is exactly 'rows with no vector'".
src/seed/lessons.base.ts'sMERGED_ENTRIEScomment instructs authors to do this: "Where a merged entry sharpens the covering row, sharpen that row's CONTENT — the drift/refresh path carries content changes safely."tests/lessonsBaseSeed.test.tshas passing tests for exactly this behaviour — "refreshes a lesson whose curated content DRIFTED — a re-seed applies a corpus correction (+ re-embed)".
So three shipped mechanisms tell an author to correct a row, and the delivery guard silently makes the correction undeliverable.
The guard's own stated rationale is about DELETION, not about updates: "Forward-only: a data migration that deletes is not a delivery, it is a reset." UPDATE was swept into the pattern alongside the genuinely destructive verbs, and nothing distinguished them because no correction existed to notice.
Acceptance criteria
tests/lessonsMigrationDelivery.test.tsaccepts the correction shape the generator actually emits, and ONLY that shape: anUPDATE "Lesson"keyed onWHERE "sourceRef" = …. AnUPDATEwith nosourceRefpredicate, or one touching another table, still fails.DELETE/TRUNCATE/DROP/ALTERremain rejected outright — the guard's deletion rationale is unchanged and its protection is not weakened to pass.- A test proves an
UPDATEthat does NOT key on"sourceRef"is still rejected, so the narrowing is asserted rather than assumed. - The guard's
"embedding"assertion is reconciled with the correction shape: a correction legitimately writes"embedding" = NULL, which the currentexpect(text).not.toMatch(/"embedding"/)also rejects. Permit the literalNULLwrite and keep rejecting a vector literal. - A correction generated from a real edit to
src/seed/lessons.base.tspasses the whole suite end to end, demonstrated by the test rather than asserted in prose. - The two corrections this bug blocked are applied in the same PR or in a named follow-up:
notes.html #319's body gains the usefulness-is-not-the-test arm, and itskinds: ['bug']is widened to unconstrained.
Context refs
motir-aitests/lessonsMigrationDelivery.test.ts— thedestructivefilter, and the"embedding"assertion beside it.motir-aiscripts/generate-lessons-migration.ts— thedriftedbranch,hashes(), and the generated header describing the UPDATE contract.motir-aisrc/seed/lessons.base.ts—MERGED_ENTRIES' instruction to sharpen a covering row's content, and thenotes.html #319row carrying the blocked widening in a comment.motir-aisrc/seed/embedMissingLessons.ts— the sweep that consumes a nulled vector.motir-aitests/lessonsBaseSeed.test.ts— the two passing drift/re-embed tests the delivery guard contradicts.