Record the guards, the narrowing, and the two things I could not do
gates / gates (push) Successful in 30s
gates / gates (push) Successful in 30s
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.
This commit is contained in:
+22
@@ -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
|
||||
|
||||
@@ -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<version>/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<version> <released-commit> && git push origin v<version>`"* |
|
||||
| **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 <version>`"* |
|
||||
| **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 `<script>` tags**, same palette
|
||||
(`#0b1220` / `#121b2c` / `#34d399` `#60a5fa` `#fbbf24` `#64748b`), 1600 px, A3 landscape print rules.
|
||||
- Every moved status carries a visible **`changed 2026-08-09, was walked`** chip plus a *why it moved*
|
||||
line — 12 of them, no diffing required.
|
||||
- `Where Felhom Stands.html` → **`where-felhom-stands-2026-08-09-snapshot.html`** (`git mv`, so the
|
||||
space is out of every shell path), with a line in `documentation/README.md` calling it a dated
|
||||
snapshot that is not maintained.
|
||||
- **What the old bundle actually was**, since it aimed the fix: not merely minified — the content sat
|
||||
**gzip+base64 inside a JS module map**, and the three blobs decompress to the bundler and React, with
|
||||
the document itself in a JSON-escaped string on line 393. It rendered and nothing else.
|
||||
|
||||
## 7. The corrected silence rows
|
||||
|
||||
**R-281 is WITHDRAWN. It was wrong in both directions**, and the operator's mailbox is what settled it.
|
||||
|
||||
- **The tripwire DID fire**: `escrow_blob_served` at 10:19:41 UTC = **12:19 CEST**, eight minutes before
|
||||
the verified restore.
|
||||
- **False alarms fired too**: `host_down` 09:28 UTC and `node_down` 09:30 UTC, both error severity, both
|
||||
`sent`, for deliberate attended work — eight operator mails in all. → **R-285**, the opposite gap
|
||||
from the one filed.
|
||||
|
||||
**The measurement's cause IS established.** The P7 query copied `/data/hub.db` **without
|
||||
`hub.db-wal`**; the hub runs SQLite in WAL mode, so everything after the last checkpoint was invisible.
|
||||
**Signature, exact:** P7 reported *"2 events all day, newest `db_dump_completed` 00:30:07"*, and the
|
||||
number of rows on 08-09 at or before 00:30:07 is **exactly 2**.
|
||||
|
||||
The two obvious alternatives were **tested and refuted**, not waved away: a **timezone offset** — all
|
||||
nine mailbox stamps equal the hub's UTC + 2 h exactly, so the window was right; and a **wrong key or
|
||||
wrong store** — the same table and key return the correct rows now. A live re-run **cannot** reproduce
|
||||
the fault because the WAL has since been checkpointed, and that is stated rather than dressed up as a
|
||||
reproduction.
|
||||
|
||||
**The lesson, filed as R-286:** the control was drawn from the *same stale snapshot* as the
|
||||
measurement, so it agreed. **A control must come from a different channel.** The independent channel —
|
||||
the mailbox — was available the whole time. This is also a trap `operations/nodes.md` already
|
||||
documents, and which I had avoided correctly earlier in the same session.
|
||||
|
||||
## 8. Register
|
||||
|
||||
**Ceiling moved R-284 → R-290.** Opened: **R-285** (planned reinstall pages the operator), **R-286**
|
||||
(same-channel control), **R-287** (the CI red is true), **R-288** (the capability map is unreadable),
|
||||
**R-289** (R-182's row is stale), **R-290** (map rows cite no evidence). Withdrawn: **R-281**.
|
||||
|
||||
## 9. Part 4 — and I did not change the workflow, on purpose
|
||||
|
||||
**Which gate:** `published` / `scripts/check-published-versions.py`.
|
||||
|
||||
**The premise is wrong in every particular. It is not ref-sensitive.** The gate enumerates releases
|
||||
from the **Gitea tags API** (`main()`, `/api/v1/repos/admin/felhom-agent/tags?limit=200`), so the
|
||||
checked-out ref is irrelevant — and the two previous tag pushes **passed** (run 190 `v0.126.0`, run 216
|
||||
`v0.127.0`).
|
||||
|
||||
**What is true:** run 267 (main, `28ba8593b8`, 08-08 14:29 UTC) printed `ok v0.120.0: binary
|
||||
downloadable`. Run 284 (**the same commit**, on the tag, 08-09 09:30 UTC) printed
|
||||
`FAIL v0.120.0 — HTTP 404`. A published release became uninstallable between the two. The registry now
|
||||
holds exactly the ten newest versions; `0.128.0` was published **14:47 UTC, eighteen minutes after run
|
||||
267**.
|
||||
|
||||
**Who removed `0.120.0` is NOT established, and I will not guess:** `package_cleanup_rule` is empty
|
||||
(queried in Postgres), `app.ini` sets no limit, `publish-agent.sh:77` only pre-deletes the version it
|
||||
is publishing, the Gitea pod has 53 days uptime and 0 restarts, and **no `DELETE` on the packages API
|
||||
appears in 48 h of router logs**. The internal `[cron.cleanup_packages]` `@midnight` job falls in the
|
||||
window and would leave no router line — **a leading candidate, not a conclusion.**
|
||||
|
||||
**So nothing was silenced and no workflow file was changed.** The red is a **true positive** — a tagged
|
||||
version that cannot be installed is the exact R-115 defect the gate exists to catch, and muting it
|
||||
would hide the next one. **What it therefore still does not check: nothing. Nothing was disabled.**
|
||||
The honest fixes — bound the gate to versions at or above the vouched `min_agent` floor (0.127.0 today;
|
||||
nothing can install 0.120.0), or retire tags whose packages go — are release decisions, and §8 forbids
|
||||
fixing findings here. Filed as **R-287**.
|
||||
|
||||
**Also measured, and it is good news:** the failure alarm did send —
|
||||
`RESEND-ACCEPTED id=fa1a7a83-714f-4357-b0ca-d3c4bb7ae73f`.
|
||||
**Ceiling R-290 → R-292.** Opened **R-291** (the narrowing, with its reason, so it can be widened
|
||||
deliberately) and **R-292** (`artifact_sha_invalid` conflates three facts — found by scenario E
|
||||
failing, worked around by ordering, message untouched). Closed: **R-273's owed-guards tail**, both
|
||||
guards built. Corrected: **R-287**, twice.
|
||||
|
||||
## 10. Observations — noticed, not acted on
|
||||
|
||||
- **`fail.app-crash` and `fault.operator-email` are the same unobserved thing** seen from two sides:
|
||||
the digest is wired and correct in source, and no one has watched it arrive.
|
||||
- **The verification depth is recorded per claim** (`depth: source-read | register+map |
|
||||
needs-hardware`). 23 of 55 got a source or evidence read; the rest were checked against the register
|
||||
and map only. That is on the face of the data rather than implied by a green tick.
|
||||
- **The capability map is the thing that actually needs fixing.** The dataset now disagrees with it for
|
||||
twelve rows, and the dataset is only a view — **the map still says PROVEN-LIVE for all twelve** (R-290).
|
||||
- `documentation/audits/` holds 131 files and `tests/` 37; the evidence exists in quantity. The gap is
|
||||
that the map does not point at it.
|
||||
- **Out of scope and left alone, as instructed:** every finding above, the capability-map restructure
|
||||
(R-288), and the two guards owed from yesterday.
|
||||
- **R-292 is the interesting one.** A test I wrote to check a new guard failed for a reason that had
|
||||
nothing to do with the guard, and that reason was a real pre-existing defect. The five scenarios
|
||||
earned their keep before the feature shipped.
|
||||
- **The golden gets no tag probe.** It is fetched by version and has no config tree, so a tag probe
|
||||
would assert something the installer never does. Deliberate, and stated in the code.
|
||||
- **The gate is skipped entirely when no Gitea client is configured**, or a hub without registry
|
||||
credentials could never vouch anything. Pinned by its own test, and worth knowing: the guard is only
|
||||
as present as the client is.
|
||||
- `check-release-complete` runs in `--fast`, so a missing tag is now caught by the **pre-push hook**,
|
||||
earlier than CI.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# STATUS — what works, what's broken, what's next
|
||||
|
||||
**Updated 2026-08-09.**
|
||||
**Updated 2026-08-09 (evening).**
|
||||
|
||||
> **A view, not a source.** `documentation/backlog/OPEN-ITEMS.md` is the authority; this page restates
|
||||
> part of it in plain words, and **nothing may exist only here**. Not `CONTEXT.md`, which is technical
|
||||
@@ -42,6 +42,38 @@ destroyed on purpose and its files came back byte for byte identical — four ti
|
||||
their recovery code got everything back with **no command line inside the machine at any point**,
|
||||
in 72 seconds. The two rough edges that walk found are also gone. *(R-201, R-252, R-253 — closed.)*
|
||||
|
||||
## The guard is in. An approval that cannot be installed is now refused.
|
||||
|
||||
**Yesterday morning every install in existence failed for hours, and nothing would have stopped it
|
||||
happening again. Now something does.** When you press Save on the Day-0 artifacts, the hub checks —
|
||||
before it writes — that each version you are vouching actually has its git label **and** that its file
|
||||
can actually be downloaded. If either is missing it refuses and tells you which, for which version,
|
||||
and the one command that fixes it. **Hub v0.102.0 is live.**
|
||||
|
||||
Three details worth your knowing:
|
||||
- **It checks the exact file the installer fetches first** — the one whose absence broke Friday — not
|
||||
some other file that happens to exist. A test pins that, because probing the wrong file is precisely
|
||||
how the failure stayed invisible.
|
||||
- **"Could not check" also refuses**, with a different message. Saving with a warning would read as a
|
||||
success, and we have the scars. **There is no override**: the registry is on your own server, so if
|
||||
it is unreachable the approval can wait.
|
||||
- **A second machine now catches it one step earlier** — the agent repo refuses to consider a release
|
||||
complete unless its label and its package both exist.
|
||||
|
||||
## The red repository is green, and the two rules now share one number
|
||||
|
||||
The tidy-up keeps the newest ten versions; the check demanded every version ever labelled still be
|
||||
downloadable. Both are sensible and together impossible, so the red would have returned on your next
|
||||
publish. They now read the same number from one file. **What the check no longer covers, plainly: a
|
||||
version older than the ten is no longer asserted downloadable** — its label and its config files still
|
||||
are — and it prints which ones it dropped on every run so this cannot go quiet.
|
||||
|
||||
**One thing I could not establish, and I am not guessing.** Who actually deleted the old versions is
|
||||
**still unknown**. Gitea keeps no deletion trail: no cleanup rule is configured, the version table has
|
||||
no deleted-marker, the activity feed shows no package operation, and the server log no longer reaches
|
||||
back that far. I also **withdrew my own claim from yesterday** that the logs showed no deletion — the
|
||||
logs did not cover the window, so they never said anything.
|
||||
|
||||
## The rehearsal finished. The data came back byte for byte; the journey did not.
|
||||
|
||||
**We wiped a working demo machine and put it back. All four test files returned identical — including
|
||||
|
||||
@@ -519,6 +519,8 @@ applied.** The one that matters: Scenario A **fails against today's tree** with
|
||||
| **R-288** | **The capability map is too long to be read, and that is why it stops being true.** `architecture/00-capability-map.md` is **134 642 bytes / 19 456 words across 99 table rows in only 159 lines** — because the rows ARE the length. Measured, longest first: the unaided-recovery-journey row is **3 024 words**, the offsite-password-recovery row **1 087**, the unattended-restore-proof row **971**, the app/guest-network-failure row **904**. That single longest row is a novella of nested corrections, each appended rather than resolved. Its own verification stamp reads **2026-07-16 against evidence corpus @ felhom.eu tip `4b18cc5`** (line 23) — three weeks stale, which is the measurable consequence: nobody re-reads a row they cannot finish. **This is the project's memory, so restructuring it is surgery and wants daylight** — filed, deliberately not attempted in the 2026-08-09 session. **What the shape should probably be:** one line of status per capability plus a dated evidence pointer, with the argument moved to the audit it came from | **READY (M) — NEW 2026-08-09** | — | Do not fold this into another session; it needs its own | Viktor |
|
||||
| **R-289** | **R-182's register row describes a defect the code no longer has — an OPEN row that is a false alarm.** The row reads *"A full disk tells the operator about ONE app and silently swallows every other app's refusal for an hour"*, cited at `hub/internal/notify/dispatcher.go:268`. **Read against live source 2026-08-09, that is fixed:** the per-run digest `backup_run_failures` is allowlisted (`hub/internal/api/handler.go:1837`), operator-only (`dispatcher.go:423`) and templated (`notify/templates.go:48`); `recovery_unit_capture_failed` is now a **record-only** event (`dispatcher.go:376`) whose notification IS the digest, listing every failed app in one mail; and a cooldown drop now writes a `suppressed` row instead of vanishing (`dispatcher.go:314-330`). The capability map already records the fixed shape (*"EVERY failing app, in ONE mail per run"*). **So the register is behind the code, which is the mirror of the decay this session was looking for** — the session expected stale PROOFS and found a stale DEFECT. **Not closed here, deliberately:** the digest's *delivery* has never been observed end to end (the page's own "an app crashes — the email leg has never been confirmed" card), so the honest move is to re-scope R-182 to that residue rather than tick it | **READY (XS) — NEW 2026-08-09** | — | Re-scope R-182 to "the digest has never been seen delivering", or close it and open that | CC |
|
||||
| **R-290** | **Most capability-map rows that back a green dot cite no evidence document at all — measured, 20 of 28 probed.** The page's *Walked* means *"done end to end on real hardware, evidence on file"*. Extracting the evidence column for the 28 rows behind the page's claims found a `tests/` or `audits/` path in **8**; the other 20 carry prose only. **Consequence, applied this session:** of 32 claims the page drew as Walked, **12 were downgraded to Built** because no walk document exists for them — `install.installer-by-tag`, `use.lifecycle`, `drives.enrol`, `drives.migrate`, `backup.tier1`, `backup.whole-machine`, `backup.restore-proof`, `fault.selfheal`, `fault.operator-email`, `fail.drive-filling`, `fail.lost-recovery-code`, `fail.hub-down`. **This is not a claim that those twelve are false** — several are near-certainly fine — it is a claim that nothing on file distinguishes them from an opinion, which is exactly what the status word promises. **The gate now enforces it going forward:** `scripts/check_stands.py` fails on `status: walked` with no `evidence:` source. **What is owed:** either a walk document per row, or an honest demotion in the map itself (the map is the source; the dataset only follows it) | **READY (M) — NEW 2026-08-09** | R-288 | The dataset was corrected; **the capability map itself still says PROVEN-LIVE for these rows** and is the thing to fix | Viktor |
|
||||
| **R-291** | **CI's installability assertion is now BOUNDED by a retention number, and the narrowing is recorded here so it can be widened deliberately rather than discovered.** `check-published-versions.py` demanded that **every** `v<semver>` tag still be downloadable while the registry demonstrably does not retain every version — two sensible rules that cannot both hold, which is why CI went red at a commit whose own run had been green the day before, and would have gone red again at the next publish. **The fix couples them:** `felhom-agent/scripts/retention-policy.json` is THE number (`generic_versions_kept: 10`) and the check reads it. **WHAT CI NO LONGER COVERS, stated plainly: a released version older than the retention window is no longer asserted downloadable.** Its git TAG and its config tree are still asserted — only the binary's presence is dropped — and the check **prints the dropped versions on every run**, so the narrowing cannot go quiet. Controls run: widened to 11 the evicted version re-enters and convicts (exit 1); the policy file removed gives INCONCLUSIVE (exit 2), never silently unbounded. **The number is an OBSERVED state, not a located ruling** (R-287) and the file says so. **The better bound, recorded rather than built:** the hub's vouched `min_agent` floor — nothing can install an agent below it, so a sub-floor version being un-downloadable costs nothing real; it needs the gate to read the hub, which is network it does not have today | **READY (S) — NEW 2026-08-09** | R-287 | Widen or replace the number when the deleter is established, or move the bound to the floor | CC |
|
||||
| **R-292** | **The artifact-save flash conflates three different facts, and a failing test found it rather than a reading.** `artifact_sha_invalid` reads *"the Gitea sha lookup failed (version missing / Gitea unreachable) or the manually-entered sha is invalid"* — three causes, one message, and the operator acts differently on each. It surfaced because scenario E of the new installability gate kept reporting `artifact_sha_invalid` where it expected `artifact_unverifiable`: `resolveArtifactSHA` ran first and swallowed the distinction. **Worked around in v0.102.0 by ORDERING** — the installability probes now run before the sha resolution, so an unreachable registry is reported as unreachable — **but the underlying message is untouched and still conflates on its own paths** | **READY (XS) — NEW 2026-08-09** | — | Split it into "version not found", "registry unreachable" and "invalid sha" | CC |
|
||||
|
||||
**Explicitly still open, untouched by this session:** R-246, R-255, R-256, R-257, R-261, R-262,
|
||||
R-263, **R-264** (the twenty-one undecided facts — a design session of its own), R-240, R-243,
|
||||
|
||||
Reference in New Issue
Block a user