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
+49
View File
@@ -1,3 +1,52 @@
## CI — a Gitea Actions runner, and a red run that reaches a person (2026-08-02, R-168)
**No version bump anywhere: nothing in the product repos is compiled, built or deployed by this.**
Recorded explicitly so the omission reads as a decision rather than a miss.
**What this closes.** Session 1 (same day) gave every repo one gate entry point and a
`.githooks/pre-push` that refuses a failing push. That hook is per-clone and `--no-verify` skips it,
so nothing independent of the person pushing ever saw whether the gates passed. This is the
independent half, and with it **R-29 CLOSES** — on the demonstrated alarm, not on a green run.
**`.gitea/workflows/gates.yml` (new)** — triggers on `push`, `runs-on: felhom-gates`, obtains the
source with a shallow `git fetch` of the **exact pushed SHA** from the in-cluster Gitea Service, and
runs `scripts/repo_gates.py --fast` and nothing else. **No `uses:` step anywhere** — JavaScript
actions need a node runtime the host-mode runner does not have, and probe P3 measured that a plain
`git fetch` is sufficient and lands on the pushed commit. No `|| true`; the entry point's exit code
IS the job's result.
**The alarm, which is the half that matters.** Probe P5 measured that a failed run produces **no
mail, no notification row and no log line** from Gitea. A red tick in a web UI nobody watches is
exactly the defect R-29 filed, rebuilt one layer up — so the workflow sends its own email on failure
via Resend (the hub's existing transactional path) and **prints the provider's accepted id**, making
"a message left the machine" an observable. **Demonstrated, not asserted:** a deliberately broken
commit pushed with `--no-verify` produced run #6 `failure` and
`RESEND-ACCEPTED id=5ff34766-c5f8-4588-8104-08296aeb45ab`.
Two traps found while building it, both worth keeping because each looks like something else:
the runner image has **no `curl`** on purpose (python3 and git only — so the step uses `urllib`
rather than growing the image), and `api.resend.com` sits behind **Cloudflare, which 403s the default
`Python-urllib` User-Agent with error 1010** — a failure that reads exactly like an auth failure and
is not one.
**The standing limit, written into the workflow itself: it REPORTS, it cannot REFUSE.** Every 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. Making it blocking needs branch
protection plus a PR workflow, which changes how the operator works → **R-169**, waiting on them.
**`documentation/audits/SPIKE-ci-runner-2026-08-02.md` (new)** — all six probes, method, measurement
and ruling; none produced a STOP. Also records a near-miss worth more than the probes: a `| tail -5`
inside my own census query silently dropped rows and looked exactly like a baseline drift big enough
to change the task. **An instrument that can drop results silently is not a measurement.**
**`CLAUDE.md`** gains the matching rule from session 1's red-proofing: a `go test -run` pattern that
matches no test prints `ok` and exits 0, so a red-proof using `-run` must first prove the filter
matched something.
**`CONTEXT.md`** gains S-8 (CI detects, does not block, and why that is structural), S-9 (a detector
that tells no one is not finished), S-10 (the runner is unprivileged because DooPlex is Tier 2), and
S-11 (CI reproduces the workspace's sibling layout, because two entry points depend on it).
## Gate enforcement — one entry point per repo, and a pre-push hook (2026-08-02)
**No version bump: `scripts/` carries no version, and this is tooling.** Recorded explicitly so the