Planning bug: a card forbade the only layer at which its own acceptance criterion was implementable — a prescribed layer is a CLAIM about that layer, and it owes the ADR check
A planner record, not build work. Found while running MOTIR-3334 (PR motir-core#2240) on 2026-08-21. The correction is already applied in that PR; this card holds the rule.
What happened
MOTIR-3334 carries a non-goal and an acceptance criterion that cannot both be satisfied:
- Non-goal: "The CHILD'S KIND IS NOT THE DISCRIMINATOR, and no replacement discriminator belongs in the cascade. The containment belongs upstream."
- AC: "A child created under an already-done parent does not inherit that parent's doneness."
The card names the two upstream containments it expects to carry that AC. Neither can:
- A parent may not reach
implemented/in_reviewwhile a child is unimplemented — already shipped, already kind-agnostic (CONTAINER_CLAIM_STATUS_KEYShas held BOTH keys since MOTIR-3229; the scoped-run half is MOTIR-3268). It cannot reach a child filed after the parent finished, which is the AC's whole case. - A child must not be filed under a parent that is already finished — cannot be a refusal, because
docs/decisions/status-derivation.md§5 settles the opposite in as many words: "adoneparent given a freshtodochild returns totodo. Adoneparent is not permanently done." Refusing that create deletes MOTIR-2888's deliberate behaviour.
So the only layer at which the AC is expressible is the one the non-goal forbids. The run shipped the guard there anyway and said so on the record, because obeying the non-goal literally would have deleted the carve-out while re-opening the data loss the carve-out existed to prevent.
The rule this asks for
When a card FORBIDS a layer and prescribes another, the prescription is a CLAIM about the prescribed layer, and it owes the same rung-2 verification as any other claim: read the ADR that governs that layer and confirm it can express the containment. A layer name is not a design. status-derivation.md §5 was already in the repository, unchanged, and one grep would have shown that containment #2 contradicts it — before the card asserted it as the answer.
The tell, and it is what makes this hard to catch: the forbidden layer is forbidden for a GOOD reason (a kind carve-out really was the wrong shape), and the prescribed layer is named at the right ALTITUDE (upstream), so the instruction reads as a correction rather than as an unverified assertion. The gap is between "the containment belongs upstream" and "an upstream mechanism exists that can carry it" — two different sentences, and only the first was written.
Corollary — deleting a guard is a claim too. A card that says delete X owes the check that whatever X was accidentally protecting is still protected afterwards. Here isCascadeExempt was the wrong instrument and was also, incidentally, the only thing standing between a 17-minute-late cascade and the children filed in the meantime.
Secondary finding, minor
The same card asks, conditionally, "if that guard already exists for implemented … this card says why it did not cover in_review." It did cover in_review, from the day MOTIR-3229 shipped. The conditional was correctly hedged, so this is a note rather than the defect — but a reader who skipped the hedge would have gone looking for a gap that was never there. Recorded in lib/workItems/statusLadder.ts so it is not re-derived.
Evidence
CONTAINER_CLAIM_STATUS_KEYS = new Set(['implemented', 'in_review'])—lib/workItems/statusLadder.tsonorigin/main@4658a844, tested atworkItemsService.applyStatusTransition.docs/decisions/status-derivation.md§5, Cross-cutting semantics, the recompute-not-a-ratchet bullet.- The mechanism that makes the residue real: production
job_run+ the event ULID — event01M0FNNFQVY6Y3Q8QEG159905Temitted2026-08-20T13:27:15.963Z,status-derivation/transitionedstarted13:44:34.642Z, attempt 0 (17 min 18.7 s of queue delay, not a retry), children created13:41:45/13:41:56.
Acceptance criteria
plan-rules/phase-deepen.md's precondition-verification rule gains a PRESCRIBED-LAYER limb: a card that forbids one layer and names another owes the ADR/code check that the named layer can express what is being moved there, cited on the card.grepthe pack for the new limb text.- The same limb carries the deletion corollary: a card saying delete X names what X was protecting and where that protection moves to.
- The change lands as a
motir-metabranch + PR (never a direct commit tomain).