09 §3 decision 250 (operator 2026-10-15 11:26/11:29): a release needs no golden, CC raises the floor after the three test boxes, the golden is weekly; 247 reversed, 7 stands. golden_currency_gate judges the golden's AGE (>7 days); Sunday golden step; rule files, CLAUDE.md, runbooks, build skill follow; R-1045 closed (floor 0.313.0); R-1051 floor route, R-1052 new-box path, R-1053 rc tags never moved
gates / gates (push) Successful in 6m37s
gates / gates (push) Successful in 6m37s
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -44,10 +44,12 @@ 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) 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`.**
|
||||
line) and REPORT overwritten. **A controller release needs no golden** (operator ruling 2026-10-15
|
||||
11:26, `09` §3 decision 250; decision 7 stands): push the image only when it is ready for EVERY box (the
|
||||
household's update button offers the highest tag); raise the floor, MinAgent declared, only after it ran
|
||||
on Tester 1, demo-hp and demo-felhom through the normal update path and the box walk's checks are green
|
||||
on all three — not at night unless the night brief says so. **Agent releases still stop for the
|
||||
operator's vouch.** The golden is baked weekly and vouched by the operator; CC never vouches. **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.
|
||||
|
||||
@@ -138,10 +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 — 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`,
|
||||
- **The golden, weekly** (operator ruling 2026-10-15 11:26, `09` §3 decision 250; decision 7 stands): **a controller
|
||||
release needs no golden** — the floor carries it, MinAgent declared, after the three test boxes (`09` §3 decision 250
|
||||
item 2). The golden is baked **every Sunday night** when the floor is above it (`runbooks/nightly-rotation.md`) and
|
||||
before ANY drill or fresh install, per `documentation/runbooks/RUNBOOK-manual-build.md` §4.1; the operator vouches it. 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.
|
||||
|
||||
@@ -16,6 +16,15 @@
|
||||
> and holds nothing of its own; this file does hold its own content, namely the standing rulings below.
|
||||
|
||||
|
||||
> **2026-10-15 (afternoon) — the release rule, `09` §3 decision 250 (operator 11:26/11:29):** a controller release needs
|
||||
> NO golden — push the image only when it is ready for every box (the household button offers the highest X.Y.Z tag;
|
||||
> `-rc.N` tags are skipped), raise the floor with the declared MinAgent after Tester 1, demo-hp and demo-felhom ran it
|
||||
> through the normal update path and the box walk is green; agent releases still wait for the operator's vouch; the golden
|
||||
> is baked every Sunday night when the floor is above it and vouched by the operator. Decision 247 („bake first", the
|
||||
> same morning) is reversed; decision 7 stands. `golden_currency_gate.py` convicts only a golden more than 7 days old
|
||||
> while a release is ahead. CC has no scoped route to the global floor (R-1051) — only the operator page with the
|
||||
> operator's password. A box on an `-rc` tag is never moved by the floor (R-1053).
|
||||
|
||||
> **Night 2026-10-14 — https only and the media drive events.** Operator rulings recorded as `09` §3 **244** (R-1033 is
|
||||
> P1) and **245** (drive events = option A: the box handles each event per app). **Decided by CC unattended — operator
|
||||
> may reverse:** `09` §3 **246** — https-only is enforced on the box (traefik `felhom-forwarded` chain: a
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Ready for the first real tester (Tester-2): yes. Tester 2 (a laptop) is off; nothing was sent to it.**
|
||||
|
||||
**Updated 2026-10-15.** Agent 0.156.1 + its bundle on all three boxes; controller 0.312.0 on demo-felhom, 0.313.0-rc.4 by hand on Tester 1 and demo-hp (Tier 0); goldens 0.312.0 and 0.313.0 BAKED, not vouched; floor 0.311.0; hub 0.150.0 (0.151.0 written, not deployed). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
**Updated 2026-10-15 (afternoon).** Agent 0.156.1 + its bundle on all three boxes; controller 0.313.0 is the floor (vouched golden 0.313.0) — demo-felhom and demo-hp run it, Tester 1 still runs the test build rc.4 (R-1053); hub 0.150.0 (0.151.0 written, not deployed). Open items: see `documentation/backlog/OPEN-ITEMS.md`. Older notes, day by day:
|
||||
`documentation/backlog/STATUS-HISTORY.md`. Reports: `REPORT-night-2026-10-14.md`, `REPORT-immich-rollout-2026-10-14.md`, `REPORT-seo-felho-tarhely-2026-10-14.md`, `REPORT-immich-repair-2026-10-14.md`, `REPORT-power-cut-fixes-2026-10-14.md`, `REPORT-night-power-2026-10-13.md`, `REPORT-poweroff.md`.
|
||||
|
||||
## What works
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
## What we work on
|
||||
- The power-cut findings still open (R-1004, R-1005, R-1010, R-1012, R-1013).
|
||||
- Media apps on every drive (R-1023): BUILT in controller 0.313.0 and proven on Tester 1 — every drive in every media app, a new drive made ready by the box, a pulled drive loses nothing, a safe removal lets go first and the drive comes back by itself. It reaches the other boxes with your vouch and floor.
|
||||
- Media apps on every drive (R-1023): live with the 0.313.0 floor — every drive in every media app, a new drive made ready by the box, a pulled drive loses nothing, a safe removal lets go first and the drive comes back by itself.
|
||||
|
||||
## Waiting on you — decide
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
| 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 golden 0.313.0, then raise the floor to 0.313.0** (it carries the https fix of 0.312.0 too). Hub → Configuration → Day-0 artifacts: golden **0.313.0**, agent **0.156.1**, min_agent **0.155.0**, Save. Then, a separate save: global floor **0.313.0**, min_agent **0.155.0**. If not: the other boxes keep plain http and one-drive media apps.
|
||||
- **Releases ride the floor now (your rule, 2026-10-15):** no golden per release; the golden is baked every Sunday and you vouch it then. **Decide (R-1051):** CC cannot raise the floor without your own hub password. Pick: a small hub route with its own key for CC (a hub release, my pick), or your word that CC may use your password for this one save. If you do nothing: you keep raising the floor yourself.
|
||||
- **Hub 0.151.0 (a mail when a drive comes back): may I deploy it?** Say „yes". If not: you keep getting only the „disconnected" mail.
|
||||
- **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).
|
||||
|
||||
@@ -128,6 +128,12 @@ reconciles only on change and reports which generation it has converged to).
|
||||
**Geo is *not* in the agent's desired state** — it's customer→hub→Cloudflare (§7); the agent never
|
||||
touches WAF.
|
||||
|
||||
**The floor and the golden after `09` §3 decision 250 (operator, 2026-10-15).** A controller release needs no golden:
|
||||
the floor carries it to every box (MinAgent declared), raised after the release ran on the three test boxes and the box
|
||||
walk is green; the golden is baked weekly and vouched by the operator; a new box installs the vouched golden and updates
|
||||
itself to the floor (R-1052: not yet proven end to end). Setting the global floor today needs the operator page with the
|
||||
operator's password — there is no scoped route for CC (R-1051).
|
||||
|
||||
**The controller floor and its agent requirement (hub v0.112.0, R-472).** The managed controller floor
|
||||
rides the report ACK. `store.ResolveManagedFloor` decides per box whether to serve it, from three
|
||||
inputs: the floor in force (per-customer override, else global), the vouched Day-0 manifest (golden
|
||||
|
||||
@@ -168,8 +168,9 @@ 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**~~ **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,
|
||||
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). *Superseded by decision 247 at 07:48 on 2026-10-15 and REINSTATED by decision 250 at
|
||||
11:26 the same day — it stands.* 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,9 +916,28 @@ 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 (11:26/11:29) — a release needs no golden; CC raises the floor; the golden is weekly (recorded before the work)
|
||||
|
||||
250. **A controller release needs no golden; CC raises the floor; the golden is baked weekly.** *Operator ruling 2026-10-15
|
||||
11:26/11:29.* Reverses decision 247 (07:48 the same day); decision 7 stands again.
|
||||
1. **The image goes to the registry only when it is ready for every box** — every household's manual update button
|
||||
offers the HIGHEST tag in the registry (`TriggerUpdate("manual")`), while the floor path targets the floor.
|
||||
2. **CC raises the floor itself**, with the release's MinAgent declared, when both guards hold: (a) the release ran on
|
||||
the three test boxes (Tester 1, demo-hp, demo-felhom) through the normal update path; (b) the box walk's checks are
|
||||
green on all three afterwards (`scripts/https_only_check.py` included). Not at night unless the night brief says so.
|
||||
*Route:* the only way to save the global floor today is the operator page with the operator's own password; CC
|
||||
does not use it for this until the operator grants a scoped route (R-1051).
|
||||
3. **Agent releases (and their bundles) still stop for the operator's vouch;** a floor whose MinAgent is above the
|
||||
vouched agent is never set.
|
||||
4. **The golden is baked weekly** (Sunday night, `runbooks/nightly-rotation.md`); the operator vouches it. A new box
|
||||
installs the vouched golden and updates itself to the floor (not yet proven end to end — R-1052).
|
||||
5. The push gate judges the golden by its AGE (older than 7 days while the floor is above it), not by the newest
|
||||
CHANGELOG heading (`scripts/golden_currency_gate.py`).
|
||||
|
||||
### 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
|
||||
247. ~~**Bake first.** A controller version is RELEASED only when its golden is baked.~~ **REVERSED by decision 250,
|
||||
the same day (11:26).** The original text, kept: 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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
10:45:53 deploy fields (env, required, after_start): [('DOMAIN', True, None), ('SUBDOMAIN', True, None), ('PLEX_CLAIM', True, True), ('HDD_PATH', True, None)]
|
||||
10:45:53 deploy with NO claim code -> 202 {"ok": true, "message": "The install has started – the card shows how it is going"}
|
||||
10:46:23 state: running gate: {"state": "closed", "since": "2026-10-15T08:45:53Z", "hosts": ["plex.enkisfelhom.hu"]}
|
||||
10:46:53 gate after 30 s more (Plex fully started): {"state": "closed", "since": "2026-10-15T08:45:53Z", "hosts": ["plex.enkisfelhom.hu"]}
|
||||
10:46:53 the household's „Kész” on the unclaimed Plex -> 409 {"data": null, "error": "Plex does not report the connection yet. Try again in a minute.", "ok": false}
|
||||
10:46:53 app page: 200 claim box shown: True
|
||||
@@ -0,0 +1,20 @@
|
||||
10:46:53 == (1) a stranger over the PUBLIC path (no cookie)
|
||||
10:46:53 GET https://plex.enkisfelhom.hu/ (Accept text/html) -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fplex.enkisfelhom.hu%2F body='<a href="https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fplex.enkisfelhom.hu%2F">Found</a>. '
|
||||
10:46:54 GET https://plex.enkisfelhom.hu/web/index.html (Accept text/html) -> 302 loc=https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fplex.enkisfelhom.hu%2Fweb%2Findex.html body='<a href="https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fplex.enkisfelhom.hu%2Fweb%2Findex.html">Found</a>. '
|
||||
10:46:54 GET https://plex.enkisfelhom.hu/identity (Accept application/json) -> 401 loc=- body='{"error":"this app is waiting for its first setup"}'
|
||||
10:46:54 GET https://plex.enkisfelhom.hu/library/sections (Accept application/json) -> 401 loc=- body='{"error":"this app is waiting for its first setup"}'
|
||||
10:46:54 GET https://plex.enkisfelhom.hu/:/prefs (Accept */*) -> 401 loc=- body='{"error":"this app is waiting for its first setup"}'
|
||||
10:46:54 == (2) a stranger on the LAN path (traefik :443 on the guest)
|
||||
10:46:58 GET / (Accept text/html) -> HTTP/2 302
|
||||
location: https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fplex.enkisfelhom.hu%2F
|
||||
<a href="https://felhom.enkisfelhom.hu/__gate/start?rd=https%3A%2F%2Fplex.enkisfelhom.hu%2F">Found</a>.
|
||||
10:47:01 GET /library/sections (Accept application/json) -> HTTP/2 401
|
||||
{"error":"this app is waiting for its first setup"}
|
||||
10:47:01 == (3) the household: dashboard session -> the gate handshake
|
||||
10:47:11 step1 → felhom.enkisfelhom.hu/__gate/start; step2 → plex.enkisfelhom.hu/__felhom_gate/cb; step3 set-cookie felhom_gate: yes
|
||||
10:47:14 with the gate cookie: GET /identity -> HTTP/2 200
|
||||
{"MediaContainer":{"size":0,"apiVersion":"0.1.0","claimed":false,"machineIdentifier":"fb56097105b927dd42576cd879499a50c0
|
||||
10:47:14 == (4) the server itself (docker network, no token)
|
||||
10:47:17 identity: {"MediaContainer":{"size":0,"apiVersion":"0.1.0","claimed":false,"machineIdentifier":"fb56097105b927dd42576cd879499a50c064c9fc","version":"1.41.4.9463-630c9f557"}}
|
||||
sections-no-token: HTTP 200
|
||||
prefs-no-token: HTTP 200
|
||||
@@ -26,6 +26,12 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-10-15 (afternoon) — the floor reached 0.313.0
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|---|---|---|---|
|
||||
| **R-1045** | **An unclaimed Plex answers everything without a token to a private address — and through the tunnel every request comes from one.** (P2) | CLOSED 2026-10-15 — its closing condition met: the global floor is 0.313.0 (operator 11:29), so every box that reports updates to a controller that asks Plex itself whether it is claimed; the setup gate closes the public path on every controller (`9a23ee5`) | `audits/media-build-2026-10-15/A-plex-gate/`, `G7/` |
|
||||
|
||||
## 2026-10-15 — the media build (controller v0.313.0)
|
||||
|
||||
| Row | What | Closed | Evidence |
|
||||
|
||||
@@ -192,12 +192,12 @@ stopping line that lies.
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| **R-1023** | Storage & devices | P2 | **Build: media apps see every drive, and the box says when they must restart (operator rulings 2026-10-14, `09` §3).** Scope from `architecture/15-media-library.md`: one bind per drive of `userdata/media/<library folders>` at the same path; the drive-absent gate per bind (today it keys on the app's one `HDD_PATH`, and the boot gate waits only for THAT drive — measured after the 17:25 power cut); the restart notice after a drive is added, removed or comes back, with „restart now” / „later” and a per-drive „apps restarted since” mark; removal, export and backup classes taught that a shared library is no app's own (R-1027, R-1019). **S5/S6 measured 2026-10-14:** a pulled drive stops none of the media apps (they key on another drive), they keep a dead view; on return nothing restarts them — so the restart notice MUST also cover „a drive came back”, the restart must be stop+start, and Sonarr/Radarr's own health page says nothing about an unreachable root folder, so the box must say it. **Night 2026-10-14 measured every event × app (`15` §10, `audits/media-drive-events-2026-10-14/`):** nothing is lost while a drive is gone (K1), so the build needs NO scan/unmonitor/torrent guard; it DOES need: (1) one bind per PRESENT drive only — a bind to an absent path makes the app unstartable (K3-4: 500 „error while creating mount source path … permission denied”); (2) the media apps recreated without the drive BEFORE a safe removal unmounts it (R-1044); (3) a returned drive restarts every container that binds it, FileBrowser included (K5: 2–10 s per app); (4) „restart now” cuts a playing stream (K5-4: cut, back in 22 s) — check Plex/Jellyfin sessions first or offer „later”; (5) the stale view is found with NO disk I/O: each container's `/proc/<pid>/mountinfo` shows the old device with `shutdown` (K4). Narrowed 2026-10-14 night. **BUILT 2026-10-15 — controller v0.313.0 + catalog `0188430` (`15` §11; `audits/media-build-2026-10-15/`): proven on Tester 1 (G1–G4, G6) and demo-hp 9201 (G7).** What is left is delivery. | **WAITING-ON-OPERATOR** — golden 0.313.0 baked; the vouch and the floor are the operator's (decision 247) | the operator's vouch of golden 0.313.0, then the floor | After the floor: read back that demo-felhom and Tester 1 report 0.313.0 through the normal path; then close | CC |
|
||||
| **R-1023** | Storage & devices | P2 | **Build: media apps see every drive, and the box says when they must restart (operator rulings 2026-10-14, `09` §3).** Scope from `architecture/15-media-library.md`: one bind per drive of `userdata/media/<library folders>` at the same path; the drive-absent gate per bind (today it keys on the app's one `HDD_PATH`, and the boot gate waits only for THAT drive — measured after the 17:25 power cut); the restart notice after a drive is added, removed or comes back, with „restart now” / „later” and a per-drive „apps restarted since” mark; removal, export and backup classes taught that a shared library is no app's own (R-1027, R-1019). **S5/S6 measured 2026-10-14:** a pulled drive stops none of the media apps (they key on another drive), they keep a dead view; on return nothing restarts them — so the restart notice MUST also cover „a drive came back”, the restart must be stop+start, and Sonarr/Radarr's own health page says nothing about an unreachable root folder, so the box must say it. **Night 2026-10-14 measured every event × app (`15` §10, `audits/media-drive-events-2026-10-14/`):** nothing is lost while a drive is gone (K1), so the build needs NO scan/unmonitor/torrent guard; it DOES need: (1) one bind per PRESENT drive only — a bind to an absent path makes the app unstartable (K3-4: 500 „error while creating mount source path … permission denied”); (2) the media apps recreated without the drive BEFORE a safe removal unmounts it (R-1044); (3) a returned drive restarts every container that binds it, FileBrowser included (K5: 2–10 s per app); (4) „restart now” cuts a playing stream (K5-4: cut, back in 22 s) — check Plex/Jellyfin sessions first or offer „later”; (5) the stale view is found with NO disk I/O: each container's `/proc/<pid>/mountinfo` shows the old device with `shutdown` (K4). Narrowed 2026-10-14 night. **BUILT 2026-10-15 — controller v0.313.0 + catalog `0188430` (`15` §11; `audits/media-build-2026-10-15/`): proven on Tester 1 (G1–G4, G6) and demo-hp 9201 (G7).** What is left is delivery. **2026-10-15 11:29: the operator vouched golden 0.313.0 and raised the global floor to 0.313.0 (read back from the hub DB).** demo-felhom moved to 0.313.0 by itself through the floor; demo-hp was put back on 0.313.0 by hand (it ran the test tag rc.4 and has its own floor override 0.311.0); Tester 1 still runs rc.4, which the floor never moves (R-1053) — fenced in the 2026-10-15 afternoon task. | **NARROWED** — delivered by the floor; Tester 1 waits on R-1053's step | R-1053 (Tester 1 back on a release tag) | Put Tester 1 on the 0.313.0 tag (or let R-1053's fix move it); read it back; close | CC |
|
||||
| **R-330** | Storage & devices | P3 | **Disk health Phase 2 — the three SMART attributes the wire does not carry.** The failing drive's most telling counter was **187 `Reported_Uncorrect`**, sitting at normalized **1** against threshold **0** with a raw count of **1001** — one point from failing and structurally unable to get there. Also wanted: **199 `UDMA_CRC_Error_Count`** (cabling) and **188 `Command_Timeout`**. None are on the agent→controller wire today, so v0.215.0's ladder could not use them. Phase 2 also persists periodic SMART **samples** (the right home is `metrics.MetricsStore`, NOT the Phase-1 state file, which is one record per disk and must stay that way). **This is a declared WIRE change, so under the G-1 gate the hub must model the new fields in the SAME session** — that is precisely why it was kept out of Phase 1, where it would have turned a one-word severity fix into a three-repo change | **READY** — **2026-10-12: the wire half is CLOSED in this row** — released in agent v0.150.0 (boxes run 0.154.0), controller and hub decode 187/188/199. **Only open work:** the verdict slice (use the three counters in the disk verdict and the household's words) and persisting samples. **READY (M) — NEW 2026-08-14** **2026-10-06 night: the wire half BUILT on main (unreleased):** the agent sends SMART 187 `reported_uncorrect`, 188 `command_timeout` (raw, vendor-packed on some drives — carried as reported), 199 `udma_crc_errors` (pointer + omitempty: unknown is absent, never 0); the controller decodes them and the hub's host-report mirror models them (G-1 wire gate green; it convicted the agent half alone). **Carried only** — no verdict, banner, mail or alarm reads them (`TestR330_CountersChangeNoVerdictYet`); using them changes what a household is told and is the next slice, with persisting samples. Red-proofs `audits/night-burndown-2026-10-06/r330/`. Ships with agent v0.150.0 and the next controller and hub releases. | R-328 (closed) | The verdict slice: 187/188/199 into the disk verdict with tests; then persist samples | CC |
|
||||
| **R-332** | Storage & devices | P3 | **The new Hiba-from-counters path has never fired on real hardware.** v0.215.0's whole point is a verdict the product could not previously reach, and it is proven only against the committed fixture's values in unit tests (12 scenario groups, 11 of 12 red-proofs failing as required). The live validation on demo-hp proved the **negative** — three healthy disks still read Rendben across the deploy, no false alert — and the **severity wire** end to end, but no live disk has actually reached Hiba. **This is the honest gap and it must not be closed by pointing at the fixture tests**: the drive that produced the fixture is in DooPlex, which is Tier 2 and never a drill target, and the demo boxes are all-flash and healthy | **WATCHING — NEW 2026-08-14, NARROWED same day.** One item originally in this gap is now PROVEN LIVE: the **persisted state surviving a controller restart**. The v0.215.0→v0.216.0 redeploy destroyed and rebuilt the container, and the new one read back a `changed_at` written by the PREVIOUS version (`2026-08-14T07:23:14.640216851Z`, still intact at 09:31:35Z) instead of re-baselining — Scenario L on real hardware, not just the production-path unit test. **What remains unproven is the verdict itself, plus the stronger restart half: an already-ALERTED disk not re-alerting** | a real degrading disk, or an injection harness | **Closing condition:** a live disk reaching Hiba from counters, OR a deliberate injection through the REAL pipeline (agent `/disks` → controller check → hub event), not a hand-set verdict | CC |
|
||||
| **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 |
|
||||
|
||||
## Security & access — 21 rows (P1 2, P2 4, P3 9, P4 6)
|
||||
## Security & access — 20 rows (P1 2, P2 3, P3 9, P4 6)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -220,10 +220,9 @@ stopping line that lies.
|
||||
| **R-779** | Security & access | P4 | **[P3-LOW] Part A's "two outside addresses seen as two" is proven through the simulated tunnel only; on the REAL tunnel the second outside address (ep0, one request allowed) was refused by Cloudflare's edge with 403 and never reached the box.** Measured 2026-10-01 19:51 UTC (`audits/visitors-2026-10-01/A/L2-demo-hp-real-tunnel.txt`): no log line on demo-hp; demo-hp's box has no geo restriction in its settings, so a Cloudflare ZONE rule (country or bot, not read) refused a German datacenter address. DooPlex's own address on the real tunnel was seen as itself. **Needs:** one sign-in from a second Hungarian address (the operator's phone off wifi) while DooPlex is locked out — 2 minutes; and say which Cloudflare rule refused ep0. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (a phone), CC reads the logs** **Re-ranked 2026-10-03: P3→P4: a proof gap on the real tunnel; operator-only follow-up.** | — | — | CC + operator |
|
||||
| **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. **NARROWED 2026-10-15 (Part A, `audits/media-build-2026-10-15/A-plex-gate/`):** the live catalog's Plex has `setup_gate: true` (app-catalog `9a23ee5`). Proven on demo-hp 9201 with an EXPIRED claim code: the server came up unclaimed and open on the docker network (`/library/sections`, `/:/prefs` 200 with no token), while a stranger over the public path got only the gate (HTML → 302 to the dashboard's `/__gate/start`; `/identity`, `/library/sections`, `/:/prefs` → 401 „waiting for its first setup”), the same on the LAN path; the household passed with its dashboard session and reached Plex. Removing the app removed the gate file 9 s later (the tick). **Left open:** the gate opens on the household's „Kész, beállítottam” even while Plex is still unclaimed (no probe: Plex answers XML to the probe's request) — closed by R-1028's build (the controller claims and opens on a real claim). **2026-10-15 (v0.313.0):** the controller asks Plex itself whether it is claimed — after the claim on the Plex page and before „Kész” may open the gate (`TestPlexDonePress_RefusedWhileUnclaimed`; G5 on 9201: no code, gated, „Kész” 409). Two background security reviews of the catalog pushes found the old-controller half: an optional code (fixed: required again, `after_start` for 0.313.0) and the remaining residual — on a controller before 0.313.0 a code that ran out leaves an unclaimed Plex that „Kész” can open. A status probe meant to close that OPENED an unclaimed Plex while Plex started (G7f; removed within the hour). The residual ends when the floor carries 0.313.0. | **WAITING-ON-OPERATOR** — closed by the floor at 0.313.0 | the floor at 0.313.0 | After the floor: close | 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)
|
||||
## Box system & updates — 13 rows (P2 1, P3 9, P4 3)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -234,10 +233,12 @@ stopping line that lies.
|
||||
| **R-972** | Box system & updates | P2 | **The shipped controller and hub programs are built with Go 1.24.13 and golang.org/x/crypto v0.31.0, and the agent with Go 1.26.0, so dozens of fixed Go security bugs (including html/template escaping fixes) are still in what runs on boxes and the hub.** Go 1.24 has had no security fixes since Go 1.26 was released (Go supports the two newest major releases: https://go.dev/doc/devel/release#policy); the scanners name fixes in Go 1.26.9 and 1.27.2. Also: the controller image's Debian 12 base has 12 fixable critical packages, and the Samba image has 48 fixable high ones. Fix: build all three on a supported, patched Go; update x/crypto; rebuild the base images. Scanner output: `audits/security-review-2026-10-12/scanners/`. | OPEN — measured on the shipped binaries | — | Bump the toolchain and bases in their own release task | CC |
|
||||
| **R-862** | Box system & updates | P3 | **Tester 2 cannot take the config bundle until one by-hand bootstrap is done: its `felhom-os-apply` (agent 0.142.0) predates the bundle mode, and no signed job can write a root file on it.** FOUND 2026-10-04 (R-840 build, Part C): the route reaches every box installed from installer 1.31.0 on, and the demo boxes (bootstrapped by CC); Tester 2 has every root file of agent 0.142.0 (installer 1.30.0) and lacks only the R-858 wrapper fix, which matters only for a Docker step it gets solely from a signed job. CC has no route to Tester 2 (its door admits only the operator's WireGuard peer; `felhom-op` cannot become root). The steps: `runbooks/config-bundle.md` "Tester 2". Then CC signs the bundle and reads it back. | **WAITING-ON-OPERATOR** — the operator said (2026-10-04 ~19:05) he will try through his tunnel **2026-10-07 07:58 (`09` §3 decision 170): the operator believes Tester 2 never set up the off-site backup (no escrow); the laptop may stay off for days; the tester plans to add an HDD.** **2026-10-07 (Part H, read only):** in Tester 2's last 10 notifications (2026-10-05 09:16 → 2026-10-07 06:58) the household got 0 mails, the operator 10 (host_down ×9, expected_dbdump_missed ×1); control: the household channel shows on the other three customers. So the household is not mailed daily while the box is off; no row (`audits/day-2026-10-07/H/`). | the operator's WireGuard tunnel | the operator runs the three bootstrap commands; CC sends the bundle | operator |
|
||||
| **R-78** | Box system & updates | P3 | **`local_api` authority ruling — auto-reconcile vs detect-only** **MIGRATED FROM `ROADMAP.md` 2026-08-22 (R-369) — originally filed 2026-07-25, size M, roadmap state `idea (deferred OUT of R-77 on purpose)`.** Moved verbatim; nothing added or reinterpreted. The roadmap keeps its copy as history, marked moved. **An OWED OPERATOR DECISION, not a defect — moved because an owed ruling hidden among feature ideas is the shape this session exists to remove.** | **OPEN — migrated from ROADMAP 2026-08-22, rank unchanged** | — | R-77 ships detection because the fix is genuinely undecided, and **both directions can lose customer-visible function**. **Direction 1 (today):** `controller.yaml` wins and drift is silent → the 2026-07-25 island migration blinded the whole fleet's control plane for 17.5 h (drive gate, guest-reboot recovery, quiesce/backup all degrade). R-77 makes that loud but does not stop it recurring. **Direction 2 (`bootstrap.json` wins, auto-reconcile on boot):** a guest whose `controller.yaml` is CORRECT and whose `bootstrap.json` is stale — a half-completed re-provision, a hand-repaired guest, a setup-wizard box — gets a **working channel clobbered on the next restart**, fleet-wide and silently, during a routine deploy. That is not obviously better than the bug. Needs a spike: which writer is authoritative per field (endpoint vs fingerprint vs token — `mergeLocalAPI` replaces the whole block, so they cannot be reconciled independently today), whether the agent side should stamp a generation/mtime so 'newer wins' is even expressible, and whether reconcile should require an operator ack. Until then the drift alert plus a manual edit is the supported path. | CC |
|
||||
| **R-468** | Box system & updates | P3 | **[P3-LOW] THE GOLDEN WAIVER — goldens on a cadence, not per release (operator ruling 2026-09-13).** 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 by 2026-09-01, R-404/R-417). **The ruling: bake WEEKLY, and always before any drill or fresh install.** Every release still raises the FLOOR, so both demo boxes keep getting each release in ~20 s; only the golden — which protects a fresh install and nothing else — moves to a cadence. **The mechanism (built 2026-09-13):** `documentation/tests/golden-waiver. **⚠ CORRECTED THE SAME DAY (R-472): between bakes the floor does NOT carry a release — the hub holds any floor above the vouched golden (publish-train rule 1), so releases between bakes reach the demo boxes only by hand-deploy.**yml`, four lines (`issued`, `expires`, `reason`, `register_row: R-468`), read by the gate. While valid, a golden BEHIND the record makes the gate print a loud ADVISORY and exit 0; when it expires the gate is red again until someone bakes or renews. **The 14-day cap is enforced by the gate, not the runbook** — a longer, undated, unparseable, reason-less or row-less waiver is INCONCLUSIVE (exit 2), never 0 and never silently ignored. **It never covers a golden that is UNRECORDED (R-385)** — that is not a cadence choice. **A dated waiver cannot be forgotten; it just expires** — the difference from R-242's original rule, which recurred the day after it was written. Tests: `scripts/test_golden_currency_gate.py` cases 5–15 (E/F/G/H, a 15-day, absent, unparseable, bad-row and empty-reason waiver each 2; the R-421 decoy — a file saying only `expires` — 2). **This is a PRE-CUSTOMER arrangement: the first external install retires it** (delete the file in that commit). Cadence written into `RUNBOOK-manual-build.md` §4.2 and the `felhom.eu` end-of-session checklist. **Does NOT touch R-242's open half (nothing gates the VOUCH).** | **WATCHING — rank P3-LOW; owner: CC (renew ≤ 14 days or bake); retire at the first external install** | — | — | CC |
|
||||
| **R-468** | Box system & updates | P3 | **[P3-LOW] THE GOLDEN WAIVER — goldens on a cadence, not per release (operator ruling 2026-09-13).** 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 by 2026-09-01, R-404/R-417). **The ruling: bake WEEKLY, and always before any drill or fresh install.** Every release still raises the FLOOR, so both demo boxes keep getting each release in ~20 s; only the golden — which protects a fresh install and nothing else — moves to a cadence. **The mechanism (built 2026-09-13):** `documentation/tests/golden-waiver. **⚠ CORRECTED THE SAME DAY (R-472): between bakes the floor does NOT carry a release — the hub holds any floor above the vouched golden (publish-train rule 1), so releases between bakes reach the demo boxes only by hand-deploy.**yml`, four lines (`issued`, `expires`, `reason`, `register_row: R-468`), read by the gate. While valid, a golden BEHIND the record makes the gate print a loud ADVISORY and exit 0; when it expires the gate is red again until someone bakes or renews. **The 14-day cap is enforced by the gate, not the runbook** — a longer, undated, unparseable, reason-less or row-less waiver is INCONCLUSIVE (exit 2), never 0 and never silently ignored. **It never covers a golden that is UNRECORDED (R-385)** — that is not a cadence choice. **A dated waiver cannot be forgotten; it just expires** — the difference from R-242's original rule, which recurred the day after it was written. Tests: `scripts/test_golden_currency_gate.py` cases 5–15 (E/F/G/H, a 15-day, absent, unparseable, bad-row and empty-reason waiver each 2; the R-421 decoy — a file saying only `expires` — 2). **This is a PRE-CUSTOMER arrangement: the first external install retires it** (delete the file in that commit). Cadence written into `RUNBOOK-manual-build.md` §4.2 and the `felhom.eu` end-of-session checklist. **Does NOT touch R-242's open half (nothing gates the VOUCH).** **2026-10-15 (`09` §3 decision 250, operator):** the cadence is now the RULE, not a waiver — a controller release needs no golden (the floor carries it), the golden is baked every Sunday night when the floor is above it (`runbooks/nightly-rotation.md`), and `golden_currency_gate.py` convicts only a golden MORE THAN 7 DAYS old while a release is ahead (CASE 18–21, red-proved). The waiver file stays as a dated exception for a missed Sunday. | **WATCHING** — the weekly bake is a night-routine step; the first external install still retires the waiver arrangement | — | — | CC |
|
||||
| **R-1004** | Box system & updates | P4 | **After an unclean stop every guest ext4 volume waits ~43 s in multi-mount protection before it mounts, so each restart after a power cut takes ~1.5 min longer.** Kernel: „EXT4-fs warning (device dm-6): ext4_multi_mount_protect: MMP interval 42 higher than expected, please wait" on dm-6/dm-7 and loop0/loop1, demo-hp and Tester 1, every round of `audits/night-power-2026-10-13/` (R1 `repairs.txt`). | **READY** — filed 2026-10-14 | — | CC: find where MMP is switched on for the guest volumes (Proxmox `pct` create, or our own mkfs), whether it protects anything on a one-host disk, and measure the restart without it on a scratch guest | CC |
|
||||
| **R-1013** | Box system & updates | P4 | **After a power cut the agent's start-up stale-lock pass logs an ERROR „pct unlock failed … no lock found" when Proxmox's own start-all removed the lock in the same second.** The outcome is the goal (no lock), so the line is a false alarm in the log. Seen on demo-hp 2026-10-14 12:07:44–45 UTC (`audits/power-cut-fixes-2026-10-14/E/after-boot.txt`; Proxmox: „removed left over backup lock from '9201'!"). Small, but the agent's release for this session is spent (v0.156.1). | **OPEN** — filed 2026-10-14 | — | CC: treat „no lock found" from `pct unlock` as already clear (INFO), with a test; ride the next agent release | CC |
|
||||
| **R-1010** | Box system & updates | P3 | **The agent's stale-lock janitor cannot remove a half-removed `vzdump` snapshot: Proxmox's delete fails on the first missing volume („Failed to find logical volume pve/snap_vm-9201-disk-0_vzdump") and the janitor retries the same call every 10 min forever, leaving the other volume's snapshot and the snapshot entry.** Seen 2026-10-14 after a backup ran out of space on a full host root (LVM could not write its metadata; `audits/power-cut-fixes-2026-10-14/README.md` C4). Fixed by hand with `pct delsnapshot 9201 vzdump --force`. | **READY** — filed 2026-10-14 | — | CC: on that error, retry once with `--force` (a sudoers line or the API's `force=1`), with a test that replays the error text; next agent release | CC |
|
||||
| **R-1052** | Box system & updates | P3 | **The new-box path is not proven: a fresh install from a golden older than the floor reaching the floor by itself at first boot.** `09` §3 decision 250 relies on it (the golden is weekly, releases ride the floor). Proof owed at the next controller release: a throwaway VM on demo-hp installed from the ISO (the vouched golden one release behind the floor); time from claim to the floor; the controller's self-update log lines; then the VM and its hub records removed. | **OPEN** — filed 2026-10-15 | the next controller release | Run it at the next release; quote the times | CC |
|
||||
| **R-1053** | Box system & updates | P3 | **A box running a controller test tag (`0.313.0-rc.N`) is never moved by the floor, and says so only at DEBUG.** `util.ParseVersion` wants X.Y.Z, so `maybeAutoUpdate` logs `current … not parseable (dev?) — skip` and the box stays on the test build forever (measured 2026-10-15 on demo-hp and Tester 1 after the floor reached 0.313.0; demo-felhom, on a release tag, moved by itself). The same parser keeps an rc tag off the household's update button — that half is wanted. Today's guard is procedural (the `felhom-build-deploy` skill: write the release tag back after testing). Tester 1 still runs rc.4. | **READY** — filed 2026-10-15 | — | A controller fix: treat `X.Y.Z-rc.N` as a pre-release BELOW `X.Y.Z` for the floor (and keep it off the button), log the skip at WARN; a test per case. Meanwhile put Tester 1 on 0.313.0 | CC |
|
||||
|
||||
|
||||
## Monitoring & notifications — 10 rows (P2 1, P3 6, P4 3)
|
||||
@@ -256,7 +257,7 @@ stopping line that lies.
|
||||
| **R-1049** | Monitoring & notifications | P3 | **A pulled drive mails the operator „unexpectedly disconnected” (error), and nothing ever mails that it came back.** MEASURED 2026-10-14 night (`audits/media-drive-events-2026-10-14/K8-hub-mails.txt`, the hub DB with its WAL from the running pod, and the mailbox as the second channel): two pulls → two `storage_disconnected` mails; the hub RECEIVED both `storage_reconnected` and both `health_recovered` (severity info) and logged no notification for any of them — they are not in `recoveredPairedDownTypes` (`hub/internal/notify/dispatcher.go`), so the severity gate drops them. The R-97a shape: told it broke, never told it healed — the operator cannot tell a returned drive from one still gone without opening the hub. **BUILT 2026-10-15 — hub v0.151.0 (`12552aca`): both pairs in `recoveredPairedDownTypes`, cooldowns ended by the next loss (`TestR1049_DriveBackMailsBothEdges`, red-proved). Not deployed: the operator's „yes” first (decision 162); the image build waits for it too.** | **WAITING-ON-OPERATOR** — hub v0.151.0 code pushed, not built, not deployed | the operator's „yes” to the hub deploy | On the „yes”: build + deploy hub v0.151.0; read a „came back” mail on the next pull | CC |
|
||||
|
||||
|
||||
## Hub & operator — 12 rows (P3 7, P4 5)
|
||||
## Hub & operator — 13 rows (P3 8, P4 5)
|
||||
|
||||
| ID | Category | Sev | What | State | Blocked on | Next action | Owner |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
@@ -272,6 +273,7 @@ stopping line that lies.
|
||||
| **R-844** | Hub & operator | P4 | **The household's OS-update line exists only on the hub's customer timeline.** 2026-10-04: the box itself has no event surface for agent results (the controller UI shows no timeline), so `os_update_applied` is a hub customer event (info: recorded, never mailed). Its stored text is the hub's English sentence; the hu/en bundle text (`mail.event.os_update_applied`) is used only if it is ever mailed. Fix direction: a controller-side line (the controller already polls the agent's local API) when the box gets a household timeline. `audits/os-guest-lane-2026-10-04/partG/hub-customer-timeline-demo-hp.txt` | **READY — owner: CC** **2026-10-05 (burn-down night): NEEDS A DESIGN** — a household timeline on the box does not exist yet. | — | — | CC |
|
||||
| **R-1002** | Hub & operator | P4 | **A box the household switched off reads ONLINE on the hub's hosts page for its first 45 minutes** (seen live 2026-10-13, R-998 proof, `audits/poweroff-2026-10-13/B-hub-watch.log`: the „Power: Kikapcsolva…" line at 18:53, the KIKAPCSOLVA badge only at 19:38, when the host report turned stale). The customer page read „warn" in part of that gap (seen, not captured). No alarm and no mail fired; the operator page is only slow to say it. | **READY** — filed 2026-10-13 | — | CC: let `hostDisplayStatus` read `deliberate_off_at` before staleness, with a render test per state; next hub release | CC |
|
||||
| **R-1011** | Hub & operator | P3 | **The Day-0 „Agent" list offers step-bundle packages (`0.156.1-step1`, `0.152.0-step1` …) as agent versions; one has no agent binary, so the hub shows the bundle JSON's sha (`FileSHA256` falls back to the first file) and a vouch would point fresh installs at a package with no binary.** Seen 2026-10-14 (`/configuration`: two „0.156.1" entries, `ac83ea41…` is the step bundle). | **READY** — filed 2026-10-14 | — | CC: list only versions whose package holds the `felhom-agent` file (refuse a missing preferred file instead of falling back), with a test; next hub release | CC |
|
||||
| **R-1051** | Hub & operator | P3 | **CC cannot raise the global floor without the operator's own password.** `09` §3 decision 250 lets CC raise the floor after the three test boxes, but the only route is the operator page (`POST /configuration/global-floor`, Basic auth with the operator password, `hub/internal/web/configs.go` `handleSetGlobalFloor`); the hub's `/api/v1/` Bearer routes carry no floor. Read 2026-10-15 (no save made). The smallest grant: a hub route `POST /api/v1/operator/floor` (floor + declared MinAgent only, the same checks as the page) behind its own Bearer key stored out-of-band on DooPlex — or the operator's word that CC may use the operator password for this one save. | **WAITING-ON-OPERATOR** — filed 2026-10-15 | the operator's pick | On a scoped route: build it (a hub release, the operator's „yes"), prove a no-op save, write it into the `felhom-build-deploy` skill's release steps | operator |
|
||||
|
||||
|
||||
## Business & legal — 14 rows (P2 4, P3 2, P4 8)
|
||||
|
||||
@@ -98,9 +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).** **⚠ 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.
|
||||
**Raise the floor to a release with no golden (hub v0.112.0+, R-472).** **This IS the release route** (`09` §3 decision
|
||||
250, 2026-10-15: a controller release needs no golden; decision 247 of the same morning was reversed). Only after the
|
||||
release ran on Tester 1, demo-hp and demo-felhom through the normal update path and the box walk is green on all three.
|
||||
|
||||
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<VER>' CHANGELOG.md` shows
|
||||
@@ -252,9 +252,9 @@ 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.
|
||||
> **2026-10-15 (`09` §3 decision 250):** the weekly cadence below STANDS (the same morning's „bake first", decision 247,
|
||||
> was reversed). The weekly bake is a Sunday-night step of `nightly-rotation.md`; the push gate judges the golden by its
|
||||
> AGE (7 days) while the floor is above it, not by the newest CHANGELOG heading.
|
||||
|
||||
**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
|
||||
|
||||
@@ -12,6 +12,14 @@ of the three Tier-0 boxes over plain http and convicts any that does not answer
|
||||
negative and positive controls run first. Exit 1 is a P1 regression: file it before the app of the night. A new app
|
||||
installed tonight is covered by the next run, because the names are read from the box.
|
||||
|
||||
**Sunday night, the weekly golden** (`09` §3 decision 250, operator 2026-10-15). Read the floor and the vouched golden
|
||||
from the hub (the DB with its `-wal`/`-shm` from the running pod: `hub_settings.min_controller_version` and
|
||||
`artifact_golden_version`). **If the floor is above the vouched golden:** bake a golden at the FLOOR's version per the
|
||||
`felhom-build-deploy` skill / `RUNBOOK-manual-build.md` §4.1 (evidence `documentation/tests/golden-<VER>-<DATE>/`: the bake
|
||||
log, the registry round trip, the teardown), and put **„vouch golden <VER> (agent …, min_agent …)"** at the TOP of the
|
||||
morning list. **Never vouch** — the vouch is the operator's. If the floor equals the vouched golden: nothing to bake; say
|
||||
so in one line. A Sunday with no bake is what `golden_currency_gate.py` convicts after 7 days.
|
||||
|
||||
**The nine standing apps of demo-hp come first.** They cannot be a throwaway on guest 9201 (same stack
|
||||
name), so they are walked on the **scratch guest 9202** (R-481, built 2026-09-13: under the demo-hp
|
||||
customer, hub off, tunnel off, `https://192.168.0.114` with the same Host names and password).
|
||||
|
||||
@@ -10,8 +10,8 @@ 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.
|
||||
**2026-10-15 (`09` §3 decision 250): a floor above the golden with a declared MinAgent IS the release route** (the same
|
||||
morning's „bake first", decision 247, was reversed). The golden is weekly.
|
||||
|
||||
**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
|
||||
|
||||
@@ -41,7 +41,9 @@ it and do not bypass the hook for it.**
|
||||
|
||||
Two things still hold:
|
||||
|
||||
* A push that touches **code** is still refused — bake first or do not push code.
|
||||
* A push that touches **code** is still refused — but since 2026-10-15 (`09` §3 decision 250) only when the newest
|
||||
golden is **more than 7 days old** while a release is ahead of it; a release itself needs no golden. Bake the weekly
|
||||
golden (Sunday, `nightly-rotation.md`) or do not push code.
|
||||
* **Every other gate still refuses every push.** If a push is refused during a drill it is NOT the
|
||||
golden gate, and the message will name which gate it was.
|
||||
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
## 2026-10-15 — golden_currency_gate: the golden is judged by its AGE (`09` §3 decision 250)
|
||||
|
||||
- A controller release needs no golden (operator ruling 2026-10-15 11:26). The gate no longer convicts because the newest
|
||||
controller CHANGELOG heading is ahead of the newest bake; it convicts when a release is ahead AND the newest bake is MORE
|
||||
THAN 7 days old (the date in its evidence directory). Ahead and at most 7 days old: an advisory, exit 0. Unchanged: an
|
||||
unrecorded golden (R-385) at any age; a malformed waiver is exit 2; a valid waiver turns the age conviction into an
|
||||
advisory; documents-only pushes stay advisory (R-404). The gate stays offline, so it cannot see the floor or the vouch —
|
||||
„release ahead" stands in for „floor ahead".
|
||||
- `test_golden_currency_gate.py` CASE 18–21: 3 days → 0, 8 days → 1, exactly 7 → 0, an unrecorded golden however fresh → 1.
|
||||
Red-proved both ways (limit 0: CASE 18/20 fail; limit 30: CASE 19 fails).
|
||||
- `repo_gates.py`: the advisory block quotes the golden's age and names the weekly bake.
|
||||
|
||||
## 2026-10-14 (night) — https_only_check.py: the box-walk check for „https only” (R-1033)
|
||||
|
||||
- New `scripts/https_only_check.py`. Reads each box's public names from the box (traefik labels + dynamic files, the
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Golden-currency gate (R-242) — a controller release is not DELIVERED until a golden carries it.
|
||||
"""Golden-currency gate (R-242) — the golden must not fall more than a WEEK behind the releases.
|
||||
|
||||
── THE AGE RULE (2026-10-15, operator, `09` §3 decision 250) — read this first ───────────────────
|
||||
|
||||
A controller release needs NO golden: the floor carries it to every box and a new box installs the vouched golden
|
||||
and updates itself to the floor. The golden is baked WEEKLY (Sunday night, runbooks/nightly-rotation.md). So this gate
|
||||
no longer convicts because the newest CHANGELOG heading is ahead of the newest bake; it convicts when the release is
|
||||
ahead AND the newest bake is MORE THAN 7 DAYS old (by the date in its evidence directory). Ahead and at most 7 days old
|
||||
is an advisory. Everything below still holds: an unrecorded golden (R-385) is convicted at any age; a malformed waiver
|
||||
is exit 2; a valid waiver turns the age conviction into an advisory. The gate stays offline (`--fast`): it cannot read
|
||||
the floor or the vouch from the hub, so „release ahead" stands in for „floor ahead" (the floor follows a release within
|
||||
hours, after the three test boxes). Pinned by test_golden_currency_gate.py CASE 18-21.
|
||||
|
||||
(The text below is the gate's history; its „not DELIVERED until a golden carries it" premise is the old rule.)
|
||||
|
||||
|
||||
Run from the repo root: python3 scripts/golden_currency_gate.py
|
||||
Exit 0 clean · 1 convicted (a released controller has no golden) · 2 inconclusive.
|
||||
@@ -151,6 +165,9 @@ REGISTER_PATH = os.environ.get("GOLDEN_GATE_REGISTER",
|
||||
os.path.join(ROOT, "documentation", "backlog", "OPEN-ITEMS.md"))
|
||||
# The HARD LIMIT on a waiver's life. Enforced here and nowhere else — see the module docstring.
|
||||
WAIVER_MAX_DAYS = 14
|
||||
# The age rule (decision 250): a golden at most this many days old may lag the newest release.
|
||||
GOLDEN_MAX_AGE_DAYS = 7
|
||||
NEWEST_BAKE_DATE = None # set by newest_baked(): the date in the newest bake's evidence directory
|
||||
|
||||
# `## v0.206.0 — …` on the FIRST such line: the CHANGELOG is newest-first by convention.
|
||||
RELEASED_RE = re.compile(r"^##\s+v(\d+)\.(\d+)\.(\d+)\b")
|
||||
@@ -258,6 +275,8 @@ def newest_baked():
|
||||
same = [lbl for k, lbl in found if k[0] == key[0]]
|
||||
if len(same) > 1:
|
||||
print(" %d bakes of %s on file; the NEWEST is read: %s" % (len(same), ".".join(map(str, key[0])), label))
|
||||
global NEWEST_BAKE_DATE
|
||||
NEWEST_BAKE_DATE = datetime.date.fromisoformat(key[1])
|
||||
return key[0], label
|
||||
|
||||
|
||||
@@ -399,6 +418,19 @@ def main():
|
||||
|
||||
if released > baked:
|
||||
lag = sorted(v for v in every_released if v > baked)
|
||||
today = datetime.datetime.now(datetime.timezone.utc).date()
|
||||
age = (today - NEWEST_BAKE_DATE).days if NEWEST_BAKE_DATE else None
|
||||
print(" golden age : %s day(s) (baked %s; the limit is %d — `09` §3 decision 250)"
|
||||
% (age, NEWEST_BAKE_DATE, GOLDEN_MAX_AGE_DAYS))
|
||||
if age is not None and age <= GOLDEN_MAX_AGE_DAYS:
|
||||
print("")
|
||||
print("GOLDEN CURRENCY GATE ADVISORY — the golden (%s) is behind the newest release (v%s, %d release(s): %s) "
|
||||
"but only %d day(s) old. Releases ride the floor; the golden is weekly (decision 250). A new box "
|
||||
"installs %s and updates itself to the floor."
|
||||
% (vstr(baked), vstr(released), len(lag), ", ".join(vstr(v) for v in lag), age, vstr(baked)))
|
||||
print("golden currency gate OK — the golden is at most %d days old (NOTE: the BAKE, not the vouch)"
|
||||
% GOLDEN_MAX_AGE_DAYS)
|
||||
return
|
||||
if wstate == "valid":
|
||||
print("")
|
||||
print("GOLDEN CURRENCY GATE ADVISORY — WAIVED, NOT CLEAN: controller v%s is released and "
|
||||
@@ -415,16 +447,18 @@ def main():
|
||||
"a valid waiver covers it (NOTE: this checks the BAKE, not the vouch)")
|
||||
return
|
||||
print("")
|
||||
print("GOLDEN CURRENCY GATE FAILED: controller v%s is released and NO golden carries it "
|
||||
"(newest bake is %s; %d release(s) behind)." % (vstr(released), vstr(baked), len(lag)))
|
||||
print("GOLDEN CURRENCY GATE FAILED: controller v%s is released and the newest golden (%s) is %s day(s) old "
|
||||
"— more than %d (decision 250: the golden is baked weekly; %d release(s) behind)."
|
||||
% (vstr(released), vstr(baked), age, GOLDEN_MAX_AGE_DAYS, len(lag)))
|
||||
if wstate == "expired":
|
||||
print("The waiver at documentation/tests/golden-waiver.yml EXPIRED on %s (%s). It ran "
|
||||
"out, as a dated waiver is meant to: bake a golden, or renew it with a new dated "
|
||||
"commit (at most %d days)." % (winfo["expires"], winfo["row"], WAIVER_MAX_DAYS))
|
||||
print("A machine installed right now would receive v%s — the release is written, tested and "
|
||||
"pushed, and NOT delivered." % vstr(baked))
|
||||
print("Fix: bake a golden per documentation/runbooks/RUNBOOK-manual-build.md §4.1, then vouch "
|
||||
"it (a THREE-field change: golden_version + agent_version + min_agent).")
|
||||
print("Fix: bake the weekly golden at the floor's version per documentation/runbooks/RUNBOOK-manual-build.md "
|
||||
"§4.1 (runbooks/nightly-rotation.md, Sunday) and put „vouch golden X\" on the operator's morning list — "
|
||||
"the vouch is the operator's (a THREE-field change: golden_version + agent_version + min_agent).")
|
||||
print("If the cadence ruling covers this release, issue a DATED waiver at "
|
||||
"documentation/tests/golden-waiver.yml (RUNBOOK-manual-build.md §4.2) — never a bypass.")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -328,17 +328,17 @@ def _print_advisory_block(advisories):
|
||||
# "which version owes a golden" is a second thing that can be wrong.
|
||||
for line in text.splitlines():
|
||||
t = line.strip()
|
||||
if t.startswith("newest released controller") or t.startswith("newest golden baked"):
|
||||
if t.startswith("newest released controller") or t.startswith("newest golden baked") or t.startswith("golden age"):
|
||||
print("!! %s" % t)
|
||||
print("!!")
|
||||
print("!! This push touches DOCUMENTS ONLY, so it can neither create this debt nor clear")
|
||||
print("!! it — and the push that DOES clear it (a bake record under documentation/tests/)")
|
||||
print("!! is itself documents-only. Blocking here blocked the cure.")
|
||||
print("!!")
|
||||
print("!! WHAT CLEARS IT: bake a golden per documentation/runbooks/RUNBOOK-manual-build.md")
|
||||
print("!! section 4.1, then vouch it (a THREE-field change: golden_version + agent_version")
|
||||
print("!! + min_agent). The debt stays visible in STATUS.md and in the controller repo's")
|
||||
print("!! own golden-notice until then.")
|
||||
print("!! WHAT CLEARS IT (`09` §3 decision 250): the WEEKLY golden — bake at the floor's version per")
|
||||
print("!! documentation/runbooks/RUNBOOK-manual-build.md section 4.1 (nightly-rotation.md, Sunday), and the")
|
||||
print("!! operator vouches it (golden_version + agent_version + min_agent). The gate convicts only a golden")
|
||||
print("!! more than 7 days old while a release is ahead of it.")
|
||||
print("!" * 78)
|
||||
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ def main():
|
||||
|
||||
fails += waiver_cases()
|
||||
fails += rebake_cases()
|
||||
fails += age_cases()
|
||||
|
||||
if fails:
|
||||
print()
|
||||
@@ -148,7 +149,7 @@ def run_gate_env(env):
|
||||
return p.returncode, p.stdout + p.stderr
|
||||
|
||||
|
||||
def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",)):
|
||||
def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",), bake_date="2026-01-01"):
|
||||
"""Build a tree where the CHANGELOG lists `released` (newest first) and one golden `baked` has a
|
||||
real-shaped bake log. Returns (env, tmpdir)."""
|
||||
tmp = tempfile.mkdtemp(prefix="golden-waiver-")
|
||||
@@ -157,8 +158,8 @@ def synthetic_tree(released, baked, waiver_text, register_rows=("R-468",)):
|
||||
for v in released:
|
||||
fh.write("## v%s — synthetic (2026-01-01)\n\nbody\n\n" % v)
|
||||
ev = os.path.join(tmp, "tests")
|
||||
os.makedirs(os.path.join(ev, "golden-%s-2026-01-01" % baked))
|
||||
with io.open(os.path.join(ev, "golden-%s-2026-01-01" % baked, "bake.log"), "w",
|
||||
os.makedirs(os.path.join(ev, "golden-%s-%s" % (baked, bake_date)))
|
||||
with io.open(os.path.join(ev, "golden-%s-%s" % (baked, bake_date), "bake.log"), "w",
|
||||
encoding="utf-8") as fh:
|
||||
fh.write("[golden] upload OK (HTTP 201)\nGOLDEN_VERSION=%s\nGOLDEN_SHA256=%s\n" % (baked, REAL_SHA))
|
||||
reg = os.path.join(tmp, "OPEN-ITEMS.md")
|
||||
@@ -286,4 +287,37 @@ def rebake_cases():
|
||||
return fails
|
||||
|
||||
|
||||
# ── The age rule (2026-10-15, `09` §3 decision 250) ─────────────────────────────────────────────
|
||||
#
|
||||
# A release needs no golden; the golden is weekly. The gate convicts only when the release is AHEAD and the newest
|
||||
# bake is MORE THAN 7 days old. COMPANION RED-PROOF (2026-10-15): set GOLDEN_MAX_AGE_DAYS to 0 → CASE 18 and CASE 20
|
||||
# fail (a 3-day and a 7-day golden convicted); set it to 30 → CASE 19 fails (an 8-day golden passes). Restored.
|
||||
def age_cases():
|
||||
fails = []
|
||||
today = datetime.datetime.now(datetime.timezone.utc).date()
|
||||
ago = lambda n: (today - datetime.timedelta(days=n)).isoformat()
|
||||
BEHIND = (["9.9.9", "9.9.8", "9.9.7"], "9.9.7")
|
||||
|
||||
def check(label, released_baked, bake_date, want_rc, must=(), must_not=()):
|
||||
env, tmp = synthetic_tree(released_baked[0], released_baked[1], None, bake_date=bake_date)
|
||||
try:
|
||||
rc, out = run_gate_env(env)
|
||||
finally:
|
||||
shutil.rmtree(tmp, ignore_errors=True)
|
||||
bad = [m for m in must if m not in out] + ["NOT expected: " + m for m in must_not if m in out]
|
||||
if rc != want_rc or bad:
|
||||
fails.append("%s: rc=%d (wanted %d) %s\n%s" % (label, rc, want_rc, bad, out[-900:]))
|
||||
else:
|
||||
print("%s ok" % label)
|
||||
|
||||
check("CASE 18 (age: release ahead, golden 3 days old -> 0, advisory)", BEHIND, ago(3), 0,
|
||||
must=("ADVISORY", "3 day(s) old", "golden currency gate OK"), must_not=("GOLDEN CURRENCY GATE FAILED",))
|
||||
check("CASE 19 (age: release ahead, golden 8 days old -> 1)", BEHIND, ago(8), 1,
|
||||
must=("GOLDEN CURRENCY GATE FAILED", "8 day(s) old", "more than 7"))
|
||||
check("CASE 20 (age: exactly 7 days is the limit and PASSES)", BEHIND, ago(7), 0, must=("ADVISORY",))
|
||||
check("CASE 21 (age: an UNRECORDED golden is convicted however fresh — R-385)", (["9.9.9", "9.9.7"], "9.9.8"),
|
||||
ago(0), 1, must=("UNRECORDED",))
|
||||
return fails
|
||||
|
||||
|
||||
sys.exit(main())
|
||||
|
||||
@@ -51,6 +51,23 @@ ssh felhom-pve "pct exec 9201 -- docker ps --filter name=felhom-controller --for
|
||||
|
||||
Check current live version first: same `docker ps` command, or `cat /etc/felhom-controller-image`.
|
||||
|
||||
### Releasing a controller (`09` §3 decision 250, operator 2026-10-15 — a release needs NO golden)
|
||||
|
||||
1. **Test images get an `-rc.N` tag** (`./build.sh 0.313.0-rc.1 --push`). The household's update button takes the HIGHEST
|
||||
tag in the registry, and an `-rc` tag is not a version to it (`util.ParseVersion` wants X.Y.Z) — so it is never offered.
|
||||
**But a box running an `-rc` tag is never moved by the floor either** (`maybeAutoUpdate: current … not parseable —
|
||||
skip`, DEBUG only): after testing, write the release tag back into `/etc/felhom-controller-image` (R-1053).
|
||||
2. **Push the release tag only when it is ready for EVERY box** — from that second every household's button offers it.
|
||||
3. **The three test boxes through the normal update path:** a per-customer floor for Tester 1, demo-hp and demo-felhom at
|
||||
the release, MinAgent declared; read back each box's `docker ps` and its `SetFloor`/self-update log lines.
|
||||
4. **The box walk green on all three** — `python3 scripts/https_only_check.py` included (felhom.eu).
|
||||
5. **Raise the global floor**, MinAgent declared (from the CHANGELOG header): hub → Configuration → Managed updates →
|
||||
floor + `min_agent`, Save; read back from the hub DB with its WAL (`hub_settings.min_controller_version` and
|
||||
`…_declared_min_agent`). **Route (R-1051):** the only way today is the operator page with the operator's own password;
|
||||
until the operator grants a scoped route, this save is the operator's — CC asks for it with the exact values.
|
||||
6. Not at night unless the night brief says so. **Agent releases still stop for the operator's vouch**; never set a floor
|
||||
whose MinAgent is above the vouched agent. The golden is baked weekly (Sunday, `nightly-rotation.md`); CC never vouches.
|
||||
|
||||
## Agent (felhom-agent → felhom-pve)
|
||||
|
||||
Runs as the NON-ROOT `felhom-agent` user: `/usr/local/bin/felhom-agent --config /etc/felhom-agent/agent.json`
|
||||
|
||||
Reference in New Issue
Block a user