Planning bug: `run.md` says the design-result publish is "keyed off the `design/MOTIR-<n>-<slug>` branch" — it is keyed off ANY resolvable `MOTIR-<n>` in the branch ref or the PR title, on every PR
Found 2026-08-19 while running MOTIR-3126, whose AC 1 puts a design asset and its implementation in one card — so the PR is a subtask/* one carrying design/repository-set/**.
The inaccuracy
motir-meta/prompts/run.md, in the motir run <parent> step-5 design-stopper block, states:
CI publishes the note + mock + screenshot onto the card from a step in
ci.yml'sdesign-guardsjob, keyed off thedesign/MOTIR-<n>-<slug>branch
The second half is narrower than the mechanism. Read on origin/main:
ci.yml'sdesign-guardsjob has no branch condition — it runs on everypull_request(and onpush: main, where the publish step alone is gated byif: github.event_name == 'pull_request').scripts/upload-design-assets.mjs'sresolveTargetKeytakes the FIRST resolvable<PREFIX>-<number>from, in order:DESIGN_TARGET_KEY(explicit) →DESIGN_PR_REF(the branch ref) →DESIGN_PR_TITLE. Thedesign/prefix is not read, and no other branch check exists.
So any PR whose branch or title carries a MOTIR-<n> publishes its changed design assets to that card.
Evidence
PR #2158, branch subtask/MOTIR-3126-effective-repo-domain, run 32278643416, job 96151884665:
Published 3 design artifact(s) to MOTIR-3126 (target from branch); evidence cmt0c7tib01aai2phqh2mj761.
(target from branch) is the resolver naming which rung answered — a subtask/* branch, publishing exactly as a design/* one would.
Why it is worth correcting
The sentence reads as a precondition, and the cost of believing it runs in both directions:
- A run that ships a design asset on a
subtask/*branch concludes nothing was published, and either hand-publishes or splits the card into two PRs to get a prefix it never needed — which is a real cost, because ONE SUBTASK = ONE PR. - The opposite, and worse: a run that ships design assets on a branch carrying a DIFFERENT card's id (a drive-by asset edit inside a code PR) does not expect to publish and does — onto the wrong card. Nothing in the runbook warns about that today.
Acceptance criteria
-
The
run.mdsentence names the real resolution order —DESIGN_TARGET_KEY→ branch ref → PR title, first resolvable key wins — and says the job runs on every PR regardless of prefix. -
It states the consequence the current wording hides: a PR carrying design assets publishes them to whichever card its branch/title names, so a drive-by asset edit inside another card's PR lands on that other card.
-
What the
design/*prefix ACTUALLY buys is stated separately and correctly (it is a CI-SKIP prefix — Playwright/Vitest/images — not a publish key), so the two are not re-conflated.⚠️ AMENDED 2026-08-20,
motir run MOTIR-3130— the PARENTHETICAL is FALSIFIED BY DRIFT, and the criterion is discharged as a POINTER. MOTIR-3148 (99826661, merged 2026-08-19T22:39+02:00 = 20:39 UTC) replacedci.yml'sstartsWith(head_ref, …)gate with achangesjob that reads the DIFF — every expensive lane now readsneeds.changes.outputs.app/.images, andappis false only when every changed path is docs / design / plan-seed. This card was created 17:02 UTC the same day, so the parenthetical was TRUE when written and the drift POSTDATES it by ~3.5 h: genuine drift, not an enumeration that was wrong at authoring time.Restating it would put a freshly-false mechanism back into
run.md, and MOTIR-3265 (todo,ready, same parent — "run.md's branch-prefix rule still cites thestartsWith(head_ref, …)CI gate that MOTIR-3148 replaced") already OWNS that correction across step 8's paragraph, the worktree block and the Vercel prefixes. So the "not a publish key" half — this card's own claim — is stated, and the "what it IS" half is a POINTER to step 8 carrying no mechanism (notes.html#219: make the runner READ the shipped check rather than restate it in prose that can drift).Co-located write, NAMED not wired. One line inside the block this card rewrites also stated the stale mechanism ("the ONE CI lane a
design/*branch does NOT skip (ci.ymlskips Vitest / E2E / images on that prefix)"); leaving it would have made this card's own block self-contradictory three lines apart, so it became a pointer to step 8. That advances MOTIR-3265's AC 1 by one line and blocks none of its scope — whichever merges second rebases and must not replace. Noblocked_byis owed; either order is correct.run.mdline 1233 — step 8's own sentence — is left UNTOUCHED. -
No behaviour change in
motir-core: this is a runbook correction only.
Context refs
motir-meta/prompts/run.md— themotir run <parent>step-5 design-stopper blockmotir-core/.github/workflows/ci.yml— thedesign-guardsjob and itsPublish the design resultstepmotir-core/scripts/upload-design-assets.mjs—resolveTargetKeymotir-core/vitest.design.config.ts+ci.yml's skip conditions — what thedesign/*prefix actually gates
Delivered (2026-08-20, motir run MOTIR-3130)
motir-meta PR #297, branch docs/MOTIR-3130-design-publish-key — one file, prompts/run.md, +53/−4. Re-verified on origin/main ebd3a3df: ci.yml:350 (no if:/needs: on the job; only the publish STEP is gated on github.event_name == 'pull_request'), scripts/upload-design-assets.mjs:85 resolveTargetKey and :66 parseWorkItemKey, :362 collectChangedDesignFiles (-- design/**) and :336 classifyDesignPath. Every claim on this card holds exactly.
Two realisations of the AC-2 hazard, both POSTDATING this card, are now cited in the rule:
notes.html#337 / MOTIR-3177 — one transposed digit in a commit subject addressed 126 design artifacts to a manual billing task in another epic. Guarded since byDESIGN_EVIDENCE_NOT_A_CHILD.- MOTIR-3213 — MOTIR-3183's three
design/ai-chat/*artifacts published onto MOTIR-3049, a pure/api/v1card with no design subtask, evidencecmt1a0dcd0016i2n8a2sfu5gu, under a green check. Guarded since by failing RED on an untrusted diff base.
So the block now names the three structural guards that DO exist — NotALeafError, DESIGN_EVIDENCE_NOT_A_CHILD, the untrusted-base red — and says plainly that a drive-by asset edit on an ordinary LEAF branch passes all three (the key parses, the card is a leaf, the base is trusted). Naming which guards do not cover the case is what stops the next reader assuming one of them does.
One addition beyond the literal ACs, argued in the PR body: the run most likely to commit a drive-by asset edit is a plain motir run <leaf>, which never opens the motir run <parent> section — so the artifact-obtainable pre-done check (Build/serve rules, read by every run) gains a two-sentence pointer at the mis-publish direction. The mechanism itself is stated once.
Checks: motir-meta has no CI, no package.json, no lint/format gate. plan-rules/'s measure.py / conserve.py / verify.py are scoped to prompts/plan-rules/*.md and never read run.md — nothing to run, and nothing trimmed to chase a script. Not a UI change, so no "How to test" is owed. AC 4 holds: no motir-core file is touched.