Planning bug: the design gate makes the design card a CHILD of the card it gates — a prerequisite must be a SIBLING wired `blocked_by`, never a child
Repos · motir-meta + motir-ai, in that order. A rule change, not product code — and a standing planning RULE has TWO homes, so this card carries both: the runbook the manual planner reads (prompts/) and SHARED_PLANNING_RULES, which the shipped planner reads. It completes only when BOTH have merged.
⚠️ RE-FILED 2026-08-25 23:0x — the original premise was rejected (Yue)
This card was first filed as a
motir-coredefect inparentStatusRollupService: "a card that carries its own acceptance criteria is completed by its PREREQUISITE child's merge." That is not a bug. The rollup's rule — every childdone⇒ the parent isdone— is correct, because a child IS a parent's content. What was wrong is the SHAPE it was fed: the design card was made a child of the card it gated. A design prerequisite is a SIBLING wiredblocked_by, never a child. The producer of the wrong shape isrun.mdguard #3, and that is what this card changes. Nothing inmotir-coreis touched.
What happened
During motir run MOTIR-3490 on 2026-08-25:
- Guard #3 (the design gate) fired: MOTIR-3490 is UI-touching and
design/roadmap/depicted neither the grouped root node nor a truncated level. Correct so far. - The remedy was applied as written — "PROPOSE a
type: designSubtask for the surface" — and MOTIR-3493 was created under MOTIR-3490, plus theblocked_byedge. - #2280 merged at 22:25:42, MOTIR-3493 →
done, and MOTIR-3490 →donein the same beat with all six of its acceptance criteria unbuilt — because from the rollup's side it had one child and that child was complete. (An earlierin_progress → in_reviewat ~22:07 is the same thing one rung down.)
The tree has since been corrected by hand: MOTIR-3493 is now a root-level task (type: design) that blocks MOTIR-3490, with the blocked_by edge unchanged and MOTIR-3490 holding no children at all. That instance needs nothing further; this card is about the rule that produced it.
Why the rule produced it
prompts/run.md guard #3 said:
If NONE exists → PAUSE. Do NOT improvise and do NOT build the UI. PROPOSE a
type: designSubtask for the surface (create_plan→add_plan_items{ op: 'add' }…), and carry the edge on the SAME plan as{ op: 'modify', workItemId: <the UI code Subtask>, patch: { blockedByAdd: [...] } }
The word Subtask is the defect. A subtask cannot be a root (prisma/sql/work_item_triggers.sql — it is the one kind that must have a parent), and ALLOWED_CHILD_TYPES admits bug → [subtask], so when the gated card is a parentless leaf the only kind-legal placement the remedy leaves is under the gated card itself. The rule never says "make it a child"; it makes a child unavoidable. And the guard's own framing — "the UI code Subtask" — assumes the gated card is a subtask hanging under a story, which is the one case where a sibling placement happens to be legal by accident.
The correct rule has nothing to do with kind: a prerequisite goes beside the card it gates, at the same level, and the ONLY thing joining them is the blocked_by edge. Its kind then follows from where it sits — subtask when the gated card has a container parent (a sibling under that same parent), task at the root or under an epic when it does not.
Acceptance criteria
prompts/run.mdguard #3's remedy states the placement explicitly: the proposed design card is a SIBLING of the gated card — the sameparentKeythe gated card has — and is NEVER parented under it, with the kind following the placement (subtaskunder a container parent; a root-leveltaskwhen the gated card is itself parentless). Theblocked_byedge stays exactly as it is today; it is what makes the sibling a prerequisite.- The remedy also states, in one line, WHY: a child is read as the parent's CONTENT by
parentStatusRollupService, so a prerequisite parented under the card it gates completes that card on its own merge. Cite the MOTIR-3490 / MOTIR-3493 fixture by key and date, one sentence, no recap. - Guards #4 (the missing-precondition card) and #5 (the repo split) are audited for the same wording and carry the same placement rule — or an explicit one-line cross-reference to it. Any run-time remedy that PROPOSES a prerequisite is in scope; state which ones were checked, in the PR body.
- The plan-time mirror is closed: the
plan-rules/*files instructing the planner to add atype: designcard for an unspecified surface, or a missing precondition, state the same rule — so a plan authored from scratch cannot produce the shape either. - A grep proves it: after the change, no file under
prompts/instructs anyone to add atype: designcard under the card it gates. Run it and paste the output in the PR body — the sweep is by FILE, not by remembering where the rule lives. - The
motir-aimirror ships in the same pass (Yue, 2026-08-25: "if motir-meta needs update, motir-ai needs too"): the rule lands inSHARED_PLANNING_RULES(src/llm/planningRulePacks.ts) — generic, unbranded, no source-file citations — with atests/planningRulePacks.test.tsblock pinning the limb AND its delivery into every cell its host reaches, and the conservation baseline refreshed throughscripts/refresh-planning-rules-baseline.tsrather than by hand. parentStatusRollupServiceis NOT changed by this card — and the PR body says so, so a later reader does not re-open the rejected premise.
Delivery
- motir-meta #322 —
run.md(the named rule + guards #3/#4),plan-rules/core.mdgate 4,plan-rules/kind-leaf.md,plan-rules/kind-bug.md,_shared.md,plan-procedure.md. Guard #5 audited and already correct (it proposes a sibling per repo). - motir-ai #297 —
THE_DESIGN_GATE+VERIFY_EVERY_PRECONDITION, the test block, the refreshed baseline. - Both on the branch
subtask/MOTIR-3496-prerequisite-is-a-sibling; both PR titles carry the key, so the first merge defers and the last one closes the card.
Context refs
prompts/run.md— guard #3 (the design gate, the producer), guards #4/#5 (the same shape), and themotir runCREATES NO WORK ITEM EXCEPT Abugblock that already carries the edge-on-the-same-plan mechanics.prompts/plan-rules/core.mdgate 4 ·prompts/plan-rules/kind-leaf.md(the planning-time design gate) ·prompts/plan-rules/kind-bug.md— the plan-time half.motir-ai/src/llm/planningRulePacks.ts—SHARED_PLANNING_RULES;tests/planningRuleConservation.test.tsis the guard that goes red on a re-word.motir-core/lib/issues/parentRules.ts—ALLOWED_CHILD_TYPES/TYPES_REQUIRING_PARENT: why "Subtask" forces the child placement; read, not changed.motir-core/lib/services/parentStatusRollupService.ts—agg.done === agg.total, the rung ladder, and theagg.total === 0guard that makes an EMPTY parent safe; read, not changed.- MOTIR-3490 / MOTIR-3493 — the fixture and the corrected shape.