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

Planning bug: MOTIR-2903 specified its mechanism against a "PR set" that carries no diff — the precondition was asserted as already-shipped ("this is the same read"), in a clause whose job was to price the card

Done
Description

The planner defect, not the product defect. motir run MOTIR-2903 reached guard #4 (the "X doesn't exist" claim gate) before opening a worktree. MOTIR-2903 is now blocked on MOTIR-2922, which builds the substrate it assumed.

What happened

MOTIR-2903's criterion 2 specifies the mechanism as "a card is flagged when a merged PR's diff touches every path its acceptance criteria name", and justifies its cost in one clause: "The channel already computes description↔graph references; this is the same read against the PR set."

It is not the same read. Verified on origin/main @ 64fb9e6e:

  • GithubPullRequest stores provider · repoId · number · state · merged · headRef · title · workItemId · linkedManually · createdAt · updatedAt. No changed-path column, and no merge instantmerged is a boolean and updatedAt moves on any later delivery.
  • No model among the schema's 82 holds a pull request's files. The only model storing repository paths is DesignAsset.sourcePath, and CI pushes those; nothing reads them back from a pull request.
  • githubPullRequestRepository exposes nine methods; none returns a path.
  • lib/github/ holds twenty read leaves; none calls GET /repos/{owner}/{repo}/pulls/{n}/files. historicalPullRequests.ts walks a repository's merged pull requests and normalizes their metadata only.

So the card's central deliverable rested on a data set that does not exist, and the run had to stop.

What makes this worth a card rather than a shrug

The clause did the damage precisely because it was reassuring. "This is the same read" is an estimate justification: it tells the reader the expensive part is already built, which is why the card is sized at 2 points and 40 minutes. A reader checking that claim would have had to open prisma/schema.prisma — and nothing in the authoring pass asks anyone to. Gate 1 reads the repo COLUMN, gate 14 reads the criteria's shape and ordering, the likely-repo-straddle advisory resolves path prefixes against the connected-repo registry. Not one of them reads a SCHEMA, so "the field this criterion consumes exists" is not a question the plan-side machinery can currently ask, at any tier. ⚠️ AMENDED 2026-08-17 — the last clause is FALSE and is corrected in §3 of the close-out below. True of the three instruments named here; false of any tier. plan-rules/phase-deepen.md's claim-vs-pointer gate enumerates a column verbatim, is mirrored in the shipped planner, was loaded by the pass that authored the card, and landed twelve days earlier. Left in place, quoted rather than rewritten, because the wrong conclusion is half of what this record is now for.

And the card is itself a planning-bug record, which is the sharp end of it: the home story exists to concentrate the corpus that stops this class, and a card filed into it shipped an unverified precondition of exactly the kind MOTIR-2075 and notes.html #42/#43 are about. The gate fired at RUN time, correctly, which is the backstop working — but the backstop is run.md guard #4, and guard #4 firing at all is defined there as "itself a plan mistake to log".

The narrower, more useful observation

The precondition that failed was a FIELD, not a card. The advisory channel already catches a card consuming another CARD that is not done (likely-missing-edge), and it caught nothing here because there was no work item to name — the missing thing was a column. A criterion that names a symbol, a column, a table or a route is making an existence claim about the CODE, and the plan has no instrument that reads code. ⚠️ AMENDED 2026-08-17 — also false; see §3. motir-ai ships code_search / code_explore over a per-repo code graph, composed into the planner's own tool set by retrievalTools.ts, which treeGeneration.ts imports. run.md guard #4 covers it in prose, at run time, after a session has already been spent reading the card.

Whether that gap deserves its own mechanism is a separate question from MOTIR-2903's — and it is worth noting that MOTIR-2922 does not close it. Recorded here rather than acted on, since the correction to the plan is already applied and nobody has scheduled the general form.

What was already done

  • MOTIR-2922 created — the changed-path and merge-instant capture, sealed under gates 1, 14 and 16.
  • MOTIR-2903 blocked_by it, and transitioned to Blocked.
  • MOTIR-2903's description amended on the record with the measurement, with criterion 2's coverage rule narrowed to what actually fires on its own fixture, and with criterion 3's open call decided (non-blocking, at the dispatch tier).
  • The lesson added to motir-meta/notes.html.

