Planning bug: a card that ENUMERATES a population measured it with `find` over the working tree, and shipped a count 4 short
⚠️ AMENDED ON THE RECORD, 2026-08-20, by the run that executed this card. Two clauses below were written under conditions that no longer hold, and both are corrected here rather than quietly edited. (1) The repo pin WORKS now — MOTIR-3078's data migration and MOTIR-3086's code have since reached production, so the parenthetical that used to open this card ("the pin is omitted —
UNKNOWN_TARGET_REPOstill refuses one") is stale. Tested, not inferred:update_work_item targetRepos: ['motir-meta', 'motir-ai']succeeded at 21:17 UTC. (2) The acceptance criteria were ONE HOME SHORT. A standing planning RULE lives inplan-rules/for the manual planner AND inSHARED_PLANNING_RULESfor the shipped one; the criteria named only the first. Corrected as the fourth criterion below. Yue, declining a proposal to file that mirror as a separate subtask: "a planning bug is not a subtask, and a work item (bug included) can carry multiple repos." — so the second home is a REPOSITORY on this card, not a sibling card.
Repos · motir-meta (primary) + motir-ai. One pull request per repository, both titled with this card's key; the card completes only when BOTH have merged.
Filed about MOTIR-3069, whose table named THREE *.mock.html with no .png export. There are seven, and there were seven at the moment the card was written.
What happened
MOTIR-3069 was filed from a sweep on MOTIR-3054's branch and carried this reproduce block, which is also how it measured the population:
$ for m in $(find design -name '*.mock.html'); do [ -f "${m%.mock.html}.png" ] || echo "$m"; done
design/boards/board.mock.html
design/work-items/links.mock.html
design/work-items/list.mock.html
Run against the COMMITTED tree it returns seven, and it returns the same seven at origin/main and at b305d567 — the very merge the card was filed from:
$ git ls-tree -r --name-only b305d567 -- design | python3 -c "
import sys; files=set(sys.stdin.read().split())
print(len([m for m in files if m.endswith('.mock.html') and m[:-10]+'.png' not in files]))"
7
The four the card missed — datepicker, filter, relationships, tree-scale — were all added in June 2026 and have never had an export, so nothing changed between the filing and the run.
The mechanism
find reads the working tree. MOTIR-3054 was a 51-mock re-export sweep, so its worktree held rendered .pngs that were never committed — and every mock with one of those was invisible to the check. The measurement was taken inside the process that was perturbing the thing being measured, and the result reads as a clean, reproducible three-row table.
Why it was not caught at authoring time. Nothing about the output looks partial: three rows, each verifiable by hand, each true. notes.html #296's reproduce at the reporter's base did fire on the run — it is what produced the seven — but that rule is written for a defect that will not reproduce, i.e. a count going DOWN. A count that comes back HIGHER reads as drift since filing and gets absorbed, which is exactly what this run nearly did before checking the dates.
Why it cost something
The card's last acceptance criterion asks for a green pnpm vitest run --config vitest.design.config.ts, and the card's own deliverable is a guard over that population — so a card that renders three and guards all of them is internally contradictory and leaves the lane red. The count was load-bearing rather than descriptive, and a card that enumerates a population it also gates has no slack in it.
The correction (already applied)
- MOTIR-3069 amended on the record: title, table and first acceptance criterion, with the amendment banner naming the date and the evidence. All seven rendered on motir-core#2147.
- Its reproduce block replaced with a
git ls-treeform, so the command in the card answers about what is COMMITTED.
What to change here
- A limb on the authoring rule for a card that ENUMERATES: a population is measured against the committed tree (
git ls-tree/git grepon the ref), never withfind/ls/ a baregrep -rover a working tree — and a sweep's own worktree is the worst place to take the measurement, because the sweep's uncommitted output is exactly what hides the population. The natural home isplan-rules/phase-deepen.mdbeside the NEGATIVE limb (a card explaining a defect by what does not exist owes the grep, not the verdict); this is its positive twin — a card COUNTING a population owes the ref the count was taken on. - The run-time half, beside
notes.html#296's reproduce-at-the-reporter's-base: a count that comes back HIGHER than the card's is not automatically drift. Date the additions (git log --diff-filter=A) before deciding — if they predate the card, the enumeration was wrong, which is an amendment, not a re-scope. - A
notes.htmlentry carrying the lesson (the corpus half; the card here is the record half — both are owed). - (added on amendment) The RULE's second home in
motir-ai— see criterion 4.
Acceptance criteria
plan-rules/phase-deepen.mdcarries the enumeration limb, stating the committed-tree requirement and naming the sweep-worktree trap.notes.htmlcarries the matching mistake entry, with the count bumped and the.mistakemarkup matched.- A card that enumerates without naming the ref its count was taken on is identifiable from the rule text alone — i.e. the rule says what the card must SHOW, not only what its author must do.
- (added on amendment)
SHARED_PLANNING_RULES(motir-ai/src/llm/planningRulePacks.ts) carries the same limb — the rule's SECOND home — appended toVERIFY_EVERY_PRECONDITION's existing A COUNTED SET bullet, with a phrase pin in the guard suite. Deliberately NARROWER than themotir-metawording: the sweep-worktree trap and the run-time drift-vs-wrong-enumeration procedure trigger on facts the shipped planner cannot observe (no worktree, no branch, no base), so they are EXCLUDED — and the exclusions are asserted as ABSENCES rather than merely omitted, so the next reader holding the full source cannot restore them by accident.