Appended section only; the existing content is unchanged. Separate from de5a3e5
because a commit cannot contain its own hash and the spec asked the report to
name it.
Records the four XS edits, and why documentation/backlog/README.md was NOT
touched: the proposed ROADMAP/OPEN-ITEMS state convention rests on a claim that
P3's Status column never takes READY/BLOCKED/WAITING-ON-OPERATOR. It does —
R-90 (BLOCKED), R-91 (WATCHING), R-110 (WAITING-ON-OPERATOR), R-86 (NEXT), plus
nine bare READY rows in P2's recovery-gaps sub-table. Writing it down would have
added a new false invariant to the chain that exists to remove them.
13 KiB
REPORT — installer-channel record correction + R-29 filing (2026-07-29)
Two commits, documentation only. No code, no version bump, no CHANGELOG entry, no build, no deploy,
no box touched. Written as REPORT-<topic>.md per CLAUDE.md:82-87 so root REPORT.md (the E-2
increment-1 report) is preserved.
| # | Commit | Baseline | Scope |
|---|---|---|---|
| 1 | d4c07873ca0c3d3e547373a9fafc0e472a6535e8 |
36d635a4cdc1, unmoved |
Retract a false R-94/E-2d finding; open R-110 |
| 2 | (this commit) | d4c07873, unmoved |
File R-29 to the register; three record defects; this report |
Commit 1 — d4c07873
What was false
36d635a4 recorded that felhom-bootstrap.sh fetches the installer from the hub, that the hub
therefore serves 1.19.0, and that a fresh ISO install runs the pre-E-2 installer. All three wrong.
The claim had propagated into two OPEN-ITEMS.md rows, the ranking rationale, and ROADMAP.md:149.
Confirmation table — all PASS
| # | Claim | Read at | Result |
|---|---|---|---|
| F1 | bootstrap fetches from the website | scripts/iso/felhom-bootstrap.sh:96 |
PASS — INSTALL_URL="${FELHOM_INSTALL_URL:-https://felhom.eu/scripts/felhom-host-install.sh}" |
| F2 | hub-rendered command points at the same URL | customer_unified.html:563, :1262 |
PASS — and three emission sites, not two: :563 static, :1262 JS error branch, :1267 JS success branch |
| F3 | website serves /scripts/ from a git-sync tree tracking main |
manifests/webpage.yaml — nginx :74-77, sparse-checkout CM :211-218, git-sync :272-281, init :299-307 |
PASS — --branch=main --period=30s --link=current; sparse-checkout /website/ + /scripts/; location /scripts/ { root …/current; }. No image build, no ArgoCD step |
| F4 | hostInstallVersion selects nothing |
configs.go:28, :487; render_test.go:219; customer_unified.html:494 |
PASS — repo-wide grep returns exactly those 4 code sites; all other hits prose. Rendered as a text label |
| F5 | every generated flag is parsed by 1.22.0 | generator customer_unified.html:1206-1239 vs parser felhom-host-install.sh:1175-1212 |
PASS — --mode --cores --memory --vmid --node --acl-storages --operator-pubkey-file --preserve-state-from --skip-provision --dry-run --preflight-only --allow-new-leaf (+ --customer-id); every one a parser case. No functional gap |
| F6 | installer is 1.22.0 | scripts/felhom-host-install.sh:187 |
PASS |
| F7 | the drift test is hollow | render_test.go:219-221 |
PASS — strings.Contains(html, hostInstallVersion) compares the constant to itself; passes at any value |
Live command 1 — what the URL actually serves
$ curl -fsS https://felhom.eu/scripts/felhom-host-install.sh | grep -m1 '^SCRIPT_VERSION='
SCRIPT_VERSION="1.22.0" # the SINGLE version source (F-1): -h, the run banners, and the hub
Live command 2 — the drift gate's real state
$ python3 scripts/hostinstall_gates.py; echo "exit=$?"
ok: SCRIPT_VERSION=1.22.0
ok: header has no version literal
FAIL: hub Setup-tab hostInstallVersion=1.19.0 != SCRIPT_VERSION=1.22.0 (F-1: bump both together)
ok: age is in the installed package set
… (six further ok lines) …
hostinstall gates: 1 FAILURE(S)
exit=1
Phase 0 source read — PAIRING reaches the same installer invocation
Mode selection felhom-bootstrap.sh:537-541: a fresh VM with no baked customer-id calls
run_pairing. On HTTP 200 from /api/v1/appliance/poll the loop writes the hub-delivered
FELHOM_CUSTOMER_ID + FELHOM_RETRIEVAL_PASSPHRASE into the 0600 env, re-sources it, and calls
run_direct in the same invocation (:495-499). run_direct is the single site that fetches
$INSTALL_URL (:322-330), builds the args (:334) and invokes bash "$SCRIPT_TMP" "${args[@]}"
(:343). The customer it yields is the one the operator bound — claimable. So the ISO leg is the
spine for E-2d, not an obstacle to it.
Rows changed
| ID | Before | After |
|---|---|---|
| R-94 (line 14) | READY — deferred until E-2d, blocked on E-2d, false ISO/hub framing |
READY (XS), blocked on nothing, retracted + re-scoped to three legs |
| R-94 (line 16) | duplicate row, READY #2, 1.19.0 vs 1.20.0 |
deleted — merged |
| R-110 | did not exist | opened, WAITING-ON-OPERATOR (S) |
| E-2d | ISO implied as obstacle | Next-action appended; ISO is the spine |
| ranked list | 1 R-95 · 2 R-94 (high-consequence) · 3 R-86 · 4 R-87 | 1 R-95 · 2 R-94 de-ranked · 3 R-86 · 4 R-87 · 5 R-110 |
Files: documentation/backlog/OPEN-ITEMS.md, documentation/backlog/ROADMAP.md,
documentation/runbooks/day0-install.md.
Commit 2 — R-29 filing + record hygiene
The §1.1 ruling — is R-29 the right home for a non-design-v2 gate? Yes. Proceeded.
R-29's title says "the design-v2 green gates", and scripts/hostinstall_gates.py is not one — it
comes from drill F-1 (2026-07-12) and postdates the item. Four things decide it anyway:
- R-29's own audit list already spans well beyond design-v2 subject matter. It names
docker_run_volume_path_gate(docker mount safety),offbox_rename_gate,app_row_dedup_gateandmanifest_bearer_gate(secrets —runbooks/secrets.md:76). The title is a misnomer relative to the item's own body. - Part (b) — "the systemic half is the real item" in R-29's words — is stated with no
design-v2 restriction. It is about the enforcement mechanism: "the gates run only when a human
remembers to run them… decide where they run (pre-push hook,
build.shstep, or a CI job) and make a red gate block the train." That is gate-agnostic and repo-wide. hub_confirm_gate.pyis already on R-29's list and lives in the samescripts/directory ashostinstall_gates.py. Wiring one and not the other would be arbitrary.- Identical failure shape, identical genre. Both self-describe as mechanical grep-assertions
(
hostinstall_gates.py:2,hub_confirm_gate.py:1-8); both exist, assert something true, and are invoked by nothing.
R-29 has already absorbed one independent re-raise without minting an ID (2026-07-18 rehearsal note) and says so explicitly. This is the third. No new ID minted.
Orphan-search evidence — hostinstall_gates.py and hub_confirm_gate.py
Re-established at d4c07873. Pattern hostinstall_gates\|hub_confirm_gate.
| # | Scope | Command | Result |
|---|---|---|---|
| S1 | felhom.eu, all file types |
grep -rn "$PAT" . --exclude-dir=.git |
19 hits, zero invocations. All are docstrings (scripts/hostinstall_gates.py:5, scripts/hub_confirm_gate.py:7), code comments (hub/internal/web/configs.go:27, scripts/felhom-host-install.sh:189) or prose (REUSE.md:62, CONTEXT.md:540,564, hub/CHANGELOG.md:371,1292,1351,1385, scripts/CHANGELOG.md:483,524, 3 files under documentation/audits/, ROADMAP.md:149,158, OPEN-ITEMS.md:14) |
| S2 | sibling repos | grep -rln "$PAT" /mnt/5_hdd/felhom.eu/git --exclude-dir=.git minus this repo |
3 files, all in .claude-memory/ (MEMORY.md, drtier-by-default-2026-07-12.md, polish-batch-2026-07-13.md). Notes, not invokers |
| S3 | ~/.claude |
grep -rln "$PAT" /home/kisfenyo/.claude, and targeted on settings*.json, skills/, hooks/ |
Zero hits in settings, skills or hooks — where an invoker would live. Remaining hits are file-history/ (Claude Code's own backups of files edited in past sessions) and paste-cache/ (pasted task specs). Neither is an invocation site |
| S4 | git hooks | ls -1 .git/hooks/ | grep -v '\.sample$' |
Empty — every hook is a .sample stub |
| S5 | build files | find . -type f \( -iname Makefile -o -iname '*.mk' -o -iname justfile -o -iname 'Taskfile*' \) |
Only hub/Makefile; grep -n gate hub/Makefile → zero occurrences |
| S6 | CI | find . -type d \( -name .github -o -name .gitea -o -name .woodpecker* -o -name .drone* -o -name .circleci \) |
Empty — felhom.eu has no CI configuration at all |
Of the four gates in scripts/, only site_gates.py is mandated (CLAUDE.md:153);
manifest_bearer_gate.py is named in runbooks/secrets.md:76.
Rows changed
| ID | Before | After |
|---|---|---|
| R-29 | absent from OPEN-ITEMS.md (grep -c → 0) while present at ROADMAP.md:158 since before the 2026-07-27 rebuild |
opened, READY (S for (a) / M for (b)), owner CC |
| R-94 | leg (b) stood alone | leg (b) carries → R-29 as its class. Row otherwise untouched |
| E-2d | cited :322-341 for an invocation at :343 |
cited :322-343, with the fetch / args / call each pinned separately |
| R-95, R-86, R-87 | READY #1, READY #3, READY #4 |
READY — markers dropped (see below) |
Not added to the ranked list under "Why the READY rows rank this way": that list is a top-N rationale, not a complete ordering (R-99, R-102, R-103, R-104, R-108, R-109, R-89, R-92, R-93 and E-2d are all READY and absent from it). Ranking R-29 was not asked for and would be a priority claim this task has no basis to make.
§2.2 — markers dropped, not renumbered
The #1/#3/#4 markers duplicated ranked-list positions 1/3/4 for exactly those three rows; two
orderings of one set is what produced the hole when the #2 row was merged in d4c07873. Removing
the duplicate leaves the ranked list as the single maintained ordering.
The other two record defects
ROADMAP.md:147— cited a non-existent R-164; it means controller v0.164.0's deliberate-stop filter. Corrected. (It had already cost one max-ID scan a false positive.)CONTEXT.md:564— asserted in the present tense that the single VERSION source is "gated byscripts/hostinstall_gates.py". The gate exists, asserts exactly that, is red, and runs nowhere. Corrected to say the cross-check exists but is not enforced, tracked as R-94 leg (b) / R-29.hub/internal/web/configs.go:27carries the same false claim in a code comment and was left alone — R-94 leg (b) territory, needs a hub build.
Files: documentation/backlog/OPEN-ITEMS.md, documentation/backlog/ROADMAP.md,
documentation/backlog/README.md, CONTEXT.md, this file.
Commit 3 — de5a3e5 — the record-hygiene rider
Baseline 7383400a, unmoved. Four XS items from 7383400a §9; the three deferred observations stay
deferred.
| File | Change |
|---|---|
CONTEXT.md:540 |
"scripts/hub_confirm_gate.py enforces" → asserts, but is not enforced (R-29). Third instance of the class after :564 and configs.go:27 |
REUSE.md:62 |
Same claim, "enforces zero". The rule stays — never native confirm()/prompt() is correct guidance and this is a reuse-reference row — only the enforcement claim changes |
OPEN-ITEMS.md:4 |
Root REPORT.md = overwritten per-session; REPORT-<topic>.md = non-clobbering sibling (CLAUDE.md:82-87), 14 of them. Prohibition unchanged |
OPEN-ITEMS.md:55 |
Heading scoped to "the TOP READY rows" + a half-sentence that it is deliberately not a full ordering. No row added to the list |
hub/internal/web/configs.go:27 — the fourth instance — left alone (R-94 leg (b), needs a hub build).
Part 3 NOT done — its stated evidence is false
The spec proposed recording a convention: "ROADMAP's P3 Status column only ever takes idea or a
SHIPPED/PROVEN-LIVE variant, and never takes READY, BLOCKED or WAITING-ON-OPERATOR", and
concluded from that vocabulary split that ROADMAP Status is disposition while OPEN-ITEMS
State is live actionability.
Refuted inside P3 itself (ROADMAP.md, lines 113–172):
| Line | ID | P3 Status cell |
|---|---|---|
| 151 | R-90 | BLOCKED on Hetzner CX33 availability (operator, 2026-07-27) |
| 152 | R-91 | WATCHING — gated on demo-felhom's first post-migration PBS backup |
| 150 | R-110 | idea — found 2026-07-29, **WAITING-ON-OPERATOR (a ruling, not a defect)** |
| 137 | R-86 | **NEXT — operator ruling 2026-07-27** |
And P2's recovery-gaps sub-table (lines 99–107) uses a bare READY for nine rows, including E-2.
Both halves fail: the column does take the OPEN-ITEMS vocabulary, and BLOCKED/WATCHING/
NEXT are live-actionability words, not dispositions. The real shape is a free-text status field
that has absorbed both vocabularies over time — usually disposition, sometimes not. Writing the
proposed convention down would install a new false invariant in the commit chain whose whole
purpose was removing them, so per the spec's own instruction ("If you think this ruling is wrong, say
so and make no edit") documentation/backlog/README.md was not touched.
The narrow question that prompted it still resolves benignly: R-29 and R-94 reading idea in
ROADMAP and READY in OPEN-ITEMS is not a defect and needs no sync. What is unsupported is
generalising that into a rule.
Standing note
No gate was wired, fixed, run as a hook, or deleted by any of the three commits. Filing R-29 is
not doing R-29; the enforcement decision (pre-push hook / build.sh step / CI) and the audit of the
remaining eight gates are R-29 part (b), M-sized, and its own task.