From 3302a33cf5d4484fe50bcfb5fb91f54c6eee2509 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Thu, 15 Oct 2026 08:21:08 +0200 Subject: [PATCH] =?UTF-8?q?09=20=C2=A73=20decisions=20247-249=20(operator?= =?UTF-8?q?=202026-10-15):=20bake=20first=20(supersedes=20decision=207);?= =?UTF-8?q?=20D1=3DA=20Plex=20own=20token=20at=20use;=20D2=3DA=20HSTS=20on?= =?UTF-8?q?e=20day.=20Rule=20file,=20CLAUDE.md,=20manual-build=20+=20publi?= =?UTF-8?q?sh-train=20runbooks=20follow;=20R-1041/R-1046=20closed=20(decid?= =?UTF-8?q?ed);=20R-1050=20dated=20HSTS=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS --- .claude/rules/unprompted-work.md | 6 ++++-- CLAUDE.md | 7 ++++--- STATUS.md | 4 +--- .../architecture/09-update-architecture.md | 18 ++++++++++++++++-- documentation/backlog/CLOSED-ITEMS.md | 7 +++++++ documentation/backlog/OPEN-ITEMS.md | 8 ++++---- documentation/runbooks/RUNBOOK-manual-build.md | 9 +++++++-- documentation/runbooks/publish-train-rules.md | 3 +++ 8 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.claude/rules/unprompted-work.md b/.claude/rules/unprompted-work.md index 9a82377d..bceccf44 100644 --- a/.claude/rules/unprompted-work.md +++ b/.claude/rules/unprompted-work.md @@ -44,8 +44,10 @@ it **first** in the morning note. A decision you cannot write in that shape is o Throwaway apps only; the standing apps and `bentopdf` stay. 5. **Evidence off the machine at the end of each phase**, before any revert (R-320). 6. **One release per repo per session**, with a CHANGELOG entry (controller: with its `MinAgent` - line), REPORT overwritten, floor raised to deliver it. **No golden unless a drill or fresh install - needs one** (the waiver, R-468). **No `--no-verify`.** + line) and REPORT overwritten. **A controller release is RELEASED only when its golden is baked: + bake → the operator vouches → the operator raises the floor** (operator ruling 2026-10-15, `09` §3 + decision 247, which supersedes decision 7). Never ask for a floor for a version with no golden; CC + never vouches and never moves a floor. **No `--no-verify`.** 7. **An enumerated gap becomes a row in the same session — or, if it is small, is fixed in it** (the size rule). Prose is not a record. 8. **Hungarian text is searched with ASCII fragments**, with a positive and a negative control. diff --git a/CLAUDE.md b/CLAUDE.md index a9ad9d92..d6894fad 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -138,9 +138,10 @@ something, not only sessions that touch `documentation/` — which is why it is - **Root `STATUS.md`** — at the end of every session in which something shipped, broke or was decided. It is a **view** of `OPEN-ITEMS.md`; nothing may exist only there. One screen, written for the operator in plain language, and deliberately **not** `CONTEXT.md`. -- **The golden, on its cadence** (operator ruling 2026-09-13): **weekly, and before ANY drill or - fresh install**, bake + vouch + raise the floor per `documentation/runbooks/RUNBOOK-manual-build.md` - §4.1. Not per release. Between bakes the dated waiver (§4.2, `documentation/tests/golden-waiver.yml`, +- **The golden — bake first** (operator ruling 2026-10-15, `09` §3 decision 247, superseding the 2026-09-13 + weekly cadence for releases): **a controller release is RELEASED only when its golden is baked** — bake → the + operator vouches → the operator raises the floor, per `documentation/runbooks/RUNBOOK-manual-build.md` §4.1. Never + ask for a floor for a version with no golden. Also before ANY drill or fresh install. Between bakes the dated waiver (§4.2, `documentation/tests/golden-waiver.yml`, ≤ 14 days) keeps `golden_currency_gate.py` advisory; **when it expires the gate is red and stays red until someone bakes or renews — that is the mechanism, so do not `--no-verify` past it.** A nightly or drill session that starts on a fresh install checks the golden FIRST. diff --git a/STATUS.md b/STATUS.md index 330d0d10..5518d710 100644 --- a/STATUS.md +++ b/STATUS.md @@ -38,12 +38,10 @@ | D5 | R-993 (R-991) | Approve the minimum machine (`audits/sizing-2026-10-13/SIZING.md` §5: x86-64, 4 cores, 8 GB, 256 GB SSD)? | **Approve** | Change it: one edit | no hardware page; a buyer with a Raspberry Pi can still download the installer | the hardware page | | D6 | R-992 | The scratch box cannot read the private drill catalog: give it a read-only login, or make that copy readable? | **A read-only login** | Readable copy: the drill catalog becomes public | every new-app test needs hand-staging | new-app tests | | D7 | R-990, R-989 | Rule yes / no / later on the forum ideas (`audits/forum-research-2026-10-12/FELHOM.md` §5) and the selfh.st ideas (`audits/selfhst-survey-2026-10-12/FELHOM.md` §6) | — | — | nothing changes; the research ages | — | -| D9 | R-1046 | How may the box talk to Plex: with the household's Plex account token, or by trusting the box's own address in Plex? | **Trust the box's address** — no account token is copied | The token opens the household's whole Plex account | Plex stays manual: no new drive appears in Plex by itself | the media build | -| D10 | R-1041 | How long must browsers remember „https only”? | **Keep one day now; one year after a month with no trouble** | One year now: a mistake locks a name out for a year | it stays one day; safe, a little weaker | — | | D8 | R-206 | Install DooPlex's Docker settings from git (restarts Docker), and prune the build cache weekly: yes or no? | **Yes, in a quiet window** | — | a rebuild of DooPlex still has the settings in git | — | ## Waiting on you — do -- **Vouch controller 0.312.0 and raise the floor** (hub). It makes every box https only, Tester 2 included. If not: the other boxes keep answering plain http (R-1033, P1). +- **https only for every box (controller 0.312.0):** CC bakes its golden first (your rule of 2026-10-15: bake → vouch → floor); then you vouch the golden and raise the floor. CC tells you when the golden is ready. - **Remove the test server „plex-k6” from your Plex account** (plex.tv → Settings → Authorized Devices). The claim test passed and the server is gone from Tester 1. If not: a dead server stays listed in your Plex apps. - System poster needs a refresh: every public name answers only over https (http → 301). - **Clear demo-hp's own floor** (hub → Demo HP → floor: empty → Save floor). It still reads 0.311.0. If not: the next global raise passes demo-hp by. diff --git a/documentation/architecture/09-update-architecture.md b/documentation/architecture/09-update-architecture.md index ee88f021..b422aad4 100644 --- a/documentation/architecture/09-update-architecture.md +++ b/documentation/architecture/09-update-architecture.md @@ -168,8 +168,8 @@ These are rulings, not proposals. Anything specced against a different assumptio ### 2026-09-13 (afternoon) — the floor carries a release without a golden, and every backup counts -7. **A floor carries a release past the vouched golden when the release's MinAgent is declared with - it** (R-472; hub v0.112.0). Inside the golden the manifest's MinAgent governs, as before. Above it, +7. ~~**A floor carries a release past the vouched golden when the release's MinAgent is declared with + it**~~ **SUPERSEDED by decision 247 (operator, 2026-10-15): bake → vouch → floor; never a floor for a version with no golden.** The hub mechanism below still exists; the PROCESS no longer uses it. (R-472; hub v0.112.0.) Inside the golden the manifest's MinAgent governs, as before. Above it, the MinAgent the operator declares with the floor — read from the release's CHANGELOG header, which `minagent_header_gate.py` now guarantees — goes into the same per-box agent comparison. An undeclared floor above the golden is still HELD, and both floor forms refuse to save one. **Why:** a @@ -915,6 +915,20 @@ its length, and both fixes cost something the household would notice — operato 127. **The agent's three by-design abilities (`03` §3.1) stay for now**; revisited before the first paying customer. *Operator ruling 2026-10-05.* (R-861) +### 2026-10-15 (07:48–08:20) — bake first; Plex through its own key; HSTS one day (recorded before the media build) + +247. **Bake first.** A controller version is RELEASED only when its golden is baked. The order is always: bake the golden + → the operator vouches it in the Day-0 manifest → the operator raises the floor. No report, STATUS line or brief may + ask the operator to raise a floor for a version with no golden. **Supersedes decision 7 (2026-09-13).** Why: a floor + above the golden left a fresh install on an older controller than the fleet, and a release with no golden could not + be vouched. *Operator ruling 2026-10-15 07:48.* +248. **D1 = A (R-1046): the box drives Plex with the server's own `PlexOnlineToken`**, read from the server's + `Preferences.xml` at the moment of use: never stored by the controller, never logged, never in evidence, never sent + anywhere but `http://:32400`. Plex's „allowed without auth" list is NOT used. *Operator ruling + 2026-10-15.* +249. **D2 = A (R-1041): HSTS stays one day now**; raised to one year after a month with no https trouble; never preload, + never `includeSubDomains`. *Operator ruling 2026-10-15.* + ### 2026-10-14 (22:13) — https-only is P1; drive events = option A (recorded before the night run) 244. **R-1033 is P1.** Every public name of every box must answer only over https; plain `http://` (login pages diff --git a/documentation/backlog/CLOSED-ITEMS.md b/documentation/backlog/CLOSED-ITEMS.md index a13c02ab..6edc9016 100644 --- a/documentation/backlog/CLOSED-ITEMS.md +++ b/documentation/backlog/CLOSED-ITEMS.md @@ -26,6 +26,13 @@ --- +## 2026-10-15 — operator rulings D1 and D2 (media build brief) + +| Row | What | Closed | Evidence | +|---|---|---|---| +| **R-1041** | **Decide how long browsers must remember „https only” (HSTS), and whether to preload** (P3) | CLOSED 2026-10-15 — DECIDED: `09` §3 decision 249 (D2 = A): one day now, one year after a month with no trouble (R-1050), never preload, never includeSubDomains | full text `git show 3e5b9222:documentation/backlog/OPEN-ITEMS.md` | +| **R-1046** | **The box can drive Plex only with the household's Plex account token** (P3) | CLOSED 2026-10-15 — DECIDED: `09` §3 decision 248 (D1 = A): the server's own `PlexOnlineToken`, read from `Preferences.xml` at use, never stored, logged or sent anywhere but the Plex container; „allowed without auth” is NOT used | full text `git show 3e5b9222:documentation/backlog/OPEN-ITEMS.md` | + ## 2026-10-14 — the media folders meet in the live catalog | Row | What | Closed | Evidence | diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 6819fba3..3e82e474 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -124,7 +124,7 @@ stopping line that lies. | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | -## Apps & catalog — 19 rows (P2 1, P3 13, P4 5) +## Apps & catalog — 18 rows (P2 1, P3 12, P4 5) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -144,7 +144,6 @@ stopping line that lies. | **R-905** | Apps & catalog | P4 | **wger's collected style files (283 MB) go into every wger backup, though the app rebuilds them at every start.** Since R-762's fix (`DJANGO_DEBUG=False` + `collectstatic`, catalog `cf1ed43`) the static files sit in a named volume that the backup legs copy like data (measured 283 MB, `audits/design-build-2026-10-06/`); `collectstatic` regenerates them, so they are rebuildable bytes in every Tier-1 unit, Tier-2 copy and off-site snapshot. wger is `lifecycle: hidden` and no box runs it (hub read 2026-10-08). Options to weigh: a non-backed-up volume class for regenerable data, or an anonymous volume. Found by the R-762 helpers 2026-10-08. | **OPEN — needs a design before wger is shown** | — | Design the „regenerable volume" exclusion (catalog + controller backup legs) | CC | | **R-995** | Apps & catalog | P4 | **Removing an app through the product can leave unnamed Docker volumes and an empty import folder behind.** MEASURED 2026-10-13 on demo-felhom after 61 removals (`documentation/audits/sizing-2026-10-13/METHOD.md` „Teardown”, `evidence/leftover-volumes-after-bench.txt`): 8 unnamed volumes (4–72 KB; immich 1, romm 2, privatebin 3, onlyoffice 2 — volumes the images declare themselves) and `userdata/import/{paperless,calibre,grimmory}` (empty, created by the deploy). The removal answered 200 and its own leftover check was clean, because it looks only at labelled volumes. No data risk; disk and tidiness only. Removed by name on the bench. | **READY** — filed 2026-10-13 | — | Make the removal account for the containers' unnamed volumes (and an empty import folder it created), with a test that deploys an image that declares a VOLUME | CC | | **R-1043** | Apps & catalog | P4 | **Sonarr, Radarr and Prowlarr build their login redirect as an absolute `http://` URL.** They trust forwarded headers only from their „Trusted networks” (the household set `192.168.0.0/24`; traefik is `172.18.x`), so they ignore `X-Forwarded-Proto: https` and answer `Location: http:///login` to an https visitor. Since controller v0.312.0 that hop is sent back to https by traefik (a GET, no password), so it is cosmetic; a template could seed traefik's network as trusted. (`audits/https-only-2026-10-14/``1A-3-4.txt`) | **OPEN** — filed 2026-10-14 | — | Seed the docker network as a trusted proxy in the *arr templates (with the R-1030 seed), measure the redirect | CC | -| **R-1046** | Apps & catalog | P3 | **The box can drive Plex only with the household's Plex account token.** MEASURED 2026-10-14 night (`audits/media-drive-events-2026-10-14/``K2-5-plex-access.txt`): a claimed Plex answers 401 to every call without a token, even from localhost. The only token the box holds is `PlexOnlineToken` in the server's own `Preferences.xml` — the household's plex.tv account token, which also opens their account. The other way is Plex's „List of IP addresses allowed without auth” (`allowedNetworks`) set to the controller's address — no account token, but every container on that network is then trusted too. Needed for R-1024 and R-1037. | **WAITING-ON-OPERATOR** — filed 2026-10-14 night | the operator's pick of the two ways | Decide; then wire Plex with it | operator | | **R-1047** | Apps & catalog | P3 | **Installed Sonarr and Radarr from before app-catalog `bc54c01` lose their library path on their next start.** MEASURED 2026-10-14 night on the bench (`audits/media-drive-events-2026-10-14/``K7-old-install.txt`): the old template mounted the library at `/media/tv` / `/media/movies`; after the new template's one same-path bind the root folder is missing (health „Missing root folder”), the records stay, nothing is deleted. Repair through the apps' own API, no file moved (same inode): add the same-path root folder, `PUT /series/editor` (`/movie/editor`) `rootFolderPath` + `moveFiles:false`, rescan, delete the old root folder. Left over: Radarr's collections keep the old root folder (a separate setting), and the health page shows the old warning until its next check. | **READY** — filed 2026-10-14 night | — | A one-time migration step in the controller for Sonarr/Radarr installed before `bc54c01` (the K7 calls + Radarr's collection root); find which boxes have one | CC | | **R-1048** | Apps & catalog | P3 | **Prowlarr into the live catalog (full new-app checklist), linked by the box.** Today it is in the drill catalog only (test, 2026-10-14): config in a named volume, no media folders; the box linked it to Sonarr and Radarr by script (Prowlarr → Settings → Apps, full sync — `audits/media-all-drives-2026-10-14/tools/wire.py`). The live catalog needs the full new-app checklist (`app-catalog-felhom.eu/REUSE.md`), Hungarian copy, the apps page, and the link made by the box at install. | **READY** — filed 2026-10-14 night | — | Add it with the checklist; the link with R-1024 | CC | @@ -207,7 +206,7 @@ stopping line that lies. | **R-331** | Storage & devices | P4 | **Disk health Phase 3 — growth-rate detection, and retiring the static 64.** The v0.215.0 count backstop (64 unreadable sectors → Hiba) is **a judgement from ONE drive**: the observed benign excursion peaked at 16 and cleared inside an hour, and the terminal run passed 64 at 13 Aug 11:28 and never came back. It is deliberately a backstop BEHIND the sustain rule, not the primary signal, but it is still a magic number tuned on a single sample and it will be wrong for some drive. With Phase 2's history the box can ask the question that actually matters — *is this count climbing, and how fast* — which distinguishes a drive with eight stable aging sectors from one adding forty a day, something no static threshold can do. Revisit 64 when that exists | **READY (M) — NEW 2026-08-14** | R-330 | Growth-rate rule over persisted samples; re-derive or delete the static 64 | CC | | **R-1044** | Storage & devices | P2 | **„Safe removal” of a media drive is not safe while the media apps run.** MEASURED 2026-10-14 night on Tester 1 (`audits/media-drive-events-2026-10-14/``K3-3-eject.txt`): `POST /api/storage/eject` for Media C made the agent detach the guest's bind (the guest then shows an EMPTY folder) and keep the raw host mount, and the controller deregistered the drive — but the running media containers still HELD the filesystem and read its files after the „safe” removal. So pulling the disk then is a pull of a mounted, in-use filesystem. Also: after re-attaching, the agent mounted it again but the drive stayed unregistered until „Meglévő meghajtó csatolása” (`register-mounted`) — a household step after every safe removal. Restarting an app after the eject failed (K3-4: the bind source is gone → 500). | **OPEN** — filed 2026-10-14 night | R-1023 | Safe removal = pause that drive's torrents → recreate the media apps without its bind → unmount; keep the drive registered as „removed safely” so its return needs no household step; a test on the order of the three | CC | -## Security & access — 21 rows (P1 2, P2 4, P3 10, P4 5) +## Security & access — 21 rows (P1 2, P2 4, P3 9, P4 6) | ID | Category | Sev | What | State | Blocked on | Next action | Owner | |---|---|---|---|---|---|---|---| @@ -218,7 +217,6 @@ stopping line that lies. | **R-976** | Security & access | P3 | **A family member's session can be copied out of a family-gated app, and a dashboard password reset does not end family or gate sessions; so a stolen session keeps working for up to 30 days.** Fix: give each app its own derived token instead of the session itself, and end every family and gate session on a reset. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN | — | Fix with tests in a controller release | CC | | **R-970** | Security & access | P2 | **Whoever holds a box's own hub API key — the hub, or anyone with a copy of the hub database — can read the box's settings file with every secret in it and replace that file through the internet; the box does not check that such a change is signed.** This contradicts `01` §4 (the box exposes no control endpoint) and §10 (a hub compromise alone cannot forge commands). Nothing in the product calls this route any more. Fix: remove the route, or require an operator-signed op for it. Security review 2026-10-12 (`audits/security-review-2026-10-12/`); detail held off-git for the operator (`/mnt/5_hdd/felhom.eu/evidence/security-review-2026-10-12-PRIVATE/`). | OPEN — confirmed by a second reader | — | Remove the route with a test that it is gone; release the controller | CC | | **R-954** | Security & access | P2 | **We cannot tell whether anyone used the old hub operator key (R-950) or the open hub read routes (R-951) before 2026-10-12; if someone did, they could have read every box's break-glass root credential and every customer's box report.** The exposure began in February 2026; the logs that could show it cover only 2026-10-12 from about 09:54 and one day in June, so about 238 days are not visible. Fix (operator decision): change the break-glass root password of every box (tester-1 holds a real person's data), or accept the risk; either way, keep ingress access logs long enough to answer this next time. | WAITING-ON-OPERATOR — filed 2026-10-12 by the security review; the log search was not completed in-session | operator decision | Decide: rotate every box's break-glass root password now, or accept | operator | -| **R-1041** | Security & access | P3 | **Decide how long browsers must remember „https only” (HSTS), and whether to preload.** Since controller v0.312.0 every box sends `Strict-Transport-Security: max-age=86400` (one day), no `includeSubDomains`, no preload — the night brief's limit (R-1033, decision 244). A longer value protects the first visit after a day away; preload makes even the first visit ever safe but is hard to undo (a browser list) and binds every subdomain of the customer's domain. | **WAITING-ON-OPERATOR** — filed 2026-10-14 | the operator | Choose: keep one day; or e.g. 180 days without preload; or preload (all subdomains https forever) | operator | | **R-1042** | Security & access | P3 | **The boxes' Cloudflare keys cannot change zone settings, so Cloudflare's own „Always Use HTTPS” cannot be switched on.** Measured 2026-10-14 on all three zones (`audits/https-only-2026-10-14/``1A-1-zones-and-keys.txt`): each `cf_api_token` is valid and sees only its own zone, but GET/PATCH `settings/always_use_https`, `ssl`, `security_header` answer 403; the redirect and transform rule phases too. The https-only fix therefore lives on the box (decision 246). A second layer at Cloudflare needs „Zone Settings: Edit” (and, for headers, „Transform Rules”) added to each customer's key — the hub's zone-reach check stays. | **WAITING-ON-OPERATOR** — filed 2026-10-14 | a key permission only the operator can grant | Optional: widen each customer key, then switch „Always Use HTTPS” on per zone | operator | | **R-955** | Security & access | P4 | **The cross-box check of the hub API is done in source and tests but not fully proven: if a later check masks a missing id check, a box's key could read or change another box's hub record without any test noticing.** Read 2026-10-12: every box-facing `/api/v1` route takes the box from its own key or refuses a different id; 14 routes already had a refusal test, 5 got one (`crossbox_refusal_test.go`). Left: red-prove the 4 new tests that were not red-proved (as the restore-directive one was), and the live read-only check with one demo box's key against the other demo box's id. | OPEN — filed 2026-10-12 by the security review | — | Red-prove the 4 tests; run the live GET check on the two demo boxes | CC | | **R-924** | Security & access | P2 | **The operator signing keys exist only on DooPlex, with no passphrase and in no backup — a loss of DooPlex means no box ever accepts a signed agent update, config bundle or OS step again without an on-site re-enrollment.** MEASURED 2026-10-09: `/mnt/5_hdd/felhom.eu/felhom-op-operational`, `felhom-rec-recovery`, `felhom_op_ed25519` (0600 `kisfenyo`); `ssh-keygen -y -P ''` opens all three (no passphrase); `/mnt/5_hdd/felhom.eu` is in no backup set, the off-site job does not carry them. `04` §1 says the operational key is "passphrase-protected" and the recovery key "kept offline" — today neither holds (custody ruling 1 of 2026-09-16 put both on DooPlex for this phase; `04` §3.1). Both keys share one fate, so the cold key cannot do its one job (re-pin after losing the operational key). `runbooks/total-loss-of-dooplex.md` S6. | **NARROWED 2026-10-09 — operator ruling: option A** (print both AND copy them). The three key files and their public halves ride the nightly encrypted DooPlex→ep0 copy (`scripts/dooplex-offsite/`, a missing key refuses the push); the Sunday restore test proves each private key still derives its own public half (never printed). **LEFT:** the operator prints S6a/S6b (`runbooks/break-glass-sheet.md`); the keys still have no passphrase (custody ruling 1 of 2026-09-16 stands until the first sale). | operator: print | Operator: print the two keys; then close | operator | @@ -232,6 +230,7 @@ stopping line that lies. | **R-904** | Security & access | P4 | **Cloudflare can read every household's app traffic; replacing it with our own relay is a later item.** Facts (reviewer discussion 2026-10-08; `01`): app traffic and the dashboard reach the box through the Cloudflare Tunnel (`01` §5 trust table, rows end-user ↔ apps and customer ↔ controller UI; §7), and the tunnel's public end is Cloudflare's edge, where TLS ends — so Cloudflare can technically read that traffic (the FAQ says so since 2026-10-08, R-900; „TLS ends at the edge" is not written in `01` — add it there). What Cloudflare gives today, free: inbound reach with no router setup, the CGNAT answer (`01` §4, §7); certificates (`01` §7, the free tier covers one level below a zone); the geo-WAF the hub enforces (`01` §5 last row, §7); flood protection (not in `01`). The alternative named: our own EU relay over WireGuard with TLS passthrough by SNI, certificates on the box, the geo-block on the relay. Its costs: one more machine the operator keeps up, and a single point of reach for every box; weaker flood protection; about a week of work after a spike. Operator ruling 2026-10-08 09:07 (`09` §3 decision 184): a later item. | **DEFERRED** — **2026-10-10: the asked sentence is in `01` §7** („TLS ends at Cloudflare's edge …”); the spike stays deferred. **DEFERRED — after the first customers (operator ruling 2026-10-08 09:07)** | — | A spike after the first customers (the relay's reach, cost and flood behaviour, measured) | operator | | **R-1040** | Security & access | P4 | **Until the household makes Sonarr's or Radarr's first admin, any container on the box can read the app's API key.** Measured on the bench 2026-10-14 with the R-1030 seed in place: `AuthenticationRequired=Enabled`, `AuthenticationMethod=None` — `/initialize.json` from another container answers 200 with `apiKey`. Outsiders are blocked by the box's setup gate in that window; a compromised other app is not. | **OPEN** — filed 2026-10-14 | — | Seed the API key at install (`SONARR__AUTH__APIKEY`, measured to work) so the box knows it, and close the window by telling the household in the first steps; or seed a Forms user | CC | | **R-1045** | Security & access | P2 | **An unclaimed Plex answers everything without a token to a private address — and through the tunnel every request comes from one.** MEASURED 2026-10-14 night on a throwaway `plex-k6` on Tester 1 (`audits/media-drive-events-2026-10-14/``K6-1-unclaimed-plex.txt`): with no claim, `/library/sections`, `/:/prefs` and `/myplex/account` answer 200 with NO token from the Docker network. Through cloudflared, Plex sees cloudflared's private address, so a publicly routed unclaimed Plex would be open to anyone — server settings and every library. R-1028's late code leaves exactly such a server. NOT measured over the public path (the throwaway had no route). Harm if true: a household's media and Plex settings open to the internet. | **OPEN** — filed 2026-10-14 night | — | Measure on a throwaway with a public route on a Tier-0 box (P1 if it holds); the build: no public route until `PlexOnlineToken` is set | CC | +| **R-1050** | Security & access | P4 | **Raise HSTS to one year on or after 2026-11-15, if no https trouble was reported.** `09` §3 decision 249 (D2 = A, operator 2026-10-15): HSTS stays `max-age=86400` now; after a month with no https trouble it becomes one year (`31536000`); never preload, never `includeSubDomains`. The value is `HSTSMaxAgeSeconds` in `felhom-controller/controller/internal/infra/infra.go`, pinned by `TestRenderForwardedHeaders_HTTPSOnly`. „Trouble” = any report of a name that will not open, or a box answering http after the floor. | **WATCHING** — filed 2026-10-15 | the date | On or after 2026-11-15: read the register and the operator's mail for https trouble since 2026-10-15; none → a controller release with one year (test updated), bake → vouch → floor | CC | ## Box system & updates — 11 rows (P2 1, P3 7, P4 3) @@ -344,6 +343,7 @@ stopping line that lies. |---|---|---| | R-887 | 2026-10-19 | no CI job lost in the 7 days after the S21 DooPlex change (act-runner fetch timeout + crawler rate limit); detail in the R-887 row | | R-435 | 2026-10-20 | Tester 1's weekly off-site clean-up ran inside a hub window with no drop alarm (box log + mailbox); detail in the R-435 row | +| R-1050 | 2026-11-15 | HSTS one year if no https trouble since 2026-10-15; detail in the R-1050 row | | R-1035 | 2026-11-11 | „felhő tárhely” + „saját felhő tárhely” impressions and average position, and the new page's own impressions/clicks/position, 4 weeks after publishing; operator supplies the read; detail in the R-1035 row | | R-528 | 2026-11-12 | no failed oom_check / unexplained app_oom since 2026-10-12; detail in the R-528 row | | R-1035 | 2026-12-09 | the same three numbers 8 weeks after publishing, compared with the 4-week read; a null at 4 weeks is not a verdict, both reads are needed; detail in the R-1035 row | diff --git a/documentation/runbooks/RUNBOOK-manual-build.md b/documentation/runbooks/RUNBOOK-manual-build.md index f78f13da..79d2113d 100644 --- a/documentation/runbooks/RUNBOOK-manual-build.md +++ b/documentation/runbooks/RUNBOOK-manual-build.md @@ -98,8 +98,9 @@ the manifest vouches the target FIRST; any MinAgent must be satisfied fleet-wide boxes below it automatically, and flags them); **save the floor LAST** — the DB row acts immediately on every box below it, on their next report. -**Raise the floor to a release with no golden (hub v0.112.0+, R-472).** Between bakes this is the -normal route — do not hand-deploy. +**Raise the floor to a release with no golden (hub v0.112.0+, R-472).** **⚠ NOT USED SINCE 2026-10-15 (`09` §3 +decision 247): bake → vouch → floor; never ask for a floor for a version with no golden.** The mechanism is kept +here because the hub still has it. 1. Build and push the controller image (above). Do not install it on any box. 2. Read the MinAgent from the release's header: `grep -m1 -A3 '^## v' CHANGELOG.md` shows @@ -251,6 +252,10 @@ after reading the dry-run; nothing schedules it. The August 2026 `--keep 7` run ### 4.2 Cadence — goldens are WEEKLY and before any install, not per release (operator ruling 2026-09-13) +> **⚠ SUPERSEDED for releases on 2026-10-15 (`09` §3 decision 247):** every controller release gets its golden before +> its floor — bake → the operator vouches → the operator raises the floor. The weekly bake and the before-any-install +> bake below still hold for weeks with no release; the waiver keeps the gate's own mechanism. + **What was measured before the ruling:** 25 goldens in 26 days in August, almost one per release, because `golden_currency_gate.py` trips on every release by design and the only honest ways past it were a bake or a declared `--no-verify` (thirteen of those by 2026-09-01 — R-404/R-417). **The diff --git a/documentation/runbooks/publish-train-rules.md b/documentation/runbooks/publish-train-rules.md index fb604884..802b5ecb 100644 --- a/documentation/runbooks/publish-train-rules.md +++ b/documentation/runbooks/publish-train-rules.md @@ -10,6 +10,9 @@ Publish and vouch the artifacts in the Day-0 manifest **before** any floor movem points at an unvouched (or unpublished) version bricks self-updates: boxes are told to move to a version they cannot verify. (GL-1 supply-chain arc; see the go-live package records.) +**⚠ Since 2026-10-15 (`09` §3 decision 247) the process never uses a floor above the golden: bake → vouch → floor.** +The mechanism below stays in the hub and is described for reading the hub, not as a route. + **Since hub v0.112.0 (operator ruling 2026-09-13, R-472): the manifest leads the floor inside the golden; above it, the release's own declared MinAgent does.** A controller image is pulled by tag from the registry, so a floor above the golden does not need a golden to be delivered — it needs to know