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

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

Done
Description

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:

  1. A parent may not reach implemented / in_review while a child is unimplementedalready shipped, already kind-agnostic (CONTAINER_CLAIM_STATUS_KEYS has 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.
  2. A child must not be filed under a parent that is already finishedcannot be a refusal, because docs/decisions/status-derivation.md §5 settles the opposite in as many words: "a done parent given a fresh todo child returns to todo. A done parent 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.ts on origin/main @ 4658a844, tested at workItemsService.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 — event 01M0FNNFQVY6Y3Q8QEG159905T emitted 2026-08-20T13:27:15.963Z, status-derivation/transitioned started 13:44:34.642Z, attempt 0 (17 min 18.7 s of queue delay, not a retry), children created 13: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. grep the 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-meta branch + PR (never a direct commit to main).