`SHARED_PLANNING_RULES` has NO historical baseline — invariant B compares the rebuild to itself, and the 58 phrase assertions pin 1.20% of it, so a re-wording of the shipped planner corpus passes every test
Repo: motir-ai. One PR. Surfaced 2026-08-17 by the motir run MOTIR-2934 execution, under notes.html #27 (an out-of-scope defect found mid-subtask is logged, never absorbed). Nothing in motir-meta is edited by this card.
⚠️ AMENDED ON THE RECORD 2026-08-18 by the
motir run MOTIR-2967that executed this card (PR moooon-B-V/motir-ai#243). The measurement table below originally gave the corpus as 143 079 chars, yielding 1.05 %. That is the character count of the FILEsrc/llm/planningRulePacks.ts— identifiers, backticks and theCORPUS_ORDERtable included — not of the constantSHARED_PLANNING_RULES, which is 125 476. The table now carries the re-derived figures; the original numerals are struck through beside them so the reading that was acted on stays visible. The finding is unaffected — true coverage is 1.20 %, so the card was pessimistic about its own denominator by 14 %, and the argument it makes from that number stands in every direction. Evidence:SHARED_PLANNING_RULES.length= 125 476 vsreadFileSync('src/llm/planningRulePacks.ts','utf8').length= 143 079, on a cleanorigin/mainat034b22a;git log b763dd8..034b22a -- src/llm/planningRulePacks.tsis empty, so the file is byte-identical to what the reporter read and "corrected in between" is excluded. Filed as planner telemetry: MOTIR-2968.
The defect
The planner corpus has two homes. Only one of them can detect that its text changed.
COMPRESSION.conserve.py holds each pack against a fixed historical commit that never moves forward, so a re-worded rule surfaces as a replace and fails. That is the whole reason MOTIR-2773 and MOTIR-2934 exist — the check is sensitive, and both cards were about paying that sensitivity honestly rather than dulling it.
src/llm/planningRulePacks.ts has no equivalent, and the test that looks like one is self-referential. tests/planningRulePacks.test.ts, "INVARIANT B — disjoint cover":
const rejoined = PLANNING_RULE_PACK_ORDER.map((p) => PLANNING_RULE_PACKS[p]).join('\n');
const corpusLines = SHARED_PLANNING_RULES.split('\n');
expect(packLines.length).toBe(corpusLines.length);
expect([...packLines].sort()).toEqual([...corpusLines].sort());
Its own comment states the reason: "the exported constant IS the rebuild, so any line dropped by the partition is a line missing from it." Both sides are derived from the same packs, so they move together. Invariant B is a real and useful check — it catches a line in two packs, a line no cell reaches, a broken partition — and it is structurally incapable of noticing that a rule's words changed.
The residual protection, MEASURED rather than assumed
That comment defers to "the ~45 per-rule assertions in treeGeneration.test.ts". Measured on origin/main at 034b22a (re-derived 2026-08-18 — see the amendment note above):
string/template literals inside a .toContain(...) in tests/treeGeneration.test.ts | 58 ( |
| total pinned characters | 1 511 ( |
SHARED_PLANNING_RULES size | 125 476 chars ( |
| upper-bound coverage | 1.20 % ( |
| median pinned literal | 15.5 chars ( |
| snapshots / golden files / corpus hash | 0 / 0 / 0 |
And toContain is a positive-presence assertion. It proves a phrase exists; it cannot see a re-worded sentence outside the 58, a deleted sentence outside the 58, or any inserted sentence at all — nothing asserts absence. So ~99 % of the corpus the product actually plans with is unpinned in both directions.
Why this is not covered by the other home
"The text is lifted from motir-meta, which is baselined" is true and does not help: nothing compares the two. The lift is a human copy, the mirror is a SELECTION (~13.6 % of the packs) rather than a copy, and the packs have already drifted in placement — the claim-vs-pointer gate is core.md gate 2 in one home and sits under phase-deepen in the other. A baseline at the home protects the home.
The sharp edge is that invariant B READS as the conservation check. It is named for an invariant, it is grouped with the AC references, and it compares two things — so a reader confirms the corpus is protected and stops. A blind spot that reads as a verdict is worse than a visible absence, which is the same shape as notes.html's scanner-blind-spot family.
Deliberately NOT decided here — the dispositions differ by an order of magnitude in size
RESOLVED 2026-08-18: disposition 1 was chosen and applied (PR moooon-B-V/motir-ai#243). The reasoning, and the refusal of the other three, is recorded in the comment block above describe('INVARIANT B …') in tests/planningRulePacks.test.ts per AC 2.
- A historical baseline in
motir-ai, with MOTIR-2773's classifier ported — a committed golden copy of the constant + a check that permits whole ADDED sentences and fails areplace/delete/fragment. Proven twice on this corpus; catches the actual risk (a silent re-wording). Recommended. ✅ CHOSEN. - A cross-repo equality check — assert the mirror's rule text against the packs it was lifted from. It states the claim we actually want, and
motir-aicannot readmotir-metaat test time, so it needs a vendored copy — which is a second conservation problem wearing the first one's clothes. If this is chosen, re-scope or SPLIT the card before building; it does not fit this estimate. ❌ Refused — worth its own card. - Widen the pinned-phrase set. Not a disposition — a mitigation. It moves 1.20 % upward and can never become a claim about the whole. ❌ Refused.
- Accept the exposure and WRITE IT DOWN — amend invariant B's comment so it stops implying a coverage it does not have. Legitimate, and the one option that must not be taken silently. ❌ Refused as the WHOLE deliverable; done as half of it (AC 4).
Acceptance criteria
- The three measurements above are re-derived on a clean
origin/maincheckout and quoted BEFORE any edit — the literal count, the pinned-character total, the constant's size — so the fix is measured against a recorded gap and not a re-derived one. - One disposition is chosen and applied, and its reasoning is recorded in a comment block at the invariant-B test in
tests/planningRulePacks.test.ts, including why the others were refused. - Under disposition 1 or 2: a re-wording of one word of
SHARED_PLANNING_RULESFAILS the new check, and a whole appended sentence PASSES it — both proved by a test intests/, not by description. - Invariant B's own comment no longer implies the per-rule assertions cover the corpus; it states the measured share and names what the new check covers instead. Under disposition 4 this criterion is the entire deliverable.
pnpm vitest run tests/planningRulePacks.test.ts tests/treeGeneration.test.tspasses, and the PR body quotes it. The full-suite number is CI's to report, not this card's.- No rule TEXT in
src/llm/planningRulePacks.tsis edited by this card — the deliverable is the check, and a baseline that was captured after an unnoticed edit proves nothing.
Context refs
src/llm/planningRulePacks.ts— the ~32 named rule constants,CORPUS_ORDER,PLANNING_RULE_PACKS,composePlanningRules, andSHARED_PLANNING_RULESderived over them.tests/planningRulePacks.test.ts— INVARIANT B — disjoint cover, the threeitblocks and the comment that defers totreeGeneration.test.ts.tests/treeGeneration.test.ts— the 58toContainliterals that are the current protection.- READ-ONLY reference, not edited:
motir-meta'sprompts/plan-rules/COMPRESSION.conserve.py(is_addition()/classify()/check()'s fixture subtraction) is the mechanism disposition 1 ports, and MOTIR-2934 is the worked example of paying its sensitivity honestly.