CONTEXT + REPORT: v0.286.1 (R-753 visitors apart, R-772, R-773), floored + golden
gates / gates (push) Successful in 31s

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-01 22:06:02 +02:00
parent a28155f858
commit a4a753b9e2
2 changed files with 40 additions and 33 deletions
+13 -1
View File
@@ -7,7 +7,19 @@
>
> Ask Claude Code: "Please update CONTEXT.md with what we did today"
Last updated: 2026-10-01 (v0.285.0 — controller image retention, decision 56; R-751 nil-stack crash in the update clean-up)
Last updated: 2026-10-01 night (v0.286.1 — the box tells visitors apart, R-753 / decision 63; R-772; R-773)
> **2026-10-01 night — v0.286.0 (9202 only) → v0.286.1 (floored, golden 0.286.1 vouched).** Operator ruling `09` §3
> decision 63: extend the box's own gate; first the box tells visitors apart (R-753). `internal/infra`: `felhom-tunnel`
> network `172.16.253.0/29` (ip-range `.4/30`), cloudflared ALONE at `.2`, traefik at `.3`; traefik's websecure trusts
> forwarded headers from `172.16.253.2/32` only and passes `felhom-forwarded@file` (removes client-writable
> host/path/address headers, fixes `X-Forwarded-Port: 443`). `EnsureBaseStack` now RECONCILES a running traefik/cloudflared
> whose rendered files changed (recreate; refuses dropping a certificate resolver). `internal/web/clientaddr.go`: `clientIP`
> = the hop traefik saw (rightmost XFF, only when the peer is traefik), the tunnel hop → `CF-Connecting-IP`; `rateKey`
> IPv6 per /64. **Readers take the visitor from the RIGHT, never the leftmost** — catalog apps that read the leftmost have
> `<router>-xff` (19, catalog). R-772: `HealthProbeResult.NotChecked`, seen on the next tick (0.286.1). R-773: a removed
> app restored gets its sign-up lock back (`SetupGateByRestore`). Open: R-776..R-779 (felhom.eu register). The permanent
> family gate (R-780) was SPIKED and passed — not built; the operator decides.
> **2026-10-01 — v0.285.0.** Operator ruling `09` §3 decision 56 (R-745): `stacks/controller_image_retention.go` keeps
> the running controller image + the previous (swap record `update-state.json` via `UpdateState.RecordedPrevious`; version
+27 -32
View File
@@ -1,40 +1,35 @@
# REPORT — v0.285.0: a box keeps two controller versions (decision 56); the update clean-up's crash (R-751) — 2026-10-01
# REPORT — v0.286.1: the box tells visitors apart (R-753, decision 63); R-772; R-773 — 2026-10-01 (night)
Full session report: `felhom.eu/REPORT-rulings-2026-10-01.md`. Evidence: `felhom.eu/documentation/audits/rulings-2026-10-01/B/`.
Architecture read: `09` §3 decisions 30, 45, 52, 53, 56; `03-host-agent.md` (the controller swap); agent
`internal/localapi/controllerswap.go`.
Full session report: `felhom.eu/REPORT-visitors-2026-10-01.md`. Evidence: `felhom.eu/documentation/audits/visitors-2026-10-01/`
(A = visitors apart, D = R-772/R-773, E = rollout). Architecture read: `01-topology-and-trust.md` §5/§7, `09` §3 decisions
45–49, 57–63.
## What changed
## Baselines
controller `c1b123c64955` (v0.285.0) → `a28155f` (v0.286.1). Agent unchanged (0.138.0; MinAgent 0.131.0).
- `internal/stacks/controller_image_retention.go` — `RetainControllerImages(ControllerImageRecord)`: keeps every image a
container uses, the running version's tag, the previous one (the swap record, `selfupdate.UpdateState.RecordedPrevious`;
version order only when the record names none present), every version above the running one and every non-version tag;
deletes older and untagged controller images by exact name/ID; nothing while the controller swaps itself. Called 3
minutes after every start (`cmd/controller/main.go`).
- `internal/stacks/image_retention.go` — R-751: `RetainImagesAfterUpdate` returns when the app is gone after its rescan
(it dereferenced a nil stack in a goroutine; a panic there ends the process).
- `internal/stacks/r650_main_test.go` — both retention seams are no-ops in the package's tests.
## Measured before building
The agent's swap rolls back to what `/etc/felhom-controller-image` named when the swap began — the running image. The
controller never hands it a previous image (`SwapController(ctx, target)`). So the self-update's own roll-back needs only
the running image; "the previous" is kept for a roll-back by hand, as ruled.
## Commits
`1e8d045` R-753 · `b53721d` R-772 · `1e216d3` R-773 · `d416742` v0.286.0 CHANGELOG/README · `79ef715` R-772 order fix
(found live) · `a28155f` v0.286.1 CHANGELOG.
## Tests and red-proofs
`TestControllerRetention_*` (6), `TestRecordedPrevious`, `TestRetainImagesAfterUpdate_AppGoneDoesNotPanic`. Each guard was
broken on purpose and its test failed with the wrong value visible: the previous dropped from the keep switch (0.284.2
deleted), the record ignored (0.283.1 deleted), the swap check removed (deleted while swapping), the in-use check removed
(an image a container uses deleted), the success check removed (a failed swap's image named as previous), the nil-stack
check removed (panic). Full suite `go build/vet/test ./...` green; `controller_gates.py --fast` OK.
Full suite green (`go build/vet/test ./...`, rc 0) before each build; controller gates green (go-parity after the three
new keys were registered). Red-proofs, each seen FAILING on the mutant: RP-A1 (leftmost hop), RP-A2 (the tunnel hop as
the key), RP-A3 (no reconcile of a running traefik), RP-D1 (no-container record healthy), RP-D1b (the 0.286.0 order),
RP-D2 (no lock on a restore) — files under `audits/visitors-2026-10-01/{A,D}/RP-*`.
## Live
- **9202 (0.286.0, then 0.286.1, hand-set):** simulated tunnel — a stranger's 5 wrong dashboard logins with a rotating
forged leftmost address locked only him; the household from another address in at once; LAN/impostor forgeries
counted as themselves. R-772: a stopped probe container read `healthy false, not_checked true` 14 s later (0.286.1).
R-773: Karakeep removed + restored → `opened_by: restore`, stranger `/signup` 403 and `users.create` 403, data back.
- **Both demo boxes (floor 0.286.1, min_agent 0.131.0):** each made `felhom-tunnel`, wrote the clean-up file, recreated
traefik and moved cloudflared to `.2` by itself (~5 s). demo-hp's REAL tunnel: an app sees
`6.6.6.6,37.191.56.193, 172.16.253.2`; `X-Forwarded-Host` forged → gone; the dashboard counter keyed on
`37.191.56.193`, locked after 5. Every demo-hp app answers through the tunnel. **ep0's one request was refused by
Cloudflare's edge (403) before the box** — the second outside address is unmeasured on the real tunnel (R-779).
- **Golden 0.286.1** baked, round-tripped, vouched (golden 0.286.1 / agent 0.138.0 / min_agent 0.131.0); the hub then
refused golden 0.285.0 (R-120). `felhom.eu/documentation/tests/golden-0.286.1-2026-10-01/`.
| box | controller images before → after | Docker images (`system df`) | how |
|---|---|---|---|
| 9202 | 5 → 2 | 3.17 → 3.10 GB | bootstrap deploy (self-update is off there); previous by version order |
| demo-hp 9201 | 84 → 2 | 15.2 → 11.65 GB | floor 0.285.0; previous from the swap record |
| N100 9201 | 76 → 2 | 6.07 → 1.11 GB | floor 0.285.0; previous from the swap record |
Each kept 0.285.0 (running) and 0.284.2 (previous). Every app container stayed up. Golden 0.285.0 baked and vouched.
## NOT validated live
- The app page's "not checked" line (render test only); R-773 on a block-only app (opengist/wishlist — same code path).
- A box rolling back below 0.286 with the new traefik (R-778, reasoned).