Story test gate (Vitest) — the coverage floor, the route→service→gate seams, and the guards that stop a second policy implementation
The story's per-repo coverage and integration gate. It runs AFTER every code card in this story has merged, because it measures their real, combined result — the seams BETWEEN the cards are exactly what each card's own unit tests mock away.
Three jobs, in the order they matter here.
1 · Architecture guards the coverage number cannot see. This story's characteristic failure is not an untested branch, it is a policy answer derived somewhere the model does not know about — the shape the members card found sitting in projectMembersService since 6.4. So:
- No second policy implementation. No file outside
lib/permissions/**andlib/projects/access.tsderives an administrative answer for itself: aprojectRole === 'admin'comparison, or anisWorkspaceManager(...)call used as a gate, outside those modules is a failure. Allow the known-legitimate exceptions by NAME with a reason, never by directory glob. - Every administrative key is enforced. Each of the twelve is
enforcement: 'enforced'and appears as a literal in at least one gate call outsidetests/— the orphan guard at full strength for this story's keys, so a key cannot be marked enforced by a card that forgot to wire it. - No manage key on a read. The read operations the domain cards deliberately left alone — the vocabularies, the board projection, the repository views, the members list, the AI settings — are asserted reachable by a project MEMBER. This is the guard that catches an over-tightening, which is the failure mode a coverage number is blindest to.
2 · The route→service→gate seams, against real Postgres. One representative operation per domain, driven end to end: an admin passes, a member gets 403 with the code string consumers read, a non-browser gets 404 not 403 (the no-existence-leak posture), and a workspace owner passes on every access level. Per-card unit tests mock the gate; this is where the real resolution runs against real rows.
3 · The coverage floor. Coverage over the story's changed surface, topped up to the project's per-file gate (≥90% branch / function / line) wherever the merged result falls short. Do not pre-subtract what the code cards covered — that is unknowable until they land, and "already covered" is the expected normal, not a reason for this card to have nothing to do.
Acceptance criteria
- The three guards above exist as real assertions with real failure messages, and each is demonstrated to FAIL — a guard nobody has seen red is not evidence. Follow the pattern the existing
the guard can actually failblock intests/permissions/noUngovernedOperation.test.tsalready sets. - The seam tests run against real Postgres (not mocks), covering at least one write per domain: member, board, workflow, automation, field/component/label, estimation, repository, code access, AI settings.
- Coverage over the story's changed files meets the project gate; the PR body names any file that needed topping up and what was added.
- The parity table from the seam card is still green and untouched — if this card needs to change it, that is a finding about a domain card, not a test to adjust.
pnpm testgreen;pnpm lintand the prettier check pass repo-wide.
Context refs
tests/permissions/accessParity.test.ts·catalog.test.ts·getPermissions.integration.test.ts·inventoryCoverage.test.ts·noUngovernedOperation.test.ts— the suite this joins; the last one holds the fail-demonstration pattern to copy.lib/permissions/**·lib/projects/access.ts— the only modules allowed to derive a policy answer.lib/services/projectAccessService.ts—assertPermissionand the error posture the seam tests assert.motir-core/CLAUDE.md— the ≥90% per-file coverage gate and the real-Postgres integration convention.- Every code card in this story: the seam · the retirement · members · board · workflow · vocabularies · repository · AI settings.