v0.227.1: the damage classifier matched restic's ordinary progress output
gates / gates (push) Successful in 11s

A patch and not a rebuilt 0.227.0: that tag was already running on demo-hp, and
re-pushing changed bytes under a live tag is the :latest hazard with extra steps.

looksLikeRepositoryDamage matched bare "pack ", "tree ", "snapshot ", "blob ". A
HEALTHY restic check prints "check all packs" and "check snapshots, trees and
blobs" -- so any check that failed for a NON-damage reason, a connection dropped
mid-run for instance, would have been classified as a corrupted repository and
told the customer their backups may be damaged. That is the false alarm that
teaches an operator to ignore the true one.

Caught by the NEGATIVE control, built from the real bytes of a real passing
check on demo-hp. The spec made the negative control mandatory and this is what
it was for: a control that has only ever seen the failing case proves nothing.

Signatures are now phrases from restic's own error wording.

Also in this commit: CONTEXT.md records the three rulings (take the flag and
skip, due-ness not a weekday, publish on OffboxReportStatus not the R-331 dead
fields) plus the measurement a future session would otherwise assume wrongly --
THE STRUCTURE CHECK DOES NOT CATCH SILENT CORRUPTION. README documents the job,
the route and the config, and corrects a line that listed four debug backup
routes when only two exist. REUSE gains three rows, including one that records
R-398 was my own mistake so nobody re-files it.
This commit is contained in:
2026-08-30 21:22:23 +02:00
parent 0d52a42c17
commit 45770f2282
6 changed files with 270 additions and 8 deletions
+17 -6
View File
@@ -233,15 +233,26 @@ func (m *Manager) CheckOffboxIntegrity(ctx context.Context) IntegrityResult {
// already alarms when the store cannot be reached.
func looksLikeRepositoryDamage(out []byte) bool {
s := strings.ToLower(string(out))
// THE SIGNATURES ARE PHRASES, NOT WORDS, AND THE REASON IS A BUG THIS FILE ALREADY HAD.
//
// The first draft matched bare `"pack "`, `"tree "`, `"snapshot "` and `"blob "`. Those appear in
// restic's ORDINARY PROGRESS OUTPUT — a healthy run prints `check all packs` and
// `check snapshots, trees and blobs` — so a check that failed for a NON-damage reason (a dropped
// connection mid-run, say) would have been classified as a corrupted store and alarmed the customer
// that their backups were damaged. Caught by the negative control in
// TestR359_HealthyRealOutputIsNotDamage, using the real bytes of a real passing check.
//
// Every phrase below is restic's own error wording, taken from the 2026-08-30 damaged-pack run on
// demo-hp or from restic's check source — never paraphrased.
for _, sig := range []string{
"pack ", // "pack 1234abcd: not found in index" / "... size mismatch"
"load index", // a broken index
"blob ", // "blob not found"
"tree ", // "tree 1234: file ... blob not found"
"snapshot ", // "error for snapshot ...: ..."
"does not match", // "Pack ID does not match, want <id>, got <id>" — the measured one
"not found in index", // a blob or pack the index promises and the store lacks
"blob not found", //
"size mismatch", //
"ciphertext verification failed",
"integrity error",
"repository contains errors",
"repository contains errors", // restic's own summary verdict
"failed to load index", //
} {
if strings.Contains(s, sig) {
return true