Not in scope here

  • Building anything. This card is telemetry about the planner; the buildable work is MOTIR-2922.
  • MOTIR-2903's own subject matter, which is unchanged and still correct.

Close-out verification — motir run MOTIR-2923, 2026-08-17

1. The corrections HELD, with one half-discharge that is now fixed

claimed in "What was already done"verified
MOTIR-2922 created, sealed under gates 1, 14 and 16Held. targetRepo: motir-core, type: code, executor: coding_agent, 5 pts / 100 min, explanationMd present, seven acceptance criteria, out-of-scope and context refs written.
MOTIR-2903 blocked_by it, transitioned to BlockedHeld. readiness.ready: false, one open blocker, status blocked.
MOTIR-2903's description amended on the recordHeld as to the mechanism — the amendment is in the DESCRIPTION, not merely a comment (the check MOTIR-2907 exists to force) — but HALF-DISCHARGED as to criterion 2. The narrowed coverage rule and the missing substrate were recorded in an appended section three thousand words below, while criterion 2 itself still read "touches every path its acceptance criteria name" and "this is the same read against the PR set", verbatim. A runner implements the criteria. Both clauses struck inside criterion 2 today, dated, with the original wording quoted and the rule-to-implement stated there.
The lesson added to notes.htmlHeld — mistake #294, motir-meta PR #211, merged 2026-08-17T18:58Z. Located by content; this card cites no number, so nothing could drift.

2. Every rung-2 citation re-verified — all EXACT

