chore(coverage): drop client branches threshold 80→75 to unblock CI #559
Reference in New Issue
Block a user
Delete Branch "worktree-chore+issue-556-drop-client-branches-coverage-gate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
branches: 75(was80) infrontend/vitest.client-coverage.config.ts— the only production changedocs/adr/013-client-branches-coverage-threshold.mddocumenting rationale, baseline, and ratchet ruleContext
The client-project branches figure sits at ~75%, below the 80% gate. Per #496, closing this gap is a long-tail grind driven by Istanbul's parent/child denominator coupling — each new child coverage improvement can drag parent percentages down. The 80% gate has been blocking CI (including the birpc-race
coverage-flake-probe) without corresponding benefit.The drop to 75% matches the current measured state. Lines/functions/statements stay at 80% because they don't suffer the same accounting problem. The ADR captures the ratchet rule: gate rises by 3pp each time
mainsustainsthreshold + 3ppfor ≥30 consecutive days — direction is up-only.Test plan
unit-testsjob passes withbranches: 75coverage-flake-probe.yml(workflow_dispatch) succeeds end-to-endfrontend/vitest.client-coverage.config.tsthresholds block anddocs/adr/013-client-branches-coverage-threshold.md— nothing elseCloses #556
🤖 Generated with Claude Code
🏛️ Markus Keller — Senior Application Architect
Verdict: ✅ Approved
What I checked
This is a CI-gate adjustment with an ADR — exactly the right shape for lowering a quality bar deliberately. The rationale (long-tail-grind in Istanbul branch accounting, parent/child denominator coupling) is real and matches what I reviewed in #496.
Observations
ADR quality is good. The "Why this layer is different" section correctly distinguishes browser-mode component coverage from backend unit/integration test coverage. That distinction prevents cargo-culting 75% into the backend suite — it needs to be there explicitly, and it is.
ADR number 013 is correct. The duplicate-012 collision (
012-browser-test-mocking-strategy.mdand012-nsenter-for-host-service-management-in-ci.md) is a pre-existing issue that this PR correctly treats as out of scope. It should be addressed in a separate cleanup PR.Ratchet rule is concrete. "3pp when 3-PR-average sustains threshold+3pp for ≥30 days, direction up-only" is observable and unambiguous — far better than a free-text "revisit every 6 months."
Security-relevant uncovered components are called out. Listing login/forgot-password/reset-password/register as priority targets for ratchet-up work is the right framing. It makes the gap legible without overstating the risk.
No documentation update required. This PR touches no backend packages, no routes, no DB schema, no infrastructure components — none of the doc-update triggers in my review checklist apply.
One minor note
The ADR baseline says
~75%(approximate). When CI runs confirm the exact figure, consider adding a follow-up comment to the ADR or the issue with the precise number. The tilde is honest but the ratchet arithmetic works better with a precise floor.Summary
Correctly scoped, correctly documented, correctly numbered. The 75% gate = 0pp headroom is acknowledged in the Consequences section. CI will be the real test — approve it there on green.
👨💻 Felix Brandt — Senior Fullstack Developer
Verdict: ✅ Approved
What I checked
One config line changed, one ADR created — no production code, no test code. TDD red/green cycle does not apply here; the gate itself is the test, and CI is its execution environment.
Code review
vitest.client-coverage.config.ts:47— the change is exactly right:The inline comment is the right kind of why comment — it explains the asymmetry that would otherwise invite a future "let me normalize this back to 80" edit. Six months from now a developer sees three values at 80 and one at 75, reads the comment, follows the ADR link, and understands immediately. No comment needed on the other three values — they're symmetric and self-explaining.
docs/adr/013-client-branches-coverage-threshold.md— structure and content are solid:One small nit (non-blocking)
The ADR's baseline says
~75%. The tilde is honest (the run wasn't completed locally before this PR) but the ratchet rule hinges on the exact number. When CI confirms the figure, it would be cleaner to update the tilde to the exact percentage and commit SHA. Not a blocker — the ADR notes the approximate nature and that's acceptable.Summary
Clean, minimal, well-documented. Exactly how a CI gate adjustment should be done.
🔧 Tobias Wendt — DevOps & Platform Engineer
Verdict: ⚠️ Approved with concerns
What I checked
CI workflows, threshold change, and the headroom arithmetic.
Observations
The change is correct and minimal. Only two files touched: the config and the ADR. No workflow files modified, no Compose changes, no secrets involved. Clean diff.
The flake-probe concern (from original review) is addressed.
coverage-flake-probe.ymlis not modified — it remains a diagnostic tool for the birpc race, not a coverage enforcer. The threshold check happens insidenpm run test:coverage; the probe inherits the new gate value automatically. That's the right design.Blocker-level concern: 0pp headroom
The original issue proposed
80 → 70(7pp headroom) specifically to absorb coverage variance across the 20-cell matrix. This PR ships80 → 75with the baseline at~75%, meaning zero headroom.This is a deliberate choice by the maintainer and I understand the reasoning, but the operational risk is real:
Recommendation: Run the flake probe once on this branch before merging. Look at the per-cell branches percentage across all 20 cells. If any cell comes in below 75.5%, lower the gate to 74% or 73% to give a real margin. The ADR already documents the ratchet rule — a 74% initial floor ratchets up exactly the same way.
If the maintainer has measured 75% and is confident it's stable, this concern is acknowledged and accepted. The ADR's Consequences section does call it out ("near-zero headroom"), which is the right level of honesty.
No other concerns
coverage-flake-probe.ymlsingle-responsibility is preserved🔒 Nora "NullX" Steiner — Application Security Engineer
Verdict: ✅ Approved
What I checked
Security surface of the change and the ADR's treatment of security-relevant coverage gaps.
Findings
No security regression introduced. This PR touches:
branches: 75)No production code paths changed. No test files removed. No
skipIfguards added. No exclude-list growth. The diff is exactly what it should be.The ADR's security treatment is correct. The "Security-relevant uncovered components" section correctly identifies and lists:
src/routes/login/+page.sveltesrc/routes/forgot-password/+page.sveltesrc/routes/reset-password/+page.sveltesrc/routes/register/+page.svelteThe framing is accurate: "lowering the gate does not create this gap — it makes the existing state legible." These components were already uncovered before this PR. The ADR makes the debt visible and names the priority order for ratchet-up work, which is the right approach.
Backend test coverage gates are explicitly excluded in the Non-goals section. Backend
@RequirePermissionenforcement tests, auth-cookie handling, and permission boundary tests are tested at a different layer with different tooling. Their 80% gate is untouched.One observation
When the ratchet-up work begins (tracked in #496), the auth pages listed above should be first in line. Branch coverage on login/password-reset flows is where authentication regression risk lives. A missed
{#if}branch on an error state could hide a case where the login form silently fails open. That's not a blocker for this PR — the gap exists today regardless of the gate value — but it should be the first item on the #496 ratchet plan.🧪 Sara Holt — QA Engineer & Test Strategist
Verdict: ⚠️ Approved with concerns
Context on my usual stance
I quote 80% branch coverage as a floor in most contexts. I am not contradicting that standard here — the ADR's "Why this layer is different" section correctly explains why browser-mode component coverage (Svelte template branches: class bindings,
{#if}guards, empty/error state toggles) has different accounting than backend unit/integration tests. Different layer, different appropriate threshold. I reviewed this distinction in the original #556 review and stand by it.What I checked
Test pyramid integrity, gate signal quality, and the ratchet mechanism.
Concern 1: Near-zero headroom (non-blocking, but flagged)
Setting the gate at 75% with a measured baseline of ~75% means any PR that introduces a new component with uncovered branches — even one small component — could drop below the gate and re-block CI. The long-tail-grind problem runs in both directions: improving a child's coverage can drag a parent's reported figure down.
This is noted in the ADR's Consequences section. The maintainer has acknowledged it. I'm flagging it again because it affects the CI experience for #553 (the active unblocking target) and any PR that follows.
Concern 2: Baseline precision
The ADR records
~75%(approximate). For the ratchet rule to be actionable, the floor needs to be a precise number. "Ratchet up when 3-PR-average sustains ≥78% for 30 days" requires knowing whether the current baseline is 75.0% or 75.4%. A 0.4pp difference is meaningful when headroom is already zero.Recommendation: Once CI runs confirm the exact figure, update the ADR baseline block to replace
~75%with the actual measured percentage and the commit SHA from the passing CI run. This keeps the ratchet rule honest.What's done well
📋 Elicit — Requirements Engineer & Business Analyst
Verdict: ✅ Approved
What I checked
Acceptance criteria coverage, requirement completeness, and traceability.
Acceptance criteria assessment
From #556:
branches: 70invitest.client-coverage.config.ts:47lines,functions,statementsremain at 80coverage-flake-probe.ymlsucceeds end-to-endFrom the decision comment (pre-approved additions):
~75%(approximate — exact number pending CI confirmation)Requirement quality
Scope is clean. Diff contains exactly two files: the one-line config change and the ADR. No scope creep, no accidental changes.
Traceability is complete. ADR-013 references #556 (this PR) and #496 (ratchet tracking). The config comment references ADR-013 and #496. The PR description references #556 (with
Closestag). The chain is: issue → ADR → config → PR.Non-goals are explicit. The ADR does not touch
vitest.config.ts(server project), does not raise actual branch coverage, does not relax existing tests. These boundaries are stated and respected.One open item
The exact baseline percentage is
~75%(approximate). The ratchet rule's first trigger ("sustains ≥78% for 30 days") is precise, but the floor it's measured against is not. When CI confirms the number, the ADR should be updated with the exact value. This is a polish item, not a blocker — the intent is clear and the mechanism is sound.🎨 Leonie Voss — UX Designer & Accessibility Strategist
Verdict: ✅ Approved
What I checked
UI/UX surface, component impact, accessibility test coverage.
Findings
No user-facing components are added, removed, or modified in this PR. No Svelte templates change. No CSS tokens change. No routes change. No interactive elements, focus states, touch targets, or ARIA attributes are affected.
This is purely a CI configuration gate adjustment and an ADR. Nothing renders differently to any user — senior researcher on a tablet or millennial on a phone.
One downstream note
The ADR correctly lists the auth pages as uncovered-branch priorities for ratchet-up work:
login/+page.svelteforgot-password/+page.sveltereset-password/+page.svelteregister/+page.svelteWhen that ratchet-up work starts (tracked in #496), these pages deserve visual-state branch coverage that includes error states and loading states — these are exactly the branches where dark-mode contrast regressions and mobile-overflow issues hide. The axe-playwright E2E runs are the accessibility quality gate for these pages; they are explicitly not relaxed by this PR, which is confirmed by the Non-goals section.
LGTM.