diff --git a/REPORT.md b/REPORT.md index 22f7135..494389b 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,130 +1,65 @@ -# REPORT — the host report carries the box's addresses (agent v0.119.0, 2026-07-31) +# REPORT — this repo gets a place to put a gate (2026-08-02) -**Overwritten** per the standing rule. Pairs with **hub v0.85.0**; the agent half is useless alone. -Full live evidence: `felhom.eu/documentation/audits/host-addresses-visible-2026-07-31.md`. +**Overwritten** per the standing rule. The prior contents (the host report carries the box's +addresses, agent v0.119.0, 2026-07-31) have their durable record in `CHANGELOG.md` and +`felhom.eu/documentation/audits/host-addresses-visible-2026-07-31.md`; nothing was lost. -## 1. Baselines +**No version bump, no build, no deploy.** Nothing compiled changed — this touches `scripts/` and +`.githooks/` only. Stated explicitly so the omission reads as a decision rather than a miss. The +agent stays **v0.119.0** and no binary was copied to any host. -| Repo | `main` @ start | Version before → after | -|---|---|---| -| felhom-agent | `6b5dade4dcc5` | **0.118.1 → 0.119.0** | +## What changed -Clean-tree gate held before the build (`git status --porcelain` empty, `HEAD == origin/main`). +| Commit | What | +|---|---| +| `054e85a` | `scripts/agent_gates.py` + `scripts/test_agent_gates.py` + `.githooks/pre-push` + `CLAUDE.md` | +| `75245a4` | `CHANGELOG.md` | -## 2. What changed and why +Baseline on arrival: `4663df7ff3cc`, clean, `HEAD == origin/main` — matched the spec's anchor. -The hub could not show a managed box's LAN IP **anywhere**, because nothing reported one: -`HostMetrics` carried node/cpu/memory/disk/loadavg/uptime/temp/wrapper-sha and no address of any kind. +## Why this repo got an entry point with exactly one gate -**Two plausible sources were checked and rejected before any code was written** — this is the -substantive part. `lan_resolver.host_ip` is an OPTIONAL config value, absent unless that feature is -configured. `DeriveHostIP(local_api.listen_addr)` returns `169.254.253.1`, because since the R-50 -island migration the local API binds a link-local address that is **byte-identical on every box**. -Either would have produced a confident wrong answer, which is worse than the blank it replaces. +A census of all thirteen gate scripts across the four felhom repos on 2026-08-02 found one clean +correlation: **every check a `CLAUDE.md` tells a person to run was passing, and two of the four +nobody is told to run were failing** — one since 14 July. **This repo was the extreme case: nothing +at all ran against it, and its `REUSE.md` — 90 cited paths — was checked by no one.** -New wire field **`addresses[]`**, one entry per (interface, address). Deliberately iface+cidr rather -than a single `lan_ip`: a Proxmox host legitimately holds several — a management bridge, a tailnet, -the WG tunnel — and picking one to call "the" LAN IP is a guess the agent is not entitled to make, -and is silently wrong on a box whose bridge is not `vmbr0`. The agent reports what exists; the hub -labels. +`scripts/agent_gates.py` runs `reuse_refs_check` over this repo's `REUSE.md`, streams its output and +exits worst-wins non-zero. One gate is the honest size of it today; the file exists so the agent is +not the one repo with nowhere to put a check, and so the pre-push hook has the same entry point in +all four repos. It grows when the agent grows a second gate. -## 3. The filter was chosen by measurement, not reasoning +**The shared checker is never copied here.** `reuse_refs_check.py` stays in `felhom.eu/scripts/` and +is invoked at `/../felhom.eu/scripts/`; a copy would recreate exactly the drift it detects. +**A missing sibling clone is a FAILURE with the path printed, never a skip** — fail-closed, because a +runner that quietly drops a gate is the inert-seam failure this project has shipped four times. +`test_agent_gates.py` pins that by copying the entry point into a lone directory with no sibling and +asserting it convicts. -`ip -o addr show` on **both** demo boxes established that `IsGlobalUnicast()` is the whole rule: it -drops loopback, IPv6 link-local (`fe80::/10`, one per bridge) and IPv4 link-local (`169.254/16` — -exactly the island literal above). It needs **no veth/fwbr/tap denylist**, because on a Proxmox host -that per-guest plumbing carries no IP at all and self-excludes. +The 90 citations now resolve as **88 exact, 1 by suffix** (`localapi/debuglogs_test.go` → +`internal/localapi/debuglogs_test.go`) and **1 cross-repo** (`hub/internal/store/dr_recipe.go`, which +lives in the hub), 0 failures. None of them was genuine drift — the checker was what needed fixing, +not the REUSE map. -The unit fixtures are those two interface tables **transcribed verbatim, including the address-less -rows** — the no-denylist claim rests on those rows really being empty, and a fixture that omitted -them would have proved the claim by assuming it. +## `.githooks/pre-push` -## 4. Files +Runs `agent_gates.py --fast` and refuses the push, printing a line before and after because an absent +log line is not evidence a hook ran. Limits, written into the hook itself: **per-clone** +(`core.hooksPath` is local config; this clone is armed — `git config --get core.hooksPath` → +`.githooks`) and **`git push --no-verify` bypasses it on purpose**, which must be stated in a session +report when used. It was **not** used against this repo; both real pushes ran the hook and passed. +The half that is neither per-clone nor skippable is CI — `felhom.eu` `OPEN-ITEMS.md` **R-168**. -- `internal/hub/hostaddr.go` (**new**) — `HostAddress`, the `AddressEnumerator` seam, - `systemInterfaces`, `filterHostAddresses`, `Collector.collectAddresses` -- `internal/hub/hostaddr_test.go` (**new**) -- `internal/hub/report.go`, `internal/hub/collect.go` — the field + its population -- `internal/hub/contract_test.go`, `internal/hub/report_test.go`, - `internal/hub/testdata/host-report.golden.json` -- `CHANGELOG.md`, `REUSE.md` +## Verification -## 5. Commit +``` + reuse-refs OK (exit 0) -`14642e3` — `v0.119.0 — the host report carries the box's addresses`, pushed to `main` (trunk-based). - -## 6. Tests + red-proofs - -Suite **874 → 883** (+9), `go build ./... && go vet ./... && go test ./...` **rc=0, 29 packages ok**. -Run as a command separate from the commit, per standing rule 1. - -| # | Mutation | Observed | Reverted | -|---|---|---|---| -| 1 | drop the `!IsGlobalUnicast()` continue | `FAIL` — the island address and every `fe80::` leak through, on **both** host fixtures | yes | -| 2 | drop the `!i.Up` guard | `FAIL … a DOWN interface's address was reported` | yes | -| 3 | make `collectAddresses` return `[]HostAddress{}` (the inert-seam shape) | `FAIL … the collector did not run the filter over the enumerator's output` | yes | - -**The repo's own no-nulls invariant caught the new field on the first run** — -`TestHostReport_FieldNamesAndEmptyCollections` failed because `addresses` marshalled as `null`. Fixed -by making the collector return a non-nil slice and setting `[]` in the fixture, matching how every -other collection is handled. That guard did its job unprompted. - -**The seam DEFAULTS to the real enumerator** (`addrEnum == nil` → `systemInterfaces`), deliberately -inverting the nil-reporter-means-off convention the optional stanzas use. Those gate on a config -feature; this one has no dependency and no flag, so a forgotten wiring call in `main.go` would have -shipped it silently empty — the inert-seam failure recorded four times in this project. - -## 7. Cross-repo contract - -`testdata/host-report.golden.json` is duplicated **byte-identically** in -`felhom.eu/hub/internal/api/testdata/`, and `TestHostReport_ContractMatchesGolden` fails on any -top-level key drift. Both goldens moved in this arc and were diffed to confirm they stayed identical; -`addresses[0]`'s key set is now asserted bidirectionally alongside the existing sections. - -## 8. Live validation - -**Method: the agent's own `--selftest=hub`**, which prints "the report it would send" — the wire -itself, not a proxy. On demo-felhom running the deployed 0.119.0: - -```json -"addresses": [ - { "iface": "tailscale0", "cidr": "100.70.170.35/32" }, - { "iface": "tailscale0", "cidr": "fd7a:115c:a1e0::5236:aa24/128" }, - { "iface": "vmbr0", "cidr": "192.168.0.162/24" }, - { "iface": "wg-felhom", "cidr": "10.77.0.2/32" } -] +all agent gates OK ``` -Present: the LAN address. Absent: `lo`, `vmbr9`'s `169.254.253.1`, every `fe80::`. Matches the -`ip addr` ground truth exactly. +`python3 scripts/test_agent_gates.py` → **4 tests, OK** (0 before). No Go file was touched, so no Go +test count changed. -## 9. Deploy + publish + vouch — all three, deliberately (R-115) - -| Leg | Result | -|---|---| -| Deployed `felhom-pve` (demo-felhom) | 0.118.1 → **0.119.0**, `systemctl is-active` → `active`, backup kept as `.bak-0.118.1` | -| Deployed `demo-hp` | 0.118.1 → **0.119.0**, `active`, backup kept | -| **Published** to Gitea | sha256 `e37aca824d0f57bb13b859a9a3cfcc472e84395d6f290ee90228b1933ab9fa43`, upload HTTP 201, **round-trip GET verified** | -| **Vouched** in the Day-0 manifest | read back: `agent=0.119.0`; golden `0.188.0`, `min_agent 0.113.0`, `wrapper_sha256` all preserved **verbatim** | - -`min_agent` deliberately **not** raised — `addresses[]` has no controller coupling, so holding the -controller floor for it would be an unnecessary fleet-wide write. - -**Trap hit and recorded:** the Gitea package listing sorted **lexicographically** ranks `0.96.0` above -`0.118.1`, which read as "nothing since 0.96.0 was ever published" — the exact shape R-120 recorded -for SQL `MAX()`. Re-sorted by semver before picking the version. Anyone auditing that endpoint by eye -will hit it too. - -## 10. Not covered - -- A box with **two LAN bridges** — handled by design (one row per interface) and unit-tested, but no - such box exists to observe. -- **IPv4-only / IPv6-only** hosts; every box here is dual-stacked via tailscale's ULA. -- No new privilege was taken: `net.Interfaces()` is a netlink/procfs read, so the sudoers fence is - untouched and the "a health check issues no block I/O" rule is honoured. - -## 11. Teardown - -This run provisioned nothing — no guest, no VM, no scratch customer. The only mutations are the two -in-place binary upgrades (previous binaries retained as `.bak-0.118.1` on both boxes) and the Day-0 -manifest vouch. +Full cross-repo detail, every red-proof, the `core.hooksPath` probe measurements and the hub +deployment: `felhom.eu/REPORT.md`.