docs(report): TASK-F - lifecycle, HP USB byte-proof, R-55 live leg, Part 4 skip

This commit is contained in:
2026-07-21 16:37:25 +02:00
parent 3b41a982fe
commit 0073691231
+94 -123
View File
@@ -1,147 +1,118 @@
# REPORT — TASK-E: pre-invite sweep + HP mini ISO (2026-07-21)
# REPORT — TASK-F: app lifecycle + HP USB write + R-55 live leg (2026-07-21)
Baselines at start (all verified, all clean): controller `83f20c8` v0.156.0, agent `935904f` v0.92.1,
felhom.eu `d4ac52d` hub v0.68.1, app-catalog `34d50a3`.
Baselines at start (verified, all clean): controller `a96226a` v0.157.0, app-catalog `857ba53`,
felhom.eu `0b2c598`, agent `eba040d` v0.93.0 (source only — untouched this task).
## Per-part status
| Part | Status |
|---|---|
| 1 — app lifecycle states | **DONE + LIVE-PROVEN** — controller v0.158.0, plus **v0.158.1 fixing a defect I shipped** |
| 2 — HP USB write | **DONE — byte-proven** |
| 3 — R-55 live leg | **DONE — PROVEN LIVE** |
| 4 — resolvability table | **SKIPPED by operator ruling** (no Docker Hub login) |
| Part | Status | Ships as |
|---|---|---|
| 1 — escrow wordlist | **DONE** | agent **v0.93.0** (not built/deployed; rides the next train) |
| 2 — R-55 stop-honesty | **DONE — scope corrected by operator ruling** | controller **v0.157.0**, deployed to 9201 |
| 3 — catalog honesty | **DONE, with one ruling** | catalog: wanderer re-pinned, plant-it retired, R-41 slice 1 |
| 4 — operator one-pager | **DONE** | `documentation/PROMPT-TEMPLATE.md` |
| 5 — §6 leftovers | **DONE** | controller **v0.157.1** + `scripts/build-hub.sh` **v1.23.0** |
| 6 — HP mini ISO | **DONE, 2 artifacts not 3** (see below) | `v1.22.0` hp ISOs, both loaders |
| 7 — rulings + R-17 | **DONE** (R-17 execution is operator-console) | ROADMAP + capability map |
## Part 2 — the HP boot stick
Two questions were put to the operator rather than guessed; both are recorded where they landed.
Written on felhom-pve under a format-operation evidence bar. **Two USB-shaped devices were present,
and that turned out to matter:**
## Part 6 — the HP mini ISOs
- **`/dev/sdb` — ADATA HD710 PRO, 932G. EXCLUDED. It is the live customer data drive**, mounted at
`/mnt/hdd_1` and bind-mounted into guest 9201 (`mp8`) — immich and calibre-web data. Its by-id
path is `usb-ADATA_…`, but **`udevadm` reports `ID_BUS=ata` and sysfs `removable=0`**, so the two
required positive checks exclude it on their own. A bare-`/dev/sdX` workflow would have offered
`sdb` and `sdc` side by side with nothing but a letter between them.
- **`/dev/sdc` — Samsung Flash Drive, serial `0375123010001586`, 119.5 GiB. The target.**
**Version: rebuild at `v1.22.0`, no bump.** Phase-0 probe confirmed **no functional change to the ISO
tooling since the v1.22.0 artifacts** were built on 2026-07-19: the last content commit before that
build was `8feed6d` (08:57:51, build at 09:00), and everything since is docs or `chmod +x` mode bits.
`ISO_VERSION` tracks the Felhom release the ISO is tagged to; adding a build profile does not move it,
and the profile name already makes the artifacts unmistakable, so no date-stamping was needed.
Checks, all passed: `ID_BUS=usb`; `removable=1`; absent from `findmnt` (device and every partition);
absent from `/etc/fstab`; not an LVM PV (only `/dev/sda3`); no ZFS pools exist on the host; not
referenced by any guest config; 0 open handles. Destroyed content: an old `iso9660` "PVE" install
image. **Operator confirmed the printed device before any byte was written.**
New: `scripts/iso/profiles/hp.profile`, committed. It mirrors `n100.profile` with **one deliberate
difference — the loader default stays at the stock signed `shim`.** `mkimage` exists to work around
the N100's AMI firmware GRUB relocation defect; it is not a Felhom requirement, and it costs Secure
Boot (it is unsigned). HP business firmware should take shim.
| ISO | sha256 | bytes |
|---|---|---|
| `felhom-pve-9.2-1-v1.22.0-hp-generic.iso` (**shim**) | `2b51d74c0eaf5155fd42a137dff803b9ab8692294cedcb727c9a80658c9f7823` | 1 705 338 880 |
| `felhom-pve-9.2-1-v1.22.0-hp-generic-mkimage.iso` (**mkimage**) | `09700442c297edcf81bdb9d6fddd14e79ba62091e7c2f183fa687c513f061142` | 1 704 482 816 |
Both in `180:/mnt/5_hdd/felhom.eu/felhom-iso/out/` with `.sha256` + `.manifest.txt`. Build logs in
`felhom-iso/logs/build-hp-{shim,mkimage}-2026-07-21.log`. Input PVE ISO `proxmox-ve_9.2-1.iso`,
sha256 `4e88fe41…`. Both manifests: `secret-bearing : no`.
**`validate-answer` was gated on OUTPUT text, not `$?`** — the documented trap (it exits 0 on
failure). Both logs show `The answer file was parsed successfully, no errors found!` through the
`gate_validate_answer` output-parse gate.
### Two artifacts, not three — and why that is the right answer
TASK-E asked for a shim pairing ISO, a mkimage pairing ISO, and a separate `hp-safety` match-nothing
ISO. **Both ISOs above ARE safety ISOs.** The `n100-safety` precedent is not a separate flag: it is
the committed profile built as-is, because that profile ships a match-nothing disk filter. A third
artifact would have been a byte-for-byte duplicate of one of these.
Arming the real install requires the target SSD's `ID_SERIAL_SHORT`, which **cannot exist until the
board is in hand** — inventing it is exactly the input the conventions say to ask about rather than
guess. So all HP artifacts are deliberately inert, which satisfies "the first thing the new board ever
runs must be provably harmless" more strongly than the three-artifact split would have.
**Verified from INSIDE both ISOs, not merely from the profile** — the embedded `answer.toml` extracted
with `osirrox` reads:
The identity was **re-asserted at write time**, not only at survey time, in case of re-enumeration
between the confirm and the write.
```
[disk-setup]
filesystem = "ext4"
filter-match = "all"
filter.ID_SERIAL_SHORT = "REPLACE-WITH-TARGET-SSD-SERIAL"
1705338880 bytes (1.7 GB, 1.6 GiB) copied, 55.9 s, 30.5 MB/s
device sha256: 2b51d74c0eaf5155fd42a137dff803b9ab8692294cedcb727c9a80658c9f7823
iso sha256: 2b51d74c0eaf5155fd42a137dff803b9ab8692294cedcb727c9a80658c9f7823
RESULT: MATCH - byte-proven
```
### Operator boot cheat-sheet — HP mini
`dd … oflag=direct conv=fsync`, then the ISO's exact byte-length read back **from the device** and
hashed — byte-proof, not exit-code-proof. The source ISO's sha256 was verified after transfer to
felhom-pve and matches the published hash. The customer data drive was re-checked after the write and
is still mounted at both its paths.
1. **Flash `…-hp-generic.iso` (shim) FIRST.** Leave Secure Boot **ON**. Expect: GRUB loads, the Felhom
boot screen appears, the installer starts and then **aborts having touched no disk**. That is
success — it proves the loader and the firmware setting before anything destructive exists.
2. **If it fails at GRUB** — a `relocation` error, "no bootable device", or a hang before the boot
screen — that is the N100's F1 defect on this board too. Switch to
`…-hp-generic-mkimage.iso` **and set Secure Boot OFF** in firmware. mkimage is unsigned; with
Secure Boot enforcing the firmware refuses it outright ("Access Denied" — proven, RUNBOOK-B leg 3).
3. **The safety ISO always precedes the real one.** Never boot an install-armed ISO on this board
until one of the two above has been observed to abort cleanly.
4. **To arm the real install**, tell CC which loader worked and give it the internal SSD's
`ID_SERIAL_SHORT` (`lsblk -o NAME,SERIAL` from any live boot). **Confirm it is the internal SSD and
not an attached backup drive** — that check is what protected the N100's external HDD on the
rehearsal run. CC copies `hp.profile` to an uncommitted working profile with the real serial (the
`n100-demo.profile` precedent — a real serial is never committed) and rebuilds.
5. **No remote install step was attempted.** The boot is physically yours.
### Boot cheat-sheet (unchanged, restated)
## Part 4 — the operator one-pager
1. **Boot the stick with Secure Boot ON.** This is the shim variant. Expect the Felhom boot screen,
then the installer **aborting having touched no disk** — that is success.
2. **On a GRUB failure** (`relocation`, "no bootable device", a hang before the boot screen): use
the **mkimage** ISO and set **Secure Boot OFF**. It is unsigned; SB-enforcing firmware refuses it.
That ISO is at `180:/mnt/5_hdd/felhom.eu/felhom-iso/out/…-hp-generic-mkimage.iso` and is not on
this stick.
3. **Both artifacts are inert** (match-nothing disk filter). The real install ISO does not exist yet
— it needs the HP's internal SSD serial, which needs the board in hand. Send me the serial and
which loader worked, and I will build it.
4. **The first boot is yours.** No remote install step was attempted.
`documentation/PROMPT-TEMPLATE.md` gains a standard section between the header and §0. Mandatory for
M+ tasks and anything carrying a STOP; guidance covers audience, the four things it must say in order,
and the rule that it is a summary and never the only place a requirement appears. TASK-B/C/D cited as
precedent. §10's seam-discipline row was **verified present** (line 229) and not duplicated.
## Part 1 — app lifecycle (summary; detail in `felhom-controller/REPORT.md`)
## Part 5 — both §6 leftovers
`available` / `hidden` / `abandoned` as a `.felhom.yml` field, because the `retired/` directory move
shipped hours earlier would have orphaned every customer running a withdrawn app. Deploy gate is
server-side and fail-closed; unknown values fail open; orphan detection never sees the field.
- **`.gitignore` anchor** → controller v0.157.1. Details and the both-directions verification are in
`felhom-controller/REPORT.md`.
- **The hub `build.sh` was not in any repo.** Adopted as `scripts/build-hub.sh` **v1.23.0**: build
logic byte-identical (verified by diff), plus a `SCRIPT_VERSION` and a header naming the split. The
operative path `/mnt/5_hdd/felhom.eu/build/felhom-hub/build.sh` is now a **symlink** to the repo
copy — one source of truth, repo edits live immediately (the `install_skills.py` pattern). Previous
copy retained as `build.sh.pre-adopt-2026-07-21`.
Live on 9201 through the real endpoints: plant-it renders **0 times** on the catalog page (control
app 10), a direct deploy POST returns **409 „Ez az alkalmazás jelenleg nem telepíthető."**, the app
page carries the permanent notice with no Telepítés button.
## Part 7 — rulings recorded
**v0.158.0 shipped a defect that I caught during live validation, not from the suite:** the lifecycle
predicates had pointer receivers, and html/template cannot call those on the non-addressable value
the handler passes — so **every `/apps/<slug>` returned 500**, for every app, while compiling cleanly
with a fully green test suite. Fixed in v0.158.1. The lesson is recorded in the controller report and
the R-57 row: **a template method call is only checked when that template actually runs**, so a
render test is the only coverage for template-facing methods.
R-25b (full-teardown cascade, three acks + typed name, **re-sized S → M**, spec to follow, no longer
blocks R-3) · R-11 (direct Messenger; the doc is the architect's) · R-42 (option (a)) · R-4 (**COMPLETE**
— freemail.hu operator-verified) · R-55 + R-41 slice 1 marked shipped · **new R-56** (app difficulty
classification — the constructive half of the glance ruling; glance stays). Capability map updated on
the boot-orphan row and the escrow-ceremony row.
## Part 3 — R-55 live leg, PROVEN
### R-17 — spike-lite result: it CANNOT be done through tooling
immich (drive-backed) stopped through the **real UI endpoint**`compose down` → 0 containers;
calibre-web (drive-backed) and bookstack (not drive-backed) left running; `pct reboot 9201`.
Ruled DELETE. CC did **not** improvise credentialed API calls, and found there is no tooling path:
> `14:29:28 [gate] … live bind confirmed — recreating drive-backed app calibre-web … onto /mnt/felhom-drives/hdd_1`
> `14:29:29 [gate] … 1 drive-backed app(s) left stopped — zero containers means the customer stopped them on purpose`
- `felhom-tenantsync.sh` does not touch Hetzner at all — PBS namespaces/tokens only.
- The hub **can** delete sub-accounts (`hetznerapi.DeleteSubaccount`), but its only caller is
`offsite.Deprovision`, which selects by the `felhom-customer=<id>` label as part of a customer RESET.
A legacy archive sub-account from the 2026-07-09/10 spikes carries no such label, so no verb targets it.
- The only in-repo references are **test fixtures** (`hub/internal/hetznerapi/fake.go` generates the
`u629193-sub%d` pattern; `dr_recipe_test.go` uses it as a literal). Nothing live points at it.
**immich came back STOPPED.** Hours earlier the identical fixture brought it back running — same
experiment, opposite result. calibre-web recreated, bookstack back, `[bootrecon] no boot-orphaned
apps`, **zero alerts**, reboot → steady state in **~15 s**. The `left stopped` line fired in
production for the first time, so the honoured path is observable rather than silent.
**Operator console action:** Hetzner Storage Box console → box `u629193` → Sub-accounts →
`u629193-sub1` → confirm the directory holds only the old-box archive → Delete. The 9/9 byte-identical
restores are the sign-off; deleting the sub-account destroys its data.
## Part 4 — skipped
## Observations (recorded, not acted on)
Operator ruled no Docker Hub login. The gate therefore still exits 2 (INCONCLUSIVE) on a full sweep
rather than producing anonymous throttled accusations — which is the designed behaviour, not a
failure. One `docker login` on DooPlex plus
`python3 scripts/check-image-resolvable.py` closes it whenever convenient.
1. **A gate I shipped nearly shipped a lie, and it is the most transferable thing here.** The first
full catalog resolvability sweep reported **24 of 65 pins dead**, including `postgres:16-alpine`
and `redis:7-alpine`. They are fine — Docker Hub had started throttling. Worse, `docker manifest
inspect` prints `toomanyrequests: …` and **still exits 0**, the identical exits-0-on-failure shape
as `validate-answer` in the ISO tooling. Two independent instances of the same trap now sit in two
different toolchains; it is worth treating "does this tool signal failure through its exit code?"
as a standing question rather than a per-tool footnote. The gate now resolves ambiguity to
INCONCLUSIVE, never to an accusation.
2. **The full 52-app resolvability table is still owed.** DooPlex is not logged in to Docker Hub, so
the sweep exits 2 (incomplete) rather than producing the table. One `docker login` + re-run closes it.
3. **R-55's live reboot leg is not run** — see `felhom-controller/REPORT.md`. Shipped and wired, not
yet proven live.
4. **wanderer is re-pinned but never deployed.** It went from 1 service to 3 with a second public
hostname; it deserves a real deploy on the demo box before anyone is invited to it.
5. `reuse_refs_check.py` resolves REUSE.md citations against the repo root and so cannot follow a
cross-repo pointer; the catalog's `healthprobe.go` citation still flags. Path corrected; the
checker limitation is pre-existing.
6. **The `felhom-testing` skill's "known flake" line was actively harmful** — it instructed every
future session to dismiss a true positive, for weeks. Removed, and generalised: "known flake, just
re-run it" is a diagnosis and needs evidence like any other.
## Remaining
- **Agent v0.93.0 is source-only** — the recovery-code wordlist fix is committed but unbuilt and
unpublished; it rides the next agent train. Nothing on any host runs it yet.
- **R-17** — deleting the `u629193-sub1` archive is an operator **console** action; no tooling verb
targets it. Exact steps are in the previous session's report.
- **HP first boot** is physically yours (above).
- **The full catalog resolvability table** is still owed (Part 4).
- **wanderer** was re-pinned last session to a 3-service upstream shape and has never been deployed.
- **`hidden`** lifecycle state has no catalog user yet; only `abandoned` is exercised end to end.
## Observations
1. **I pushed a commit that does not build.** A `git stash`/`stash pop` used to compare
`reuse_refs_check.py` output silently discarded the staged index, so the first lifecycle commit
carried only the new test files. Repaired with a follow-up commit rather than a force-push —
rewriting already-pushed history is the worse trade. **Never `git stash` mid-staging.**
2. **The stale-credential trap bit again, and the memory note already described it.** The controller
password in `~/.config/credentials` is wrapped in **single** quotes; I stripped only double
quotes, sent the quotes as part of the password, and concluded the credential was stale. It was
not. Strip both quote styles.
3. **Two USB devices, one of them live customer data**, is the case the `ID_BUS=usb` + `removable=1`
rules are actually for — the dangerous device carried a `usb-…` by-id name and would have looked
plausible in any listing that showed model and size alone.