R-840/R-859/R-860 records: 11 §5.3.1 + §5.4.2, 03, 04, 00; runbooks config-bundle + os-updates-test-waits; register 333→334 (R-840/859/860 closed, R-861/862 opened); STATUS; report; evidence
gates / gates (push) Successful in 31s

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-10-04 20:40:08 +02:00
parent c01d48f457
commit 79f07a7f10
18 changed files with 478 additions and 10 deletions
@@ -211,6 +211,17 @@ downloadable — but `curl`, `libcurl*` and `libssh2` were "not covered" in the
ALREADY ran the newer version and so installed nothing. `[PROPOSAL]` step 1 reports the full installed
`package=version` set after the run, and approval covers every version ring 0 runs healthy.
### 5.3.1 Test approvals end with the test — BUILT 2026-10-04 (hub v0.133.0, R-859) `[FACT]`
An approval made while a TEST override (`OS_APPROVE_AFTER`, `OS_APPROVE_NIGHTS`, `OS_DOCKER_APPROVE_NIGHTS`) is active
carries a `test` mark (amber on the System page). At every hub start WITHOUT an override, every test approval that no
real approval has superseded is cancelled: never served again, ring-1 boxes bumped, one operator event
`os_release_cancelled` each; what boxes installed stays; the ruled wait approves the same set again as a real release.
A one-time backfill marked the AUTOMATIC approvals made under 24 h after first seen; the 2026-10-04 guest and host test
approvals (which Tester 2 installed on its first night) were cancelled at 18:20 UTC. The operator's Docker button
approval of that day stays in force (decided by CC unattended — operator may reverse). Runbook:
`runbooks/os-updates-test-waits.md`. Evidence: `audits/r840-config-bundle-2026-10-04/partD/`.
### 5.4 Who runs it, and with what permission
- **The agent runs every OS update**, for the host and for the guest (`pct exec`). The controller does
@@ -289,6 +300,46 @@ os-apply: FAILED rc=<n> step=<download|install> — dpkg state: <dpkg --audit fi
`restart-needed` lists processes still mapping deleted libraries (C11); `reboot-needed` is yes when that list holds
PID 1 or `lxc-start`, or a kernel was installed.
### 5.4.2 The config bundle: a box's root-owned files by a signed job — BUILT 2026-10-04 (agent v0.143.0, hub v0.133.0, installer 1.31.0, R-840) `[FACT]`
Decision 96. Before it, the signed `agent_update` replaced only the binary; wrappers, units and sudoers lines reached
an installed box by reinstall or by hand.
- **One source of truth.** `BUNDLE_FILES` in `felhom-os-apply` is the ONE table of root-owned paths (22: sudoers ×2, the
five wrappers, the crash guard and its units + config, the agent and rollback units, the start-limit drop-in, the mgmt
watchdog and its tmpfiles/units, the OOB belt's four files). `scripts/build-config-bundle.py` builds the bundle from it
reproducibly; `release-agent.sh` publishes it beside the binary; the hub vouches its sha with the agent (exact-name
lookup); the installer (1.31.0) installs it through the same code (`--install-bundle`, root only, refused through
sudo). A test fails on a root path the installer names that the bundle lacks.
- **The route.** Signed `agent_config_update` {agent_version, bundle_sha256}. The agent is a courier; the root wrapper
re-verifies the signature against the root-owned signers file, the host binding (`os-trust.json`), the window and its
own nonce, then the sha, every path (R16) and every content check before the first write: `visudo -cf`, `sh/bash -n`,
Python compile, unit sections, no `RuntimeDirectory=` (G1), `User=felhom-agent`, `nft -c`, and that the route
survives (the sudoers keeps the `--plan` line; the new wrapper keeps the bundle mode). Policies: replace / if-absent
(`crash-guard.conf`, an operator setting) / oob (only on a box with the belt). Atomic per file, sudoers last,
previous copies kept (last 3). Self-check: `visudo -c`, `sudo -l -U felhom-agent` lists the route, the new wrapper's
`--self-check`, the self-update wrapper's usage, the crash guard's status equals `kernel.panic`; any failure puts
every previous copy back. A newly installed crash guard is started (`enable --now`): `kernel.panic` for this boot, no
reboot. Record `/etc/felhom/config-bundle.json`; the agent reports it (`system.config_bundle`, "none" when absent);
the facts mode adds drift (files changed by hand).
- **The trust root is not changed by a bundle** (R17, tested). A missing signers file is created only with the
installer's pinned key, only after a job that key signed (pinned equal to the installer by a test). Signer rotation is
a later, separate act (`04` §3).
- **Bootstrap (corrects the brief).** The self-update wrapper cannot install a bundle (fixed sh, binary-only), and no
signed job can write a root file on a box whose `felhom-os-apply` predates 0.143.0. Such a box needs ONE by-hand step
(`scripts/felhom-bundle-bootstrap.sh`: only the new `felhom-os-apply`). Done on both demo boxes; Tester 2 needs the
operator (`runbooks/config-bundle.md`).
- **Visibility.** System page "Root files" column; alarm `os_config_bundle_behind` after 7 days
(`OS_ALARM_BUNDLE_BEHIND_AFTER`; decided by CC unattended — operator may reverse).
- **Measured live** (`audits/r840-config-bundle-2026-10-04/partB/`): both demo boxes had every file equal to the release
except `felhom-os-apply`; after the bootstrap the signed bundle wrote 0 of 22 (21 same, 1 setting kept), self-check
ok, capability probe 71/71. demo-hp: a wrong-sha job refused (nothing changed); a bundle with one deliberate change
wrote exactly that file; the 0.143.0 bundle undid it; a replayed job was rejected. The installer path on demo-felhom:
0 written, services active. 22 of 22 wrapper rules red-proved.
- **Not a boundary yet — R-861.** The agent's sudoers already lets the agent user reach root without the operator key
(a hookscript, a boot unit, the escrow self-test run as root, the self-update of its own binary). The trust-root rule
is defence in depth until R-861 is closed.
### 5.5 When
Inside the household's night window, after the backups:
@@ -472,6 +523,13 @@ Each step returns to the operator for go or no-go.
§8.2.
4. **Fleet view and alarms** (§5.7). **BUILT 2026-10-04** — hub v0.131.0/v0.131.1; §8.3.
5. **Slow lane: Docker engine.** **BUILT 2026-10-04** — agent v0.142.0, hub v0.132.0; §5.8.
**Root files to installed boxes (R-840): BUILT 2026-10-04** — agent v0.143.0, hub v0.133.0, installer 1.31.0; §5.4.2.
**Test approvals end with the test (R-859): BUILT** — hub v0.133.0; §5.3.1. **The golden carries the approved guest
release** (`build-golden.sh` 3.2.0 `GOLDEN_GUEST_PKGS`; golden 0.293.0 baked with none in force — 49 Debian updates
pending for the next real approval; the host stays with its first night's OS leg). **A host pass at install time is
not needed:** measured on Tester 2, the agent's first OS leg ran right after the box's FIRST whole-guest backup, 17 min
after enrolment (16:24/16:25 UTC: 49 guest + 106 host packages, 110 s + 44 s, healthy). An installer pass would cost
~45 s and run BEFORE any whole-guest backup exists (no undo) — not built.
6. **Slow lane: host kernel and Proxmox packages, with the reboot.**
7. **Later:** the Proxmox major upgrade (PVE 9 → 10), drilled on ring 0 first.