fix(#4135): headline baseline coverage, opt-in strict gate, git-history widening (#4376)

* test(#4135): regression rows for pristine regen coverage collapse

RED skeleton: src/pristine-baseline.cts exports findPristineInGit as a
null-returning stub (wired into verifyFile after the #4145 orphan tier,
behavior-neutral) so the git-history rows fail behaviorally, not at require
time. Failing-first rows: baseline_covered aggregate on a 1-of-13
multi-version fixture, coverageHeadline typed renderer, the opt-in
--min-baseline-coverage gate (exit 3, >= threshold semantics, vacuous-pass
and malformed-value boundaries), git-history baseline recovery (dropped-line
catch + surviving-line verify + older-commit hop), findPristineInGit unit,
Step 5a workflow headline contract, and the installer-side
describeBaselineCoverage honest N-of-M summary with the collapse disk-state
pinned. Negative-space rows pin today: non-git ok_no_baseline posture,
no-match-no-adoption, #3657 drift never rescued, canonical precedence, and
no git tier without --pristine-dir.

* fix(#4135): headline baseline coverage, opt-in strict gate, git-history widening

The #3407 promotion rule regenerates gsd-pristine/ baselines from the
INCOMING release source and keeps only candidates byte-identical with the
OUTGOING recorded hash — correct in isolation, but on a multi-version jump
the surviving set is precisely the files upstream did NOT change. The
verifier then reports ok_no_baseline (advisory, exit 0) for everything
else, and no surface distinguishes a 12-of-13-unverified green run from a
fully-verified one: the human summary printed Checked/Failures only, the
JSON had no coverage aggregate, and the installer's update output gave
per-bucket counts without N-of-M framing.

All three issue directions, none exclusive:

- Report coverage prominently: --json gains an additive baseline_covered
  aggregate; the human summary leads with 'Baseline coverage: N of M
  file(s)...' on every run plus an advisory section naming each skipped
  file and reason; the installer prints an honest covered-of-modified line
  via the exported describeBaselineCoverage helper (typed return, exact
  contract); workflow Step 5a computes and prints the headline before any
  pass/fail framing.
- Fail louder on low coverage: opt-in --min-baseline-coverage <0..1>
  exits with new documented code 3 when coverage falls below the
  threshold (>= semantics; empty run vacuously passes; content failure
  exit 1 outranks it; malformed values are usage errors, exit 2).
  Default posture unchanged — no_baseline stays advisory per #934.
- Widen the promotion rule (its only trustworthy form): when no baseline
  resolves under gsd-pristine/ and a hash is recorded, the verifier now
  recovers the baseline from the config dir's own git history — the
  workflow's documented Option A — anchored by the same authority every
  tier trusts, exact pristine_hashes sha-256 equality. Read-only
  (git log/git show, windowsHide per #685), bounded (100 commits/file,
  10s/subprocess), null-on-any-failure so ok_no_baseline remains the
  universal fallback. Tier order: canonical join -> #4145 orphan scan ->
  git history -> OK_NO_BASELINE; #3657 drift and canonical precedence
  untouched.

Hash validation in saveLocalPatches is NOT relaxed — the collapse is
legitimate conservatism; hiding it was the bug. Measured on the issue's
shape (13 files, 12 changed upstream, 1.10->1.12): non-git installs report
baseline_covered 1/13 with the headline and can gate at exit 3; a
git-managed config dir with the outgoing bytes in history verifies 13/13.

Review fixes folded in: workflow headline derives the unverified count
from checked - baseline_covered (not the drift+no_baseline sum), and the
new site-scoped allow-test-rule annotation carries its ADR-456 see-ref on
the marker line.

Emitted-Drift-Ack-Growth: reapply-patches.md — #4135 — +20 lines / ~1.5 KB, prose and bash only: two additive parse lines (BASELINE_COVERED, CHECKED_COUNT), a Step 5a coverage-headline block printed BEFORE any pass/fail statement (documents the opt-in --min-baseline-coverage exit-3 gate), and one Option B sentence noting the verifier's read-only git-history fallback. No step ordering, gate, tool-invocation, or dispatch shape changed; 5a's fail/drift/advisory handling is unchanged, the headline only precedes it.

* chore(#4135): backfill PR number into changeset fragment

---------

Co-authored-by: agent-4135 <agent-4135@gsd.local>
This commit is contained in:
Tom Boucher
2026-09-06 05:26:05 -04:00
committed by GitHub
parent c95b734145
commit 0aa4202f6a
7 changed files with 933 additions and 15 deletions

View File

@@ -10217,6 +10217,29 @@ function recoverOrphanedPristine(pristineDir, relPath, recordedHash, canonicalSk
}
}
/**
* #4135: honest N-of-M accounting for gsd-pristine/ baselines after an
* update. The #3407 promotion rule keeps only hash-validated candidates
* (byte-identical across the version span), so a multi-version update
* legitimately ends with near-zero baselines — the collapse itself is NOT a
* bug to hide; hiding it is. This renders the covered-of-total line the
* update output prints either way, so "1 of 13" can never present like a
* fully-covered run. Pure function (typed return) so tests lock the exact
* contract without matching console prose.
*/
function describeBaselineCoverage(totalModified, covered) {
const total = Math.max(0, totalModified);
const have = Math.min(Math.max(0, covered), total);
const uncovered = total - have;
return {
complete: uncovered === 0,
uncovered,
text: uncovered === 0
? `gsd-pristine/ baselines cover ${have} of ${total} modified file(s)`
: `gsd-pristine/ baselines cover ${have} of ${total} modified file(s) — ${uncovered} will be reported no_baseline by the reapply verifier`,
};
}
/**
* Detect user-modified GSD files by comparing against install manifest.
* Backs up modified files to gsd-local-patches/ for reapply after update.
@@ -10467,6 +10490,17 @@ function saveLocalPatches(configDir, pristineCtx) {
if (removed > 0) {
console.log(' ' + yellow + 'i' + reset + ' Removed ' + removed + ' stale gsd-pristine/ snapshot(s); regenerated ' + regenerated + ' of those — falls back to over-broad verify heuristic for the rest');
}
// #4135: the honest N-of-M coverage line. Preserved/rescued/regenerated
// are disjoint buckets (see their accounting comments above), so their
// sum is exactly the files that ended this update with a hash-valid
// baseline. A partial result renders as an info line, not an error:
// the collapse is legitimate (#3407), hiding it was the bug.
const coverage = describeBaselineCoverage(modified.length, preserved + rescued + regenerated);
if (coverage.complete) {
console.log(' ' + green + '✓' + reset + ' ' + coverage.text);
} else {
console.log(' ' + yellow + 'i' + reset + ' ' + coverage.text);
}
}
}
return modified;
@@ -14209,6 +14243,7 @@ module.exports = {
reportLocalPatches,
validateHookFields,
populatePristineDir,
describeBaselineCoverage,
_resolveUserArtifactStagingRoot,
_tryResolveUserArtifactStagingRoot,
finishInstall,