Skip to content

Fix/staleness gate honest verdicts - #551

Merged
hyperpolymath merged 3 commits into
mainfrom
fix/staleness-gate-honest-verdicts
Jul 28, 2026
Merged

Fix/staleness gate honest verdicts#551
hyperpolymath merged 3 commits into
mainfrom
fix/staleness-gate-honest-verdicts

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (would change existing behaviour)
  • 🕳️ Soundness fix (fixes a checker/proof false-negative)
  • 📖 Documentation
  • 🧹 Refactor / tech debt (behaviour-preserving)
  • ⚡ Performance
  • 🔧 Build / CI / tooling

How has this been verified?

Checklist

  • My commits are signed (git commit -S).
  • I ran the project's own checks/tests locally and they pass.
  • New files carry the correct SPDX-License-Identifier (code/config MPL-2.0,
    prose CC-BY-SA-4.0); I did not relicense existing files.
  • Docs are updated, and no public claim now overstates what the code does.
  • I have not introduced a soundness hole (or I have flagged where I might have).

Notes for reviewers


Summary by Gitar

  • CI / Tooling:
    • Updated staleness gate to fail on named defects rather than calendar time in ci checks
    • Confirmed deny-list negatives and fixed a cwd-relative shallow probe in CI scripts

This will update automatically on new commits.

hyperpolymath and others added 3 commits July 21, 2026 16:22
MEASURED 2026-07-21: 294 of 350 consumers were red on the single pin
d7c2271 (2026-06-26) — every one of them green a fortnight earlier, with
nothing changed in any consumer. aspasia is the clean natural experiment:
same pin, 2026-07-09 run at 32 commits / 12d = passing notice; 2026-07-21 at
63 commits / 24d = hard error.

standards moves ~2.6 commits/day, so a consumer exhausts both window budgets
~14 days after any propagation. Holding the fleet green under that rule means
re-pinning ~300 repos every fortnight (order 7,800 PRs/year). A gate that
fails everyone on a timer is not a guard: it trains the estate to ignore it,
and it buries the findings that matter — the same sweep shows 148 real
Workflow security linter failures and 74 anti-pattern failures that read as
noise once the fleet is uniformly red.

1. Age outside the window is now a ::notice, not an error. The window is
   still computed and still reported, so propagate-workflow-pins.sh and the
   Hypatia sha_bump_propagation rule keep their signal.

2. A named deny-list (KNOWN_BAD_BEFORE) replaces age as the hard failure.
   Each entry is <reusable>:<fix-sha>; a pin that is a strict ancestor of the
   fix carries that defect and is rejected at any age. This is what the window
   was only ever a proxy for, and it is strictly better in both directions: a
   RECENT pin carrying the defect is now caught, and an old pin carrying none
   is no longer punished by the calendar.

   First entry is e9c8888 (#441). Before it, hypatia-scan-reusable and
   the validate-hypatia-baseline job cached the built Hypatia scanner under a
   keyless key while the build steps were guarded by `if [ ! -d ]`, so the
   first scanner build ever cached was reused forever and scanner fixes never
   took effect. A pin older than this reports a FALSE GREEN. All 294
   consumers on d7c2271 predate it by one day and stay red — now for a true
   and actionable reason.

3. Integrity no longer rests on the runner's clone. The UNKNOWN / "may be
   forged" verdict was firing on legitimate pins: awesome-haskell pins
   governance-reusable@5a93d9d5 and was accused on four consecutive runs over
   17 days, while that commit verifies as a true ancestor of main locally in
   both treeless and --depth 200 clones and via the compare API (behind=0,
   ahead=90). The mechanism was never reproduced off-runner.

   A hard FORGED verdict now requires either a COMPLETE local clone (not
   shallow, not partial) or confirmation from
   GET /repos/{nwo}/compare/{pin}...{branch}. Where neither is available the
   gate warns and passes: "cannot verify" is not "compromised". Zero API calls
   on the happy path — the server is consulted only when about to accuse.

   The deny-list gets the same fallback, so a degraded runner cannot silently
   skip it; "could not check" is reported as SKIPPED, never as passed.

Tests: the hermetic fixture suite goes 12 -> 16 cases. Out-of-window now
asserts exit 0 AND the advisory notice (a new run_case_out helper — an exit
code alone cannot distinguish "passed silently" from "passed with the notice
the propagation path depends on"). New coverage: deny-list rejects at any
age, rejects even when in-window, does not reject the fixing commit itself,
and is scoped per reusable. 16/16 pass, and the suite stays hermetic because
a complete fixture clone resolves the forged case without the network.

Also verified against live standards history and in degraded-clone mode; both
give identical verdicts for fresh / denied / old-but-clean / forged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…low probe

Follow-up to the previous commit, closing two holes found by adding a test for
the path production actually takes.

1. The deny-list's NEGATIVE was still trusting local `merge-base` on a partial
   clone — which is ALWAYS the case in CI (`--filter=tree:0`) and is the one
   operation measured unreliable there. A treeless clone resolves every commit,
   so the API fallback was gated on "commits do not resolve" and was therefore
   dead code in production. Trace for awesome-haskell (pin 5a93d9d, which
   predates the Hypatia fix and must be denied): local branch taken -> a wrong
   negative -> `continue` -> not denied -> classify_pin also mis-fires -> API
   says ahead=90 -> OUT_OF_WINDOW -> notice -> PASS. That is the exact false
   green this list exists to prevent, on the very cohort that proved the bug.

   Now: a local POSITIVE is still trusted (no false positives observed, and a
   deny must not require the network), but a local NEGATIVE is accepted only
   from a COMPLETE clone; otherwise it is confirmed server-side.

2. `clone_is_complete` used `rev-parse --git-dir`, which returns a path
   relative to the CWD — and the CWD here is the CONSUMER's checkout, which
   actions/checkout makes shallow by default. So `.git/shallow` would almost
   always exist and every standards clone would be called incomplete. Uses
   `--absolute-git-dir` now. Caught by test 13c, which failed for exactly this
   reason when run from a shallow clone.

3. governance-reusable.yml passes GITHUB_TOKEN to the staleness step. The
   server-side confirmation is unauthenticated (60/hr per runner IP) and
   exhausting it degrades the gate to ::warning. That fail-open is deliberate
   and loud, and the common path never calls the API (a genuine deny is a local
   positive) — but a token removes the cliff. It reaches consumers only as they
   re-pin past this commit, which is precisely the propagation the deny-listed
   pins need anyway.

Tests: 16 -> 19 cases, all passing, still hermetic (the API is pointed at a
closed port). The new cases cover the production path that cases 1-12 could not
reach, because they all run against a complete `git init` fixture:
  * partial clone: local positive still denies without the network
  * partial clone: unverifiable deny-list check is reported SKIPPED, not passed
  * complete clone: local negative is trusted silently (no SKIPPED warning)
run_case_out gained a leading-'!' inversion so a test can assert the gate did
NOT say something. Suite verified from two different working directories, since
the bug in (2) was cwd-dependent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hyperpolymath
hyperpolymath merged commit 9620f97 into main Jul 28, 2026
16 of 17 checks passed
@hyperpolymath
hyperpolymath deleted the fix/staleness-gate-honest-verdicts branch July 28, 2026 07:09
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant