Discovered 2026-08-21 by Yue, on MOTIR-3296: "we will add new lessons in the future, so we should add lesson with db migration which will run automatically in CI." Verifying that instruction surfaced a second, larger defect in the same plan.
MOTIR-3294 was authored claiming the lesson store has "no way to say which kind or type of card a lesson applies to", and its children planned a schema change and migration to add one. All of it already ships:
| planned as new | on origin/main |
|---|---|
| a set-valued category column | Lesson.categories String[] |
| set-membership filtering | AND "categories" && ARRAY[…]::text[] |
| empty ⇒ universal | the clause is OMITTED when no categories are supplied |
| categorized rows | ['ordering','design','task-type','tooling'] on seed rows |
The real gap is two orders smaller: the values are topical rather than structural, and SelectForInjectionInput has no categories field, so the planner's one shared helper cannot reach a filter that exists one layer below it.
This is an occurrence of phase-deepen.md's NEGATIVE limb — a negative is the only claim you can write without looking at anything — not a new rule. Bump that limb's warrant count; do not add a sibling rule. One git grep categories on the model would have closed it.
manual card the pipeline can do, forbidden by a lesson already in the storeMOTIR-3307 was authored type: manual, executor: human: run the seed against the deployed database. fly.toml's release_command is pnpm prisma migrate deploy, so the pipeline applies data migrations on every release with no operator.
lessons.base.ts already carries the lesson that forbids exactly this:
"check whether the service's deploy pipeline already runs migrations automatically on release … ship environment-specific DATA as a forward-only, guarded, idempotent data migration rather than a hand-run script … Reserve manual/human for work the pipeline genuinely cannot do — dashboards, secrets, third-party accounts — not for data a migration can carry."
It could not reach the planner who needed it. The runbook searches notes.html (the MOTIR-819 rule); this lesson lives in the product's retrieval store, which the runbook never queries. A lesson that is correct, on-point and unreachable is the exact failure MOTIR-3293 exists to fix, demonstrated against the plan for that epic.
run.md's mistakes-corpus step (MOTIR-819) states that the runbook's lesson search covers notes.html ONLY, and that the product's store holds lessons the runbook cannot see — so a planner authoring against motir-ai reads lessons.base.ts too, until the epic makes one corpus of them.notes.html as well, or the card records that it is and names the entry.notes.html carries the matching entry for this incident, with both defects.main.motir-ai prisma/schema.prisma — Lesson.categories String[], the column planned as new work.motir-ai src/repositories/lessonRepository.ts — the overlap clause and its omit-when-empty behaviour.motir-ai src/seed/lessons.base.ts — the deploy-pipeline lesson, and the rows already carrying categories.motir-ai fly.toml release_command · .github/workflows/ci.yml:107.motir-meta prompts/plan-rules/phase-deepen.md — the NEGATIVE limb this is an occurrence of.motir-meta prompts/run.md — the MOTIR-819 mistakes-corpus step that searches notes.html alone.