Planning bug: a card put a per-phase asymmetry in the SHARED tier — the clause was true on one phase and forbade the other phase's deliverable
Opened by Zhu Yue ·
Found while running MOTIR-4056, on review by Yue, after the pull request was already open (motir-ai#366).
What the card specified
The card drafted its core rule text with this sentence, and it shipped verbatim into the shared tier:
"The requirement is above you in this message … and it states what the user asked for; your target's own body is what THIS level narrows it to. Neither of them is yours to rewrite."
Its stated premise for the split was:
"Phrased once per phase, because only the completion differs — a whole CARD for
author, a complete LEVEL forlay."
Why it is wrong
The target's body plays OPPOSITE roles on the two phases, and the card assumed one role for both.
| phase | the target's body | evidence on origin/main |
|---|---|---|
author | the OUTPUT — the thing the session writes | buildLevelContext renders YOU ARE WRITING: <cardRef> (src/llm/authorSession.ts); buildRequirementContext already says "What you produce is a CARD, and that card's body is the narrowed statement its children are laid against" |
lay | an INPUT — written by an earlier author session, divided into children | buildLayMessages(target, depth) in src/llm/treeGeneration.ts, and target is string | null — the ROOT level has no target body at all |
So the sentence is correct on lay and forbids the deliverable on author: a rule in the tier both phases compose, telling author not to rewrite the very body it exists to write.
Why it matters more than a wording slip
It is the SAME defect this card was re-scoped to fix, recurring inside the re-scoped draft. MOTIR-4131 records the first occurrence: motir-ai#351 addressed a start-gate to the phase being gated. This is that mistake one clause down — a sentence delivered to two readers that is only true for one — written by a card whose whole subject is who a rule is addressed to, and which carries an explanation section about exactly that failure.
That is the finding. The lesson A rule stated where its actor never reads it is not a rule covers a rule in a file nobody loads; this is the sibling case it does not reach — a rule in a file everybody loads, that is only true for some of them. Placement was correct and the ADDRESSEE was still wrong.
Why nothing caught it
INVARIANT B — disjoint coverasks whether a line is in two packs. A line incoreis in ONE pack and reaches every cell, which is the shape it is designed to permit.- The routing baseline asks WHO receives a segment, and the answer — every cell — was correct and intended.
- The conservation classifier asks whether surviving text changed. It was a pure addition.
- The card's own suite asserted the rule reaches every cell and nothing about whether it is true there.
No guard in this corpus asks whether a core segment is TRUE on both surfaces. The act test in planningPhaseAxisGate.test.ts asks the adjacent question — can each surface DISCHARGE the rule, by tool name — and a claim about which body you may rewrite names no tool, so it passes.
Acceptance criteria
- A guard exists that fires when a
coresegment makes a claim that is phase-specific. The mechanism is the deliverable of this card and is not pre-decided here; the act test's "can this surface discharge it" framing is the nearest shipped precedent, and the honest starting point is that this instance names no tool and so escapes it. - The guard is proved against THIS instance: the withdrawn sentence, restored to
core, goes RED — asserted against the real corpus, not a toy string. - If no mechanical guard is defensible at acceptable cost, that verdict is written down WITH its reasoning and the card closes on an authoring rule instead — a
coresegment states in its own doc comment which surfaces it is true on, asserted by presence. A verdict of "not mechanisable" is an acceptable outcome; leaving it undecided is not. - Whatever ships does not go red on the ~12
coresegments already in the corpus, and that is measured rather than assumed.
Context refs
motir-aisrc/llm/planningRulePacks.ts—PLAN_AGAINST_THE_REQUIREMENTand its two per-phase segments, as correctedmotir-aitests/part2PlansAgainstTheRequirement.test.ts§2 — the regression, pinned in both directions plus a negative oncoremotir-aitests/planningPhaseAxisGate.test.ts§1 — the act test, the nearest existing guard, and why it does not reach thismotir-aisrc/llm/authorSession.ts·src/llm/treeGeneration.ts— the two sessions the roles are read off- MOTIR-4131 — the first occurrence of this family, on the same card
Discussion
No comments yet.
Adding to this discussion signs you in on app.motir.co and brings you back to this request.