`createMockStore` in `triage.test.ts` defined **neither**
`getTaskWorkflowSelection` nor its async twin. So
`resolveWorkflowIrForTaskWithProvenance` **threw** calling them and took
its catch branch, reporting `source: "default"` in the sense of *"the
lookup failed"*. Production stores always expose both readers — every
case in this file was exercising a store shape that cannot exist.
Returning `undefined` models the real answer: the store **can** be asked
and says there is no selection row, which is what a pre-U11 card
actually presents.
## Why it mattered
`triage.ts`'s post-U11 intake recovery gates on that provenance. A
*failed* lookup correctly refuses to claim a workflow lacks `triage`, so
the orphan arm stayed off and the recovery depended on
`resolvePlannerLanes` **failing** and falling back to legacy ids —
correctness resting on a resolver's failure mode.
In #3141 I measured the async conversion of that site as failing 13
cases and **twice reported it as a production constraint**. It was this
harness. That is the concrete cost of a mock that cannot answer a
question production always can.
## Behaviour-preserving on its own
**380 passed across 26 triage/recovery suites.**
## What this deliberately does NOT do
It does not convert the site. I prototyped the full unblock — a
`selectionAbsent` flag on the determinate `!workflowId` branch, its
single consumer, and the async conversion — and it works: the
previously-failing suite goes **237 passed**.
But with a realistic store the orphan arm starts firing for no-selection
rows, which changes recovery flow in **5
`triage-stuck-requeue-preserve-draft` cases** that currently assert the
refusing behaviour. Whether accepting a legacy `triage` row there is
correct is a lifecycle-semantics decision about migration, not a harness
fix. So it is reverted and reported rather than bundled.
Findings and the measured branch table are on #3141.
## One correction carried from this work
I filed #3187 claiming provenance verifies resolution via `ir.id ===
workflowId`, which cannot pass for builtins. **That was wrong** — the
live code uses a symbol marker, and the text I quoted was historical
prose describing what was removed. Closed with the measurement:
```
store with NO selection readers -> source: default (catch: could not ask)
store answering builtin selection -> source: selection ✓
```
That is the same class of error this PR fixes — reasoning from what
something says rather than what it does.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Tests**
* Improved workflow-resolution test coverage by supporting stores with
no selected workflow.
* Added synchronous and asynchronous test readers for workflow
selection.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>