An unknown drawn as a zero: the defect v0.226.0's own fix introduced
gates / gates (push) Failing after 13s

Writing the REPORT's observation "the no-unit fallback already reports a zero
result, which is honest" exposed that the sentence was FALSE.

A zero UnitRestoreResult is Scenario B's shape. So RestoreFromRecoveryUnit's
fallback to RestoreApp -- which returns only an error, and whose signature is
deliberately out of scope -- would have printed "ez a mentes csak a
beallitasokat tartalmazta, adatot nem" over a restore that may have replayed the
app's entire dataset. That is an unknown drawn as a zero: the exact R-88 failure
direction this whole change exists to remove, re-introduced by the change.

UnitRestoreResult now carries CountsUnknown, the fallback sets it, and there is a
fourth sentence claiming only what is known -- the restore ran, the app is back,
and we cannot say what came back. RestoreApp's signature is untouched.

Pinned by TestUnitRestoreOutcome_NoUnitFallbackSaysUnknownNotEmpty. The A5 seam
test was corrected too: its fixture has no recovery unit, so it exercises exactly
this path and had been asserting the wrong sentence -- it now asserts the
unknown, which is what pins the fallback to it.

IT WAS THE observations GATE REFUSING THE PUSH THAT FORCED THE RE-READ. A gate
written to stop findings dying in an overwritten REPORT.md caught a live defect
instead. Also files R-397 (NotifyIntegrityOK/Failed are dead code AND the
monitoring page advertises a weekly integrity check that does not exist) and
R-398 (resticStep is not a seam, which is why R-358's ordering needed an AST
test) rather than leaving them in a file that is overwritten every session.

REPORT.md is the full run record: baselines re-confirmed, per-test results, the
five red-proofs with their observed output, the live validation with verbatim
Hungarian messages, what was NOT validated and why, teardown across three
layers, and the register 165 -> 167 -> 161.

Green gate clean: 28 packages, rc 0. All 12 controller gates OK.
This commit is contained in:
2026-08-30 19:51:16 +02:00
parent e4e0aa8f46
commit c0c8fe67bf
5 changed files with 145 additions and 26 deletions
+14 -5
View File
@@ -155,6 +155,16 @@ type UnitRestoreResult struct {
ManifestVolumes int
// ManifestDBs is len(manifest.DBDumps): the same claim for the database leg.
ManifestDBs int
// CountsUnknown marks a run whose counts could not be established AT ALL — today the one case is
// the no-unit fallback to RestoreApp, which returns only an error and whose signature is
// deliberately out of scope.
//
// IT EXISTS BECAUSE THE ZERO VALUE WOULD OTHERWISE LIE. Without it a fallback restore that really
// replayed three volumes reports VolumesReplayed=0 / ManifestVolumes=0 — the shape the surface
// reads as „ez a mentés csak a beállításokat tartalmazta, adatot nem". That is a confident false
// statement, and precisely the R-88 failure direction (degrade to NO DATA rather than to UNKNOWN)
// this whole change exists to remove. An unknown must be carried, never drawn as a zero.
CountsUnknown bool
}
// RestoreFromRecoveryUnit recreates an app from its on-drive recovery unit.
@@ -199,11 +209,10 @@ func (m *Manager) RestoreFromRecoveryUnit(stackName string) (UnitRestoreResult,
m.mu.Lock()
m.running = false // RestoreApp re-acquires the running flag
m.mu.Unlock()
// The fallback path has no unit and therefore no manifest to count against: a ZERO result is
// the honest answer, not a missing one. The surface must be able to tell "nothing came back"
// from "we never looked", and it can — ManifestVolumes/ManifestDBs are zero too, which is
// Scenario B's shape and reads as "the backup held no data", which is exactly true of a box
// with no recovery unit.
// The fallback has no unit and no manifest, and `RestoreApp` returns only an error — so the
// counts here are genuinely UNKNOWN, not zero. Reporting zero would tell a customer whose
// volumes were just restored that their backup held no data.
res.CountsUnknown = true
return res, m.RestoreApp(stackName, "")
}