From b48a7fa326dbe5505d1568ac55c66488e8de125c Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Mon, 31 Aug 2026 14:32:29 +0200 Subject: [PATCH] R-403: the restore OUTCOME names the package's date, not the run's Live on demo-hp the confirm said 11:43 (the preserved package) and the outcome said 14:23 (the copy's newest run) for the same restore. A customer reading both cannot tell which one they had, and one of the two is the flattering sentence. Part 2.3's rule is 'not a plain green success ANYWHERE', and the outcome is an anywhere. tier2UnitSourceMsg now asks UnitRestoreDate, the same resolver the confirm uses, so the two cannot disagree. TestR403_OutcomeNamesThePackageDateNotTheRunDate pins it, with a negative control for the ordinary case. --- controller/internal/web/handlers.go | 7 +++- controller/internal/web/r403_surface_test.go | 36 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/controller/internal/web/handlers.go b/controller/internal/web/handlers.go index 39eb16b..bdd0f81 100644 --- a/controller/internal/web/handlers.go +++ b/controller/internal/web/handlers.go @@ -1724,7 +1724,12 @@ func tier2UnitConfirmWithStaleness(copyDate string, proven bool, stale bool) str // date it puts in the confirm — so the sentence the customer approves and the sentence they are left // with cannot name different copies. func tier2UnitSourceMsg(cov backup.Tier2Coverage) string { - date, proven := cov.Tier2CopyDate() + // R-403: the OUTCOME names the same date the CONFIRM did — the PACKAGE's, not the copy's newest + // run. Live on demo-hp 2026-08-31 these disagreed by two and a half hours (confirm 11:43, outcome + // 14:23) after a preserved leg, and a customer reading both would not know which restore they had + // just had. §2.3's rule is "not a plain green success ANYWHERE", and the outcome is an anywhere. + date, _ := cov.UnitRestoreDate() + _, proven := cov.Tier2CopyDate() if date == "" { return "" } diff --git a/controller/internal/web/r403_surface_test.go b/controller/internal/web/r403_surface_test.go index c8be60d..003e0e8 100644 --- a/controller/internal/web/r403_surface_test.go +++ b/controller/internal/web/r403_surface_test.go @@ -3,6 +3,8 @@ package web import ( "strings" "testing" + + "gitea.dooplex.hu/admin/felhom-controller/internal/backup" ) // R-403 Group D — the surfaces must not call a PRESERVED package a FRESH one. @@ -99,3 +101,37 @@ func TestR403_TheOrdinaryConfirmIsUnchanged(t *testing.T) { t.Error("the two-argument confirm is no longer the not-stale case") } } + +// D3 — the OUTCOME names the same date the CONFIRM did. +// +// Live on demo-hp 2026-08-31 they disagreed after a preserved leg: the confirm said 11:43 (the +// package) and the outcome said 14:23 (the copy's newest run). A customer reading both cannot tell +// which restore they just had, and one of the two sentences is flattering. +func TestR403_OutcomeNamesThePackageDateNotTheRunDate(t *testing.T) { + cov := backup.Tier2Coverage{ + CopyLastSuccess: "2026-08-31T12:23:51Z", // the run + UnitPackageDate: "2026-08-31T09:43:41Z", // the preserved package + UnitLegPreserved: true, + } + msg := tier2UnitSourceMsg(cov) + pkg := fmtRFC3339Local("2026-08-31T09:43:41Z") + run := fmtRFC3339Local("2026-08-31T12:23:51Z") + if !strings.Contains(msg, pkg) { + t.Errorf("the outcome does not name the package's date %q: %q", pkg, msg) + } + if strings.Contains(msg, run) { + t.Errorf("the outcome names the RUN's date %q — the flattering one: %q", run, msg) + } + // The confirm and the outcome must agree. + date, stale := cov.UnitRestoreDate() + confirm := tier2UnitConfirmWithStaleness(date, true, stale) + if !strings.Contains(confirm, pkg) { + t.Errorf("the confirm does not name %q either: %q", pkg, confirm) + } + + // NEGATIVE CONTROL: with no preserved leg, the package date IS the fresh one and both agree on it. + fresh := backup.Tier2Coverage{CopyLastSuccess: "2026-08-31T12:23:51Z", UnitPackageDate: "2026-08-31T12:23:00Z"} + if !strings.Contains(tier2UnitSourceMsg(fresh), fmtRFC3339Local("2026-08-31T12:23:00Z")) { + t.Error("the ordinary outcome stopped naming its own package date") + } +}