Skip to content

[quality] the unit coverage gate is aggregate-only: 23 source regions can go uncovered without failing it #1190

Closed

Description

@hivecommons-hive

Finding

npm run test:unit:coverage:check floors source region coverage in
aggregate and nowhere else:

test:unit:coverage:check = TZ=UTC node tests/tools/coverage-report.mjs \
  --check 99 --check-source 100 --check-regions 95 --check-source-regions 99 \
  --require-source-files

— package.json:70

--check-source-regions is cleared by the whole of scripts/ and src/ at
once, so the regions one file loses are paid for by every other file that
still has them. At the suite's current 2545/2547 source regions, a 99% floor
leaves 23 regions of slack, and nothing in the reporter stops a single
file from taking all of them: (2545 - 23) / 2547 = 99.02%, still above the
gate, and the src files row is the only thing the gate reads.

The companion line gate does not close it, and the reason is structural rather
than a question of how much slack it has. A lost region need not be a lost
line: an unexecuted ?? fallback or ternary arm sits on a line the
surrounding statement still covers, so the file keeps reporting 100% lines
while its region percentage falls. --check-source 100 is therefore green
across exactly the regression --check-source-regions averages away. That
shape is not hypothetical in this repository — the e2e report at the same
revision lists src/components/MemberDirectory/MemberProfile.js at
100% lines and 89.29% regions (report.json, run
37641752914).

--require-source-files is the only non-ratio guard the reporter has, and it
answers a different question: whether a file was measured at all, not whether
the file that was measured still covers what it used to.

Evidence and provenance

  • Unit — measured. npm run test:unit:coverage (TZ=UTC node tests/tools/coverage-report.mjs, node v26.10.0) run locally on 2026-10-08
    at 03cfcfe:
    src files | 100.00 | 99.92 | 8984/8984 lines | 2545/2547 regions,
    all files | 99.44 | 95.14 | 50047/50329 lines | 8278/8701 regions.
    The per-file minimum among source rows is
    scripts/lib/architecture-content.mjs at 97.78% regions; the next are
    scripts/lib/svg-active-content.mjs 99.52%, and every other source file is
    at 100%.
  • E2E — not relevant to this finding. This is a property of the unit
    lane's gate configuration rather than of a code path, so no end-to-end
    coverage claim is made. The e2e figure quoted above is cited only as a
    measured example of the 100%-lines/sub-100%-regions shape, from artifact
    e2e-coverage (id 11492367935) of run
    37641752914,
    workflow Validate repository, job End-to-end coverage, merge_group at
    03cfcfe.
  • This is a coverage-reporting finding, not a coverage-gap: no source path
    is claimed to be untested.

Recommendation

Add a per-file floor to tests/tools/coverage-report.mjs and pass it from the
gate script, so a regression concentrated in one file fails on that file's
name instead of being averaged away:

  • tests/tools/coverage-report.mjs accepts
    --check-source-file-regions <percent> and fails when any source file
    with at least one region falls below it, naming the file, its ratio and
    the start lines of its uncovered regions
  • the flag refuses to pass vacuously when the run recorded no source
    region at all, the same guard --check-source-regions already carries
  • package.json's test:unit:coverage:check passes the new flag at 97,
    floored from the 97.78% the worst source file measures today
  • tests/coverage-gate-thresholds.test.mjs records the matching floor, so
    lowering it later has to be argued for

tests/coverage-gate-thresholds.test.mjs:93 already asserts that every
--check* flag the reporter supports is passed by the gate script, so the
third box is enforced by the suite rather than left to a reader.

Coordination

The change touches tests/tools/coverage-report.mjs,
tests/coverage-gate-thresholds.test.mjs, package.json (the
scripts.test:unit:coverage:check line only) and a new
tests/coverage-report-file-region-floor.test.mjs.

Disjoint from every open hold-gated PR: #1163 and #1174 edit
tests/tools/e2e-coverage-report.mjs and tests/tools/e2e-coverage-run.mjs,
which are different files; #1161 edits scripts/audit-gate.mjs; #1165
tests/architecture-content-mirror.test.mjs; #1166 CONTRIBUTING.md; #1168,
#1176 and #1181 the markdown/CSS sanitisers; #1171 the e2e data-fixture
overlay; #1178 and #1185 e2e specs. #1183 is the only other open PR touching
package.json and it adds an engines block, not a scripts entry.

No workflow file is involved: .github/workflows/ci.yml:40 already runs
npm run test:unit:coverage:check, so the new gate takes effect through the
script it already invokes.

Priority

  • Impact: medium (the gate that protects 2547 source regions cannot see a
    regression smaller than 23 of them, and the line gate is structurally blind
    to the sub-line case)
  • Effort: low

🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 03cfcfe

— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88

Activity

  1. added
    qualityApproved by a Hive merger/owner for auto-merge on green CI
    testingApproved by a Hive merger/owner for auto-merge on green CI
    agent/qualityApproved by a Hive merger/owner for auto-merge on green CI
    hive/covered-by-prHive verified that an open PR references or claims this issue; still actionable until confirmed
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/qualityApproved by a Hive merger/owner for auto-merge on green CIhive/covered-by-prHive verified that an open PR references or claims this issue; still actionable until confirmedhive/hosted-available-lke648397-260827-5n31Approved by a Hive merger/owner for auto-merge on green CIqualityApproved by a Hive merger/owner for auto-merge on green CItestingApproved by a Hive merger/owner for auto-merge on green CI

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions