MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3549

Planning bug: MOTIR-3547 was sealed UN-SIZED at ~5h across three repos, and asserted a code fact read from ONE of a tool's TWO return channels

Done
Description

Two defects from one authoring pass, both caught by motir run MOTIR-3547 twenty minutes after the card was filed. The correction is already applied — plan cmt9vveui00i7i2n8zoaariym splits the card into seven children and removes the false criterion — so this card records the defects, not the fix.

(1) Sealed with NO sizing, which is the one shape the detector cannot see

MOTIR-3547 was created with storyPoints: null and estimateMinutes: null, carrying eight acceptance criteria that enumerate a schema migration, an HTTP route, a client seam, an MCP tool, a generator change, tests in two repositories and corpus edits in two homes — roughly five hours against plan-rules/core.md gate 4's one-hour agent-run ceiling.

The mechanised guard is structurally blind to this. validate_work_item's likely-over-gate-sizing advisory returns at its executor guard and compares both numbers !== null, so a card carrying neither is invisible to it. validate_work_item returned valid: true, advisories: [] — correctly, and uselessly. plan-rules/core.md gate 20 already names this exactly: "an UNDER-authored card is the one case where every field the detector needs is the field that is missing, and un-sized is the commoner and quieter failure than over-sized."

What made it reachable. The card was filed in a conversational turn, not inside a motir plan pass, so no gate checklist ran over it. That is the standing hole: a card authored by create_work_item outside a planning pass is sealed by nobody. Gate 20's remedy — non-null storyPoints / estimateMinutes / type / executor as a condition of sealing a leaf — has no enforcement point on that path.

(2) A code fact asserted from ONE of a tool's TWO return channels

The card asserted, as measured, that search_lessons "returns no identifier … so the reinforcement call is unaddressable", citing motir-core lib/mcp/tools/searchLessons.ts:150.

That line is real and the reading of it was correct: summarizeLessonSearch renders title (scope) / body / → howToApply and no id. But it is the PROSE channel, and the tool returns two. runSearchLessons ends toolOk(summarizeLessonSearch(result), exempt(SEARCH_LESSONS_TOOL_NAME, { outcome, lessons })) — and lessons is RankedLessonDTO[], whose first field is id. Traced on origin/main, the id survives every hop: POST /v1/lessons/search (motir-ai src/app.ts, id: l.id) → RawRankedLesson.id (lib/ai/motirAiClient.ts:1153) → toRankedLessonDTO (lib/services/projectLessonsService.ts:162) → the structured payload, which lib/mcp/payloads/exemptions.ts:70 documents in full.

This passed gate 2 by the letter and failed it in substance. The rule says a code fact may not be asserted unless grepped on origin/main; it was grepped, and the grep returned a true result about the wrong scope. A single-site grep proves an absence at that site, not in the system — and the failure is invisible because the citation looks exactly like a discharged check. It also propagated: the false claim became an acceptance criterion, which is the one part of a card a run may not exercise judgement about.

Still open — what this replan did NOT settle

  • Gate 20 has no enforcement point for a card authored outside a planning pass. Whether that is a create_work_item refusal on a leaf with null sizing, a widened advisory that fires on absence, or a runbook rule for conversational filing, is undecided. The advisory cannot be the answer as written: it needs the fields that are missing.
  • The one-modify-per-target ordering trap hit this replan too — the survivor's modify was appended before the children were deepened, so its child summary carries a line the child card later contradicted, and no second modify is permitted. Recorded on MOTIR-3547's own comment; the rule (deepen first, modify the survivor last) is not written anywhere.

Context refs

  • MOTIR-3547 — the card, and the plan that corrects it.
  • plan-rules/core.md — gate 4 (the estimation gate), gate 2 (the claim-vs-pointer rule and its absence direction), gate 20 (the unsealable under-authored card).
  • motir-core lib/mcp/tools/searchLessons.tssummarizeLessonSearch and runSearchLessons's toolOk(..., exempt(...)): the two channels.
  • motir-core lib/services/projectLessonsService.ts:162, lib/ai/motirAiClient.ts:1153, lib/mcp/payloads/exemptions.ts:70 — the hops the id survives.