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.
9.4 KiB
REPORT — the guards, and one thing I could not establish (2026-08-09 evening)
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. Part 1.1 — the deleter: NOT ESTABLISHED
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 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 |
|---|---|---|---|
| 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 |
All reverted; grep -c MUTATION → 0; all seven tests green again.
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".
5. The five scenarios, as the operator sees them
| 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." |
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.
No override was built, and none is wanted. §8's halt condition did not trigger.
6. Part 4 — NOT DONE
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.
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.
7. Hub deployed
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.
8. Part 5 — dropped, as marked
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.
9. Register
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
- 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-completeruns in--fast, so a missing tag is now caught by the pre-push hook, earlier than CI.