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

Planning bug: MOTIR-2757 sat `todo`/`high`/ready for two days after MOTIR-2796's sweep shipped its entire deliverable — every readiness signal read green

Done
Description

The planner defect, not the product defect. motir run MOTIR-2757 claimed a card whose entire deliverable had already merged, and only a hand-read of origin/main stopped it from rebuilding merged work.

What happened

  • 2026-08-12MOTIR-2757 filed: workflowsService's three read methods (getWorkflow, listStatusesByProject, getStatusByKey) read on the db singleton and return nothing under motir_app. Filed rather than absorbed into MOTIR-2685, correctly, since nothing in 2685's criteria reached them.
  • 2026-08-15MOTIR-2796 (c99efdc7, PR #2059) bound 55 unbound reads across 20 services, one card per service. Three of those 55 were MOTIR-2757's three. The sweep's close-out updated docs/rls-runtime-role-inventory.md — including a new ## CLOSED — the READ surface is bound section.
  • 2026-08-17MOTIR-2757 still todo, high, readiness.ready: true, openBlockers: [], validate_work_itemvalid: true, advisories: []. A run claimed it.

The tell that existed and was not connected

docs/rls-runtime-role-inventory.md names MOTIR-2757 by key at line 274"…and, once MOTIR-2685 landed and surfaced it, by MOTIR-2757 (workflowsService's read surface; the last section below)" — and then, 245 lines later at line 519, the same file declares that surface CLOSED. One document holds both halves. Nothing reads a doc for card state, so the contradiction sat in the repository, in the card's own declared context ref, unnoticed.

Why this is the HOME's, not a finding story's

The discriminator is what the fix changes. Nothing in motir-core's product code is wrong — the binding shipped and is correct. What failed is the plan's bookkeeping: a sweep card subsuming a previously-filed card's scope, and nothing closing the subsumed card. The correction (closing MOTIR-2757) is already applied.

It is a RECURRENCE, which is the bar for leaving the lesson tier

  • MOTIR-1715"MOTIR-1502 left stuck (blocked) after later card (MOTIR-852) absorbed its deliverables — skeleton + search_work_items already shipped." Same shape, filed and closed already.
  • MOTIR-2860 — filed against a defect MOTIR-2734 already owned; "archived as a duplicate the same hour."
  • MOTIR-1827 / MOTIR-1839run.md's own cited fixtures: a run rebuilding work already on main, twice in one day.

Four instances, three mechanisms (absorbed-by-sweep, duplicate-of-in-flight, rebuilt-after-merge), one consequence: a card that is ready and wrong.

Acceptance criteria

  1. A card whose scope is subsumed by another card cannot sit ready and silently un-closed: name the mechanism and implement it, on the mechanize-don't-sharpen call (CORPUS-MAINTENANCE.md's third tier, MOTIR-2164 / notes.html #225). Prose in plan-rules/ is explicitly NOT an acceptable resolution.

  2. The recommended mechanism, to be confirmed or replaced with evidence: extend the existing non-blocking advisories channel (proseGraphAdvisoryService) with a subsumption family: a card is flagged when a merged PR's diff covers what its body names while the card itself has no linked PR. validate_work_item returned advisories: [] for MOTIR-2757, which is the observation to invert.

    AMENDED 2026-08-17 by the close-out of MOTIR-2923. Two clauses of this criterion as authored were wrong. They are struck here, and not only in the "Recorded 2026-08-17" section below, because a runner implements the criteria — an amendment recorded 3 000 words further down and a criterion quietly left standing look identical from the top of the card.

    • Coverage rule — as authored: "a card is flagged when a merged PR's diff touches EVERY path its acceptance criteria name." Measured, that rule fires on nothing, including this card's own canonical fixture: the only path in MOTIR-2757's acceptance-criteria span is tests/permissions/userlessTenantRead.test.ts, which has no commits since that card was filed, while the path the sweep actually took (lib/services/workflowsService.ts) sits in its Context refs. Implement instead: at least ONE path the card's BODY names was touched by a merged pull request that is not this card's own and that merged after this card was filed, the advisory reporting the first such path with its covering pull request.
    • Cost — as authored: "The channel already computes description↔graph references; this is the same read against the PR set." It is not the same read: the PR set carries no diff. GithubPullRequest has no changed-path column and no merge instant (re-verified on origin/main @ 48de3843, unchanged from the 64fb9e6e reading below). That substrate is MOTIR-2922, which blocks this card. The 2 points / 40 minutes on this card were priced against a data set that does not exist, so re-estimate this card when 2922 lands rather than inheriting them.
  3. Blocking vs advisory is an open call to record on this card before code lands. Note the counter-evidence from MOTIR-2075 and run.md's guard #4: a non-blocking advisory is only as good as the step that reads it, and here no step read one. Weigh surfacing it at CLAIM time (claim_next_ready) instead, where a run cannot miss it — the MOTIR-2709 argument that a notice delivered to someone who has stopped looking is a record of the thing happening again.

  4. Retro-checked against the real incidents: MOTIR-2757's state on 2026-08-16 fires the check; MOTIR-1715's MOTIR-1502 fires it; a genuinely-ready card in the same sprint does NOT. Assert all three against fixtures, so the firing set is calibrated on the defect rather than on invented input.

  5. The exemption path is a named predicate with its own test — a boundary-contract card legitimately shares paths with its sibling and must not be flagged forever.

Out of scope

  • Re-opening MOTIR-2757 (closed with evidence; the binding is correct on main).
  • The notes.html lesson, which is owed separately and is a motir-meta PR.
  • MOTIR-2734's flag retirement, which independently removes the mode-split that hid the original defect.

Context refs

  • lib/services/proseGraphAdvisoryService.ts — the advisory channel to extend.
  • lib/workItems/proseVsGraph.tsisOrderingCheckExempt is the model for "a named predicate, not an inline condition".
  • docs/rls-runtime-role-inventory.md:274 and :519 — the two halves of the contradiction, in one file.
  • MOTIR-1715, MOTIR-2860, MOTIR-1827, MOTIR-1839 — the prior instances.
  • motir-meta/prompts/run.md — guard #4's advisory-reading block, and the drive-by close-out rule.

Recorded 2026-08-17 by motir run MOTIR-2903 — criterion 2's mechanism, and criterion 3's open call

Criterion 2 invites the mechanism to be "confirmed or replaced with evidence", and criterion 3 asks for the blocking-vs-advisory call before code lands. Both are answered here rather than in the pull request, because the first answer is what blocks this card.

Criterion 2 — the recommended mechanism is CONFIRMED as the only one that works, and its substrate DOES NOT EXIST

The sentence "this is the same read against the PR set" is the part that does not hold. Verified on origin/main @ 64fb9e6e: GithubPullRequest stores provider · repoId · number · state · merged · headRef · title · workItemId · linkedManually · createdAt · updatedAtno changed-path column and no merge instant — no model among the schema's 82 holds a pull request's files, githubPullRequestRepository's nine methods return no path, and no leaf in lib/github/ calls GET /repos/{owner}/{repo}/pulls/{n}/files. So the check cannot be built here as written. That substrate is now MOTIR-2922, which blocks this card.

The cheaper plan-side substitute was considered and is ruled out by measurement, not by taste. Comparing this card's prose against the COVERING card's prose needs no GitHub call at all — and it is dead on this card's own canonical fixture. The card that actually absorbed MOTIR-2757 is MOTIR-2846, whose description contains zero occurrences of workflowsService, getWorkflow, listStatusesByProject, getStatusByKey or lib/services/workflowsService.ts; its parent MOTIR-2796 contains none either, and none of that story's twenty-one children names a workflow read. The two cards describe the same change in disjoint vocabularies — one names a scanner's verdict list and a count, the other names three methods — so no string either author wrote is common to both. c99efdc7 touching lib/services/workflowsService.ts on 2026-08-15 is the only artifact where the overlap is a fact. Criterion 4 is therefore unsatisfiable by any check that reads only the plan, which is the evidence criterion 2 asked for.

One narrowing of criterion 2's wording, also measured. "Touches EVERY path its acceptance criteria name" does not fire on MOTIR-2757 either: the only path in that card's acceptance-criteria span is tests/permissions/userlessTenantRead.test.ts, which has no commits since the card was filed, while the path the sweep actually took — lib/services/workflowsService.ts — sits in its Context refs. Whole-body coverage fails too: of the five paths the body names, c99efdc7 touched two. So the coverage rule the implementation carries is "at least one path the card's BODY names was touched by a merged pull request that is not this card's own, and that merged after this card was filed", and the advisory reports the first such path with its covering pull request. Full coverage is the rule that reads well and fires never.

Criterion 3 — DECIDED: non-blocking, delivered at the tier a run cannot miss

The advisory never gates readiness, and it passes buildDispatchProseAdvisories' filter — so it reaches claim_next_ready, dispatch_prompt and the CLI dispatch renderer, exactly where criterion 3 points, as well as validate_work_item (the surface that returned advisories: [], which is the observation to invert).

Both halves of that are load-bearing, and each answers one of criterion 3's two arguments.

Why not blocking. The check is heuristic in a direction that fails toward the false positive: a path touched by a later merge is very often the ordinary case of two cards working the same file in sequence. As a gate that would make legitimately-ready cards unstartable and train the author to stop naming paths in card bodies — the same inversion WorkItemProseAdvisoryDto's header records for the reference tier, where a gate would make the remedy "write WORSE card bodies". MOTIR-2075's lesson is that a non-blocking notice is only as good as the step that reads it; the answer to that is to fix the step's INPUT, not to make a heuristic authoritative.

Why the dispatch tier is the fix for exactly that. MOTIR-2075's advisory fired into validate_work_item, a surface nothing in the run's path was obliged to call. buildDispatchProseAdvisories is a different situation: claim_next_ready returns it in the claim payload, so the run that claims a subsumed card is handed the finding in the same response that hands it the card. Its header already states the rule this family inherits — every SHAPE advisory passes the filter because it is "dispatch-relevant BY CONSTRUCTION" — and a card whose deliverable may already be in the repository is the most dispatch-relevant finding there is. That is also the MOTIR-2709 argument satisfied: the notice is delivered to someone who has not stopped looking, because they cannot start without reading the response it rides in.