The closing story of Epic 2 (Issue tracking core): the epic-level test closer, modeled on Subtask 1.4.7 (the Story-1.4 work-item-model closer). Its job is to audit the coverage Stories 2.1–2.5 already shipped, fill the genuine gaps no single feature story owned, extend coverage gating to the Epic-2 modules that are not gated yet, and record an epic-level coverage matrix — NOT to re-duplicate the per-story tests that already ship with each feature.
Why this is an audit-and-fill story, not a write-the-tests story (decision-ladder rung 2 — the already-shipped code outranks the card). The stub described Vitest over CRUD / illegal-transition / type-parent / key-uniqueness plus a Playwright create→detail→status flow. Almost all of that already exists, because each Epic-2 feature story shipped its own scoped tests (the design-before-code, tests-with-the-feature canonical depth): the full 30-cell kind-parent matrix (kind-parent-matrix.test.ts), depth + cycle triggers (repository.test.ts), 20-wide gap-free concurrent key allocation (service.test.ts + project-counter.test.ts), transition validation incl. illegal / unknown / no-op / open-mode / atomicity (transition-validation.test.ts), workflow management / delete-reassign / restore-defaults / readiness (tests/workflows/*), CRUD + edges + moves + links + revisions, RLS across work-item / workflow / project / workspace, and five E2E specs. Re-writing those would be duplication. So 2.6 does at the EPIC scale exactly what 1.4.7 did at the Story scale.
The real gaps this Story closes (each verified absent in the current tree): (1) there is no epic-level coverage matrix — 1.4.7 wrote one only for Story 1.4’s model; (2) the default-workflow transition tests only SAMPLE edges, so there is no graph-complete conformance guard over all 15 default transitions + every non-edge; (3) no single cross-story scenario threads the whole epic lifecycle (create tree → assign → multi-hop status walk → archive → list/tree/readiness) end to end; (4) coverage GATING (vitest.config.ts coverage.include + per-file ≥90% thresholds) covers ONLY the four Story-1.4 work-item files from 1.4.7 — workflowsService / workflowsRepository are ungated and workItemsService grew across 2.3–2.5 after the 1.4.7 numbers were taken; (5) there is no consolidated E2E acceptance journey proving the type-parent rule + the full status lifecycle + list/tree reflection as one user-visible flow.
No design subtask, no manual subtask — by design, not omission. This Story builds no UI (the E2E subtask drives the UI Stories 2.3–2.5 already shipped), so the design gate does not fire; and the real-Postgres test harness (tests/helpers/db.ts + the local PG, finding/Story 1.0 + 1.4.7) and the CI test + e2e jobs already exist, so there is no SaaS / secret / dashboard prerequisite to provision. All five subtasks are type: test, executor: coding_agent. Tests use real Postgres (never mocks; the only allowed vi.mock is getSession()), per motir-core/CLAUDE.md.
pnpm install && pnpm prisma generate && pnpm prisma migrate dev against a fresh local DB (PG at :5433).tests/EPIC2_COVERAGE.md — every scope bullet from the 2.6 card (issue CRUD, type-parent rules, workflow transitions, key uniqueness, issue list/tree reads, assignees, RLS) maps to a covering test (file → describe), tagged inherited / filled / added, mirroring tests/integration/work-items/TEST_COVERAGE.md (1.4.7).pnpm test — green. The graph-conformance suite (tests/workflows/transition-conformance.test.ts) and the full-lifecycle scenario (tests/integration/work-items/epic2-lifecycle.test.ts) both pass against real Postgres.lib/workflows/defaultWorkflow.ts to drop one transition edge locally → the conformance suite FAILS (proving it sweeps the real edge set, not a hardcoded copy). Revert.pnpm test:coverage — green, with the per-file ≥90% (branches/functions/lines) thresholds now ALSO enforced on lib/services/workflowsService.ts + lib/repositories/workflowsRepository.ts, and still passing on the four Story-1.4 work-item modules after Epic-2 growth. The coverage-numbers table in tests/EPIC2_COVERAGE.md matches the latest run.pnpm test:e2e --grep epic2-acceptance — the consolidated acceptance journey drives the real shell: create an issue via the modal (the parent picker offers only legal parents for the chosen kind), open its detail page, walk it through a multi-step status lifecycle via the status picker (illegal target unavailable), and see it reflected in both the issue List and the Tree.test job runs pnpm test:coverage (so the new thresholds gate every PR) and the e2e job runs Playwright (the acceptance journey runs on a subtask/* branch — E2E is NOT skipped, unlike seed/* / design/*).