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

Planning bug: a dedup card offered two owners for one write and neither option carried the ADR that one of them was gated by — and its criteria named one of the two collisions

Done
Description

Filed by the motir run MOTIR-2582 of 2026-08-17. The correction is already applied — this card is telemetry, not scheduled work.

The defect

MOTIR-2582 was a good card. It found a live duplicate — MOTIR-1167 (8.5.11, launch readiness) and MOTIR-730 (10.1.3, Epic 10) each planned to introduce the platform-staff flag, requirePlatformStaff() and the gated /admin route, neither naming the other — verified on origin/main that neither half existed, and wrote out two dispositions:

"(a) 8.5.11 owns the gate + route as the launch foundation, and 10.1.3 is re-scoped to the audited cross-tenant READ layer on top of it (with a blocked_by edge onto 8.5.11); or (b) 10.1.3 owns it, 8.5.11 is blocked_by 10.1.3, and Epic 10's ordering moves ahead of launch."

Neither option mentions MOTIR-729 (10.1.2) — the keystone ADR that fixes the flag-vs-enum choice, the requirePlatformStaff() contract, the 404-not-403 posture, /admin's placement and the PlatformAuditLog shape.

cardplans the gateblocked_by the ADR
MOTIR-730 (10.1.3)yesyesMOTIR-729
MOTIR-1167 (8.5.11)yesno — never had the edge

So the two cards were not symmetric duplicates. One was a gated build of the primitive and one was an ungated build of it, and the decision card described them as interchangeable. Option (a) is the right call on the merits — the merged design/platform-admin/design-notes.md says the day-1 panels exist "because Story 8.5 (launch readiness) needs them before Epic 10 runs", Story 8.5 is in_progress/high and Story 10.1 is todo/lowest — but applied literally it would have dropped a 45-minute decision gate from in front of the one role in the product that legitimately reads across every tenant.

The second half — the criteria named one collision, and there were two

MOTIR-2582's acceptance criteria are about the gate and the route. They are silent on the audit record, where the same shape was sitting:

  • MOTIR-1167 planned "a simple admin-action log row" for its support actions.
  • MOTIR-730 planned the PlatformAuditLog model for its cross-tenant reads.
  • Story 10.3 plans to append governance actions to a log of its own.

Three writers, one table. A run that answered exactly what was asked would have deduplicated the gate and left the audit record duplicated behind it. A duplicate is a symptom of two passes working the same area without meeting, and that condition does not produce exactly one collision.

Why this is not MOTIR-2075 / MOTIR-2433 / MOTIR-2831 / MOTIR-2848 / MOTIR-2877

That family is about a real dependency recorded as PROSE instead of an edge, and about the advisory channel that surfaces it. Here every edge was correct and present. MOTIR-730 genuinely was blocked_by MOTIR-729; MOTIR-1167 genuinely was not, and had no reason to be while it owned nothing. Nothing was mis-recorded. The defect appears only at the moment the two cards are COMPARED — and comparison is precisely what a dedup card does and what no validator does.

validate_work_item MOTIR-2582 returned valid: true with two likely-missing-edge advisories, both correct and both harmless: MOTIR-1167 and MOTIR-730 are the cards this one EDITS, not substrate it consumes. Guard #4's advisory check runs clean and cannot see this.

Correction applied by this run

  • create_work_item MOTIR-2896 (8.5.16, under Story 8.5) — the single owner of the platform-staff primitive: the platformRole flag, requirePlatformStaff(), the 404-gated app/(admin)/admin/ segment, and the PlatformAuditLog model. Both collisions resolved into one card.
  • link_work_items: MOTIR-2896 blocked_by MOTIR-729 (the inherited edge — the point of this bug); MOTIR-1167 blocked_by MOTIR-2896; MOTIR-730 blocked_by MOTIR-2896.
  • update_work_item MOTIR-1167 and MOTIR-730 — each now states, on the record, that it CONSUMES the gate and introduces neither it nor a second audit table; both re-estimated. MOTIR-729 amended to record that it now gates a launch card and must be sprinted ahead of Sprint 41; targetRepo pinned on all three.
  • notes.html #284 — motir-meta PR #198.

Residual — SETTLED 2026-08-17 by this card's close-out

This section originally read: "a rule question this card does NOT settle … Argument for: both checks are mechanical … Argument against: one occurrence. Left open deliberately." That conditional form is itself the notes.html #221 trap — a prescription a future reader finds, matches against a real recurrence, and re-litigates from scratch, with no way for the format to carry that it was already discharged. The verdict below replaces it. Do NOT restore the conditional form.

The two halves split between tiers, which is the usual right answer.

Half 1 — the EDGE union: SHARPEN. Filed as the two-home pair MOTIR-2913 (motir-meta, op-replan.md limb (2)) → MOTIR-2914 (motir-ai, planningRulePacks.ts, blocked_by 2913, seeded blocked).

Both homes were grepped first, as #221 requires, and the remedy is absent from both: shared noun 0/0, proper noun 0/0, carry the edge 0, diff the two 0, upstream blocker 0, its own blocked_by 0, every blocked_by 0. What IS present reaches "two cards own one write" in three places — core.md gate 8's taker's-seat clause (mirrored verbatim as NO TWO CARDS MAY OWN THE SAME WRITE), core.md:151, and op-replan.md limb (2) — and every remedy in all three is about scope. Gate 8 does move edges, in exactly two directions, and both point the same way: a covering test card's blocked_by re-pointed to the new owner, and every consumer's blocked_by unlinked when a card is absorbed. Four arrows, three named. The missing one is the donor's OWN blocked_by moving onto the taker — missing structurally rather than by oversight, because gate 8 is "Referrers swept" and a card's own blockers are not referrers.

