# SPIKE 2 — closing the last mechanisms, and the universal ISO's shape (2026-07-31)

> **Class: Spike.** Findings only. No production file changed, no ISO built, nothing published.
>
> Continues `SPIKE-universal-iso-2026-07-31.md`; its results are premises here and are not re-derived.
>
> **All four questions are answered, and every answer that mattered is measured on a real PVE install.**
> The session lost its venue mid-run (§0) and resumed when connectivity was restored.

---

## 0. Venue loss and recovery — what was tried

At 12:28 CEST both Tier 0 boxes went offline **simultaneously**. Routes attempted, in order:

| # | Route | Result |
|---|---|---|
| 1 | `ping 100.76.96.79` (tailnet direct) | 100 % loss |
| 2 | `ssh demo-hp` | `Connection timed out` |
| 3 | `ssh demo-hp-lan` (LAN, ProxyJump `felhom-pve`) | `Connection timed out during banner exchange` |
| 4 | `ssh felhom-pve` (the jump host) | `Connection timed out` |
| 5 | DooPlex tailscale pod | `Running 6d10h`, healthy |
| 6 | `tailscale status` from the pod | **both peers `offline`**, `last seen` rising |

Far end, not ours. **No fallback venue was used** — DooPlex is Tier 2, the 2026-07-25 ruling moved
drill VMs off it, and this task did not authorise it. An absent fence is not permission.

Connectivity was restored (operator: a provider cable fault nearby) and the boxes returned with
**uptime 7 days** — a pure network outage, no reboot. All probes then ran.

**One thing the outage revealed.** `/mnt/nvme-1tb/spike2/` did not exist when demo-hp came back, and
disk usage was unchanged at 6.6 GB: the staging `mkdir` had been inside the SSH call that timed out as
the link degraded, so **the fixture never landed and Layer 1 had nothing to clean**. The interim
report's "Layer 1 teardown INCOMPLETE" was therefore over-cautious rather than wrong — corrected here,
and the fixture was rebuilt from scratch.

---

## 1. Baselines

