Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
144
planned
1,362
shipped

Motir · Work items

MOTIR-2944Done

Planning bug: a card described a SESSION LIFECYCLE the shipped model does not have — and two of its release paths would have defeated the lock

Filed by motir run MOTIR-2786 (2026-08-17). Telemetry about the planner; the correction is already applied and this holds up nothing.

What the card assumed

MOTIR-2787 specified the lock's lifecycle against a model of a planning SESSION that motir-core does not implement. Two clauses, one family:

1. AC4 — "Release happens on close, submit, cancel, abandon, and job failure — one test per path, none skipped."

Releasing on submit does not merely fail to help; it defeats the lock. Submit is what starts the planning job, so releasing there hands the target to a second session at exactly the moment the first is waiting for its proposal — the window the lock exists to protect is the one submit opens. The run made submit a heartbeat instead.

The other four names have no shipped counterpart either: there is no "close a session" operation, no "cancel a job" operation, and PlanStatus has no failed member, so a plan whose job dies stays generating forever and no product event ever fires. They map onto three real paths — approve, decline, and the lease sweep.

2. AC3 — "The epic→story hand-off releases the epic to its prior status and acquires the story atomically."

A session's IDENTITY is its scope (@@unique([projectId, scopeKey]), and planScope.ts says so in as many words: "a thread is addressed by SCOPE, never by id"). So a session's target cannot move from epic to story — descending creates a different session row. In the shipped flow the descend is two user actions (approve the plan, then open a session on a story), with a legitimate gap between them where nobody holds anything.

Why it is one bug and not two

Both clauses were written from a mental model of one long-lived conversation whose target moves. That model is intuitive, it is what the story's own explanation describes, and it is not the one shipped. Everything downstream — the release list, the atomic hand-off, the phrase "a hand-off, not a hold" — follows from it correctly. The defect is upstream of all of them.

What would have caught it

The card cited planScope.ts in its own Context refs, and the sentence that contradicts it is that file's most emphatic line. The precondition-verification rule was applied to the NOUNS and not to the VERBS: MAX_SCOPE_TARGETS was checked, the dormant status was checked and even re-verified with a git grep, the transitions were checked. What was not checked is whether a session can DO the things the card has it doing — open, submit, close, cancel, descend. A lifecycle is as much a precondition as a symbol is, and it is the harder one to notice missing, because prose about a lifecycle reads as design rather than as a claim about shipped code.

The correction, already applied

  • Submit is a heartbeat; release runs on approve, decline and the expiry sweep.
  • handOff({ release, acquire }) is built and tested as an atomic primitive — so the behaviour the card wanted exists and is proved — while the product's descend uses the release-only form, documented at the call site.
  • All three amendments are recorded on MOTIR-2787 with their evidence, and in motir-core PR #2102's body.

The rule this suggests

When a card describes a lifecycle — opens, submits, closes, is abandoned, hands off — treat each verb as a precondition to verify, exactly as a named file or symbol is. Ask of every one: what shipped operation performs this, and what row does it write? A verb with no answer is a missing precondition wearing the clothes of a design decision.


Close-out verification (motir run MOTIR-2944, 2026-08-17)

The lesson is on origin/main as notes.html #301"Verified every NOUN the card named and no VERB", merged as motir-meta PR #226. This card cited no number when it was filed (the entry was authored minutes later and renumbered from #299 by an append collision); the accurate citation is #301. The Across the 302 mistakes count line is correct against 302 entries.

Every claim this card makes about shipped code re-verified on parent/MOTIR-2786-planning-lock, by CONTENT:

claimevidence
submit is a HEARTBEAT, not a releaseplanChangeSessionsService.ts:545refreshForSession, whose own doc reads "the heartbeat, called when the thread does something (a submit)"
release runs on approveplansService.ts:1534, comment "the epic's stories now exist, so the level the lock was held at is finished (MOTIR-2787)"
release runs on declineplansService.ts:1613, comment "Declining is as terminal for the lock as approving"
release runs on the expiry sweeplib/jobs/definitions/planTargetLockSweep.ts; releasePlanTargetLocks is best-effort AFTER the commit precisely because "the lease expires and the sweep will clear it"
handOff({ release, acquire }) is an atomic primitive, built and testedplanTargetLockService.ts:334; asserted at tests/planning/planTargetLockService.test.ts:334 / 359 / 379
the descend uses the release-only form, documented at the call siteplansService.ts:1040 releasePlanTargetLocks, called only from approvePlan and the decline path

⚠️ One claim did NOT hold: "All three amendments are recorded on MOTIR-2787 with their evidence." They were recorded as a COMMENT, and MOTIR-2787's DESCRIPTION still carried AC 3, AC 4 and AC 1 exactly as originally authored — plus AC 6 and the category: 'in_progress' bullet, which three of Yue's own comments of 2026-08-12 had superseded and which had never reached the body either. A description is the SPEC and a comment is not, and that card is in_review with motir-core PR #2102 open, so a reviewer or a re-run reading only the body would have inherited every falsified clause. Fixed in this run: nine clauses amended in the description, each marked AMENDED 2026-08-17, MOTIR-2944 (or SUPERSEDED by Yue's comments of 2026-08-12) with the original struck through and quoted inline, plus a comment on 2787 saying they were promoted from the thread. Third instance of notes.html #302's family.

The promote question — settled SHARPEN, and the ground is NOT the count

This entry's own "Why nothing caught it" section carried two claims that do not survive re-checking, and correcting them is what decides it:

  1. "Guard #4 and the precondition-verification rule are both phrased around THINGS … All nouns." False. plan-rules/phase-deepen.md step 1 enumerates SEVEN precondition kinds and the seventh is a behaviour — "the BEHAVIOUR / WORKFLOW it depends on" — landed 5613440, 2026-06-19, eight weeks before this card's subject was authored (2026-08-12T10:35Z), in a pack every leaf deepen loads. The gap is therefore a DISCHARGE MISMATCH inside a limb that WAS loaded, not a missing category: that item's parenthetical defines the thing to find as a sibling CARD that specifies the flow, so a lifecycle the SHIPPED CODE owns lands on neither that item nor the noun items.
  2. "Every existing instance in this corpus is a noun." False — the negative that drifts silently, again. #25 is a lifecycle instance whose prompt hint already prescribes "an explicit lifecycle audit step: list which provider operations the integration automates (create, update, rotate, delete)" — but scoped to third-party Marketplace integrations, so nothing generalised it to the operations of a service we ship. Family is honestly TWO, one of them vendor-scoped.

Both corrections pushed into notes.html #301 in place (motir-meta#230 — count line untouched, tag balance neutral, so it cannot collide with an append PR).

Verdict: SHARPEN — one clause, on ground (1), not on the family count. Filed as MOTIR-2953 (phase-deepen.md) → MOTIR-2954 (the SHARED_PLANNING_RULES mirror, blocked_by, seeded blocked). Filed separately from MOTIR-2878 / MOTIR-2913 / MOTIR-2929 rather than folded, on file-set overlap: none of them touches phase-deepen.md, so folding prevents no collision, and 2878 already sits at the 60-minute estimation ceiling.

Also corrected on this card: executor was human on a record card with no human-only work in it — a record card is chore / coding_agent, and human makes it read as dispatchable. Its explanationMd was null (gate 16).