11.2.11 Story vitest gate — coverage floor, the write→read seams, and the contract guards the work-item endpoints owe
The story's per-repo coverage + integration gate, run AFTER every feature card of this story has merged — it measures their REAL merged surface, so it cannot run before they land. It is not a static list of cases: each card already ships its own units (the per-subtask floor), and this card does the three things those units structurally cannot.
1. Coverage floor
Run coverage over this story's changed surface — app/api/v1/** and lib/api/v1/workItems/**, plus the changed service/repository reads — and WRITE the missing unit/branch tests wherever it sits below the project's ≥90% per-file floor. "Already covered by the per-subtask floor" is the EXPECTED normal here, not a reason the card has nothing to do: what it tops up is the seams BETWEEN cards.
2. Integration seams — real Postgres, no mocks
The tests each endpoint's own suite cannot write, because they span two cards:
- Write → read round trip: create through
POST, then find that exact row through the LIST endpoint (the keyset read + presenter path) and through the DETAIL endpoint, and assert all three agree field-for-field. This is the seam where a mapper and a read drift and no unit notices. - Presenter × keyset read: a row created with every optional field null, and one with all set, both survive the list and detail schemas — the null-shape case unit fixtures usually miss.
- Transition × workflow:
GET …/transitionsand theallowedTransitionson a refusedPOSTare produced by different code paths and must return the same set for the same item. - Links × detail: an edge written through
POST …/linksappears in BOTH the links sub-resource and the detail resource's groups, in the same shape. - Archive × list: an archived item leaves the list page and returns on restore, with the cursor walk staying correct across the change (the collection mutating mid-scan is the normal case here, not an edge case).
- ETag × PATCH: the validator the detail read issues is the one the write path accepts — a round trip proving one function owns both directions, and that a write invalidates the previous validator.
- Filter parity, end to end: the same FilterAST through the v1 list endpoint and through the
/itemsview's read yields the same key set.
3. Contract guards — the guarantees a coverage percentage cannot see
Extend tests/api/v1/story-gate.test.ts's shipped auditV1RouteSource sweep, and add the guards this story owes:
- No
/api/v1route importslib/mcp/tools/**. The two surfaces are aligned through SCHEMAS (11.6), never through imports; a route reaching into the tool layer couples a stable contract to a deliberately fluid one. - The cascade delete is unreachable: no
/api/v1route referencesdeleteWorkItem, and no route declareswork_items:delete. The omission is asserted so it cannot be undone by accident — the ADR's own condition for leaving it out. - Every route's declared scope matches the ADR §3 table, asserted table-driven over the whole tree (a GET declares
read; a work-item mutationwork_items:write; archive/restorework_items:archive), so a new endpoint cannot pick its own gate. - Every response body a v1 work-item route returns goes through the schema module — no route hand-shapes a response inline, which is what makes 11.4's "single schema source" true rather than aspirational.
- Each guard fails when violated — add the deliberately-bad fixture for every new guard, as the shipped suite does for the Prisma / transaction / wrapper rules. A guard with no failing case proves nothing.
Scope BOUNDARY
Vitest only, motir-core only, one repo one suite one PR. It does not drive the API as an external HTTP client with a real PAT — that is 11.2.12 — and it does not add endpoints, fix product defects it finds (those are their own cards, per the drive-by rule) or touch 11.3's surface.
Acceptance criteria
- Every file this story added or changed meets the ≥90% per-file branch/fn/line floor, with the top-up tests written where it did not.
- Each integration seam above is covered against real Postgres, driving the REAL routes rather than calling services directly.
- Each new contract guard is present AND has a deliberately-violating fixture proving it fails.
- The guard sweep finds the whole route tree (a guard over zero files proves nothing) and names the new route files explicitly, so a deleted route is a failing test rather than a silently smaller sweep.
- The suite runs in CI on the standard vitest lane and needs no new infrastructure.
Context refs
tests/api/v1/story-gate.test.ts— the shipped gate this extends, includingauditV1RouteSourceand its violation fixtures.tests/api/v1/pagination.test.ts·tests/api/v1/wrapper.test.ts— the existing per-concern suites.motir-core/CLAUDE.md— the coverage floor and the vitest/real-Postgres conventions.docs/decisions/public-api-conventions.md— §3 (the scope table the guard asserts) and §9 (the 4-layer rule it enforces).- Subjects: 11.2.2 · 11.2.3 · 11.2.4 · 11.2.5 · 11.2.6 · 11.2.7 · 11.2.8 · 11.2.9 · 11.2.10. Parent story: 11.2.