Planning bug: a card asserted the release re-runs the PR's matrix without reading the lanes' own `if:` — the most expensive one had been PR-exempt for nine days
Found while running MOTIR-3760 (motir run, 2026-08-30). The correction is already applied — the card is amended on the record and the decision is docs/decisions/ci-minutes-allowance.md §J.5 in motir-core PR #2467. This card is the telemetry.
What the plan asserted
MOTIR-3760's mechanism #1, authored 2026-08-28T11:46:39Z:
Deploy to Flysits behind the whole matrix, a second time.needs: [lint, typecheck, build, test, coverage, e2e, e2e-at-scale]. The PR already ran that matrix against the merge commit; the push tomainruns it again before anything deploys.
The card then framed its three options on that premise, calling a needs narrowing "the shortcut" that "trades safety for latency" — a trade that is real but much larger than stated.
Why it is false, and false at AUTHORING time rather than by drift
e2e-at-scale — the longest lane in that list, four legs at a 45-minute ceiling — carries its own condition:
if: ${{ needs.changes.outputs.app == 'true' && (github.event_name == 'push' || contains(github.event.pull_request.labels.*.name, 'e2e-at-scale')) }}
For an unlabelled pull request it does not run at all, so the push-to-main run is its FIRST and ONLY execution. Dated, not assumed:
$ git log -S"contains(github.event.pull_request.labels.*.name, 'e2e-at-scale')" -- .github/workflows/ci.yml
99826661d2b2149344d3e1a4eac2ce3b286af228 2026-08-19T22:39:19+02:00
fix(ci): the PR lane runs what the DIFF can reach, not what the branch is NAMED (MOTIR-3148) (#2166)
Nine days before the card was written. The same is true of a second fact the card did not consult: protect-main's strict_required_status_checks_policy is false (read live — gh api repos/moooon-B-V/motir-core/rulesets/17227448 → {"checks":["CI complete"],"strict":false}), so a pull request merges while BEHIND main and the push-to-main run is the repository's only verification of the merged SUM.
The shape of the mistake, which is what makes it worth recording
The card quoted the needs: list correctly and reasoned about it as a set of job NAMES. A job's membership in a run is decided by its if: and its trigger, not by the list that waits on it — and the two lines sit ~230 lines apart in the same file. So the assertion is checkable in one read and reads as already checked, because the part that was quoted verbatim is the part that is true.
The generic form: a claim that two runs execute the same work is a claim about each job's TRIGGER and CONDITION, never about the list of names that depends on them. Its cost is asymmetric — it argues for removing verification, and the verification it would have removed is the one nothing else runs.
The lesson store already carries the global row this belongs to — "A new repo that takes PRs needs PR-triggered CI", whose takeaway is verify a workflow's TRIGGERS, not its existence — written for a repo-scaffolding case and exactly right here. It is reinforced rather than duplicated (occurrence MOTIR-3760).
Acceptance criteria
motir-meta's planning rules carry the check as a standing one, in the pack that owns precondition verification: a card that asserts "X already ran / already exists / is duplicated" about a CI lane owes the lane's ownif:and trigger, dated against the card'screatedAt— not theneeds:list that waits on it.- The rule states the ASYMMETRY explicitly: the failure direction is a card arguing to REMOVE a check, so the verification is owed before the option is written down, not before it is taken.
- It cites this card and MOTIR-3760 as the fixture, with the nine-day gap.
Context refs
motir-core.github/workflows/ci.yml— thedeployjob'sneeds, ande2e-at-scale'sif:~230 lines above it.motir-coredocs/decisions/ci-minutes-allowance.md§J.5 — where the corrected reasoning landed.motir-metaprompts/plan-rules/phase-deepen.md— the precondition-verification rule this extends.