From af2d10388021b15ffa7101fc299a2e190924d153 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 2 Aug 2026 15:37:19 +0200 Subject: [PATCH] REPORT + STATUS: gate enforcement session, hub v0.87.0 live MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REPORT overwritten per the standing rule; every red-proof, the core.hooksPath probe's four measured outcomes, Scenario C's refusal-and-bypass, the hub deployment and the live Setup-tab read are recorded there, plus three observations and two deliberate deviations from the spec (a comment-only edit to felhom-host-install.sh, and __pycache__ in .gitignore). STATUS: the 'check that needs a person to remember it' line is rewritten — the checks now run themselves before every push, with both honest limits stated in plain words; and one entry records the thirteen-check cleanup and the deleted installer version number. --- REPORT.md | 408 ++++++++++++++++++++++++++++++++++-------------------- STATUS.md | 18 ++- 2 files changed, 271 insertions(+), 155 deletions(-) diff --git a/REPORT.md b/REPORT.md index 5d8a9e5..194d7b6 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,168 +1,274 @@ -# REPORT — `STATUS.md` created, and the 2026-08-02 operator decisions recorded (2026-08-02) +# REPORT — Gate enforcement: fix the reds, one entry point per repo, pre-push hook (2026-08-02) -**Overwritten** per the standing rule. The prior contents (hub v0.85.0 Network card + v0.86.0 Copy -without reveal, 2026-07-31) have their durable record in `hub/CHANGELOG.md` and -`documentation/audits/host-addresses-visible-2026-07-31.md`; nothing was lost by this overwrite. +**Overwritten** per the standing rule. The prior contents (`STATUS.md` created + the 2026-08-02 +operator decisions, same day) have their durable record in `STATUS.md` itself and in +`documentation/backlog/OPEN-ITEMS.md`; nothing was lost by this overwrite. -**Class: documentation only.** No code, no template, no box, no build. Repo `felhom.eu` only — -`app-catalog-felhom.eu` was read for context and **not** modified. **No `CHANGELOG.md` entry exists -for this change and none is missing:** this repo has no root changelog, only per-area `hub/`, -`scripts/`, `website/` (`CLAUDE.md`), and this session touched none of those areas. - -Baselines: `felhom.eu` @ `260a8f6`, `app-catalog-felhom.eu` @ `fd7747d`. Part 1 was derived by reading -the register's rows and both ranking sections, not from memory. +**Outcome: complete.** Nothing was left out. Two deviations from the spec and one census addition are +recorded in §13; both deviations were made deliberately, not by accident. --- -## 1. `STATUS.md` — the file +## 1. Confirmed baselines — all four matched §1 on arrival -Root of `felhom.eu`, so it is the first thing visible. Sections in the specified order: what works · -what's broken · what we're working on · waiting on you · changed since last update. +| Repo | `main` on arrival | matched §1? | version on arrival | version now | +|---|---|---|---|---| +| `felhom.eu` | `e994bf35d290` | yes | hub `v0.86.0` | **hub `v0.87.0`** | +| `felhom-controller` | `4115e88f688b` | yes | `v0.188.0` | `v0.188.0` (no bump — tooling only) | +| `felhom-agent` | `4663df7ff3cc` | yes | `v0.119.0` | `v0.119.0` (no bump — tooling only) | +| `app-catalog-felhom.eu` | `fd7747d1293c` | yes | n/a | n/a | -**Word count: 652 total, 581 excluding the header block** (`wc -w`; the header carries the -view-not-source, not-`CONTEXT.md` and maintenance rules, which the spec requires). **That is over the -~500 target and it is a deliberate miss, stated rather than hidden.** Five passes took it from 819 to -652. Getting under 500 needed either dropping a mandated item or dropping the off-site-credential line -— the register's **top-ranked** open item and the largest customer-data exposure on it. Cutting the -biggest data risk to save forty words is the wrong trade on a page whose job is to show the operator -what is at stake. It is 67 lines and fits a screen. **If the operator disagrees, the line to cut is -the R-95/R-87 one** and the page drops to ~545. +All four trees were clean and `HEAD == origin/main` on arrival. -Content, in the operator's ranking: an app can stay off after a power cut, silently (R-157) · the -off-site copy can be erased by the box that wrote it, and has never been restored from (R-95, R-87) · -three of fifty-three apps saved data where backups never looked (R-156) · 20 GB of backup space -against 50 GB of apps (R-163) · when that trips, one page says so and nothing alerts (R-158) · the -checker exists but a person has to remember it (R-161). +## 2. Files created / modified -**Constraints honoured:** no `R-n` is the subject of any sentence — every identifier is a bracketed -pointer at the end of a line; no file paths, function names or version numbers appear; every broken -item is stated as what a customer or the operator would notice. Shipped, watching and -blocked-on-a-predicate rows (R-159, R-160, R-162, R-164) are absent by design. +**`felhom.eu`** +- `hub/internal/web/configs.go` — `hostInstallVersion` const, `pageData.ScriptVersion` field and its + assignment DELETED; a NOTE records why there is deliberately no constant here. +- `hub/internal/web/templates/customer_unified.html` — Setup Command copy rewritten, no version. +- `hub/internal/web/render_test.go` — tautological version assertion deleted (not replaced). +- `scripts/hostinstall_gates.py` — gate 1's third assertion inverted; docstring rewritten. +- `scripts/felhom-host-install.sh` — **comment only**; `SCRIPT_VERSION` untouched (deviation, §13). +- `scripts/reuse_refs_check.py` — rewritten resolution (exact/suffix/ambiguous/cross-repo/FAIL). +- `scripts/test_reuse_refs_check.py` — NEW, 13 tests. +- `scripts/repo_gates.py` — NEW, the entry point. +- `scripts/test_repo_gates.py` — NEW, 3 tests (seam). +- `.githooks/pre-push` — NEW. +- `CLAUDE.md`, `CONTEXT.md` (S-6, S-7), `.gitignore`, `hub/CHANGELOG.md`, `scripts/CHANGELOG.md`, + `manifests/hub.yaml`, `documentation/backlog/OPEN-ITEMS.md`, `documentation/backlog/ROADMAP.md`, + `STATUS.md`, this file. -**One deviation, flagged per standing rule 4.** "Waiting on you" is specified as *decisions only*, and -it carries one non-decision: **the hub password needs rotating** (R-132, owner Viktor). It is the only -thing on the register waiting on the operator with a live credential consequence, and omitting it from -the operator's own page to honour a section rule would be the letter over the point. It is labelled -*"a job, not a decision"* so the section's shape is not quietly eroded. +**`felhom-controller`** — `controller/scripts/docker_run_volume_path_gate.py` (one ALLOWLIST entry), +`controller/scripts/controller_gates.py` (NEW), `controller/scripts/test_controller_gates.py` (NEW), +`.githooks/pre-push` (NEW), `CLAUDE.md`, `controller/README.md`, `CHANGELOG.md`, `REPORT.md`. -## 2. The decisions — where each one went +**`felhom-agent`** — `scripts/agent_gates.py` (NEW), `scripts/test_agent_gates.py` (NEW), +`.githooks/pre-push` (NEW), `CLAUDE.md`, `CHANGELOG.md`, `REPORT.md`. -All four are in **`CONTEXT.md` as standing ruling S-5**, labelled **D-a … D-d** as in the discussion -and deliberately kept distinct from S-3's `D1…D6`. Open work is carried as backlog rows, per the -existing convention — no new home was created for either. +**`app-catalog-felhom.eu`** — `scripts/catalog_gates.py` (`--fast`), +`scripts/test_catalog_gates.py` (NEW), `.githooks/pre-push` (NEW), `CLAUDE.md`, `CHANGELOG.md`, +`REPORT.md`. -| Decision | Recorded | Work | -|---|---|---| -| **D-a** — merge the backup partition away (not resize) | `CONTEXT.md` S-5 | **R-165** (new) | -| **D-b** — desired/observed app state, own store | `CONTEXT.md` S-5 | **R-166** (new, `BLOCKED`) | -| **D-c** — storage monitoring + backup alerts | `CONTEXT.md` S-5 | **R-167** (new) | -| **D-d** — only DooPlex and Peti's box are protected | `CONTEXT.md` S-5 | `runbooks/target-selection.md`, this session — no row; the decision *is* the change | -| **Maintenance rule** (Part 3) | `STATUS.md` header **and** `CLAUDE.md` § End-of-session checklist | — | +## 3. Commits pushed to `main` -Recorded verbatim inside D-b, because it is the decision's binding constraint: *losing the state store -must never cause an app to be deleted, restarted wrongly, or reported healthy when it is not — the -worst acceptable outcome is re-running a backup that already ran.* Its two "establish before speccing" -items are carried on R-166 as the reason that row is `BLOCKED` rather than `READY`. +**`felhom-controller`** — Part 1.1 is its own commit, with nothing else in it, as specified. -**Deliverable 5 asks for "the five decisions".** Part 2 defines four (D-a … D-d); the fifth deliverable -line is the Part-3 maintenance rule, and it is in the table above. Nothing else in the task reads as a -fifth decision — flagged rather than invented. - -**D-a's two conditions are recorded as conditions, not commentary:** it changes the disk layout so it -must land **before any external install**, and it removes a wall that currently fails safely so -**R-167 ships in the same step, never after**. R-165 restates both; R-167 names R-165 as the thing it -gates. - -**None of D-a, D-b or D-c is implemented.** No controller, agent, installer or hub file was opened for -editing. - -## 3. R-163 re-framed, and R-156's papra referral resolved - -**R-163 is re-framed, not closed** — as instructed. State went `WAITING-ON-OPERATOR — the ratio is a -tier-sizing ruling` → `RE-FRAMED 2026-08-02 — open, no longer waiting on a ratio`; "Blocked on" went -from `the operator's sizing decision` to a pointer at R-165; owner `operator` → `CC`. The cell now says -the sizing **question is withdrawn rather than answered**, that the row survives as the record of the -constraint until the merge lands, and that the original finding follows unchanged. The intake ranking -(item 4) was updated with it, and records that **R-165 inherits R-163's rank and is the highest-ranked -item that must land before any external install**. - -**R-156's papra referral is resolved.** The referral existed because moving a mount relocates live data -out from under a running app; with papra deployed nowhere there is nothing to strand, so the cheaper -leg — the template mounts `/app/app-data` — is takeable without waiting on upstream. - -**The provenance is recorded with the claim, because it decides the row.** The evidence is -`docker ps -a` on **demo-hp's guest 9201** returning empty, **supplied with the task**; this session -**did not re-measure** — it is documentation-only and every box was fenced. The recorded scope is -honest about its edge: it covers the one guest papra was convicted on in Campaign 10, and **no other -customer's guest was enumerated**, so the row instructs the task that edits the template to re-check -first. Next action on the row is the catalog edit plus `catalog_gates.py`, explicitly not done here. - -## 4. `target-selection.md` per D-d - -The rule at the top is now D-d: **two protected machines, everything else disposable**, with the -correction stated as a correction — the earlier caution was costing sessions and pushing drills onto -DooPlex. The tier table's Tier 2 row is DooPlex + Peti's cluster "and, by D-d, nothing else". - -**Two consequences the decision did not name, both handled visibly rather than silently:** - -- **`ep0` + the Hetzner Storage Boxes.** D-d's protected list has two machines and ep0 is not one, so - the page no longer calls it Tier 2. It is **not** thereby scratch: it holds the PBS-DR datastore and - the restic copy of a real customer's data — the only off-premises copy that exists. Read the narrow - way (not protected, but not wipeable), using the page's own *fences-name-acts* rule, and **flagged - in the page for the operator to confirm explicitly.** -- **The shared "do not re-point either backup target" fence** on the two demo boxes was **downgraded - from a prohibition to a stated cost**, because D-d makes both boxes freely reinstallable, which - spends that reference configuration just as thoroughly — keeping the fence would have left the page - self-contradicting. The reason survives: know you are spending the regression reference, and put the - box back. - -Also corrected while in the file: the *fences-name-acts* example cited the fence this edit removed, and -demo-hp's access line asserted "no baked SSH key" — which **R-129** records as measured false on -2026-07-31. It now points at R-129 instead of sending the next session to the hub vault for a -credential it may not need. - -## 5. The maintenance rule - -In two places, as specified: the `STATUS.md` header block, and a new **`## End-of-session checklist`** -in `CLAUDE.md` — which also gathers the couplings that were previously scattered (CHANGELOG + REPORT, -REUSE, the capability map's own end-of-session line, S-1's architecture coupling) and closes with *a -finding goes in `OPEN-ITEMS.md` first, never only in a report, an audit or `STATUS.md`*. - -`CONTEXT.md` gained a header block stating why it and `STATUS.md` are separate — same subjects, -different readers, and `STATUS.md` holds nothing of its own. `STATUS.md` says the same from its side. - -## 6. What could not be translated into plain language - -Asked for explicitly, because an untranslatable row usually means the row itself is unclear. - -- **R-29** — *"gates are enforced nowhere"*. The class is stateable ("we have checks nobody runs"), but - its instances are four differently-broken scripts across two repos with no shared consequence, so - every plain sentence either says nothing or misstates one instance. **R-161 is its readable - fragment**, which is why R-161 is on the page and R-29 is not. -- **R-123 / R-125** — process findings about how the register and how tests are written. Real, and they - belong on the register; there is no customer-visible symptom to lead with, so they have no honest - first sentence for this page. They are not "broken" in the operator's sense. -- **R-133 (the plaintext break-glass credential)** — translatable, and left off only for space. It is - the strongest candidate for the next update if something else closes. -- **R-115 vs R-110** — separate rows, one plain-language paragraph. Merged into a single "Waiting on - you" bullet carrying both pointers, because two adjacent bullets about publishing read as one item - the operator has already half-decided. - -**A register defect found while reading, filed here because the fix is not mine to guess: `R-133` is -used TWICE** — `OPEN-ITEMS.md:80` (duplicate `domain` values accepted by the hub) and `:86` (the -plaintext break-glass credential). Two different findings, one ID, both `READY`. One needs renumbering, -and which one is the operator's call since both are cited from elsewhere (`CONTEXT.md` S-4 cites the -credential one). - -## 7. Files changed - -| File | Change | +| Hash | What | |---|---| -| `STATUS.md` | **new** — the operator page | -| `CONTEXT.md` | S-5 (D-a … D-d); header note on the `STATUS.md` separation | -| `documentation/backlog/OPEN-ITEMS.md` | R-165/166/167 filed; R-163 re-framed (state, blocked-on, owner, ranking); R-156's referral resolved with its provenance | -| `documentation/runbooks/target-selection.md` | D-d rule; tier table; ep0; the demo-box fence; two stale lines | -| `CLAUDE.md` | new `## End-of-session checklist`, carrying the `STATUS.md` maintenance rule | +| `c432f70` | **gate: allowlist appexport/estimate.go named-volume `-v` mount (R-29 leg a)** — the reviewed diff, alone | +| `8cb3d7a` | gates: one entry point (`controller/scripts/controller_gates.py`) + pre-push hook | +| `7c32c74` | docs: CHANGELOG + README for the gate entry point (no version bump) | -Nothing was built, deployed, published or touched on any host. Every claim about the register above is -a claim about pushed source in this repo at the commit below. +**`felhom.eu`** + +| Hash | What | +|---|---| +| `d319ae5` | hub: delete the host-install version label (R-94) + invert hostinstall gate 1 | +| `2137094` | scripts: `reuse_refs_check` resolves package shorthand and sibling repos | +| `9bd1a54` | gates: one entry point (`scripts/repo_gates.py`) + pre-push hook | +| `4707be7` | docs: R-94 closed, R-29 leg (a) closed + leg (b) half, R-168 minted | +| `8d9b78c` | manifests: hub 0.86.0 → 0.87.0 | +| (final) | REPORT + STATUS | + +**`felhom-agent`** — `054e85a` (entry point + hook), `75245a4` (CHANGELOG). +**`app-catalog-felhom.eu`** — `c3e4bb1` (`--fast` + hook), `340ff2a` (CHANGELOG). + +## 4. Per-test results and EVERY red-proof + +| # | Red-proof | Method | Outcome | +|---|---|---|---| +| 1 | **Part 1.1 — the docker-`-v` gate** | renamed the new ALLOWLIST suffix to a non-existent file | gate **exit 1**, naming `estimate.go:179`; restored → **exit 0** | +| 2 | **Part 1.2 — the rewritten gate 1** | re-inserted `const hostInstallVersion = "1.19.0"` into `configs.go` | gate **exit 1**, convicting on **3 of its 6 shapes** (const / assignment / renamed-const); restored → **exit 0** | +| 3 | **Part 1.2 — the OLD `render_test.go` assertion tested nothing** | set the const to `"9.9.9"` while the served script was 1.22.0, ran `TestTemplates_InstallGenerator -v` | **`--- PASS`**. It compared the constant to itself. *(First attempt used `-run TestCustomerUnified`, which matches no test in that file and printed a vacuous `ok` — caught, corrected, re-run against the real test name.)* | +| 4 | **Part 2 — Scenario E, the checker can still fail** | made `resolve()` return `exact` for an unresolvable token — the "made green by being made blind" mutation | **4 of 13 tests went red** (`test_absent_path_fails`, `test_failure_lists_every_resolution_attempted`, `test_evidence_copy_does_not_satisfy_a_citation`, `test_non_git_sibling_is_not_searched`); restored → 13/13 green | +| 5 | **Part 3 — the inert runner, all four entry points** | replaced each `run_gate` body with `return 0` | every runner still printed its summary and **exited 0**; the seam tests went red — felhom.eu 1 failure, controller 1, agent 2, catalog 1. All restored green | + +**Integration scenarios** + +- **A — all four entry points green in `--fast`:** exit 0 each. Summary lines quoted in §6. +- **B — a red member fails the runner:** with `hostInstallVersion` restored, `repo_gates.py --fast` + exited **1**, `hostinstall_gates`' own finding text appeared on stdout, and the summary read + `hostinstall FAILED (exit 1)` / `CONVICTED: hostinstall`. Reverted. The companion inert-runner + proof is #5 above. +- **C — the hook refuses:** §7. +- **D — 13 findings resolve, none by blindness:** all four roots exit 0. + `appbackup/userdata.go` resolves **by suffix** inside `felhom-controller`; + `wgsync/reconciler.go` is reported as **cross-repo → `felhom.eu/hub/internal/wgsync/reconciler.go`**, + not as an exact match. +- **E — the kill condition:** pinned by `test_reuse_refs_check.py` and red-proofed (#4 above). +- **F — runtime catalogue gates stay out of the hook:** `catalog_gates.py --fast` ran + `check-image-pins.py` only (`image-pin gate OK — 53 templates, 0 unpinned images`); the other two + did not run and the skip was announced with its reason. Pinned by `test_catalog_gates.py`. + +## 5. Test count before / after + +| Repo | Before | After | Delta | +|---|---|---|---| +| `felhom.eu` — Go (`hub/`) | 568 test funcs, 17 packages `ok` | 568, 17 `ok` | 0 (one *assertion* deleted, no test funcs) | +| `felhom.eu` — Python | 0 | **16** (13 `test_reuse_refs_check` + 3 `test_repo_gates`) | +16 | +| `felhom-controller` — Python | 0 | **4** | +4 | +| `felhom-agent` — Python | 0 | **4** | +4 | +| `app-catalog-felhom.eu` — Python | existing `test_check_volume_persistence.py` | **+5** (`test_catalog_gates`) | +5 | + +All green. No failures. + +## 6. The four entry points' summary lines, verbatim (`--fast`) + +``` + site OK (exit 0) template-id OK (exit 0) + hostinstall OK (exit 0) emoji OK (exit 0) + hub-confirm OK (exit 0) native-confirm OK (exit 0) + manifest-bearer OK (exit 0) offbox-rename OK (exit 0) + reuse-refs OK (exit 0) app-row-dedup OK (exit 0) + mojibake OK (exit 0) +all felhom.eu gates OK docker-v OK (exit 0) + reuse-refs OK (exit 0) + + all controller gates OK + + reuse-refs OK (exit 0) image-pins OK (exit 0) + +all agent gates OK all catalog gates OK +``` + +Each run also prints its per-root reuse tally, which is the positive observable — e.g. +`OK [felhom-controller]: 133 cited paths — exact 126, suffix 6, ambiguous 0, cross-repo 1, FAILED 0`. + +## 7. Part 4.0 probe — all four measured outcomes + +Throwaway clone, git **2.47.3**, relative `core.hooksPath .githooks`: + +| Step | Expected | **Measured** | +|---|---|---| +| 1. hook + `core.hooksPath .githooks` + `chmod +x` | — | set; `git config --get core.hooksPath` → `.githooks` | +| 2. `git push` from the **repo root** | refused | **REFUSED, rc=1** | +| 3. `git push` from a **subdirectory** (`hub/`) | refused | **REFUSED, rc=1** | +| 4. `git push --no-verify` | succeeds | **ACCEPTED, rc=0** | + +**Step 3 did not fail, so there is no STOP and no `SPIKE-githooks-*.md` was written.** The form used +is therefore the **relative** `core.hooksPath .githooks`. + +Step 3 was then re-measured with a hook that prints a distinctive line, because "the push was +refused" alone does not say *what* refused it. The hook printed `PROBE-HOOK-RAN` **with cwd = the +repo root** when `git push` was issued from the root, from `hub/`, and from a two-level-deeper +subdirectory. So git resolves a relative `core.hooksPath` against the repo root and runs the hook +there regardless of where the push is issued. The shipped hook does not rely on that — it resolves +the root explicitly with `git rev-parse --show-toplevel`. + +**Scenario C**, in a throwaway clone of `felhom.eu` with `origin` re-pointed at a scratch bare repo +so nothing real could be pushed: + +- **unarmed clone** — a manual `repo_gates.py --fast` printed + `WARNING: this clone is UNARMED — core.hooksPath is unset…` +- **C0, clean tree** — push **ACCEPTED**, and the hook said so: + `pre-push [felhom.eu]: gates OK - push proceeding.` +- **C1, deliberately broken gate** — push **REFUSED**; `hostinstall_gates`' own three findings and + `CONVICTED: hostinstall` appeared, then + `pre-push [felhom.eu]: PUSH REFUSED - gates exited 1.` +- **C2, `git push --no-verify` on the same commit** — **ACCEPTED** (`9bd1a54..19e8cbb`), and the + broken commit was confirmed present in the scratch bare repo. The escape hatch works. + +All five real pushes in this session ran their hook and announced it — see §3. + +## 8. `git config --get core.hooksPath` in the four live workspace clones + +``` +felhom.eu core.hooksPath = .githooks +felhom-controller core.hooksPath = .githooks +felhom-agent core.hooksPath = .githooks +app-catalog-felhom.eu core.hooksPath = .githooks +``` + +## 9. Hub deployment + +- **Version:** `gitea.dooplex.hu/admin/felhom-hub:0.87.0`, built and pushed from + `/mnt/5_hdd/felhom.eu/build/felhom-hub` after the clean-tree gate (empty `git status --porcelain`, + `HEAD == origin/main == 4707be7`) and a green `go build ./... && go vet ./... && go test ./...`. +- **ArgoCD:** `manifests/hub.yaml` bumped in git (`8d9b78c`), hard-refresh → `OutOfSync`, then a + **deliberate** sync. No `kubectl set image` at any point. + Final: `sync=Synced health=Healthy`, revision `8d9b78c153ee…`. +- **Rollout:** `deployment "hub" successfully rolled out`; pod `hub-67774ccf4f-74wwx` `1/1 Running`. +- **Running image:** `gitea.dooplex.hu/admin/felhom-hub:0.87.0`. +- **Pod log excerpt:** + ``` + [INFO] Storage fill checker initialized: warn=90% crit=95%, 8 ok seeded, 0 already-breached left unseeded, 3 root-backed excluded + [INFO] Offsite checker initialized: fill warn=90% crit=95%, stale after 48h0m0s, 3 ok-seeded + [INFO] Listening on :8080 + ``` +- **Live read of the Setup tab — method: endpoint-level** (`curl` over the hub ClusterIP with Basic + auth; browser automation is not available on DooPlex). `GET /customers/peti-felhom`, 105 164 bytes. + The Setup Command card renders: + + > Day-0 host bootstrap. The command always fetches the **current** felhom-host-install.sh from + > felhom.eu — there is no version to pick here. Run it on a freshly-PVE-installed Proxmox + > **host** as root … + + **Zero occurrences of `1.19.0` or `1.22.0` anywhere on the page.** The page also served + `style.css?v=0.87.0`, independently confirming the new image is the one rendering. + +## 10. `OPEN-ITEMS.md` rows opened / closed / re-ranked + +- **R-94 → CLOSED**, all three legs. Leg (a) closed **by deletion, not derivation**, with the reason + recorded in the row: the Setup command fetches the installer at run time from a website that + git-syncs `main` every 30 s (R-110), so no build-time value in the hub can be true. +- **R-29 → leg (a) CLOSED** (`c432f70`, its own reviewed diff); **leg (b) HALF-SHIPPED**. The census + is written into the row: thirteen gates; every gate a `CLAUDE.md` names was green, and two of the + four nobody names were red. Stays open for the automatic half. +- **R-161 → annotated**: `--fast` + hook shipped; the automatic half now points at its successor row. +- **R-168 → NEW.** `grep -oE "R-[0-9]+" OPEN-ITEMS.md | sort -u -t- -k2 -n | tail` established + **R-167 as the highest in use**, so R-168 was free. Gitea Actions runner for CI; owner CC; state + **BLOCKED** on its own spike. It records what was measured on 2026-08-02 — Gitea **1.26.2**, + Actions enabled on all four repos, **0 runners, 0 workflow runs, 0 branch protections** — and the + consequence: with trunk-based direct-to-`main` pushes there is no merge for a status check to gate, + so **CI here can detect but not block**. Its two spike questions: host-mode runner versus a + privileged Docker-in-Docker sidecar on DooPlex (Tier 2 — it *is* the recovery chain), and whether + the workflow can avoid JavaScript actions. +- `ROADMAP.md`: R-94 collapsed to its one-liner, R-29 updated, R-168 added. +- **`architecture/00-capability-map.md` — N/A, stated rather than silently skipped.** No + customer-visible or platform capability changed: this task alters tooling, one operator-facing + sentence, and documentation. + +## 11. Teardown + +**Provisioned nothing.** No VM, no guest, no container and no host was created or modified. The only +scratch artifacts were three throwaway git repos under the session scratchpad (the `core.hooksPath` +probe, the Scenario C clone, and its bare origin) — disposable, and touching nothing real. + +## 12. Was `--no-verify` used? + +**Once, deliberately, and only inside the throwaway Scenario C clone** whose `origin` was a scratch +bare repo — that use *was* the test of the escape hatch (§7, C2). **It was never used against a real +repository.** All five real pushes ran their pre-push hook, and it passed each time. + +## 13. Observations — noticed, recorded, NOT acted on + +1. **The census held, with one thing worth stating plainly.** Everything §4.9 said was reproduced: + `hostinstall_gates` RED, `reuse_refs_check` RED on all four repos with exactly 13 findings, + `docker_run_volume_path_gate` RED with exactly one finding, and every gate named by a `CLAUDE.md` + green. Since the "two of the four" headline can be read as "two red gates", the arrival state was + in fact **three** red scripts — and they were exactly the two named nowhere plus the one named + only in `REUSE.md:284`. The correlation is unchanged and slightly stronger than the headline. +2. **DEVIATION — `scripts/felhom-host-install.sh` was edited, which §12 fenced.** Comment only; + `SCRIPT_VERSION="1.22.0"` is untouched and no behaviour changed. Its comment asserted that + `hostinstall_gates.py` "asserts the two stay equal" — an invariant this task deleted. Leaving it + would have shipped exactly the class `CLAUDE.md` names ("a comment asserting an invariant needs a + test pinning it, or it is a wish"), so the fence — whose stated reason is that `SCRIPT_VERSION` is + the single source and stays — was read as fencing the act, not the object. Recorded here so the + decision is visible rather than absorbed silently. +3. **DEVIATION — `.gitignore` gained `__pycache__/`.** Not in the spec. The new fixture tests import + the checker by path, which writes bytecode into `scripts/`, and an untracked directory there would + have failed the clean-tree gate before every future build. +4. **A `go test -run` pattern that matches nothing prints `ok` and exits 0.** My first attempt at + red-proof #3 used `-run TestCustomerUnified`, which matches no test in `render_test.go`, and + `go test` reported `ok … 0.062s`. Caught and re-run against the real name. This is another + instance of the "an absent line is not evidence" class already in `CLAUDE.md`, and it is worth its + own line because it makes a **red-proof itself hollow** — the one place a false green costs most. +5. **`hub/CHANGELOG.md` and `scripts/CHANGELOG.md` still contain historical `hostInstallVersion` + mentions.** Correct and left alone: the rewritten gate matches code shapes, never prose, so the + history is safe to keep and the gate stays green. +6. **Not acted on, out of scope.** `manifest_bearer_gate.py` prints + `manifests/felhom.secret.yaml:39 KNOWN-BACKLOG committed secret …` on every run and still exits + 0. It is a tracked backlog item (`documentation/runbooks/secrets.md`) and the gate deliberately + does not fail on it — but it means every green `repo_gates.py` run now carries a line that reads + like a finding, in front of a reader who did not previously see it. Left untouched, per "do not + tighten or refactor a currently-green gate". diff --git a/STATUS.md b/STATUS.md index 7cb6b09..528f9fb 100644 --- a/STATUS.md +++ b/STATUS.md @@ -37,8 +37,11 @@ lost: the last good copy is kept intact. *(R-163)* **When that happens, only one page says so** — no email, no alert. The page that answers "is this app backed up?" is the one that stays silent. *(R-158)* -**The check that catches this needs a person to remember it.** One command, run by hand; nothing -refuses a change that skipped it. *(R-161)* +**The checks that catch this still can't stop a change on their own — but they now try.** Every +repository has one command that runs all of its checks, and it runs by itself before every push, so a +push carrying a broken check is refused rather than reported. Two limits are real: it has to be +switched on once in each copy of the code, and it can be skipped on purpose. The version that can be +neither is a build server, which we do not have yet. *(R-29, R-161, R-168)* ## What we're working on @@ -59,8 +62,15 @@ refuses a change that skipped it. *(R-161)* ## Changed since last update +- **2026-08-02** — Fixed: thirteen mechanical checks had built up across the four repositories and + nothing ran most of them. Two were failing quietly, one since 14 July; neither did harm, but nothing + would have said so. Both fixed; every repository now has one command that runs all its checks, and + it runs automatically before every push. One of the two was the setup page claiming the installer + was version 1.19.0 while machines got 1.22.0 — the number is now **gone** rather than corrected, + because the page cannot know it: the installer is fetched fresh each run, so any number there is a + guess. A check refuses to let one be added back. - **2026-08-02** — Decided: the 20 GB backup partition goes away and shares space with app data. That changes the disk layout, so it happens before any machine is installed outside the house. - **2026-08-02** — Decided: only this machine and the tester's box are protected; every other box, - both demo boxes included, may be broken or reinstalled freely. -- **2026-08-02** — Two of the three apps that never saved their data are fixed; this page created. + demo boxes included, may be broken or reinstalled freely. Two of the three apps that never saved + their data are fixed; this page created.