feat(tools): guard against shipping an action that is not published

act_runner never runs this working copy. Every job re-clones the repository
from the Gitea server at the ref the workflow names, so a fix under .gitea/
is inert until it is pushed -- and nothing here says so. `git log` shows the
fix while CI keeps running the old file.

That gap cost a downstream repo a day: 59 commits sat unpushed while they
debugged a snapshot-name bug we had already fixed and announced as done.

ci-action-published.py compares the working tree against the remote ref,
which catches both committed-but-unpushed and edited-but-uncommitted in one
diff. It fetches first, so it compares against the server rather than a
remote-tracking ref that can itself be stale -- the same failure mode one
level down. Exit 1 on drift, 2 when the check could not run.
This commit is contained in:
2026-07-22 20:23:50 +02:00
parent 2e1c81b19c
commit 9db74aed3f
2 changed files with 129 additions and 0 deletions
+23
View File
@@ -86,6 +86,7 @@ sudo); `--uninstall` removes the block. The script defines two helpers:
| `ci template prepare-win` | Provision a Windows template over WinRM | [§5](#5-credentials--template-provisioning) |
| `ci template backup` | Timestamped copy of the template directories | [§5](#5-credentials--template-provisioning) |
| `ci retention run` | Purge old artifacts and logs | [§6](#6-maintenance) |
| `tools/ci-action-published.py` | Is the `.gitea/` CI runs the one in this checkout? | [§6](#6-maintenance) |
| `ci bench measure ...` | Phase-timing benchmark | [§7](#7-phase-c-testing) |
| `ci bench run ...` | Concurrency burn-in | [§7](#7-phase-c-testing) |
| `ci smoke run ...` | One end-to-end job with assertions | [§7](#7-phase-c-testing) |
@@ -607,6 +608,28 @@ ci retention run --retention-days 30 --aggressive-retention-days 7 --min-free-gb
See [§4.6](#46-sweep-orphans) (`ci vm cleanup`).
### Publishing `.gitea/` — committed is not deployed
act_runner never executes this working copy. For every job it re-clones the
repository **from the Gitea server** at the ref the workflow names
(`...local-ci-build@main`) and runs that copy. Anything under `.gitea/` is
therefore inert until it is pushed, and the gap is invisible from here: `git
log` shows the fix, CI runs the old file.
This has already cost a consumer a day of debugging — 59 commits sat unpushed
while a downstream repo chased a snapshot-name bug that was fixed locally.
Check before announcing any action change as done:
```bash
sudo -u ci-runner /opt/ci/venv/bin/python tools/ci-action-published.py
# OK — .gitea/ matches origin/main; CI runs what you see here.
# FAIL — lists the drifted files, exit 1
```
It catches both ways of being unpublished, committed-but-unpushed and
edited-but-uncommitted, and fetches first so it compares against the server
rather than a stale remote-tracking ref (`--no-fetch` to skip the network).
---
## 7. Phase C testing