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

Planning bug: a card's SCOPE-PROXY criterion contradicted its own instruction criterion — "no `app/` path" forbade the very file another criterion required a comment in

Done
Description

Type · planning defect (internal contradiction) · Found in · motir run MOTIR-3154, executing child MOTIR-3165, 2026-08-19. The correction is already applied; nothing is held up by this card.

What happened

MOTIR-3165 carries two acceptance criteria that cannot both be satisfied:

  • Criterion 4"planRowView.ts's and autoPlanCadenceService.ts's own status !== 'planned' short-circuits are KEPT … and each gains a one-line comment naming computePlanStaleness as the owner of the rule, so neither reads as a second source of truth."
  • Criterion 6"The PR's diff touches only lib/ and tests/: git diff --name-only origin/main shows no components/ or app/ path."

planRowView.ts is at app/(authed)/plans/planRowView.ts. Criterion 4 requires editing it; criterion 6 forbids any app/ path in the diff. The card's own Context refs name the file at that exact path, three lines above criterion 6.

The shape — a PROXY criterion outliving the thing it proxies

Criterion 6 is not a deliverable. It is a mechanical proxy for the scope boundary the card states in prose one section earlier: "It changes no component and no page." That prose is right, and easy to check by reading. The proxy converts it into a path predicate so a machine can check it — and the conversion is where it broke, because app/ in this repo holds two different things: pages and components (which the boundary is about) and server-side view-model helpers like planRowView.ts (which it is not).

The proxy was written to be checkable, and being checkable is exactly what made it wrong. A prose boundary bends around a comment-only edit to a helper; git diff --name-only | grep app/ cannot. So the more executable the criterion, the more confidently it contradicts.

Why the author did not see it: the two criteria are four lines apart and both are true individually. Nothing in the authoring pass reads criteria against EACH OTHER — every gate in plan-rules/core.md evaluates one criterion at a time (is it testable, is it ordered, does it name a not-done card), and a pairwise contradiction is invisible to all of them. This is the same blind spot as MOTIR-3164 (nothing audits what a diagnosis OMITS) one axis over: nothing audits what two criteria do to each other.

How the run disposed of it

Criterion 4 was honoured in full; criterion 6 was honoured in its INTENT and named as over-specified, on the card (the comment on MOTIR-3165) and in the PR body. The diff carries one app/ path whose change is comment-only — seven added lines, all inside the existing JSDoc above staleCountFor, no statement and no expression — so the prose boundary holds exactly as written. The specific instruction won over the mechanical proxy, which is the right precedence: without the comment the kept guard reads as a second source of truth, the exact failure MOTIR-3165 exists to remove.

The rule this suggests

A path-shaped criterion ("the diff touches only X", "no Y/ path appears") is a PROXY for a boundary stated in prose, and it is only as good as the assumption that the directory maps onto the concept. Either write the boundary in prose and let the reviewer read it, or write the path predicate against the concept rather than the tree — here, "no file under app/ whose change is not comment-only", or simply "no rendered surface changes". And when a card carries both a prose boundary and a mechanical proxy for it, say which one governs, so the runner is not the one deciding.

The generalisable half: a criterion authored to be machine-checkable should be checked against the card's OTHER criteria before it is sealed — a pairwise pass, cheap on a card with seven of them, and the only thing that could have caught this.

Acceptance criteria

  • The pairwise-contradiction check has a home in the authoring gates: plan-rules/core.md's per-card gate checklist gains a clause requiring a card's criteria to be read against each other, not only one at a time, with this card as its fixture.
  • The path-proxy rule above is written where a criterion author will read it — plan-rules/phase-deepen.md's criteria-authoring guidance — stating that a path predicate is a proxy, that it must be written against the CONCEPT rather than the directory, and that a card carrying both a prose boundary and a proxy for it must say which governs.
  • MOTIR-3165's criterion 6 is left as it stands: it shipped, it was disposed of on the record, and rewriting a criterion after the fact is what this corpus exists to avoid.

Context refs

  • MOTIR-3165 — criteria 4 and 6, and the run comment disposing of them.
  • MOTIR-3154 — the parent whose run surfaced this.
  • MOTIR-3164 — the adjacent blind spot: nothing audits what a diagnosis omits.
  • app/(authed)/plans/planRowView.ts — the file both criteria are about.