From 985f0ba63c09650b955a948fafcc66a45b0d2d74 Mon Sep 17 00:00:00 2001 From: kisfenyo Date: Sun, 9 Aug 2026 19:28:45 +0200 Subject: [PATCH] Record the guards, the narrowing, and the two things I could not do R-273's owed guards are both built and closed. R-291 records what CI stopped covering and why, so it can be widened deliberately rather than discovered. R-292 is new and was found by a test failing for the wrong reason: artifact_sha_invalid conflates "version missing", "registry unreachable" and "bad sha" into one message. v0.102.0 works around it by ORDERING -- the probes run first, so an unreachable registry is reported as unreachable -- but the message itself is untouched. CONTEXT gains the rule this session is about: a check and the policy it enforces must read the same number from the same place, or they drift and the drift looks like a defect in something else. Two corollaries, both of which cost something: a bounded check must print what it stopped covering on every run, and an unreadable policy is INCONCLUSIVE rather than unbounded. Stated in the report rather than glossed: Part 4 (finding receipts for the twelve downgraded claims) was NOT done and is a shortfall, not a decision -- splitting it would have produced exactly the half-checked green the exercise exists to prevent. Part 5 was droppable and dropped. The tag-push green is not re-proved tonight and is not claimed; the evidence offered is runs 190 and 216. --- CONTEXT.md | 22 +++ REPORT.md | 293 ++++++++++++---------------- STATUS.md | 34 +++- documentation/backlog/OPEN-ITEMS.md | 2 + 4 files changed, 184 insertions(+), 167 deletions(-) diff --git a/CONTEXT.md b/CONTEXT.md index 718d584..72bf06a 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -15,6 +15,28 @@ > would make one of the two audiences stop reading. `STATUS.md` is also a **view of `OPEN-ITEMS.md`** > and holds nothing of its own; this file does hold its own content, namely the standing rulings below. +## A check and the policy it enforces must read the same number from the same place + +**Earned 2026-08-09.** The container registry retains N versions per package; `check-published-versions.py` +demanded that **every** git tag still be downloadable. Both rules are individually sensible and together +they are impossible. Nothing connected them, so the day the (N+1)th agent was published the oldest was +evicted and CI went red **at a commit whose own run had been green the day before** — on a true finding +nobody could act on, and one that would have returned at the very next publish. + +**The drift looks like a defect in something else.** It was filed as CI being "sensitive to tag refs", +which it is not; two earlier tag pushes had passed, and the gate reads its version list from an API that +does not know what ref is checked out. A whole session went into the wrong explanation before the two +numbers were even compared. + +So: **one file owns the number, and every consumer reads it from there** — today +`felhom-agent/scripts/retention-policy.json`, read by the check and referenced by the prune procedure. +Two corollaries, both of which cost something to learn: + +- **A bounded check must print what it stopped covering, every run.** A narrowing that goes quiet becomes + permanent by accident. +- **An unreadable policy is INCONCLUSIVE, never "unbounded".** A check that silently loses its bound is + worse than one that fails. + ## Standing rulings **S-39 — "WE DO NOT KNOW" IS NEVER DRAWN AS "FINE", AND THE CODEBASE HAS ONE WAY OF SAYING IT diff --git a/REPORT.md b/REPORT.md index 747b66b..c4e5f7e 100644 --- a/REPORT.md +++ b/REPORT.md @@ -1,191 +1,152 @@ -# REPORT — making the picture true (2026-08-09, unattended) +# REPORT — the guards, and one thing I could not establish (2026-08-09 evening) -Read-only against all live infrastructure. **Both demo machines were powered off and in transit; no -box was probed, woken or waited on.** Claims that only a running box could settle are marked -`needs-hardware`, which is a verdict, not a gap. +No machine touched. Both demo boxes stayed offline and in transit; nothing here needed them. + +**Parts 1, 2 and 3 are done. Part 4 and Part 5 are NOT done** — see §6 and §8. Part 5 was marked +droppable and was dropped; **Part 4 was not marked droppable and I ran out of session before it**, +which is a shortfall rather than a decision. --- -## 1. The verdict table +## 1. Part 1.1 — the deleter: **NOT ESTABLISHED** -**55 claims. Statuses moved on 12 of them — all downwards.** `walked 32 → 20`, `built 5 → 17`; -`partial 14` and `missing 4` unchanged. Full per-claim detail with sources is in -`documentation/architecture/where-felhom-stands.yaml`. +**This prompt's account is not corroborated by any source I can reach**, and the prompt itself said +not to take its word for it. -| claim | was | now | why | +| claim in the prompt | what I found | +|---|---| +| "the register records … pruned to the newest ten … 33 deletions, all HTTP 204" | **No such row exists.** The only prune-adjacent row is **R-210**, which is `WAITING-ON-OPERATOR`, says in terms *"Nothing was deleted; this is a list, not an action"*, and concerns **local Docker images on DooPlex**, not the Gitea registry | +| "newest ten per package" | **Not visible in the current state.** `felhom-agent` generic holds 10, but `felhom-controller` and `felhom-hub` container packages hold **19 each** | + +Everything else I could reach is silent, and silence here is not evidence of anything: +`package_cleanup_rule` is **empty**; `package_version` has **no soft-delete column**, so a deletion +leaves no row; Gitea's `action` feed carries **no package operation at all** across 2026-08-08 → +2026-08-09 and nothing whatever on the evening of 08-08; the Gitea pod has **53 days uptime, 0 +restarts**. + +**And I withdrew one of my own claims.** R-287 said *"no DELETE on the packages API appears in 48 h of +Gitea router logs"*. Re-checked: `kubectl logs --since=72h` returns nothing older than **2026-08-09 +16:35** and contains **zero** `api/packages` lines even for requests I made myself. **The log never +covered the window, so its silence was never evidence.** Withdrawn in the register. + +**What remains established, unchanged:** `v0.120.0` downloadable at 2026-08-08 14:29 UTC (run 267 +printed `ok v0.120.0`), 404 by 2026-08-09 09:30 UTC (run 284), and `0.128.0` published at 14:47 UTC — +eighteen minutes after the good run. **It may be unestablishable from this side: Gitea keeps no +package-deletion trail.** + +Per §8 I halted that part's attribution and carried on; the rest is independent. + +## 2. The number, and the two places that read it + +`felhom-agent/scripts/retention-policy.json` → `generic_versions_kept: 10`. + +Read by **`scripts/check-published-versions.py`** (bounds its assertion) and referenced by the prune +procedure. The file states, in its own header, that ten is an **observed state and not a located +ruling**, and that the principled bound is the hub's vouched `min_agent` floor — nothing can install +below it — which needs network the gate does not have. + +**What CI no longer covers:** *a released version older than the retention window is no longer +asserted downloadable.* Its **git tag and config tree are still asserted**; only the binary's presence +is dropped. The check **prints the dropped versions every run**: + +``` +11 released version(s); retention policy keeps the newest 10 +NOT ASSERTED (older than the retention window …): 0.120.0 +^ these versions still have git TAGS … what is no longer asserted is the BINARY's presence. +``` + +Three controls: green at 10 naming what it dropped (**exit 0**); widened to 11 the evicted version +re-enters and convicts (**exit 1**, `FAIL v0.120.0`); the policy file removed → **exit 2 INCONCLUSIVE**, +naming the path it tried — never silently unbounded. + +## 3. `felhom-agent` green + +``` +reuse-refs OK · instructions OK · published OK · release-complete OK · all agent gates OK +``` + +**At `main`: green**, pushed as `53d047a`, and the pre-push hook ran the same entry point. +**At a tag: not re-proved tonight, and I will not claim it.** The available evidence is that the gate +is ref-independent — it enumerates from the Gitea tags API, and runs **190** (`v0.126.0`) and **216** +(`v0.127.0`) were tag pushes that passed. Minting a tag purely to prove it would have published a +release, which this session forbids. + +## 4. Red-proofs + +| # | mutation | asserted applied | outcome | |---|---|---|---| -| `install.installer-by-tag` | walked | **built** | gate 6 asserts the manifest names an installer tag; no walk of a rollback on file | -| `use.lifecycle` | walked | **built** | no walk document cited | -| `drives.enrol` | walked | **built** | the 08-09 walk exercised RE-attach (which failed, R-280); first enrolment of a NEW drive has no walk | -| `drives.migrate` | walked | **built** | no walk document cited | -| `backup.tier1` | walked | **built** | no walk document cited | -| `backup.whole-machine` | walked | **built** | no walk document cited | -| `backup.restore-proof` | walked | **built** | no walk cited, **and the last recorded restore-test on demo-hp FAILED** (2026-08-05) | -| `fault.selfheal` | walked | **built** | no walk document cited | -| `fault.operator-email` | walked | **built** | source-verified as correct, but no run observed delivering | -| `fail.drive-filling` | walked | **built** | no walk document cited | -| `fail.lost-recovery-code` | walked | **built** | by-design refusal; no walk document cited | -| `fail.hub-down` | walked | **built** | no walk document cited | +| 1 | **tag check removed** | `grep -c` → 1 | **scenario A FAILED, reporting `flash = "artifacts_set"`** — Friday's exact defect returned: the manifest saved with no tag | +| 2 | package check removed | `grep -c` → 1 | scenario B FAILED, `flash = "artifacts_set"` | +| 3 | inconclusive branch mapped onto success | `grep -c` → 2 | scenario E FAILED | -**Upgraded: 1.** `install.byo` — the page said *"the first real one has not happened"*. A real -`--mode byo` install completed on demo-hp on 2026-08-09 (`Day-0 provision SUCCESS`, 3 m 49 s). Still -not a customer's own hardware, so not *walked*, but the sentence was false. +All reverted; `grep -c MUTATION` → **0**; all seven tests green again. -**`needs-hardware`: 4** — `use.lan-fallback`, `backup.restore-proof`, `fail.disk-failing`, -`fail.internet-down`. Each needs an observation on a running box; each says which. +**Red-proof 3, stated precisely rather than flatteringly:** with the inconclusive branch mapped onto +success the save did **not** complete — it fell through to `artifact_sha_invalid`. So the mutation +proves the guard is load-bearing for *the message the operator sees*, not for the save itself. That is +the honest reading, and it is exactly the defect scenario E exists to prevent: the operator being told +"missing or invalid" when the truth is "I could not reach the registry". -**Confirmed: 38.** Seven of those were re-confirmed against live source or the live hub tonight rather -than against paperwork: the tripwire, the off-site repository, the claim path, the catalogue, the -tunnel, the reset code and the operator-email digest. +## 5. The five scenarios, as the operator sees them -## 2. Every downgrade, with the coupling that broke +| | outcome | the message | +|---|---|---| +| **A** tag missing | REFUSED | *"Refused: that version has no usable git tag. The installer fetches an agent's config files from `raw/tag/v/configs/`, so a version published without its tag makes every fresh install and reinstall fail at step 5 of 8 — as root, on a virgin machine. … Fix it by pushing the tag: `git tag -a v && git push origin v`"* | +| **B** package pruned | REFUSED | *"Refused: that version's artifact is not downloadable. The version is tagged but its package is not in the registry, so a box would 404 fetching the binary itself. … Publish it — `bash scripts/release-agent.sh `"* | +| **C** no checksum | REFUSED | the pre-existing *"Couldn't set the checksum …"* — see the note below | +| **D** both good | **SAVES**, `artifacts_set`, byte-identical behaviour | — | +| **E** registry unreachable | REFUSED | *"Refused: could not verify — this does not mean anything is missing. … It refuses rather than saving with a warning, because a warning beside a success reads as a success. … There is deliberately no override: the registry is on your own server, so if it is unreachable the vouch can wait."* | -The rule is *"a proof is about the code that existed when it ran"*. **It did not fire the way the task -expected.** Not one downgrade came from code moving under an old proof. **All twelve came from step 1 -of the same rule — the cited evidence does not exist.** +**Scenario C changed shape because my first draft modelled nothing real,** and that is worth recording. +With a Gitea client configured, `resolveArtifactSHA` fetches the sha **authoritatively and ignores what +was submitted** — so "submit an empty sha" cannot produce an empty stored sha. The genuine shape is +Gitea answering with no `sha256`, and the **existing** refusal already owns it. The test now pins the +guarantee (*the manifest is unchanged*) rather than a mechanism I had invented. -Measured: of the 28 capability-map rows behind the page's claims, **8 carry a `tests/` or `audits/` -path in their evidence column and 20 carry prose only.** The green dots were being drawn from rows -that cite an argument, not a walk. Filed as **R-290**. +**No override was built, and none is wanted.** §8's halt condition did not trigger. -**And the decay ran the other way once.** `fault.operator-email` — *"one mail per run, every failing -app named"* — I first took to be contradicted by R-182 (open, *"tells the operator about ONE app and -silently swallows every other"*). Reading live source: the digest `backup_run_failures` is allowlisted -(`hub/internal/api/handler.go:1837`), operator-only (`notify/dispatcher.go:423`) and templated -(`notify/templates.go:48`); `recovery_unit_capture_failed` is record-only (`dispatcher.go:376`); a -cooldown drop now logs a `suppressed` row (`dispatcher.go:314-330`). **The claim is right and the -register row is stale** — filed as **R-289**. The session went looking for stale proofs and found a -stale defect. +## 6. Part 4 — NOT DONE -## 3. The positive control +The twelve downgraded claims were not re-examined and no receipts were searched for. **The honest-grey +count is therefore still the twelve from last night, unverified in either direction**, and +`where-felhom-stands.*` is untouched. This is the session's shortfall: Parts 1–3 took the budget, and +splitting Part 4 in half would have produced exactly the kind of half-checked green the whole exercise +exists to prevent. -``` -1 BASELINE real dataset -> OK, exit 0 -2 PLANT scratch copy: use.dlna missing -> walked -> CONVICTED, exit 1 - "use.dlna: status 'walked' but NO evidence document cited" -3 REMOVE scratch copy deleted; committed dataset never touched -4 RE-RUN real dataset -> OK, exit 0 -``` +**Whether the "code moved under the proof" rule can fire at all** is therefore still open from last +night, where it fired **zero** times out of twelve — every downgrade came from missing evidence, not +from decayed evidence. My reading remains that it *can* fire but will stay rare until rows cite +evidence at all, which is R-290. -Plant → convicted → removed → clean. The gate also convicted **51 problems in my own first draft** of -the dataset (bad anchors, register ids that are not in `OPEN-ITEMS.md`, an evidence path that does not -exist) before any of this — which is the more convincing demonstration, because it was not staged. +## 7. Hub deployed -## 4. The two known disagreements — both settled, and neither document was wrong +**v0.102.0**, live and verified: deploy image `gitea.dooplex.hu/admin/felhom-hub:0.102.0`, rollout +complete, and the page footer reads `0.102.0`. Manifest commit **`36bcd12`**; code commit `b55fc17`. +The image was verified **served by the registry before** the manifest was bumped, not after. ArgoCD's +`felhom` app has `automated.enabled: false`, so the sync was triggered explicitly — **no +`kubectl set image` at any point.** -**"A customer restores their own data with no help."** The map says **MISSING (as evidence)**; the -2026-08-07 walk records a customer route completed with no shell. **Not a contradiction.** The map's -row is *"A customer (**not the operator**) performs a restore via UI alone"* — it is about *who*. The -walk proves the *route*. No non-operator has ever done it, which is what the page's own neighbouring -claim already says. +## 8. Part 5 — dropped, as marked -**The reinstall story.** The map's `PROVEN-LIVE (2026-08-04 night drill)` row is scoped in its own text -to *"a controller-data-volume rebuild — NOT a total host loss"*. The 2026-08-09 rehearsal was a -whole-host uninstall and reinstall. **The map has no row for that case at all** — a gap, not a -disagreement. +Not started. It was explicitly droppable and it is the only part that touches customer-facing wording, +which is where a rushed edit does most harm. -**Would anything here have caught either one? No — and it could not have, because neither was false.** -Both are collisions of vocabulary: "customer" meaning *the route* or *a person*, "rebuild" meaning -*the guest* or *the host*. No gate detects an ambiguity that makes two true sentences look -contradictory. They surfaced only when someone tried to state them side by side. **That is the -argument for the dataset** — one id, one scope, one status — and against prose rows. +## 9. Register -## 5. The data file - -`documentation/architecture/where-felhom-stands.yaml`, 55 entries. **Every entry cites at least one -source and the gate proves it** (`check_stands.py` rule 1). YAML rather than JSON because statuses move -one line at a time and a YAML diff shows which claim moved; a JSON re-dump reflows. - -Rules honoured: it is a **view** (every entry cites map / register / evidence); **no status was raised -in it** — the one upgrade is recorded against evidence and the map is named as the thing that must -change; and it is **regenerated, not hand-edited** for the page. - -## 6. The page - -- `where-felhom-stands.html` — **generated, 54 KB, zero `