Re-run on origin/main @ 48de3843 (today's head; the card measured 64fb9e6e and nothing in this area moved between them). Say it plainly rather than implying a drift that is not there:

  • 82 models — exact.
  • GithubPullRequest's scalars — exact, all eleven, in the order the card lists them. No changed-path column; merged is a Boolean and updatedAt is @updatedAt, so there is no merge instant.
  • githubPullRequestRepository — exactly nine methods, exactly as named; none returns a path.
  • lib/github/ — exactly twenty entries. Unit note: twenty files, not twenty read leaves — errors.ts, tokenCrypto.ts, webhookSignature.ts and prLinkErrorMessages.ts are not API reads. The count is right, the noun is loose.
  • No call to GET /repos/{owner}/{repo}/pulls/{n}/files — re-run repo-wide rather than only over lib/github/, since a negative scoped to one directory is the class that drifts. One hit in the whole repository and it is prose, in scripts/plan-seed/data/story-7.6.ts.
  • "The only model storing repository paths is DesignAsset.sourcePath"survives, and its qualifier is load-bearing. Attachment.blobPathname and Attachment.originalFilename also store paths; they are blob-store keys and upload filenames, not repository paths.
  • historicalPullRequests.ts normalizes metadata only — held, with a constructive addition recorded on MOTIR-2922: it already parses merged_at into a required mergedAt: Date, which NormalizedChangeRequest then drops at the persistence boundary.

3. ⚠️ The card's own central claim is FALSE — and it is the sentence a future rule would have been written from

"Not one of them reads a SCHEMA, so 'the field this criterion consumes exists' is not a question the plan-side machinery can currently ask, at any tier."

True of the three instruments it names (gate 1's repo column, gate 14's shape and ordering, the likely-repo-straddle path-prefix resolver). False of any tier, in both homes:

  • plan-rules/phase-deepen.md, the claim-vs-pointer gate: "a card may not assert a code fact — a policy branch, a helper, an exported symbol, a column, a route, an enum value, a config key — as EXISTING, NOR AS ABSENT, unless it was grepped on origin/main at authoring time", discharge "one grep per asserted fact". It names a COLUMN in its own enumeration.
  • Mirrored verbatim in the shipped plannermotir-ai/src/llm/planningRulePacks.ts (NOR AS ABSENT ×2, a column ×3), and treeGeneration.ts carries the same gate.
  • The pack was LOADED. plan-rules/MANIFEST.md routes phase-deepen.md into motir log-bug — the pass that authored MOTIR-2903 — and into the re-plan and blocked-run passes as well.
  • ORDER test: the asserted-ABSENCE limb landed 2eb70d1, 2026-08-05. MOTIR-2903 was authored 2026-08-17T16:33Z — twelve days later, same planner.
  • And the instrument exists too. motir-ai/src/llm/codeGraphTools.ts ships code_search ("Find code symbols (functions, classes, methods, files) by name in the current project's code graph") and code_explore, composed into the planner's tool set by retrievalTools.ts, which treeGeneration.ts imports. motir-core's lib/ai/codeContext.ts puts the connected repo set on every planning-job envelope. The shipped planner is handed a tool for exactly this question.

So the rule existed, the pack was loaded, and a tool for the question was in the planner's hand.

4. The narrower finding that replaces it — the CAMOUFLAGE

The gate's own closing sentence already names the failure mode: "'I was confident' is never the reason it was skipped; not having noticed that the sentence was a claim is." What is genuinely new here is the costume. Every illustration under that gate wears one — an annotation in ## Context refs that EXPLAINS what a file contains. This claim wore another: it sat inside an acceptance criterion, doing ESTIMATE work, a clause whose job was to justify 2 points and 40 minutes. A reader auditing the Context refs for explain-shaped annotations finds nothing wrong there, and a pricing sentence reads as sizing prose rather than as a premise. Same shape as notes.html #245: the governing sentence covers the case, the illustrations do not, and the failure hides in the gap.

5. Promote verdict — LESSON. No plan-rules change, no mechanization.

The open question this card left ("whether that gap deserves its own mechanism") is settled here rather than left conditional.

  1. The governing clause covers it and predates the defect by twelve days, in a pack the offending pass loads. That is the MOTIR-2280 discriminator, in its strongest form: not merely a rule exists, but this rule enumerates the exact artifact type ("a column") and was in the pass's read set. Diligence miss, not a trigger gap.
  2. A widening of the gate's TELL was considered and refused. MOTIR-2481 (notes.html #245) is the same axis — a claim camouflaged by estimate register, governing sentence covering it, illustrations not — and resolved LESSON eight days ago. Two occurrences, both LESSON. Refusing the widening also keeps a third card out of phase-deepen.md, which MOTIR-2924 (motir-meta PR #212) landed in hours ago.
  3. Mechanization is refused by MOTIR-2432's own discriminatora rule nobody has read is a diligence problem and needs no machinery — and the premise that would have motivated it is false anyway (§3): the machinery to ask "does this column exist" ships, in code_search / code_explore.

Family, honestly counted: one new instance on the camouflage axis (MOTIR-2481 is the other). The claim-vs-pointer family at large is much bigger — #221, #248, MOTIR-2082MOTIR-2095, MOTIR-2305 — and every member of it has resolved as diligence.

6. validate_work_item on THIS card — advisories disposed

valid: true, no blockers, two entries, both severity advisory (control: the same call on MOTIR-2903 returns four, two of them likely-missing-edge, so the channel is live and this is a real reading rather than a blind spot):

  • MOTIR-2903 (blocked) — the card this record is ABOUT. Not consumed; a record is never blocked_by its own subject.
  • MOTIR-2922 (todo) — named in "What was already done" as the buildable work that came out of this incident. Not consumed either; the correction is already applied, and this card holds up nothing.

Neither is an edge to wire.

7. What this close-out shipped

  • motir-meta PR #217notes.html #294 corrected in place: the false "No gate at any tier reads a schema" clause replaced with §3's evidence and §4's finding, quoted inline and dated; the Prompt hint now says its grep half IS the existing gate's discharge, so the next reader does not file a rule that shipped on 2026-08-05. Tag balance identical to the origin/main baseline ({'p': -1}); the entry numeral and the "Across the 296 mistakes" line untouched, so it cannot collide with an append PR.
  • MOTIR-2903 — criterion 2 amended in place (both false clauses struck, dated, original quoted, the implementable rule stated); a comment disposing its four validate_work_item advisories in advance (three noise — two of them ARCHIVED cards, one this record itself — one real, already wired) and recording that its 2 points / 40 minutes were priced by the false clause and need re-estimating when MOTIR-2922 lands.
  • MOTIR-2922 — a comment carrying the re-verification and two build-time reads: the merge instant already arrives on a second shipped path (historicalPullRequests.ts parses merged_at; the persistence boundary drops it, which also makes the out-of-scope backfill near-trivial for that half), and the GithubCheckRun.commitSha near-miss (a HEAD sha, not a merge sha, absent when a PR has no checks — it cannot shorten the migration).