v0.275.0: a backup's data and its version travel together (R-696, 07 §6.6, D4 option A); R-695, R-691, R-694
gates / gates (push) Successful in 23s
gates / gates (push) Successful in 23s
The unit's data files are stamped with the versions that wrote them; the capture keeps the definition the data belongs to; a restore never starts data under another version's definition (unit restores refuse a mismatch; the off-site restore writes the snapshot's definition); every tier's time is its data's; the conversion-copy release needs a dump on the new engine. File-browser sync single-flight + no empty kept folder (R-695); the kept view joins the folder's owning group, language switch resyncs (R-691); a restore-generated login is not shown as the password (R-694). Red-proofs in felhom.eu/documentation/audits/version-travel-2026-09-26/. 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:
@@ -2827,9 +2827,27 @@ func (a *stackAdapter) GetStackRecoveryInfo(name string) (backup.RecoveryInfo, b
|
||||
DataKeyEnvVars: dataKeys,
|
||||
PortableSecretEnvVars: portableNames,
|
||||
PortableSecrets: portable,
|
||||
InstalledImages: installedRefs(s.AppConfig),
|
||||
}, true
|
||||
}
|
||||
|
||||
// installedRefs renders app.yaml's `installed_images` observation as service -> `ref@digest` (v0.275.0,
|
||||
// R-696) for the data stamps. Nil when nothing was observed — absent means UNKNOWN (deploy.go).
|
||||
func installedRefs(cfg *stacks.AppConfig) map[string]string {
|
||||
if cfg == nil || len(cfg.InstalledImages) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make(map[string]string, len(cfg.InstalledImages))
|
||||
for svc, im := range cfg.InstalledImages {
|
||||
ref := im.Ref
|
||||
if im.Digest != "" {
|
||||
ref += "@" + im.Digest
|
||||
}
|
||||
out[svc] = ref
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RecoverStackSecrets returns the live decrypted values for the named secret env vars present in the
|
||||
// stack's app.yaml (the guest's own — live rootfs or PBS-restored). Absent/empty names are omitted;
|
||||
// the caller's fail-closed gate decides. Secrets come from the guest, never from the recovery unit.
|
||||
@@ -3594,6 +3612,23 @@ type updateGuardsAdapter struct {
|
||||
q *quiesce.Loop
|
||||
}
|
||||
|
||||
// DumpStamps (v0.275.0, A4) — the app's own unit's database dumps with the versions that wrote them;
|
||||
// the conversion-copy release's second condition (stacks.DumpStampSource).
|
||||
// The release reads the stamps only through this optional interface — a method nobody satisfies would
|
||||
// silently keep every conversion copy forever; this line fails the build instead (v0.275.0).
|
||||
var _ stacks.DumpStampSource = (*updateGuardsAdapter)(nil)
|
||||
|
||||
func (a *updateGuardsAdapter) DumpStamps(name string) []stacks.DataDumpStamp {
|
||||
if a.b == nil {
|
||||
return nil
|
||||
}
|
||||
var out []stacks.DataDumpStamp
|
||||
for _, d := range a.b.UnitDumpStamps(name) {
|
||||
out = append(out, stacks.DataDumpStamp{File: d.File, At: d.At, Images: d.Images})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *updateGuardsAdapter) HoldFor(name string) (bool, string) {
|
||||
if a.b == nil {
|
||||
return false, ""
|
||||
|
||||
Reference in New Issue
Block a user