Skip to content

ci: branch_partial_allowlist.json carries stale line hints on master, and master's coverage leg passes anyway #634

Description

@Yaraslaut

Found while diagnosing PR #585's red Linux / clang-coverage leg. Filed rather than folded into that PR, which repoints the hints only because its leg cannot go green otherwise.

The finding

scripts/branch_partial_allowlist.json carries stale line hints on master, and master's own coverage leg passes anyway.

That file is the one resolve_allowlist_source_line() exists to harden — the mechanism cited in morph#349 and morph#419, and the precedent morph#608/#614 built on for mutation_survivors.json. So the gate designed to catch exactly this rot is not catching it here.

Verification status: the drift is reproduced; the reason it is not caught is not established

Measured on master @ 0e3b8823, by resolving each entry's source text against the file at that revision — the same method resolve_allowlist_source_line() documents:

include/morph/util/rational.hpp  hint 1542 -> text is at line 1498
include/morph/core/bridge.hpp    hint 1498 -> text is at line 1453

Both source texts still exist and are still unique, so no disposition is wrong — only the coordinates. A third entry (backend.hpp hint 1230) resolves correctly on master and drifted only on #585's branch, which adds 94 lines to that file; that one is not part of this finding.

And yet:

$ gh run list --branch master --workflow ci.yml --limit 1
35499909035  0e3b8823  success
$ # its Linux / clang-coverage job:
Linux / clang-coverage -> success

Meanwhile the same two entries did fail on #585's branch, with the resolver's own message:

error: include/morph/util/rational.hpp:1542 has moved to line 1498. The text still matches, so nothing is wrong with the disposition -- update the `line` hint.
error: include/morph/core/bridge.hpp:1498 has moved to line 1542. ...

Note #585 does not touch include/morph/util/rational.hpp at all. So the same allowlist entry is judged differently on two revisions whose content for that file is identical.

What I did not establish: why. I read scripts/check_branch_coverage.py far enough to see that resolve_allowlist_source_line() is called at :291 and the partial-line test happens afterwards at :295 — which reads as eager resolution of every entry, and would mean master should fail too. It does not. Candidate explanations I did not test:

I did not instrument the script, did not re-run the coverage leg locally, and did not diff the two runs' configure steps. This is the open question and I am not asserting an answer.

Why it matters

Two separate costs, and the second is the larger one:

  1. The coordinates are wrong, so a reader following one lands in unrelated code — the morph#608 cost, in the file that is supposed to be immune to it.
  2. A gate that passes on master and fails on a branch that did not touch the file is not measuring what its name says. Whichever explanation above is right, the consequence is the same: drift accumulates on master unnoticed and surfaces as a red leg on whichever unrelated PR happens to trip it. qt: move QtWebSocketBackend onto the structural registration surface #585 paid that cost today — a chain-unblocking PR spent a CI cycle on two hints it did not move.

This is AGENTS.md's "verify rather than assert" case: "Ask whether the check would still pass if the feature did nothing." Here it demonstrably passes while two of its entries are wrong.

What would resolve it

  1. Establish the mechanism — instrument or locally run check_branch_coverage.py against master and against qt: move QtWebSocketBackend onto the structural registration surface #585's head and compare which entries each actually resolves. That is the whole ticket; the fix follows from the answer.
  2. If the entry loop is scoped by the coverage report, make the resolution unconditional — resolve every entry in the file regardless of whether it is consulted, the way scripts/check_mutation_survivors.py (morph#614) walks the whole document. An entry that is only validated when it happens to be needed is an entry that rots between needs.
  3. Audit all 22 entries on master once the mechanism is known.

What would change the verdict

  • Close if the two entries turn out to be correct under the resolver's real semantics and my line-matching is the thing that is wrong — in which case the resolver's docstring is misleading and that is the finding instead.
  • Re-open if a PR that does not touch an allowlisted file is ever again failed by that file's entry.

Related

Activity

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

    area: ciSubsystem: cibugSomething isn't workingtriage: invalidPremise is false

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions