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

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:
2026-09-26 10:35:22 +02:00
parent fb2bcdd5d6
commit b6810f14ff
47 changed files with 3771 additions and 135 deletions
+17 -1
View File
@@ -1337,9 +1337,25 @@ backups/primary/<app>/
├── compose/ docker-compose.yml + .felhom.yml + app.yaml (0600 — CARRIES the portable secrets)
├── db-dumps/ app-consistent DB dump(s)
├── volume-dumps/ named-volume tars
└── manifest.json image pins, secret NAMES, data_key names, portable NAMES, checksums, secret_source
├── data-stamps.json per data file: when it was written, the pins and running ref@digest that wrote it (v0.275.0)
└── manifest.json image pins (the app's CURRENT ones), secret NAMES, data_key names, portable NAMES, checksums,
secret_source, and `data` — the time and versions of the data (v0.275.0)
```
- **The data and its version travel together (v0.275.0, R-696, `07` §6.6).** Each backup leg stamps the
file it writes (`stampDataFile`: size, mtime, the definition's pins, `installed_images` as `ref@digest`); the
capture folds the stamps into the manifest's `data` block and **keeps the definition its data belongs to** —
a refresh after an update no longer rewrites `compose/` until the next data run replaces the data. A restore
(own unit, second drive, kept-data Load) starts the data under that definition and refuses, before touching
anything, a unit whose `compose/` names other pins than its data or whose files were written by different
versions (`ErrUnitVersionMismatch`); the off-site restore writes the snapshot unit's definition into the stack
dir when its version differs from what runs. An app brought back at an older version is climbed by the normal
guarded update; the restore page's first sentence says so (or, with no tested step from that version, that
the box will not update it by itself). **Every tier's time is its DATA's**: Tier 1 `data.at` (else the newest
data file, never the manifest, never a `pre-restore-*` undo copy), Tier 2 capped by the mirror's data time,
Tier 3 capped by the data time recorded at push (`settings.offsite_data_at`). A unit without `data` (written
before v0.275.0) restores as before, with a WARN.
- **The secret split (D5, schema 2, operator ruling 2026-07-30).** The unit was secret-free until
v0.188.0, and that made "restore from the drive alone" false: the fast, local, customer-doable
Tier-1/2 restore secretly depended on the slow, operator-driven whole-guest restore, because a