Family count is honestly ONE, and the ORDER test on its own would have refused the promotion: gate 8's absorbed-prerequisite clause landed 2a72b91 2026-07-26, the taker's-seat clause and op-replan limb (2) landed fcddcbd 2026-08-01, and MOTIR-2582 was authored 2026-08-10T11:22:49Z — 9 to 15 days later, same planner, which is MOTIR-2280's diligence pattern exactly. What overturns it is a sentence in the defective card itself: "Two cards own the same write, which is exactly what the sweep-the-referrers gate forbids." It cited the gate by name, applied it correctly to DETECT the collision, discharged its scope half, and left both edge halves undone. A rule that was read, named, applied and still under-delivered is a trigger gap, not a diligence miss — the totality-vs-diligence discriminator MOTIR-2878's W3 turns on, one arrow over. That ranks above any occurrence count.

Half 2 — "a duplicate does not produce exactly one collision; sweep both bodies for every OTHER shared noun": LESSON, not promoted. core.md gate 8 already carries the governing sentence — "a sweep run at the wrong grain — one file, one caller, one card — reads as complete", warrant MOTIR-2500 · 2026-08-09 · ×7. The general clause covers a one-card grain outright; only its remedy is path-shaped (grep -rc over a directory) and has no form for a card-pair grain. A rule whose governing sentence covers the case and whose illustration does not is a diligence miss (MOTIR-2280) — so this half stays where notes.html #284 already put it.

Not folded into MOTIR-2878, deliberately. The fold rule exists to stop two cards editing one file; op-replan.md and fixtures/op-replan.md are touched by none of W1–W4, so there is no collision to prevent. MOTIR-2878 is also already at estimateMinutes: 60, the estimation gate's agent-run ceiling with no CI to absorb it, and appending scope to a sealed, sized card is the MOTIR-2147 shape a sibling record card was filed about. core.md is likewise not edited by the new pair — MOTIR-2692 and MOTIR-2700 are both live claimants on it.

Close-out verification (2026-08-17)

The correction HELD, exactly as this card describes it. MOTIR-2896 exists under Story 8.5 (MOTIR-658, in_progress/high), targetRepo: motir-core, blocked_by MOTIR-729the inherited edge, which is the point of this bug — and blocks both MOTIR-1167 and MOTIR-730. All three consumers sit blocked; MOTIR-729 carries its dated "MOVED AHEAD OF LAUNCH READINESS 2026-08-17" banner and the Sprint-41 note; targetRepo is pinned on all three. MOTIR-1167 now says "do NOT invent a separate 'simple admin-action log row'. One record shape, three writers" and "this PR adds no second audit table" — a stronger correction than this card claimed.

Citations re-checked, and for once nearly all of them are exact — said plainly rather than implying drift that was not found. notes.html #284 is genuinely at #284, no collision renumber (corpus head is #290), and motir-meta PR #198 merged 2026-08-17T12:07:33Z, so the lesson is on origin/main. The design/platform-admin/design-notes.md quote is verbatim at :382–383. Story 8.5 in_progress/high and Story 10.1 todo/lowest both hold. Story 10.3's own audit log is real (scripts/plan-seed/data/story-10.3.ts — the 10.3.6 hash-chained log), so three writers, one table is right. MOTIR-2582's acceptance criteria are three bullets, all about the gate and the route, silent on the audit record — the claim holds, and the count is stated because a record's AC index is a citation like any other.

One citation is NOT re-runnable, and that is a finding rather than a drift. This card cites "validate_work_item MOTIR-2582 returned valid: true with two likely-missing-edge advisories." Re-run today it returns valid: true with zero advisories — because the advisory channel enumerates only the not-done members of a card's subtree, and MOTIR-2582 is now done with no children. The citation was accurate when written and became unverifiable the moment the card closed. A validate_work_item advisory citation is therefore the one rung-2 class that cannot be re-executed at close-out — it must be reasoned about, or re-derived on a card still in the same state. Confirmed by the control: the same call on this card (todo) returns four advisory entries naming MOTIR-729/730/1167/2896.

And the method trap fired on my own side, which is worth more than the citation check. Grepping the mirror for the sweep family in motir-ai/src/llm/treeGeneration.ts returned 0 for every stemsweep 0, same write 0, survivor 0, Referrers swept 0 — and I was one step from recording "the whole sweep-gate family is unmirrored," which would have collapsed the two-homes pair to a single card and been flatly wrong. SHARED_PLANNING_RULES has MOVED to src/llm/planningRulePacks.ts; COMPRESSION.md records the move with the old path struck through. In the real file the same stems return sweep 20, same write 1, survivor 2, ABSORBED 1. A stale path produces a clean, confident zero for an entire rule family, and a zero is the answer least likely to be re-checked — the same self-inflicted negative this corpus keeps warning about, one level down in the tooling.

Guard #4 (the advisories read) clears. All four entries on this card are plain advisory, none likely-missing-edge, and every referent is a card this record DESCRIBES rather than substrate it consumes. Nothing is blocked in fact.

No PR is owed by this card — its lesson merged as #198 and its verdict is carried by MOTIR-2913/MOTIR-2914 — so nothing here carries MOTIR-2897, and the close-out is by hand.