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-3402

Planning bug: a card with NO acceptance criteria passes every gate vacuously — and the one mechanised sizing check needs the very field an under-authored card is missing

Done
Description

A RECORD card. The plan correction is already applied — plan cmt5jdmsy00f1i3phkn9ycs7k, submitted 2026-08-23 by motir run [MOTIR-3313](motir:cmt2an72n000di4phf6t97lts) and awaiting approval.

What happened

motir run MOTIR-3313 claimed a childless bug and halted at guards #2 and #4 before creating a worktree. The card carried:

  • no ## Acceptance criteria section at all — its remaining work was two sentences under What this card is still for, beginning "Find out…" and "Consider whether…";
  • type: null, executor: null, storyPoints: null, estimateMinutes: null, targetRepo: null, explanationMd: null;
  • a first deliverable premised on request logs from Tigris, which no API and no CLI in this project exposes (see the run's comment on the card for the three readings).

Every automatic signal agreed it was startable: readiness.ready: true, openBlockers: [], validate_work_itemvalid: true, advisories: [].

⚠️ The finding — this is not one gap, it is a prose gap and a code gap that cover for each other

(1) The corpus has no rule about a card carrying NO acceptance criteria. Every plan-time gate that would have caught this card is stated over criteria — gate 14's three axes read each criterion, gate 17 asks whether each criterion is owed under every hypothesis, kind-leaf-deepen.md's precondition-wearing-a-checkbox rule judges a criterion by what its sentence describes. A card with zero criteria satisfies all of them vacuously. Measured 2026-08-23, all three homes at 0:

grep -rain "no acceptance criteria|without acceptance criteria|zero criteria|carries no criteri" \
  prompts/plan-rules/ prompts/run.md prompts/plan-procedure.md   → 0
git grep -ain (same pattern) -- motir-ai/src/llm/                → 0

Gate 16 names the STRUCTURE (descriptionMd = body + ## Acceptance criteria + ## Context refs) but states no check on its absence, and phase-deepen.md's "a card with explanationMd: null is UNDER-AUTHORED, exactly like one with no ## Acceptance criteria" is an analogy, not a gate — it names the condition while checking a different field.

(2) The mechanised half of the estimation gate is blind to an UNSIZED card, by two independent guards. overGateSizing (lib/workItems/proseVsGraph.ts, read on origin/main) opens:

if (card.executor !== 'coding_agent') return null;
…
const overPoints    = storyPoints    !== null && storyPoints    >= ESTIMATION_GATE_STORY_POINTS;
const overMinutes   = estimateMinutes !== null && estimateMinutes >  ESTIMATION_GATE_ESTIMATE_MINUTES;

MOTIR-3313's executor is null, so the check returns at line 1 — the detector requires exactly the field an under-authored card is missing. And even with an executor set, both arms are !== null-guarded, so a null-sized card is invisible. The gate's prose says plainly "any LEAF with a null story_points or null estimate is a planning bug" — the mechanised form only ever looks for over-sized, never un-sized, which is the commoner and quieter failure.

So the prose gate that covers (2) exists and was not performed, and the detector that would have performed it cannot see the case. That is why nothing anywhere went amber.

What would have prevented it

An ## Acceptance criteria heading with at least one criterion, and a non-null storyPoints / estimateMinutes, as a condition of sealing a leaf — checked rather than assumed. Both are string-and-integer level checks over fields the card already carries, with no reference resolution and no DB read — the same cheapness sizingAdvisory's own comment claims for itself ("two integers and an enum"). By CORPUS-MAINTENANCE.md's THIRD TIER that puts them in code, not in prose.

⚠️ Decide the rule-vs-lesson question at close-out, not here, and read MOTIR-1464's not-done children FIRST — a W card for this family may already exist, and a duplicate splits the warrant of the rule it is trying to strengthen. Note when you do: this is not a "widen an enumeration" case — the trigger words are absent from all three homes, which is a totality gap.

The correction that was applied

Submitted as plan cmt5jdmsy00f1i3phkn9ycs7k (2 adds + 1 modify), not written into the tree — a run proposes:

  • MOTIR-3313 keeps its incident record verbatim and becomes the container, gaining targetRepo: motir-core and its missing explanationMd.
  • A manual / human child for the Tigris support request — the only route to the two request ids, written so a documented refusal closes it.
  • A decision / coding_agent child for the recoverability question, deliberately not blocked_by the first, because it is owed under every hypothesis the parent card left open.

⚠️ Advisory disposition — likely-missing-edgeMOTIR-3313, DELIBERATELY NOT WIRED

validate_work_item on this card (2026-08-23) returns one advisory: this card's criteria name MOTIR-3313, which is blocked, with no blocked_by edge. The edge is refused, and the reason is written here because an absent edge and a considered exclusion are the same absent edge.

This card is closed against the plan being DECIDED — an act available to a person today — and not against MOTIR-3313 reaching done. Those are different events with different clocks. MOTIR-3313 now contains a manual child waiting on a vendor's support mailbox, so a blocked_by edge would hold a twenty-minute record open for however long Tigris takes to answer, or for ever if they never do. That is precisely the defect this card records, reproduced on the card that records it — and it is the shape Yue flagged on MOTIR-1748: a record card's subject is a graph you verify by READING, so its own future does not gate the reading.

The criterion below is therefore worded to read on the PLAN's decision. relates_to carries the discovery trace, which is the correct relation for a card that reports on another rather than consuming it.

Acceptance criteria

  • Plan cmt5jdmsy00f1i3phkn9ycs7k has been decided — approved, or declined with a reason — and this card records which, and (if approved) that the materialized shape matches what the plan proposed: the two children present with their types, executors and sizes, and the survivor carrying targetRepo: motir-core and a non-null explanationMd. A decline is a valid close too, recorded with Yue's reason.
  • The LESSON is confirmed present in motir-ai src/seed/lessons.base.ts on origin/main, located by content, not by a citation.
  • MOTIR-1464's not-done children are read before any rule card is filed, and this card records whether an existing W card ABSORBS this finding (amend it) or a new one is owed (file it) — with the reason either way.
  • Both halves of the finding are disposed of separately: the prose gap (no rule about absent criteria, 0 hits in all three homes) and the code gap (overGateSizing returns at its executor guard, and both arms are !== null-guarded). A disposition that addresses only one of them says why the other needs none.
  • No code is written by this card. It is a record; any repair it warrants is a separate card it names.

Context refs

  • MOTIR-3313 — the card this was found on; the run's comment there carries the full rung-2 evidence for the unreachable-premise half. relates_to, not blocked_by — see the disposition above.
  • prompts/plan-rules/core.md — the per-card gate checklist; gates 14, 16 and 17 are the ones that read criteria.
  • prompts/plan-rules/kind-leaf-deepen.md — THE ESTIMATION GATE, and "any LEAF with a null story_points or null estimate is a planning bug".
  • prompts/plan-rules/phase-deepen.md — the both-content-axes rule, whose "exactly like one with no ## Acceptance criteria" is the analogy that names this condition without checking it.
  • motir-core lib/workItems/proseVsGraph.tsoverGateSizing, its executor guard and its two !== null arms.
  • motir-core lib/services/proseGraphAdvisoryService.tssizingAdvisory, and the advisory families validate_work_item returns.
  • prompts/plan-rules/CORPUS-MAINTENANCE.md — THE THIRD TIER, the rule for when a check belongs in code rather than prose.