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

Planning bug: a `coding_agent` card carried a criterion only a BROWSER SESSION can discharge — minting a `CLI_TOKEN_GRANT` token is cookie-session-only by design, and no gate noticed

To Do
Description

Found running motir run MOTIR-3795 (runbook parent-run), 2026-08-28, at the story's first child.

What happened

MOTIR-3796 (type: verification, executor: coding_agent, 1 point / 20 min) carries acceptance criterion 3:

The reading is taken with a token holding no more than CLI_TOKEN_GRANT's permissions, so it verifies the door a dispatched agent will actually come through, not the one a workspace PAT can.

That criterion cannot be performed by a coding agent, and the reason is a deliberate security property rather than a gap. Both doors that mint such a token are cookie-session-only, and both say so in their own headers on origin/main:

  • app/api/me/api-tokens/route.ts"Deliberately COOKIE-SESSION ONLY (never PAT-authed): the PAT is for agents, but the surface that MINTS a PAT must not be reachable WITH a PAT, so a leaked token cannot mint more tokens."
  • app/api/cli/device/approve/route.ts"COOKIE-SESSION ONLY, and that is the whole security model … this route is never bearer-reachable — a PAT cannot mint more PATs." Its precondition is a signed-in GET /api/auth/device?user_code=… in a browser.

So discharging AC 3 requires a signed-in human at app.motir.co. run.md's never-ask rule forbids a run from asking for one, and _shared.md records that the account credentials are not stored in the corpus. The card is therefore unfinishable as written by the executor it names.

Why no gate caught it

This is lesson 30's shape — "Every external-service or secret/dashboard prerequisite a code subtask needs is its OWN planned subtask (type: manual/human, no PR, marked done on the user's confirmation), wired via depends_on so it's visible at PLAN time and never ambushes a motir run" — and every automatic signal was green:

  • readiness.ready: true, openBlockers: [].
  • validate_work_item returns no shape advisory for this class. likely-over-gate-sizing reads the numbers, likely-repo-straddle reads paths against targetRepo, likely-ordering-violation matches post-merge phrases. None of them asks whether the card's own executor can perform its own criteria.
  • The criterion looks like an ordinary tightening of a measurement, not like console work. It names no dashboard, no secret and no provider — the vocabulary lesson 30 taught a reader to spot.

That last point is the transferable half. The manual-prerequisite gate is written around external-SaaS vocabulary, and this criterion is about our own product's credential model — so it reads as in-house and reachable while being exactly as human-gated as a Stripe dashboard.

What the run did instead (recorded on MOTIR-3796, not hidden)

The round-trip was taken with the workspace PAT and the residue was written down. What AC 3 was actually asking — does the door a dispatched agent comes through open? — is already mechanised on origin/main:

  • lib/mcp/toolPermissions.ts:193publish_design_result: 'work_item:edit', a key CLI_TOKEN_GRANT already carries and which the tool does not widen.
  • tests/mcp/publishDesignResultTransport.test.ts:262"a token minted on CLI_TOKEN_GRANT publishes a design result end to end", with the grant read from the exported constant, so a later narrowing fails a test rather than silently changing what a sandboxed agent may do.

That runs on every pull request, which is stronger than one manual reading — but against a test database, not against the deployment. The deployment-side half is what stayed unmeasured.

Acceptance criteria

  1. plan-rules/kind-leaf-deepen.md's manual-prerequisite limb names the IN-HOUSE case, not only external SaaS: a criterion is human-gated when the surface that performs it is cookie-session-only, whoever owns it — the test is who can hold the credential, not whose product it is. The added text names the two Motir routes above as the standing instance.
  2. The addition is an ADD, never a re-wording of the surrounding rule (motir-meta's corpus convention), and prompts/plan-rules/MANIFEST.md still routes to the limb it lands in.
  3. The check is stated so a plan pass can run it in one question: for each acceptance criterion, could the card's own executor value perform it? — with coding_agent + a browser-session-only surface being the fail.
  4. No claim that validate_work_item should detect it. The advisory tier reads a card's text against the tree and the repository; can this executor do this is not a question it has the inputs for. Say so, so the next reader does not file the same finding as a missing detector.

Context refs

  • MOTIR-3796 — the card, and the comment recording the full disposition and the evidence id (cmtdlo5mx00dnhvphd5yy3jey).
  • motir-core/app/api/me/api-tokens/route.ts · app/api/cli/device/approve/route.ts — the two cookie-session-only mint doors.
  • motir-core/lib/mcp/toolPermissions.ts · tests/mcp/publishDesignResultTransport.test.ts — the constant and the guard that already answer AC 3's question in CI.
  • _shared.md § Mistakes to avoid #30 — the lesson this is a new instance of.