Branches gate was blocking CI at 75% measured coverage. The 80% floor suffers Istanbul parent/child denominator coupling (long-tail grind, per #496) that makes the remaining gap disproportionately costly to close. Drop branches to 75 to match current state; leave lines/functions/ statements at 80. ADR-013 documents the rationale and the ratchet rule for raising the gate back incrementally. Closes #556 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.9 KiB
ADR 013 — Client-Project Branch Coverage Threshold
Status: Accepted
Date: 2026-05-14
Issues: #556 — threshold drop · #496 — long-tail-grind tracking
Context
The browser-mode component test suite (vitest.client-coverage.config.ts) enforces Istanbul coverage thresholds across lines, functions, branches, and statements. The branches metric was set to 80%, but the codebase sits at approximately 75% — below the gate — causing every CI run of unit-tests and coverage-flake-probe to fail on this check alone, even when all tests are green.
Measured baseline (2026-05-14, branch feat/issue-553-birpc-async-mock-factory, head 2e6cc346):
branches: ~75% (below the 80% gate — reason for this ADR)
lines: ≥ 80%
functions: ≥ 80%
statements: ≥ 80%
Reproducer:
cd frontend && npm ci && npx vitest run -c vitest.client-coverage.config.ts --coverage
The long-tail-grind problem
In Istanbul's branch accounting, when a child component gains test coverage its branches are added to the parent's denominator. A child moving from 40% → 80% coverage can drag a parent from 78% → 72% because more branches in the call graph become reachable and must be covered. This is not a bug — it is how branch accounting works — but it means that on a large SvelteKit application the denominator grows with every coverage improvement, making an arbitrary 80% ceiling a constant grind. Per #496, the expected cost to reach 80% branches from 75% is 30–100+ commits with no guarantee of stability.
Why this layer is different
The 80% branch floor used for backend unit/integration tests is appropriate for Java service code and permission logic. Browser-mode component coverage measures Svelte template branches: conditional class bindings, {#if} blocks, empty/loaded/error state guards. These branches have a fundamentally different accounting model and a higher inherent denominator. This ADR only lowers the browser-mode component gate; the backend test coverage gates are unaffected.
Security-relevant uncovered components
The following auth/permission-boundary components currently have low or zero branch coverage. When ratchet-up work begins (see below), these are the highest-priority targets:
src/routes/login/+page.sveltesrc/routes/forgot-password/+page.sveltesrc/routes/reset-password/+page.sveltesrc/routes/register/+page.svelte
Note: the 75% figure already reflects the absence of coverage on these files. Lowering the gate does not create this gap — it makes the existing state legible.
Decision
Drop the branches threshold from 80 → 75 in frontend/vitest.client-coverage.config.ts. Leave lines, functions, and statements at 80.
The 75% figure matches the measured current state, allowing CI to pass while deliberate coverage improvement work (tracked in #496) continues without blocking other PRs. The asymmetry in the thresholds block is intentional and documented with an inline comment pointing here.
Ratchet Rule
The branches threshold ratchets up by 3 percentage points when the rolling 3-PR-average client-project branches figure on main stays at or above threshold + 3pp for ≥ 30 consecutive days. Direction is up-only — never lower the floor below 75 without a new ADR superseding this one. Manual today (verify before any vitest.client-coverage.config.ts edit); a future automation issue may codify the check.
Concretely:
- When
mainsustains ≥ 78% branches across 3 consecutive PRs for 30 days → raise gate to 78% - When
mainsustains ≥ 81% branches across 3 consecutive PRs for 30 days → raise gate back to 80%
Non-goals
- Not raising actual branch coverage — that is #496's job, tracked separately.
- Not touching the server-project coverage configuration (
vitest.config.ts) — only the client project hits the long-tail-grind pattern. - Not removing or relaxing any existing test files,
skipIfguards, or axe-playwright accessibility runs.
Consequences
Easier:
- CI unblocked —
unit-testsandcoverage-flake-probejobs pass when all tests are green - The ratchet rule creates a concrete, observable path back to 80%
Harder:
- The gate now has near-zero headroom — any branch regression that drops below 75% will fail CI immediately
- The 75% floor must not be treated as a permanent ceiling; the ratchet discipline requires active attention
References
- #496 — Branch coverage long-tail grind
- #556 — This threshold drop
- ADR 012 — Browser-Mode Test Mocking Strategy
frontend/vitest.client-coverage.config.ts— thresholds block (lines 44–51)