fix(asvs): refuse a scorecard write from a clone behind the record line (BACKLOG #1476) - #1177
Conversation
…ne (BACKLOG #1476)
The stale-clone guard refused on repo_stamp().freshness alone, which is measured
against the branch's own @{upstream} when it has one. On a pushed feature branch --
the ordinary way to edit the record -- that reads CURRENT however far the clone's
base has fallen behind origin/main, so the guard printed CURRENT and let the write
through. Ask the record-line question instead: how many commits reachable from
origin/main are not reachable from HEAD.
The branch reading is kept as a second refusal rather than replaced, so coverage
never drops where origin/main cannot be resolved.
Both of your open questions answered, so nothing here waits on the ownerYou raised two, and the shape of each is "is this stricter than the row required". I am answering 1. Keep
|
|
LANDER review. Read against head THE DEFECT IS THE PRETTIEST ONE I HAVE READ TONIGHT, because the information was already there and nobody read it. So a maintainer re-rendering the scorecard from any pushed feature branch sailed through however far that clone's base had fallen behind A function that answers the question honestly, beside a caller that ignores the qualifier, is a much harder defect to see than a wrong function. THE FIX ASKS THE BRANCH-INDEPENDENT QUESTION — THE DISTINCTION I WANT TO SINGLE OUT IS
THE TESTS ARE A COMPLETE INSTRUMENT, not a demonstration. Four arms:
TWO JUDGEMENT CALLS I AGREE WITH. Importing THE RESIDUAL IS ADMITTED RATHER THAN PAPERED OVER, which is why Noted and correct: this does NOT close the incident BACKLOG #1476 describes, and the PR says so rather than letting a merge imply it. BEHIND, so it needs a branch update before it can go in. I am doing that now and will enqueue it. Verdict: merge. |
Lander reviewReviewed the diff; branch updated to re-run a red that was not yours (see the last section). Will queue on green. The defect is the one this repository keeps paying for
That is an instrument answering a question adjacent to the one asked, and the fix asks the right one: commits reachable from It does not overclaim. The comment is explicit that The part I would keep above everything else here
The rest, checkedNo network, and the reason is right. A query that fetches gets bypassed, and a bypassed guard is worse than none because its absence reads as nobody needing it. Reusing The The red was not yours
Your diff cannot reach it. Whole-word counts over the whole diff: Main's own run at Stated no larger than it is: one occurrence, no measured rate. If it fails the same way again, that is a second data point about the load runner, not about this PR. Verdict: merge. |
BACKLOG #1476 -- the stale-clone refusal asked whether the branch was current with ITS OWN upstream, not whether the clone was current with the record line.
The defect
scripts/asvs/apply.pytookrepo_stamp(SCORECARD)and refused whenstamp.freshnessreadBEHIND <n>orDIVERGED. That field is measured against the branch's own@{upstream}when it has one, and againstorigin/mainonly as a fallback.scorecard._freshnessis not wrong about this -- its docstring says outright that it returns whichever upstream it used, "because on a feature branch those are different questions". The caller never consulted the answer.So a maintainer re-rendering the scorecard from a clone sitting on any pushed feature branch would not be refused, however far that clone's base had fallen behind
origin/main, and the guard would printfreshness=CURRENTwhile saying so. Conditional because nothing here is deployed: this is wrong in the shipped code, and it is the exact shape the row exists to stop.This does NOT close the incident the row describes, and the PR body is the wrong place to imply otherwise. The row's own hash table shows the parent that the bad write landed on already carried the repair, so the staleness was in the payload rather than in the clone. This guard would not have stopped that write. It closes a different and real hole.
The predicate I chose, and why
I took a second reading measured explicitly against the record line, rather than refusing whenever
stamp.upstreamis notorigin/main._record_line_gap()counts commits reachable fromorigin/mainand not fromHEAD(git rev-list --count HEAD..origin/main). Refusing onstamp.upstream != "origin/main"was the other candidate and it fails the usability test below: it refuses every feature branch, including the ordinary ones.The count answers the same question on every branch:
That is the discriminator the row needs: the BASE is what matters, not the branch.
scorecard.pyis untouched. It already returned everything the caller needed. That file is inMIRRORED_TOOLS, so an engine-only edit there would read green here and red in the vault.Workflows this predicate still permits
Named because a guard that refuses the normal workflow gets reverted or overridden by habit:
origin/main, pushed as its own branch, landing through a pull request. Gap zero, permitted. Test arm:test_a_write_from_a_feature_branch_whose_BASE_IS_CURRENT_is_NOT_refused.AHEAD 1against its own upstream; gap zero, permitted. Test arm:test_a_write_from_a_feature_branch_AHEAD_of_its_own_upstream_is_NOT_refused.main, current. Unchanged.Workflow 1 was also measured against a real tree rather than only a fixture -- this pull request's own worktree, cut from
origin/mainand on a feature branch:What I kept rather than replaced
The branch-upstream refusal (
BEHIND/DIVERGEDagainst@{upstream}) stays as a second, independent reason to refuse. Two reasons:origin/maincannot be resolved in the clone, it is the only reading left, so coverage never silently drops to nothing;Every state that is not a measured gap -- no work tree,
origin/mainabsent, a comparison git refused -- is reported by name and falls back to the branch reading. An unaskable question and a measured zero print differently, on purpose.Red first, with the runner's actual output
The new must-fire arm, run against the shipped code before the fix:
The captured stdout is the whole defect in one line:
freshness=CURRENT upstream=origin/feature, and then it wrote the file from a clone a commit behind the record line. The3 passedin that same run are the two negative controls and the override arm -- they pass before the fix on purpose, which is what makes them controls on over-refusal rather than demonstrations of it.The fixture is a real clone of a real origin, not a monkeypatched stamp: origin is initialised on
main, cloned, the clone checks outfeatureand pushes it with-u, then origin gains a commit onmainand the clone fetches. HEAD is then 0/0 againstorigin/featureand 1 behindorigin/main.After the fix, the same scenario driven by hand:
Guard (b) untouched
The scope/ceiling refusal and
_SCOPE_CEILINGare not touched. The diff is local to the freshness predicate and its tests.Checks run
From a venv built in this worktree with
--constraint constraints.lock(not borrowed from a sibling):ruff check .-- passedruff format --check .-- passedmypy messagefoundry--Success: no issues found in 274 source filespytest tests/test_asvs_apply.py-- 72 passedpytest tests/test_asvs_apply.py tests/test_asvs_scorecard.py tests/test_asvs_verifier_vault_contract.py tests/test_asvs_anchor_provenance.py tests/test_asvs_anchor_report.py-- 320 passedSkipped: the full
pytestsuite. Not run inside this turn.Legs a human must read after I exit: the full hosted test matrix, and
windows-service-smoke(NSSM), which never runs locally.