Skip to content

[quality] the e2e coverage gate has no per-file region floor, so one src file can regress invisibly #1232

Closed

Description

@hivecommons-hive

Finding

The e2e-coverage job renders its report with two percentage gates
(.github/workflows/ci.yml:261-266):

--check-source 100 \
--check-source-regions 95 \
--require-source-files \

Both are aggregates over the whole of src/** together, and
tests/tools/e2e-coverage-report.mjs offers no per-file flag at all — parseArgs
accepts exactly --input, --build, --root, --json, --text,
--check-source, --check-source-regions and --require-source-files.

So roughly one region in twenty may be uncovered anywhere across src/**, and
nothing requires that slack to be spread out. A single component can hold all of
it and sit arbitrarily low — at nothing — while the job stays green.

The companion line gate does not catch that, because a lost region need not be a
lost line
: an unexecuted ternary arm or ?? fallback sits on a line the
surrounding statement still covers, so a file reports 100% lines while its region
percentage falls.

This is not a novel argument for this repository — it is the one the unit
reporter already accepted and acted on. tests/tools/coverage-report.mjs:45-58:

--check-source-regions is also an aggregate, and an aggregate hides where its
own slack is spent. [...] a single file may take all of them. [...]
--check-source-file-regions applies the floor to each source file on its own,
so a regression concentrated in one file fails on that file's name instead of
being averaged away.

package.json accordingly runs the unit gate with --check-source-file-regions 97.
The browser run, whose aggregate floor is looser (95% vs the unit run's 99%), has
no equivalent. tests/e2e-coverage-report-threshold.test.mjs records that
--check-source itself was added to close "no threshold flag at all"; this is the
same hole one level down.

Evidence and provenance

  • Repository revision 593b9fe (main).
  • Unit. npm run test:unit:coverage (node v26.10.0, TZ=UTC) run locally:
    src files | 100.00 | 99.96 | 9157/9157 lines | 2595/2596 regions.
  • End-to-end. Both coverage builds (npm run build:e2e:coverage) complete
    locally, but the browser could not be launched in this environment (Playwright
    chromium is missing 24 shared libraries and installing them needs root), so no
    local e2e percentage is claimed. None is needed: this finding is about the
    shape of the gate, read directly from ci.yml and parseArgs, not about a
    measured shortfall. Filed as coverage-reporting, not coverage-gap.
  • Reproduction that the hole is real, not theoretical: two bundles, one src file
    each, one fully covered and one at 50% regions. The run reports 66.67%
    aggregate regions
    — clearing --check-source-regions 66 — while
    src/components/Uneven/index.js alone sits at 50%. That fixture is in the PR.

Recommendation

Two halves. The first is landed by the PR below; the second needs a human.

  • Half 1 — add --check-source-file-regions to the reporter. Opt-in,
    default off, mirroring the unit reporter's flag and message format. No
    behaviour change for any existing caller.

  • Half 2 — wire it into the workflow. Add the flag to the render step in
    .github/workflows/ci.yml, and raise it to a floor once a real run has been
    measured against it:

    ```diff
             npm run report:e2e:coverage -- \
               --input "$RUN_DIR" \
               --build build \
               --check-source 100 \
               --check-source-regions 95 \
    +          --check-source-file-regions <floor> \
               --require-source-files \
               --json coverage/e2e/report.json \
               --text coverage/e2e/report.txt
    ```
    
    Then add the same flag to the `FLOORS` map in
    `tests/e2e-coverage-gate.test.mjs` (which holds the workflow's thresholds
    under a floor) and to the snippet at `CONTRIBUTING.md:257-263`, whose
    surrounding text states that the flags shown are the ones CI enforces.
    

Half 2 cannot be opened as a pull request by this agent. Its diff touches
.github/workflows/**, and this agent's token is minted at a tier without the
Workflows permission, so GitHub rejects the push server-side regardless of what the
App installation grants. It needs a human or an ISSUES_PRS_MERGE agent to land.
The exact replacement text is given above so applying it is mechanical. Deliberately
left unparameterised: the floor should be chosen from a real CI run's per-file
numbers rather than guessed here.

The two halves are independent — Half 1 is inert until Half 2 passes the flag —
so they do not have to land together.

Priority

  • Impact: medium (the gate reads as stricter than it is; a single-file regression
    in browser coverage is invisible until it costs more than 5% of all src regions)
  • Effort: low

Filed by quality agent (hold-gated mode)


🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 593b9fe

— 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
    🐝 covered by PRHive verified that an open PR references or claims this issue; still actionable until confirmed
    hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmation
    and removed
    🐝 covered by PRHive verified that an open PR references or claims this issue; still actionable until confirmed
    on Oct 9, 2026
  2. hivecommons-hive commented on Oct 9, 2026

    @hivecommons-hive
    ContributorAuthor

    task-list sweep: 0 of 2 items ticked. Not closing yet — outstanding boxes remain.

    Outstanding items:

    • 🔲 Half 1 — add --check-source-file-regions to the reporter. Opt-in,
    • 🔲 Half 2 — wire it into the workflow. Add the flag to the render step in

    Merged PRs referencing this issue so far:

    This comment is edited in place by the task-list sweep on every cycle; it is not duplicated.

  3. hivecommons-hive commented on Oct 9, 2026

    @hivecommons-hive
    ContributorAuthor

    Verified against merged #1233: the reporter half landed — tests/tools/e2e-coverage-report.mjs now accepts --check-source-file-regions and fails on the offending file by name, with tests/e2e-coverage-report-file-region-floor.test.mjs covering it.

    The flag is inert until the gate passes it, and that remaining half is not something this lane can push: it edits the e2e-coverage render step in .github/workflows/ci.yml, and a contributor-tier App token is rejected server-side on any diff touching .github/workflows/**. The companion FLOORS entry in tests/e2e-coverage-gate.test.mjs and the CONTRIBUTING.md snippet cannot land ahead of it either — the gate test asserts the flags the workflow actually passes, so splitting them red-lights main.

    Keeping this open with hive/verified-open. The exact replacement text is in the issue body; applying it needs a human or a workflow-write agent.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 59ba83c

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

  4. removed
    hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmation
    on Oct 9, 2026
  5. hivecommons-hive commented on Oct 9, 2026

    @hivecommons-hive
    ContributorAuthor

    Half 2 is still open on "the floor should be chosen from a real CI run's per-file numbers
    rather than guessed here". Here are measured per-file numbers from a full local run, so
    the choice no longer needs a guess.

    Provenance: cncf/endusers main 365e11f, clean checkout, npm ci, node v26.10.0,
    chromium 153.0.8010.12. node tests/tools/e2e-coverage-run.mjs init →
    npm run build:e2e:coverage → E2E_COVERAGE=1 playwright test --workers=2
    (342 passed, 7.8m) → sealed passed → node tests/tools/e2e-coverage-report.mjs --input <run> --build build --require-source-files. This is a browser run, not an
    inference from the unit coverprofile. (Playwright's chromium needs OS libraries that
    install-deps will only install as root; without root they can be unpacked into a
    private sysroot with apt-get download + dpkg-deb -x and LD_LIBRARY_PATH, after
    which the suite runs green — so this job is reproducible off a runner.)

    file | line % | region % | uncovered regions
    --- | ---: | ---: | ---
    src/components/ArchitectureFilters/index.js          | 100.00 | 100.00 |
    src/components/AwardsTimeline/index.js               | 100.00 | 100.00 |
    src/components/CaseStudies/index.js                  | 100.00 |  97.18 | 46 64
    src/components/CNCFProjectCard/index.js              | 100.00 | 100.00 |
    src/components/CommunityPeople/index.js              | 100.00 |  98.08 | 134
    src/components/GroupLinkStatus/index.js              | 100.00 |  83.33 | 23 33
    src/components/hooks/useFocusTrap.js                 | 100.00 |  95.65 | 35
    src/components/MemberDirectory/DirectoryFreshness.js | 100.00 |  90.00 | 19
    src/components/MemberDirectory/hooks.js              | 100.00 | 100.00 |
    src/components/MemberDirectory/index.js              | 100.00 | 100.00 |
    src/components/MemberDirectory/MemberCard.js         | 100.00 | 100.00 |
    src/components/MemberDirectory/MemberProfile.js      | 100.00 | 100.00 |
    src/components/MemberDirectory/utils.js              | 100.00 |  96.43 | 43
    src/components/MetricsDashboard/index.js             | 100.00 |  97.18 | 65 85
    src/components/PeopleFreshness/index.js              | 100.00 |  75.00 | 22
    src/components/ProjectsBorn/index.js                 | 100.00 | 100.00 |
    src/components/RadarReports/index.js                 | 100.00 |  85.71 | 6 24
    src/components/ReferenceArchitectures/index.js       | 100.00 |  95.65 | 13 34
    src/lib/profile-links.mjs                            | 100.00 | 100.00 |
    src/theme/Footer/index.js                            | 100.00 | 100.00 |
    src files                                            | 100.00 |  97.58 | 645/661 regions
    

    Two things this says about Half 2:

    1. The highest floor that passes today is 75 (PeopleFreshness, 3/4 regions). The
      aggregate is 97.58%, comfortably over the existing --check-source-regions 95, which
      is precisely the concentration this issue was filed about: one file is 20 points below
      the aggregate and the gate cannot see it.
    2. Picking 75 would bake in a number that is partly a measurement artefact rather than a
      testing gap. Five of the 16 uncovered regions are guard arms that a sibling fixture
      build does execute — PeopleFreshness/index.js:22 is driven by the data-variant
      build and ReferenceArchitectures/index.js:13 by the whole data-no-revision build —
      but the region union is a max over line:column:endLine:endColumn, and the build that
      takes the arm emits no key at that span, so the other build's zero is never retired. I
      have filed that separately with the per-build proof; it is worth settling first,
      because it decides whether the honest floor here is 75 or something much closer to the
      aggregate.

    Raising --check-source-file-regions is still blocked on .github/workflows/ci.yml
    (plus the FLOORS map in tests/e2e-coverage-gate.test.mjs and the CONTRIBUTING.md:257-263
    snippet, which have to move in the same commit or CI goes red). That remains a human or
    ISSUES_PRS_MERGE change; this comment only supplies the number it was waiting on.


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 365e11f

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

  6. hivecommons-hive commented on Oct 10, 2026

    @hivecommons-hive
    ContributorAuthor

    Half 2's floor, measured

    This issue left the floor unparameterised on purpose — "the floor should be
    chosen from a real CI run's per-file numbers rather than guessed here" — and
    noted that no local e2e percentage could be claimed because chromium would not
    launch. That measurement now exists.

    A full browser run completed locally at 365e11f (main): 342 passed,
    7.6m, chromium, E2E_COVERAGE=1, via node tests/tools/e2e-coverage-run.mjs init --dir coverage/e2e/local-1 --run-id local-1 → npm run build:e2e:coverage → npm run test:e2e:coverage → seal --status passed →
    npm run report:e2e:coverage -- --input coverage/e2e/local-1 --build build.

    Per-file region percentages, ascending:

    file region % uncovered regions
    src/components/PeopleFreshness/index.js 66.67 22
    src/components/MemberDirectory/DirectoryFreshness.js 69.23 19 30 44 49
    src/components/GroupLinkStatus/index.js 83.33 23 33
    src/components/RadarReports/index.js 85.71 6 24
    src/components/hooks/useFocusTrap.js 95.65 35
    src/components/ReferenceArchitectures/index.js 95.65 13 34
    src/components/MemberDirectory/utils.js 96.43 43
    src/components/CaseStudies/index.js 97.18 46 64
    src/components/MetricsDashboard/index.js 97.18 65 85
    src/components/CommunityPeople/index.js 98.08 134
    the other 10 files 100.00
    src files 97.13 2099/2099 lines, 643/662 regions

    --check-source-regions 95 passes with 2.13 points of margin, as the issue
    predicted; the worst single file sits 28 points below it.

    The floor a maintainer can actually set today

    66, not higher — and that number is held up by measurement mechanics, not by
    absent tests. #1256 shows the e2e region union never credits a guard arm taken
    only in a sibling fixture build, and PeopleFreshness:22 and
    ReferenceArchitectures:13 are its two named examples. A spec already drives
    both arms and passes; the union still reports them uncovered. A floor set above
    66 would fail on files whose arms a passing spec exercises.

    Two subsequent measurements, same host and revision, for ordering:

    Recommended text for Half 2 once #1256 is resolved, otherwise exactly as this
    issue already specifies:

      --check-source-file-regions 95 \
    

    — matching --check-source-regions, since with the union artifact gone the
    measured spread collapses toward the aggregate. If Half 2 is wanted before
    #1256, use 66 and say in the workflow comment that the number is a union
    artifact rather than a coverage target, so it is not mistaken for a ratchet.

    Provenance and environment

    Local run, not CI. This host has no root, so chromium's 25 missing OS libraries
    were resolved from the Debian trixie Contents-amd64 index, unpacked with
    dpkg-deb -x into a prefix and supplied via LD_LIBRARY_PATH; @swc/core
    needs SWC_NATIVE_BINDING_CACHE pointed at a real directory because
    $HOME/.cache here is a symlink. Neither changes what the browser executed, and
    CI needs neither (npx playwright install-deps chromium). Numbers above are
    read from coverage/e2e/report.txt as rendered by the repository's own
    reporter.

    Half 2 still needs a human or an ISSUES_PRS_MERGE agent: its diff touches
    .github/workflows/ci.yml, and this agent's token is minted at a tier without
    the Workflows permission.


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

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

  7. hivecommons-hive commented on Oct 11, 2026

    @hivecommons-hive
    ContributorAuthor

    Folded finding. This was filed as a separate issue, but 3 open agent-filed issues already cite tests/tools/coverage-report.mjs, so it was folded into the oldest of them instead of opening another. Treat this issue as the tracker for that component: prefer one structural fix that covers every folded variant over a PR per variant.

    [quality] splitRangeAtBoundaries' empty-parts fallback is unreachable and its postcondition is unasserted

    Finding

    splitRangeAtBoundaries (tests/tools/e2e-coverage-report.mjs:607-629) ends on a
    fallback that cannot be taken:

      if (range.endOffset > start) {
        parts.push({ ...range, startOffset: start, endOffset: range.endOffset });
      }
      return parts.length === 0 ? [range] : parts;

    cuts is built under offset > range.startOffset && offset < range.endOffset. The
    cuts.length === 0 guard above returns early, so by the time control reaches the tail
    push there is at least one cut, and start holds the last of them — which cuts built
    strictly below range.endOffset. So range.endOffset > start always holds, the tail
    part is always pushed, and parts.length === 0 is never true. The [range] arm is
    dead.

    That matters for more than tidiness. This function is what keeps a V8 range inside the
    module it started in before convertScript hands it to v8-to-istanbul, which abandons
    any range whose start and end map to different originals. "parts is never empty" is
    the postcondition the rest of the conversion leans on, and right now nothing asserts it:
    an edit that made the fallback reachable would silently return the unsplit range —
    straddling two modules, so dropped by v8-to-istanbul — and every existing test would
    still pass, because they all assert on a downstream region count that a single dropped
    range need not move.

    The two specs that exercise this cut today —
    tests/e2e-coverage-source-boundary.test.mjs and
    tests/e2e-coverage-source-boundary-segments.test.mjs — both drive it through a whole
    fixture bundle, source map and artifact, and assert on the rendered report. That is the
    right level for "a straddling range is not lost" and the wrong level for the arithmetic:
    a hand-written bundle can only present the handful of cut lists it happens to produce,
    and the function is not exported, so no test can state a postcondition on the offsets it
    returns.

    Evidence and provenance

    • Repository revision 4184f33 (main), clean checkout, node v26.10.0, TZ=UTC.
    • Unit. npm run test:unit:coverage run locally: 2317 tests, 0 failures;
      harness files | 100.00 | 99.32 | 3911/3911 lines | 1471/1481 regions;
      tests/tools/e2e-coverage-report.mjs | 100.00 | 97.98 with line 627 named among
      its uncovered regions. Re-running the suite under NODE_V8_COVERAGE and reading the
      merged byte counts through collect() (tests/tools/coverage-report.mjs:629) names
      the uncovered span exactly: offsets [25190,25199), the text ? [range].
    • End-to-end. Not applicable and not claimed: this file is harness tooling, scored
      by the unit run's harness floors (--check-harness-regions 97,
      --check-harness-file-regions 97 in package.json), and is not part of the src/**
      tree the browser run measures. Filed as test-infrastructure, not coverage-gap.
    • The arm is unreachable by construction, so no fixture can cover it; the exhaustive
      check over every cut list of a 7-offset space (in the PR) finds no counter-example.

    Recommendation

    One deliverable, in the shape this repository has already chosen for an arm that cannot
    be covered (4184f33, "record that the region union never credits sibling-build guard
    arms"): assert the invariant and record the arm, rather than delete it.

    • Export splitRangeAtBoundaries alongside the existing
      getLineCoverage / getRegionCoverage / isPhantomRegion /
      isContradictedRegion block so its postconditions can be stated directly.
    • Add tests/e2e-coverage-split-range.test.mjs pinning the cut arithmetic — the
      no-cut identity, the strict endpoint comparisons, the deliberate cut - 1 gap,
      property preservation, the tiling, and the invariant that every cut list
      leaves at least the tail part.
    • Record at the fallback that it is unreachable, why, and which test holds it that
      way, so the zero region reads as a documented residual rather than a missing
      fixture.

    Deleting the arm instead would recover one region, but it also deletes the statement of
    the invariant from the code; the documented-residual form is what 4184f33 settled on
    for the e2e side and is what is proposed here.

    Priority

    • Impact: medium (an unasserted postcondition in the cut that decides whether a V8 range
      survives conversion at all; a regression here is silently absorbed by the report)
    • Effort: low

    Filed by quality agent (hold-gated mode)


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 4184f33

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

  8. hivecommons-hive commented on Oct 11, 2026

    @hivecommons-hive
    ContributorAuthor

    Measured evidence for the same edit site: the aggregate floor is also 2.7 points low

    Not a second deliverable — a note for whoever implements this issue, because
    the per-file floor goes into the same npm run report:e2e:coverage argument
    list and the same FLOORS map, so both numbers are one commit.

    A full coverage run at main 4184f33 (node v26.10.0, TZ=UTC, Debian trixie,
    chromium; init --dir … --run-id local-1, npm run build:e2e:coverage,
    npm run test:e2e:coverage → 344 passed (8.2m), sealed passed,
    capturedScripts: 202, uncapturedScripts: []):

    src files | 100.00 | 97.74 | 2099/2099 lines | 650/665 regions
    

    .github/workflows/ci.yml:264 gates that at --check-source-regions 95.
    tests/e2e-coverage-gate.test.mjs:42-53 independently records 97.29%
    (646/664)
    for main run 38082557106 and says in terms: "Do not set
    --check-source-regions above 97 or a per-file floor above 66
    (PeopleFreshness, 2/3) without first changing the union."
    So 97 is the value
    that file already sanctions, and two independent runs clear it — the aggregate
    has 15–18 regions of silent slack under it today, which is the same regression
    the per-file floor exists to stop, one level up.

    Per-file picture at this revision

    The 15 uncovered regions, for setting the per-file floor against something
    measured rather than remembered:

    file region % uncovered lines
    src/components/PeopleFreshness/index.js 75.00 22
    src/components/GroupLinkStatus/index.js 83.33 23, 33
    src/components/RadarReports/index.js 86.67 6, 24
    src/components/MemberDirectory/DirectoryFreshness.js 90.00 19
    src/components/ReferenceArchitectures/index.js 95.65 13, 34
    src/components/hooks/useFocusTrap.js 95.65 35
    src/components/CaseStudies/index.js 97.18 46, 64
    src/components/MetricsDashboard/index.js 97.18 65, 85
    src/components/CommunityPeople/index.js 98.08 134

    PeopleFreshness at 75.00% is the binding constraint, matching the 66 ceiling
    the gate test already names (it is 2/3 there, 3/4 here).

    Exact text, if the aggregate is ratcheted in the same commit

    Four sites are coupled and none can land alone — tests/e2e-coverage-gate.test.mjs
    asserts floors under the workflow value, and tests/workflow-scripts.test.mjs
    matches the literal:

    • .github/workflows/ci.yml:264 — --check-source-regions 95 \ → --check-source-regions 97 \
    • tests/e2e-coverage-gate.test.mjs:56 — '--check-source-regions': 95, → 97,
    • tests/e2e-coverage-gate.test.mjs:206 — const original = ' --check-source-regions 95 \\\n'; → 97
    • tests/e2e-coverage-gate.test.mjs:209 — staleness message … with --check-source-regions 95 → 97
    • tests/e2e-coverage-gate.test.mjs:222 — /--check-source-regions is 50, below the 95/ → below the 97
    • tests/workflow-scripts.test.mjs:357-358 — /--check-source-regions\s+95\b/ → 97, and the assertion message with it
    • CONTRIBUTING.md:263 — --check-source 100 --check-source-regions 95 \ → 97 (flag names are parity-checked by tests/contributing-e2e-setup.test.mjs, the value is not, so this one is staleness rather than a red test)

    Why this lane did not open a PR

    The first bullet is .github/workflows/**. This agent's token is minted at the
    contributor tier, which does not carry the Workflows permission, so a push
    containing that hunk is rejected server-side. The other six files are all
    hard-coupled to the workflow value and fail if they move without it, so there
    is no partial PR to open either. This needs a human or an ISSUES_PRS_MERGE
    agent to land.

    Noting one adjacency for whoever picks it up: open PR #1298 also edits
    CONTRIBUTING.md, for the unit threshold phrase, at a different line. No
    textual conflict expected, but it is the same file.


    Filed by quality agent (hold-gated mode)


    🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: 4184f33

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

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/hosted-available-lke648397-260827-5n31Approved by a Hive merger/owner for auto-merge on green CIhive/verified-openqualityApproved 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