| Baseline | Value |
|---|---|
| `ISO_VERSION` | `1.25.0` — `scripts/iso/build-felhom-iso.sh:44` |
| `SCRIPT_VERSION` | `1.22.0` — `scripts/felhom-host-install.sh:187` |
| `felhom-bootstrap.sh` @ HEAD | `21bf6a6bde0cb13e3809e2f5c136a49929dcc82eb8d40bbdf6f290a886ee8ab7` |
| PVE base | `proxmox-ve_9.2-1.iso`, `4e88fe416df9b527624a175f24c9aa07c714d3332afb1ee3dbf3879573ef2c6c` |
| `felhom.eu` HEAD | **`ea00976`**, clean, `== origin/main` |
| `proxmox-first-boot` pkg | `9.2.5` (from the ISO's `/proxmox/packages/`) |
| installed guest kernel | `7.0.2-6-pve`, GRUB `2.12-9+pmx2` |

**Fixture provenance.** Probe ISO built on DooPlex from the baseline PVE ISO
(`prepare-iso --fetch-from partition --partition-label proxmox-ais --on-first-boot diag.sh`),
sha256 `dd97dba5518c70baf4300b119e1042eabf1012d5a046082acf31e60983efaeb9`, **verified byte-identical at
both ends** after transfer. Nested VMs ran under plain QEMU/KVM on **demo-hp** (Tier 0, the designated
drill host) under `/mnt/nvme-1tb/spike2/` — never `local-lvm`, no PVE storage registered, `drill-r50`
and guest 9201 untouched.

**Both of Spike 1's fixture defects are corrected in this one:** a NIC is present **at install time**
(its absence made the installed system's interface config unmatchable at boot), and completion is
signalled by `reboot-mode = "power-off"` — the guest powering itself off — never by a changed disk hash.

Three scenarios, all installed to completion:

| Scenario | root credential | `[first-boot].ordering` | Install |
|---|---|---|---|
| **A** | `root-password-hashed = "*"` (locked) | `fully-up` | completed, powered off after **191 s** |
| **B** | `root-password-hashed = "*"` (locked) | `before-network` | completed, powered off after **190 s** |
| **C** | a **known** password (control) | `fully-up` | completed, powered off after **191 s** |

**Evidence classes:** **[MEASURED-PVE]** on a real PVE install · **[MEASURED-MECH]** measured in an
isolated container · **[STRUCTURAL]** read from shipped source or unit files.

---

## 2. Q1 — is a locked root actually locked, and what else does it lock out?

### 2.1 The account's real state — **[MEASURED-PVE]**

From the first-boot diagnostic on both locked installs (A and B), emitted to `ttyS0`:

```
passwd_S_root   : root L 2026-07-31 0 99999 7 -1
shadow_shape    : literal-asterisk
sshd_PermitRoot : yes
pam_common_auth : 1
pve_pam_realm   : 0
```

**The account is genuinely locked** — `passwd -S` reports `L`, and the `*` reached `/etc/shadow`
verbatim. Note `sshd_PermitRoot: yes`: PVE ships `PermitRootLogin yes`, so the lock is doing the work,
not sshd's configuration.

`pve_pam_realm: 0` — there is no PVE-specific file in `/etc/pam.d/` matching `proxmox|pve`, and
`pam_unix` is present in `common-auth`. **PVE uses the stock PAM stack**, which is why the §2.2
container measurement transfers to it rather than merely resembling it.

### 2.2 What can authenticate — **[MEASURED-MECH]**, first attempt invalid

**The first probe produced nothing and is recorded as failed:** it ran everything as uid 0 so `su`
never invoked `pam_unix` (`rc=0` even with a deliberately wrong password), `sulogin` died on
`tcgetattr failed` with no TTY, and a `sed` mangled the control hash. Four cases, zero observations.

Corrected (`debian:trixie`, `util-linux 2.41`, non-root caller, real pty via `script`, `chpasswd`):

| Shadow field | `passwd -S root` | `su root` as a non-root user | `sulogin` (real pty) |
|---|---|---|---|
| real hash (**control**) | `P` | **correct pw → `SU_OK`**; wrong pw → `Authentication failure` | — |
| `*` | `L` | `Authentication failure` (any password) | `Cannot open access to console, the root account is locked.` |
| `!` | `L` | `Authentication failure` (any password) | `Cannot open access to console, the root account is locked.` |

The control makes it readable: the same harness succeeds with the right password and fails with the
wrong one, so the locked rows are the module refusing rather than the harness misfiring. **`*` and `!`
are equivalent.**

**On PVE, root SSH was refused on every scenario** — but that row is **uninformative and is not
evidence**: the probe supplied a deliberately wrong password in the control scenario too, so it cannot
distinguish "locked" from "wrong password". Recorded as a gap, not a result.

### 2.3 Does the Proxmox web interface still work? — **[MEASURED-PVE]. No. It is closed.**

The product-level answer, measured against the **exact endpoint the web UI logs in through**
(`POST /api2/json/access/ticket`, realm `root@pam`), with a working control:

| Scenario | Password offered | Result |
|---|---|---|
| **C (control, known password)** | **correct** | **HTTP 200, TICKET ISSUED — login would SUCCEED** |
| C (control) | wrong | HTTP 401, no ticket |
| C (control) | empty | HTTP 401, no ticket |
| **A (locked root)** | wrong | HTTP 401, no ticket |
| **A (locked root)** | empty | HTTP 401, no ticket |

The control is what makes this a finding rather than a broken probe: **the same endpoint on the same
image demonstrably issues a ticket when the password is right.** On the locked install no password can
produce one, because no password exists.

**Therefore a locked root closes the PVE web interface completely — to the customer and to the
operator alike.** This is a product decision, not a detail: it removes the browser-based route into
the box entirely, on top of the console route (§3.2).

Reachability, incidentally measured on all three: **SSH banner at +20 s, pveproxy TLS at +26–29 s**
after power-on.

### 2.4 Does anything else in PVE break? — **not investigated**

Cron, backup jobs and other consumers of a root credential were not examined. `pveproxy` itself runs
and serves TLS (it answered `/api2/json/version`), so the daemon is unaffected — only authentication
through it is.

---

## 3. Q2 — is there a way back into a box whose setup failed?

### 3.1 Does the installed GRUB expose a recovery entry? — **[MEASURED-PVE]. Yes.**

The installed system's GRUB is PVE's own, unaffected by Felhom's ISO menu collapse (§4). Screendumped
from the locked install:

```
*Proxmox VE GNU/Linux
 Advanced options for Proxmox VE GNU/Linux
 Memory test (memtest86+x64.bin)
 Memory test (memtest86+x64.bin, serial console)
```

and inside *Advanced options*:

```
*Proxmox VE GNU/Linux, with Linux 7.0.2-6-pve
 Proxmox VE GNU/Linux, with Linux 7.0.2-6-pve (recovery mode)
```

**A recovery entry exists, the menu is editable (`e`), a GRUB command line is available (`c`), and
there is no GRUB password.** Its timeout is **1 second** — short enough that a first probe missed it
entirely (§3.5).

### 3.2 `sulogin` with a locked root — **[MEASURED-PVE]. It refuses.**

Booting *(recovery mode)* on the locked install reaches rescue mode and then stops:

```
Started rescue.service - Rescue Shell.
Reached target rescue.target - Rescue Mode.
You are in rescue mode. After logging in, type "journalctl -xb" …

Cannot open access to console, the root account is locked.
See sulogin(8) man page for more details.

Press Enter to continue.
```

**The advertised recovery path is closed on a locked-root box** — verbatim the container result,
now confirmed on real PVE. `systemd` invokes `sulogin` for both `rescue.target` and
`emergency.target`, so both are closed by the same refusal.

### 3.3 What is left — **[MEASURED-PVE]. `init=/bin/bash` works.**

Appending `init=/bin/bash` to the kernel line in the GRUB editor and booting produced:

```
Found volume group "pve" using metadata type lvm2
3 logical volume(s) in volume group "pve" now active
/dev/mapper/pve-root: clean, 60332/561936 files, 1141450/2243584 blocks
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
root@(none):/#
```

**An unauthenticated root shell on a locked-root system.** No password is involved because PID 1 *is*
the shell — `sulogin` is never reached.

| Path | Works? | Realistic for whom |
|---|---|---|
| PVE web console (`:8006`) | **No** (§2.3) | — |
| GRUB *(recovery mode)* → `sulogin` | **No** (§3.2) | — |
| GRUB editor → `init=/bin/bash` | **Yes** (measured) | **Operator only.** Requires catching a 1-second menu, editing a kernel line, and knowing the incantation. Not a non-technical customer action. |
| Live USB / rescue media | not tested | Operator only |
| SSH by key | n/a | Only if a key were baked — which R-142 says must not happen in a public image |

**So a locked-root box is recoverable, but only by someone competent at the physical console.** That is
a materially weaker objection than "no path back at all", which is what the structural reading
suggested before it was measured.

**A security corollary that is worth stating plainly: the installed GRUB has no password, so locking
root buys nothing against physical access.** Anyone at the keyboard can take a root shell in under a
minute. Locked root is a defence against *remote* credential reuse — the shared-ISO-password problem —
and not a physical-security measure. **R-152.**

### 3.4 Does locked differ from rotated? — **[MEASURED]. Yes, at exactly one point.**

- **Locked (`*`/`!`)** — `sulogin` refuses (§3.2); the web UI refuses (§2.3); `init=/bin/bash` works.
- **Rotated to a per-box value** — `pam_unix` accepts the correct password (the §2.2 control proves
  this) and the web UI issues a ticket for it (the §2.3 control proves this), so **both** the console
  and the browser open **to whoever holds it**; `init=/bin/bash` works as well.

**And this is what settles the escrow question.** A rotated password that is *discarded* is
operationally identical to a locked account: nobody holds it, so `sulogin` and the web UI refuse
either way. The only thing escrow buys is that the **operator** keeps the web-UI and `sulogin` routes.
It does not buy recoverability as such, because `init=/bin/bash` is available in every case.

### 3.5 A probe that failed, recorded as failed

The first GRUB attempt waited 3 seconds before acting; the menu's timeout is **1 second**, so the
system had already booted and the keystrokes went into the login prompt — the arrow-key escape
sequences (`^[[B`) are visible there in the screendump. It tested nothing. A later attempt typed
`init=/bin/bash` onto the `echo` line one below `linux`, which the **pre-typing verification
screendump caught** before it was booted. Both were rerun; the numbers above are from the corrected
runs.

---

## 4. Q3 — does the Felhom stub survive `before-network`?

### 4.1 Does it execute? — **[MEASURED-PVE]. Yes.**

The diagnostic emits `@@SPIKE2_HOOK_RAN@@ BEGIN`/`END` to `ttyS0`. On scenario B
(`ordering = "before-network"`) **both markers are present** (count `2`). This is a positive
observable the hook produced, which §4.4 explains is the only acceptable form of proof here.

### 4.2 Does it have what it needs that early? — **[MEASURED-PVE]. Yes, everything.**

### 4.3 Does it complete before anything is network-reachable? — **[MEASURED-PVE + STRUCTURAL]. Yes.**

The A/B comparison, same image, same diagnostic, only `ordering` differs:

| Diagnostic | **A — `fully-up`** | **B — `before-network`** |
|---|---|---|
| hook markers | 2 (ran) | **2 (ran)** |
| `uptime_s` at hook | 18.32 | **6.58** |
| `entropy_avail` | 256 | **256** |
| `etc_writable` | YES | **YES** |
| `openssl`/`chpasswd`/`systemctl`/`passwd`/`curl`/`ip` | all present | **all present** |
| `openssl_rand_len` | 32 | **32** |
| `ip_global` | `vmbr0 10.0.2.15/24` | **(empty)** |
| `network.target` | active | **inactive** |
| `network-online.target` | active | **inactive** |
| `pveproxy` | active | **inactive** |
| `sshd` | active | **inactive** |
| **`listen_22_8006`** | **3** | **0** |

**At `before-network` the hook runs with entropy, a writable `/etc`, every binary it would need, and a
demonstrated ability to generate a 32-character random string — while the box has no IP address and
nothing is listening on `:22` or `:8006`.** A credential rotation placed there has a **zero-length
exposure window, measured.**

Equally, the `fully-up` column is the measured proof of the converse: **today's ordering runs the hook
when sshd and pveproxy are already listening.** The window Spike 1 argued for structurally is now
observed.

The structural half agrees and generalises it. The shipped unit
(`proxmox-first-boot` 9.2.5, `/usr/lib/systemd/system/proxmox-first-boot-network-pre.service`):

```
After=systemd-remount-fs.service
Before=network-pre.target
Wants=network-pre.target
ConditionPathExists=/var/lib/proxmox-first-boot/pending-first-boot-setup
ConditionPathIsReadWrite=/var/lib
Type=oneshot
```

`Before=network-pre.target` + `Type=oneshot` makes it a hard barrier ahead of all network
configuration — so this holds on every machine, not just this VM. Both installs then booted to full
reachability (SSH +20 s, pveproxy +26–29 s), so the early hook does not break the boot.

**A third ordering value exists that Spike 1 did not record.** `validate-answer` accepts
**`before-network`, `network-online`, `fully-up`**; it rejects `network-pre`, `multi-user` and a bogus
value, so this is a real enum. The shipped package contains a matching unit for each.
`network-online` is the unevaluated middle option. **R-149.**

### 4.4 Does the existing pairing flow still work? — **[STRUCTURAL]**, and it is the one gap left

**Not measured.** The scenarios installed the spike's own diagnostic as the first-boot hook, not
Felhom's `stub-first-boot.sh`, so stub-plus-pairing coexistence is argued, not observed.

The structural case is strong and has a correction in it:

**The stub does not need `fully-up`.** `stub-first-boot.sh` writes three files, then
`systemctl daemon-reload`, `enable`, and `start --no-block felhom-bootstrap.service`. A grep for
`pvesh`, `pct`, `pveum` and `qm` across it returns **no match** (`rc=1`). The unit it starts carries its
own `After=network-online.target pve-cluster.service pveproxy.service`, so `--no-block` queues the job
and systemd holds it until those are satisfied.

**So `answer.toml.tmpl:27` justifies the current ordering with a dependency the stub does not have** —
it reads *"fully-up = the stub runs after pveproxy so pvesh/pct work"*, but that usage lives in
`felhom-bootstrap.sh`, which never runs under the stub's ordering. A comment asserting a **dependency**
that does not exist, and it has constrained the design ever since. **R-148.**

**Why §4.1's positive marker is load-bearing.** All four `proxmox-first-boot-*` units are guarded by
`ConditionPathExists=` / `ConditionPathIsReadWrite=`, and in systemd **a failed condition is a skip,
not a failure** — no error, successful boot. A hook that never ran and one that ran perfectly are
indistinguishable from the absence of errors. **R-150.**

---

## 5. Q4 — the boot menu, and which entry is the default

### 5.1 Where the collapse happens — **[STRUCTURAL]**, and it is not a simple removal

`iso-repack.sh` replaces the stock `grub.cfg` wholesale, rendering `grub/grub.cfg.tmpl` over it
(`:144-148`) after lifting the `linux`/`initrd` lines verbatim from the stock *Install Proxmox VE
(Automated)* entry (`:108-131`) so a PVE bump tracks automatically.

**Four independent gates enforce the single entry:**

| Gate | Location | Effect |
|---|---|---|
| exactly one `menuentry` | `:153-155` | `exit 14` |
| zero `submenu` | `:156` | `exit 14` |
| no live `proxtui`, `proxdebug`, `nomodeset`, `Rescue Boot`, `memtest`, `fwsetup` | `:160-164` | `exit 14` |
| **re-extracted from `final.iso`**: one entry, theme present, entry named `Felhom telepítés` | `:314-319` | `exit 15` |

The fourth proves the menu in the shipped image rather than the build tree — the seam-wiring lesson
applied correctly.

**What a second entry would cost:** the two count gates move from `== 1` to `== 2`, or to a named
allowlist. **The banned-token gate passes unchanged** for a stock *Install Proxmox VE (Graphical)*
entry — it uses none of those six tokens. The safety property the gates exist for (no route to a
debug/rescue/manual-disk-picker path) survives a second graphical-installer entry intact.

### 5.2 Are default and timeout settable? — **[STRUCTURAL]. Already are.**

`grub/grub.cfg.tmpl` sets `set timeout_style=menu`, `set timeout=5`, `set default=0`. The template also
records a real defect it fixed: the stock PVE config writes `timeout-style` (hyphen), **which GRUB does
not recognise as a variable name**, so Felhom's underscore spelling is the one that works.

### 5.3 The design question — **framed with evidence, deliberately not ruled**

**For interactive-as-default.** Spike 1 proved the automated path cannot be made safe on unseen
hardware: no property distinguishes a system disk from a customer's backup drive, and a filter matching
two disks silently wipes one. A tester who boots and walks away on multi-disk hardware is walking away
from an unattended destructive operation whose target cannot be predicted.

**This session strengthens that case independently.** If the public image also ships a locked root,
then a box whose install succeeds but whose pairing fails has **no web UI** (§2.3) and **no working
recovery-mode shell** (§3.2) — only an operator-grade `init=/bin/bash` (§3.3). An unattended default
maximises the number of testers who reach that state without having read anything.

**Against.** Unattended install is a selling point and most first boxes are single-disk, where the
automated path is fine.

**Operator-built and public need not match, and two profiles from one build is cheap.** An
operator-built image targets known disks, so automated-default is safe there; the public image is the
one facing unseen hardware. That split is available however the default is ruled.

---

## 6. Q5 — the resulting shape, as constraints

Constraints on a build spec, each with a release-gate criterion. **Not an implementation.**

| # | Constraint | Release gate |
|---|---|---|
| 1 | **No disk selection by property on unseen hardware** (Spike 1 §3.2/§3.3). | Rendered answer contains no `filter.*` key, **or** the image is operator-built for a named machine. |
| 2 | **The interactive installer must be reachable from the boot menu** (§5.1). | `final.iso`'s `grub.cfg` has ≥1 interactive entry **and** still no live `proxtui`/`proxdebug`/`nomodeset`/`Rescue Boot`/`memtest`/`fwsetup` — keep gate 3, relax gates 1 and 4 to an allowlist. |
| 3 | **No usable baked credential** — `root-password-hashed = "*"` installs cleanly (§1, three scenarios). | Rendered `root-password-hashed` is exactly `*`, **and no `.rootpw.txt` is emitted**. Two one-line assertions. |
| 4 | **Any per-box password is set at `before-network`** — the only ordering with a measured zero window (§4.3). | Rendered `[first-boot].ordering` is `before-network`, **and** the hook's positive marker is observed in a boot test (§4.4, R-150). |
| 5 | **No `root-ssh-keys`, ever, in a public image** (R-142). | `grep -c root-ssh-keys` on the rendered answer is `0`. |
| 6 | **The profile must be committed** — demo-felhom's baked key comes from an *uncommitted* profile, so a reference box is not reproducible from `main`. | `--profile` resolves inside the repo tree **and** `git status --porcelain` is empty for it. **R-151.** |
| 7 | **The artefact must be reproducible** (R-144: `nested-probe`'s profile is gone). | Manifest records the committed profile path + repo commit; both resolve. |
| 8 | **The recovery path must be documented, and named operator-only if it is.** §2.3 + §3.2 + §3.3: with a locked root the only way in is `init=/bin/bash` at the console. | Release notes state it, in those terms. |

**What the image must not contain:** a usable root password hash, any `root-ssh-keys`, any customer id,
claim code, retrieval passphrase or hub token, and any `filter.*` selector in the public variant.
Spike 1 §6 established by enumeration that `/answer.toml` is the only secret-bearing file, so gates 3
and 5 over that one rendered file cover the credential surface.

---

## 7. The operator's decisions

**Decision 1 — escrow or discard a per-box root password.** §3.4 reshapes this twice over:

- **Discard and lock are the same thing** for recovery — if nobody holds the password, both `sulogin`
  and the web UI refuse.
- **Neither makes a box unrecoverable.** `init=/bin/bash` works in every case (§3.3), so an operator at
  the console can always get in. The earlier fear that locking root leaves "no path back at all" is
  **measured to be false**.

So what escrow actually buys is narrower than it looked: **the operator keeps the web-UI route (§2.3)
and the `sulogin` route (§3.2) without needing physical presence.** Against that stands D6 — a hub
holding console root for the whole fleet is exactly what the hub alone must not be.

**Recommendation, offered because the measurements now support one:** discard/lock for the **public**
image, and accept operator-only physical recovery, because the credential that would be escrowed is
the same one whose fleet-wide sharing started this whole line of work. Escrow remains defensible for
**operator-built** boxes, where the operator installed the machine anyway. The decision is still the
operator's; it is no longer blocked on a measurement.

**Decision 2 — the default boot entry for a public image.** Framed in §5.3, unruled. The disk evidence
and the recovery evidence now point the same way (interactive), and the cost is the unattended
experience. The operator-built-versus-public split is cheap and available either way.

---

## 8. What is still unknown, and what it would take

| # | Unknown | What it takes |
|---|---|---|
| 1 | **Felhom's real stub at `before-network`** (§4.4) — coexistence with pairing is structural, not observed. The single remaining gap in Q3. | Build a probe ISO with `stub-first-boot.sh` as the hook and `ordering = "before-network"`, boot, confirm `felhom-bootstrap.service` still starts and pairing completes. ~40 min. **Do this before the spec.** |
| 2 | **Whether root SSH is refused *because* of the lock** (§2.2) — the probe used a wrong password in the control too, so it discriminates nothing. | Re-run the SSH leg with the control's correct password. ~10 min. |
| 3 | **What else in PVE assumes a usable root credential** (§2.4) — cron, backup jobs. | Inspect a locked install over a longer uptime. |
| 4 | **What `network-online` ordering buys** (§4.3) — a real third option, never evaluated. | One extra scenario. |
| 5 | **Whether the multi-match pick is deterministic by enumeration order** (Spike 1, still open). | Re-run Spike 1's S1 with reversed disk order. ~15 min. |
| 6 | **Whether the installer excludes its own boot medium** (Spike 1, still open). | USB boot on real hardware — HUMAN. |
| 7 | **Whether a relaxed-gate two-entry menu actually boots the stock installer** (§5.1). | Not attempted — it would mean changing repo files, which §4 forbids. |

---

## 9. R-rows opened

Continuing from R-147 (Spike 1).

| R | Severity | Finding |
|---|---|---|
| **R-148** | MEDIUM | **`answer.toml.tmpl:27` justifies `ordering = "fully-up"` with a dependency the stub does not have.** `stub-first-boot.sh` contains no `pvesh`/`pct`/`pveum`/`qm` call (grep `rc=1`); that usage is in `felhom-bootstrap.sh`, under its own `After=network-online.target … pveproxy.service` unit. A comment asserting a **dependency** rather than an invariant — and it blocked the ordering that §4.3 now measures as giving a zero credential window. |
| **R-149** | LOW | **`[first-boot].ordering` has three values, not two** — `before-network`, `network-online`, `fully-up` validate; `network-pre` and `multi-user` are rejected. The middle option has never been evaluated. |
| **R-150** | MEDIUM | **A first-boot hook that never runs is indistinguishable from one that succeeded.** All four `proxmox-first-boot-*.service` units are `Condition*`-guarded, and a failed condition is a systemd **skip, not a failure** — no error, successful boot. Any credential-rotation-in-first-boot design must be verified by a positive marker the hook emitted. |
| **R-151** | MEDIUM | **A reference box is not reproducible from the repository.** `demo-felhom` was installed from an ISO built with the **uncommitted** `n100-demo.profile`, which is where its baked SSH key comes from (Spike 1 §4.6). Gate 6 in §6 exists to stop a public image inheriting that shape. |
| **R-152** | LOW | **A locked root is not a physical-security measure, and should never be described as one.** The installed PVE GRUB has **no password** (§3.1), so `init=/bin/bash` yields an unauthenticated root shell in under a minute (§3.3, measured). Locking root defends against *remote* reuse of a shared ISO credential and nothing else. If a physical-access threat model is ever wanted, it needs a GRUB password, which is a separate decision with its own lockout risk. |

---

## 10. Teardown

All three layers, each verified positively.

**Layer 1 — demo-hp.**

| Check | Result |
|---|---|
| QEMU processes | `0` |
| `/mnt/nvme-1tb/spike2/` | removed — `No such file or directory` |
| `/tmp/paia` | removed |
| loop devices / `/mnt/s2ans` | `0` / removed |
| `/mnt/nvme-1tb/` contents | `dump e2d-images felhom-data images lost+found` — the pre-spike set |
| disk usage | **6.6 G — identical to pre-spike** |
| `drill-r50` (VM 300) | `stopped`, untouched (R-93 fixture intact) |
| guest 9201 | `running`, untouched |
| `/etc/pve/storage.cfg` | **4 storages, unchanged**; no scratch storage ever registered |
| `local-lvm` | never written |

**Layer 2 — demo-felhom.** `/tmp/paia` removed; guest 9201 `running`; the external USB drive
`931.5G usb /mnt/hdd_1` still attached and mounted exactly as found — never attached, detached,
partitioned or written. Only `device-info` reads were ever performed there.

**Layer 3 — DooPlex.**

| Check | Result |
|---|---|
| scratchpad | **1.7 GB → 60 K** (probe ISO, screendumps, squashfs/`.deb` extracts, schema test dirs) |
| control password (`s2/.ctlpw`) and `scen/C.toml` | `shred -u`'d / removed; verified absent. Never printed, never committed |
| mechanism-probe container | `docker run --rm`; **no global Docker cleanup run** (fence) |
| `felhom-iso/out/` | untouched, **17 ISOs** |
| production files | none modified — no `build-felhom-iso.sh`, no `iso-repack.sh`, no profile, no `felhom-bootstrap.sh` |

**Hub-side disposition — nothing to dispose.** No customer, host or appliance was created: the nested
VMs ran under plain QEMU with user-mode networking and no route to the hub, and no install ever
contacted it. **R-131 gains no row.**

**Published artefacts: none.** Nothing went to R2.
