MOTIR-3322Done
The lesson lifecycle is controlled — curated writes in, recurrence revives, non-recurrence retires
A lesson earns its place in the always-limited eight by still being relevant. The mechanism for that is retire by non-recurrence, and most of it ships — but the loop is open at both ends, so today it only ever removes.
What already works (lessonService, lessonRepository):
lastOccurredAton every lesson, andretirementCutoff() = now − 90 days.listForInjectionfiltersAND "lastOccurredAt" >= staleCutoff— an un-hit lesson drops out of the search.findNearestTenant+CAPTURE_DEDUP_DISTANCE— semantic matching of a new mistake against the library.recordRecurrence(id, now)— bumps the clock and re-assertsenabled, and its comment already promises the revival Yue describes: "re-activates the lesson if it had already aged out … since retirement is only a read-time injection filter."
Where the loop is open:
- The re-plan path never consults the library.
filePlanningBugfiles a bug in core and returns — no embedding, no match, norecordRecurrence. Thelog_planning_bugengine tool's own footer says "Filed by the Motir planner during a re-plan", so the commonest recurrence signal there is never lands on a lesson. A mistake can repeat indefinitely through re-plans while its lesson quietly ages out. - Global lessons can never be reinforced. The near-dup/reinforce branch is
tenant-only;globalgoes straight tocreateFromBase. The curated base corpus is global, so its rows are stamped once at seed time and expire 90 days later with no path back — and MOTIR-3296 is about to add ~195 more.
Close both and the loop runs in both directions: a repeat mistake is detected, the lesson it matches comes back into the search, and a lesson nobody has repeated in N days steps aside for one that matters.
Acceptance criteria
- A planning bug filed on the re-plan path is matched against the lesson library on the same semantic + axis basis capture uses; a hit calls
recordRecurrenceand links the bug to the lesson it matched. - A miss is a genuinely new mistake and is handled explicitly — the card states whether it creates a lesson or only files the bug, and why.
- Global lessons are reinforceable: a recurrence bumps a global lesson's clock and re-enables it, exactly as it does for a tenant lesson.
- A revived lesson reappears in the axis + text search, asserted end to end: seed → advance past the cutoff → confirm it is gone from results → record a recurrence → confirm it is back.
- The retention window stays a runtime-editable setting rather than a constant; it is not hard-coded by this story.
- The hit or miss is visible on the filed bug, so a reader can tell the third recurrence of one mistake from three unrelated ones.
Context refs
motir-aisrc/services/lessonService.ts—captureMistake's tenant-only reinforce branch,filePlanningBug,retirementCutoff,lessonRetentionDays.motir-aisrc/repositories/lessonRepository.ts—findNearestTenant,recordRecurrence, and thelastOccurredAt >= staleCutoffclause inlistForInjection.motir-aisrc/jobs/handlers/generateTree.ts—captureMistakeandfilePlanningBugcalled as separate steps.- MOTIR-3294 — the axes the match narrows on.
- MOTIR-3296 — the backfill whose global rows the second defect would expire.