Planning bug: a story's four children each excluded the client half, and the exclusion pointed at a card that did not exist
What the plan got wrong
MOTIR-4054 was decomposed into four children — a design, a store+doors, a stop, and a narration fix. Every one of them is individually well-formed. Together they do not deliver the story's headline promise: a user cannot say anything to a running plan.
The client half — the composer staying usable during a run, the send routing to the mailbox, and the queued treatment — is owned by no card.
The exact tell, and it is grep-able
MOTIR-4067's Scope BOUNDARY reads:
OUT: the composer UI. The thread's input already exists (MOTIR-2226); the PENDING treatment for a queued turn is drawn by MOTIR-4066 and rendered by the card that owns the surface.
There is no card that owns the surface. The exclusion names a referent rather than a key, and nothing checks that the referent resolves. Compare the other three:
| card | says about the composer |
|---|---|
MOTIR-4066 (design) | OUT: implementation |
| MOTIR-4067 | OUT: the composer UI → "the card that owns the surface" |
| MOTIR-4068 | IN: the stop control, the stopped/stopping treatment. Silent on a turn |
| MOTIR-4069 | IN: narrateFrame and the act-line treatment |
Each boundary is defensible on its own. The union has a hole, and the hole is exactly where the story's value is.
Why nothing caught it
validate_work_itemcannot see it. Itsreferencefamily fires on a card naming a not-donework item with no edge. Unowned work has no key to name, so the array is empty and correct.- Readiness is green. Every child's blockers resolve; nothing is waiting on anything.
- The
likely-missing-edgeseverity is about edges between cards that exist. This is an absent card, which is a different object. - The design's own allocation did not close it either.
plan-rules/type-design.md's GIVES/TAKES sweep asks what an asset gives to each named card. State B is given to nobody, and a sweep over the named set never visits it.
What made it findable
The calendar check (run.md, a done design blocker that merged AFTER the card). The design merged 2026-09-03 10:03Z; all three code cards were authored 2026-08-31. Reading the asset as a spec and diffing its drawn STATES against the children's scope boundaries is what surfaced it — not reading any card.
The correction
Plan cmtlezbvn000hhwphntex5tzc (submitted, planned, awaiting approval) adds the missing card as a sibling under MOTIR-4054, blocked_by MOTIR-4066 and MOTIR-4067. MOTIR-4068 is untouched — it is correct as written and was built in the same run.
The rule that would have caught it at plan time
A scope BOUNDARY that excludes work by pointing at another card must name that card by its MOTIR-<n> key, not by a description of it. A referent that is a phrase cannot be checked; a key either resolves or does not. This is plan-rules/phase-skeleton.md's cite-a-sibling-by-id rule applied to the OUT list rather than to the IN list — the same rule, on the half nobody reads it against.
Lesson recorded alongside this bug.
Evidence
- Read on
origin/main@76b23fd(the design merge) and onparent/MOTIR-4054-talk-to-and-stop. busy = state.phase === 'streaming'—components/planning/PlanChangeRail.tsx:126; thedisabledit produces on all three composer controls was confirmed by a headless render of the shipped component, not by reading the source.search_work_items_semanticfor the capability returns the STORY and the DESIGN as its top hits and no implementing child (coverage 1140/3973, so corroborating rather than conclusive; the four scope boundaries above are the direct evidence).