Files
felhom.eu/documentation/audits/review-round-2-2026-10-24/README.md
T

91 lines
8.6 KiB
Markdown

# Review round 2 — the architecture documents against the code, and a fresh data-loss and security look (2026-10-24, unattended)
**Baselines (read at start, 14:42 CEST):** felhom.eu `44d19042`, felhom-controller `321df1f` (v0.342.0 released;
„next v0.343.0" unreleased on top), felhom-agent `5e05627` (v0.164.0; „next v0.165.0"), app-catalog `5e196e1`, hub
deployed 0.169.0. Register: 190 rows.
**Method.** Thirteen read-only reviewers (each prompt carried the brief's fences word for word —
`reviewers/FENCES-given-to-every-reviewer.md`): seven on the documents (the brief's four groups, two of them split, plus a seventh for
`02`/`10`/`11`/`14`, which the groups did not name), four on data loss (restore and put-back; delete and retention;
keys; time and concurrency), two on security (hub + controller; agent + ep0 + installer + the DooPlex→ep0 jobs). Their
answers are CANDIDATES (`reviewers/`). The main session read every item it acted on at `file:line`; the edits and
their evidence are in `EDITS.md`.
## Part 1 — the documents (claims checked by the reviewers: 493; stale reported: 121)
| Document | Verdict |
|---|---|
| `01` topology & trust | 7 stale facts corrected; 2 product statements contradict the code → R-1266 (D45) |
| `03` host agent | 9 corrected (shipped releases, key files, listen address, swap, cloudflared's 4th state); a note: 41 local-API routes, 24 unlisted; break-glass text → R-1266 |
| `04` control-plane authorization | 3 corrected; a note: a signed op's window has no length cap → R-1265 |
| `05` hub | 9 corrected (the 6-hourly DB copy, version literals, the fifth bind-link path, 44 event types, the retired DR record) |
| `06` off-site connectivity | 7 dated notes (the design-of-record kept); „no inbound listener" → R-1266 |
| `07` backup | 12 corrected (portable secrets, R-104 fixed, keep-daily, D22/D20 released, a refusal row); §8.2c notes → R-1265; the kept-data load beside a copy → R-1256 |
| `08` alarm ladder | 10 corrected (per-type cooldowns, R-723, per-app grain, R-998 skip, §8.1 built, §9 released, the operator-only box alarms); one ruling line kept with a dated note (operator Q1 made `backup_integrity_failed` a default) |
| `09` updates | 19 dated notes and corrections (the §1 measurement, slice 6 built, the undo as shipped, digests, phase table); §6.4.x notes → R-1265; three rulings the code does not do as written → R-1266 |
| `10` localisation | 7 corrected (Part B built, ceiling 0, the wizard deleted, counts) |
| `11` OS updates | 7 corrected (cloudflared 2026.9.3, sudoers lines, the wrapper built, 32 bundle files, HOST_SLOW_RE); the System page's „Upload line" cell added |
| `12` agent tooling | 3 corrected; the five rule copies aligned (the instruction-file edits below) |
| `13` threat model | 11 facts corrected, every verdict cell left as it was and marked „re-check"; a note on the file manager's sign-in trust |
| `14` power-off | 4 corrected (sudoers vectors, line-test holder, FAQ published, the table split); notes → R-1265 |
| `15` media library | a dated note on the §10 measurement table; R-1019's fix; notes → R-1265 |
| `16` storage identity | 4 corrected; notes → R-1265 |
| `02` controller module map | 8 dated notes (markers, holders, banner alerts, cloudflare still wired, updater live, LockedFields, 22 packages with no row) |
| `_hub-review.md` / `_recovery-inventory-2026-07-28.md` | a HISTORICAL banner / a „what has moved since" banner — both dated records kept as they were |
| `00` capability map | 20 PROVEN-LIVE rows marked „re-check" with the commit that changed their mechanism (no status lowered); G15's „nightly" corrected |
| `felhom-system-poster` facts + html | the timeline's hub-DB push (text only; no drawing needed) |
**Rules, fences and promises were not edited.** Seven items that need the operator are in R-1266 (D45); about 30
behaviours with no document are written as `[DESIGN — undocumented, found 2026-10-24]` notes for the operator to confirm
(R-1265, D44).
**Instruction files edited (unprompted-work §5 — facts only):** the workspace root, felhom-agent and app-catalog copies
of `.claude/rules/unprompted-work.md` got the missing „A version number is pushed once" paragraph (now byte-identical to
the other two); `felhom.eu/.claude/rules/live-probes.md:41` — the stack path `/opt/felhom/stacks/<name>` → `/opt/docker/
stacks/<name>/app.yaml`. Details: `EDITS.md`.
## Parts 2 and 3 — findings, ranked
| # | Sev | Finding | What was proven | Row |
|---|---|---|---|---|
| 1 | **P1** | A power cut during a restore of a drive-backed app: the app runs on the cut restore's data, and a later controller restart pours the pre-restore copy back over newer writes | the real `Recover` code in a lean clone: boot 1 „copy gone → refused → marker kept", boot 2 put-back after the app ran (`proofs/H1-drive-cut-restore/`); the boot recreate's missing check read at `intermediary.go:470-476` | R-1251 |
| 2 | P2 | One rejected backup block makes the night's second-drive copy delete the app's household-file copies | **reproduced on 9202**: control kept the copy twice; one bogus path → „removed stale dest dir userdata/media" (`proofs/H2-bad-backup-block/`) | R-1252 |
| 3 | P2 | An export with „stop first" overwrites another app's restore marker | static, read at `file:line` | R-1253 |
| 4 | P2 | The hub saves the escrow package in two writes; a lost answer tells the household „the previous code still works" | static | R-1254 |
| 5 | P2 | D4's sshd drop-in reaches BYO hosts (against `11` §1); the in-force check and the all-or-nothing `sshd -t` | static | R-1257 (D43) |
| 6 | P3 | Escrow carry / set-aside name edges | static | R-1255 |
| 7 | P3 | Four restore edges (definition write, merge, a mirror race, the unused copy) | static | R-1256 |
| 8 | P3 | ep0's iperf3 takes any tunnel peer's test | static (no ep0 change) | R-1258 |
| 9 | P3 | The DooPlex→ep0 copies: the vouched agent can drop out; deletions mid-transfer; no future bound; **2026-10-25: the 02:30 push re-sends the 20:00 snapshot** | DST timing verified on DooPlex (`systemd-analyze`, Go) | R-1259 |
| 10 | P3 | Spring 2027: the leg's deadline vs the gate's clock; two legs at one instant | Go's mapping verified | R-1260 |
| 11 | P3 | The unreleased R-1250 line hold: two seeders, a removed app, alt-speed | static | R-1261 |
| 12 | P3 | Any viewer explains any upload (the upload watch) | static; `08`'s limits list now names it | R-1262 |
| 13 | P4 | Small items (a mislabelled operator mail, undeclared volumes on Remove, mount views, stale comments, a DNS-name password, rrsync) | static | R-1263 |
| 14 | P4 | Skills tooling (no size cap on the grandfathered skill; a new skill not installed) | read | R-1264 |
| 15 | P4 | Immich leaves an empty anonymous volume per recreate (296 on 9202); photos unaffected | read in the running image | R-1267 |
**Fixed in this session (small, not recovery code, red-proved, commit only — controller `b01bb4b`, CI 2294 / #480 success):**
a media restart („Újraindítás most", „Később", the safe removal) no longer starts an app the household stopped (R-721;
two tests, each seen failing with the rule off); the file manager's header name file appears whole (a race the
existing test caught 44 times in 300 runs on clean `main`; 0 in 300 after, 0 in 50 under `-race`).
**NOT-A-FINDING:** SEC-1 X2 (a passphrase enroll after the operator's host delete gets ciphertext — the window D19
accepts); SEC-1 X3 (the refused-passphrase mail is operator-only with a one-hour cooldown); DOC-D1 U4 (the fit check
that passes on a statfs error — the copy then fails and the app restarts untouched, `restore_unit.go:502-512`);
HUNT-3 D31's comment wording (no data effect); Immich's `/data` volume for data placement (R-1267 holds the leak);
the stack folder left after a clean removal (it is every catalog app's template folder).
## Machines
- **9202** (Tier 0): as found at 12:46 UTC (`9202/as-found-2026-10-24.txt`); at the end (`9202/at-end-…`) the same
containers and images, healthy (restarted by the test night chains), the catalog on the live repo, the controller
0.342.0. The only difference: Immich's running container uses a new empty anonymous volume (R-1267); the two others
my test runs made were removed by name. audiobookshelf installed and removed through the product; my marker file
removed; its second-drive copy removed by the product's remove.
- **DooPlex:** reads, lean clones in the scratchpad, the gates; no service changed. **ep0, household boxes, the hub:**
not touched (hub reads only, none needed in the end).
## Teardown (three layers)
Machine: 9202 as above. Host (demo-hp): nothing provisioned. Hub: nothing changed.