R-585: the last customer-facing producers follow the household's language

backup_integrity_ok / backup_integrity_failed now take facts and push bundle
keys (Hungarian bytes unchanged - go-parity, pinned verbatim by
TestR585_IntegrityHungarianIsUnchanged). The interrupted-operation alert
(backup_failed, customer-enabled by default) used to send the operator's
ENGLISH sentence to every household; NotifyInterruptedOperation composes it
per language, the English byte-identical to the operator's log line. The
now-callerless NotifyBackupFailed is removed. local_api_endpoint_drift is
operator-only (no customer toggle) and already English by design - not
changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-06 01:20:03 +02:00
parent 84d6ef706d
commit ff1758a21c
10 changed files with 289 additions and 45 deletions
+11 -25
View File
@@ -695,7 +695,15 @@ func main() {
// deliberate hold through it would email the customer "A biztonsági mentés sikertelen!" about an
// app nothing is wrong with. The refusal is already logged at WARN with its reason.
if appStopRecovery != nil && appStopRecovery.Alarming() {
notifier.NotifyBackupFailed(appStopRecovery.Message(), appStopRecovery.Detail())
// R-585: facts, not the operator's English sentence — `backup_failed` is customer-enabled by
// default, so this line reaches the household and must follow its language.
notifier.NotifyInterruptedOperation(notify.InterruptedOperation{
ReasonKey: appStopRecovery.Reason.MessageKey(),
Reason: string(appStopRecovery.Reason),
Restarted: len(appStopRecovery.Restarted),
Failed: len(appStopRecovery.Failed),
Refused: len(appStopRecovery.Refused),
}, appStopRecovery.Detail())
} else if appStopRecovery != nil {
logger.Printf("[WARN] [appstop] %s — not alarming: %s", appStopRecovery.Message(), appStopRecovery.Detail())
}
@@ -3773,7 +3781,7 @@ func runOffsiteIntegrityCheck(ctx context.Context, mgr *backup.Manager, n *notif
// design (08 §6.1). A weekly success e-mail is how people stop reading their alerts. It is
// still pushed, because the hub stores it and the event stream is where "was it checked?" is
// answered.
n.NotifyIntegrityOK(integrityOKMsg(res))
n.NotifyIntegrityOK(res.Duration.Round(time.Second).String(), res.ReadDataSubset) // R-585: facts; the bundle composes
return res
default:
mgr.RecordIntegrityVerdict(res)
@@ -3783,22 +3791,11 @@ func runOffsiteIntegrityCheck(ctx context.Context, mgr *backup.Manager, n *notif
// The customer gets a SENTENCE. restic's own words go to the log, truncated, where the operator
// can diagnose without a rebuild. R-379 is the reason that split exists: 615 bytes of raw
// database text reached a customer once.
n.NotifyIntegrityFailed(integrityFailedMsg, "restic check reported repository errors")
n.NotifyIntegrityFailed("restic check reported repository errors") // R-585: the sentence is event.backup_integrity_failed
return res
}
}
// R-359 customer-facing strings. Named constants because tests assert them verbatim and because a
// silent edit is how an honest message drifts into a comforting one.
const (
// integrityFailedMsg names what to do and what NOT to do. "Ne törölj semmit" is load-bearing: a
// customer who believes their backups are broken may try to "start fresh", which destroys the one
// copy that might still be partly recoverable.
integrityFailedMsg = "A távoli mentés ellenőrzése hibát talált a tárolóban. A mentések egy része sérült lehet. Ne törölj semmit, és vedd fel velünk a kapcsolatot."
integrityOKBase = "A távoli mentés ellenőrzése rendben lezajlott."
)
// ── R-87 — the nightly off-site PROOF's one caller ─────────────────────────────────────────────
//
// ONE function, like runOffsiteIntegrityCheck above, so a future debug button cannot drift from the
@@ -3859,17 +3856,6 @@ func proofEmptyDetail(res backup.ProofResult) string {
return d
}
// integrityOKMsg states what was actually checked, so a structure-only pass is never read as a
// full data verification. The depth is a fact the customer's sentence has to carry: "checked" means
// two different things depending on it.
func integrityOKMsg(res backup.IntegrityResult) string {
msg := integrityOKBase + " (" + res.Duration.Round(time.Second).String()
if res.ReadDataSubset != "" {
msg += ", a mentett adatok " + res.ReadDataSubset + "-át újraolvasva"
}
return msg + ")"
}
// updateGuardsAdapter implements stacks.UpdateGuards over the backup manager (slice 4). The stacks
// package cannot import backup, so this is the one place the two meet. Nil-safe on b: a box with
// backup disabled has no restore point, and the update is refused for that true reason.