R-699: a unit that holds no data is never an update-precondition copy (found live on 9202)
gates / gates (push) Successful in 25s

A just-installed app's unit, captured by the status refresh before any backup, satisfied
the precondition on its manifest time; tandoor's PostgreSQL was converted with no backup
of its database. Listed still; never a copy on Tier 1 or Tier 2. Red-proof RP6.

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-09-27 12:04:03 +02:00
parent b6810f14ff
commit 7fcda8f1a4
8 changed files with 89 additions and 11 deletions
+10 -1
View File
@@ -46,6 +46,9 @@ type Tier2RestorePoint struct {
// DataDate (v0.275.0, R-696) — the mirrored unit's DATA time (unitNewestArtifact on the mirror): when
// the data the copy holds was written, which a mirror run copies but never makes newer. "" = unknown.
DataDate string
// DataUnproven (R-699) — the mirrored unit is only a captured definition (no `data`, no data file):
// never a copy the update may lean on.
DataUnproven bool
}
// restorePointFromCoverage is the pure half of the predicate.
@@ -58,6 +61,7 @@ func restorePointFromCoverage(cov Tier2Coverage) Tier2RestorePoint {
PackagePreserved: preserved,
CopyLastSuccess: cov.CopyLastSuccess,
DataDate: cov.UnitDataDate,
DataUnproven: cov.UnitDataUnproven,
}
}
@@ -86,7 +90,7 @@ func (m *Manager) Tier2UnitRestorePoint(stackName string) (Tier2RestorePoint, er
// actually mirrored the unit — EXCEPT when the run preserved an older package (R-403), in which case
// the package date is the honest one, because that is what the copy really holds.
func (p Tier2RestorePoint) ProvenCopyTime() (time.Time, bool) {
if !p.Restorable || !p.CopyDateProven {
if !p.Restorable || !p.CopyDateProven || p.DataUnproven {
return time.Time{}, false
}
src := p.CopyLastSuccess
@@ -269,6 +273,11 @@ func (m *Manager) updateTierPoint(ctx context.Context, stackName string, tier in
}
pts, _ := list(stackName)
for _, rp := range pts {
if !rp.DataProven {
// R-699: a unit that is only a captured definition is not a copy of the app's data.
m.logger.Printf("[INFO] [backup] update precondition for %s: the own unit holds no data yet (no backup run has confirmed it) — not a copy", stackName)
continue
}
if at, err := time.Parse(time.RFC3339, rp.Time); err == nil {
return UpdateTierPoint{Tier: tier, At: at}, true
}