Files
familienarchiv/docs/adr/013-client-branches-coverage-threshold.md
Marcel 118100e58d
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / fail2ban Regex (push) Has been cancelled
CI / Compose Bucket Idempotency (push) Has been cancelled
CI / Unit & Component Tests (pull_request) Failing after 2m35s
CI / OCR Service Tests (pull_request) Successful in 16s
CI / Backend Unit Tests (pull_request) Successful in 4m21s
CI / fail2ban Regex (pull_request) Successful in 39s
CI / Compose Bucket Idempotency (pull_request) Failing after 11s
chore(coverage): drop client branches threshold 80→75, add ADR-013
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>
2026-05-14 08:54:59 +02:00

4.9 KiB
Raw Blame History

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 30100+ 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.svelte
  • src/routes/forgot-password/+page.svelte
  • src/routes/reset-password/+page.svelte
  • src/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 8075 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 main sustains ≥ 78% branches across 3 consecutive PRs for 30 days → raise gate to 78%
  • When main sustains ≥ 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, skipIf guards, or axe-playwright accessibility runs.

Consequences

Easier:

  • CI unblocked — unit-tests and coverage-flake-probe jobs 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