docs: R-168 SHIPPED, R-29 CLOSED on the demonstrated alarm, R-169 minted
gates / gates (push) Successful in 7s

SPIKE-ci-runner-2026-08-02.md: all six probes with method, measurement and ruling; none
STOPped. P2 (stock image has git but no python3) and P6 (a runner that loses its state
re-registers and orphans the old record) changed the design; P5 (a failed run signals
NOTHING) is why the alarm exists at all.

R-168 SHIPPED with its evidence. R-29 CLOSED — on the demonstrated alarm and not on a green
run, as required: the class it opened is answered at both ends, the hook refusing locally and
CI catching a --no-verify bypass and emailing. R-161 noted: its automatic half now exists for
the STATIC gate, while its original scope, the runtime gate, is deliberately still not
automatic and should stay that way.

NEW R-169 (grep established R-168 was the highest in use): CI can only report, because there
is no gate in the road. Making it blocking needs branch protection plus a PR workflow, both
of which change how the operator works — so it is theirs to decide, and the row states the
cost honestly rather than recommending it.

CONTEXT gains S-8 (CI detects, does not block, and why that is structural), S-9 (a detector
that tells no one is not finished, plus the curl and Cloudflare-1010 traps), S-10 (the runner
is unprivileged because DooPlex is Tier 2), S-11 (CI reproduces the sibling layout).

CLAUDE.md gains the rule earned by red-proofing: a go test -run pattern that matches no test
prints ok and exits 0, and an instrument that can silently drop results is not a measurement.
This commit is contained in:
2026-08-02 16:35:34 +02:00
parent 4cc123809c
commit c718aad1bc
8 changed files with 534 additions and 253 deletions
+36
View File
@@ -17,6 +17,42 @@
## Standing rulings
**S-8 — CI detects; it does not block, and that is structural (2026-08-02, R-168).**
A Gitea Actions runner in `gitea-system` re-runs every repo's gate entry point on every push,
independent of who pushed and of what they typed. It **cannot refuse a push**: every felhom repo
pushes straight to `main` with no pull request, so there is no merge for a status check to stand at.
That is not a gap in the runner — there is no gate in the road. The refusing half is
`.githooks/pre-push` (per-clone, `--no-verify`-able); this half is what notices when that hook was
skipped or was never armed. Making CI blocking needs branch protection plus a PR workflow, which
changes how the operator works and is **their** call → R-169. Do not "fix" this by adding branch
protection.
**S-9 — a detector that tells no one is not finished (2026-08-02, R-168 probe P5).**
Probe P5 measured that a failed run produces **no mail, no notification row and no log line** from
Gitea. So the workflow sends its own alarm on the project's existing Resend path and **prints the
provider's accepted id**, which makes "a message left the machine" an observable rather than an
assumption. The acceptance test for this work was never a green pipeline — it was a red run with a
message in hand (`RESEND-ACCEPTED id=…`). Two traps found while building it, both worth keeping:
the runner image has **no `curl`** (deliberately — python3 and git only, so use `urllib`), and
`api.resend.com` sits behind **Cloudflare, which 403s the default `Python-urllib` User-Agent with
error 1010** — a failure that looks exactly like an auth failure and is not one.
**S-10 — the runner is unprivileged, and the reason is the host (2026-08-02).**
The usual `act_runner` recipe pairs it with a `docker:dind` sidecar and `privileged: true`. Rejected:
DooPlex is **Tier 2** and *is* the recovery chain — Gitea, the hub, the registry, PBS and
k3s + Longhorn all live on it and it cannot be rebuilt from anything else. Host execution mode costs
nothing here because every CI job is one Python command. Consequence to remember: **in host mode the
job sees exactly the runner image's tools**, which is why `python3` had to be baked in (probe P2:
stock `act_runner` carries git but not python3). If a future job genuinely needs Docker, that is a
conversation, not a patch.
**S-11 — CI reproduces the workspace's sibling layout, because two entry points depend on it
(2026-08-02).** `controller_gates.py` and `agent_gates.py` invoke the shared `reuse_refs_check.py`
that lives in the `felhom.eu` clone next door and is deliberately never copied, and both repos'
`REUSE.md` files cite a path that lives in the hub. Their workflows therefore clone `felhom.eu` as a
sibling; without it the gate fails **closed** — correctly, but for the wrong reason. Verified that CI
and the local hook then agree exactly (controller 126 exact / 6 suffix / 1 cross-repo).
**S-6 — the hub renders no host-install version, and the gate pins its absence (2026-08-02, R-94).**
The Setup tab's *"host-install 1.19.0"* label is **deleted, not derived**. Deriving it is not
achievable honestly: the Option-1 command downloads `felhom-host-install.sh` from the website **at