codex, grok and kimi all landed on the same hole at e42ba31, and they are
right. The record row keyed only on fj_took, so install/status/remove failures
called `no` without gating it. A dispatched job only needs SOMETHING answering
runs-on: drill — and this leg removes locally, telling the operator to delete
the stale runner by hand, so a leftover drill-labeled runner from the previous
drill is the designed-for aftermath, not a contrived case. It answers the
fixture exactly, and the row then read "PASS — registered, took a job,
removed" for a drill whose install never happened.
The exit code was never wrong: every one of those failures also called `no`.
What was wrong is the row, and the row is what outlives the run in
drills/<v>.md. A row claiming a lifecycle that did not happen is exactly what
the gate exists to refuse — kimi's framing, and it is the right one.
forgejo_leg_row folds all five assertions, and the leg no longer dispatches
after install or status has failed: a foreign runner's success could only ever
be evidence about someone else's runner. The regression composes the real
functions over a payload carrying a newer successful run, the way the leg
composes them.
The GitHub leg has the identical shape and is byte-frozen by #129's contract;
left alone deliberately, and worth a sibling issue for triage.
Refs #129
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round 2 of !130. Both blockers were mine, and one was the same mistake I had
just fixed one layer up.
1. fj_pre still took `head -n1`. The verdict folds max over every id, but the
PRE-DISPATCH snapshot did not, so an oldest-first payload named an old run
as the baseline — and a later poll finding the same body then read the
PREVIOUS drill's run as this dispatch's result. That is a false PASS on the
take-a-job assertion, strictly worse than the false FAIL entry[0] caused
inside the verdict. Both sides now share forgejo_max_task_id, and a test
composes them the way the leg does so the pair cannot drift apart again.
2. test/drill.sh copied its pretty-printed fixture from /tmp/fjfix — a scratch
path that existed only on the box the fix was built on. Everywhere else the
cp failed, the guard returned pending, and the suite was 65/66. The claimed
66/66 was true on one machine. The fixture is written inline like every
other one; verified by deleting the scratch dir and running the suite from
a clean tree under env -i.
Refs #129
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses grok's and kimi's REQUEST_CHANGES on !130.
1. forgejo_run_verdict read only the FIRST entry of actions/tasks. That
payload accumulates, so the moment a repo is drilled twice our run shares
it with older ones — and nothing documents the sort order. A green job
then reports as a timeout: a false FAILURE on the gate this leg exists to
provide. It now inspects every entry and lets the newest id above pre_id
decide. Newlines are stripped first, so a pretty-printed payload parses
like a compact one.
2. A mint that yielded nothing degraded to SKIPPED "no registration token
source" — violating #129's own acceptance ("token source present but the
instance is unreachable -> the leg FAILS; it must not skip and must not
pass") and sending the operator to check an env var they had already set.
forgejo_token_verdict separates absent inputs from a configured leg that
could not mint; only the former skips.
3. The pre---yes confirm block still announced a GitHub runner alone.
Refs #129
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#109 shipped `rig forgejo-runner` and the drill never touched it, so the
release gate could pass with the whole family broken. Its only coverage was
15 argument-handling checks in test/cli.sh.
The Forgejo half cannot be a copy of the GitHub one. Measured against
forgejo.heavyduty.builders (8.0.3+gitea-1.22.0), not read from the docs:
GET actions/tasks carries NO `conclusion` field — `status` holds the terminal
outcome — `id` is a global task id while the URL ends in run_number, and the
payload lists ASSIGNED tasks only, so a queued run is invisible (measured:
200s of total_count 0 while the web UI showed the run). That makes "no new
id" the only signal the runner never took the job, which is the verdict this
leg exists to produce.
forgejo_run_verdict() is the testable core of that, grep-and-sed rather than
jq for the same reason json_field() is. Tests were written first and cover
the cases that must NOT pass: a stale run with the pre-dispatch id, an
assigned-but-still-running task, and a cancelled run.
Refs #129
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Override via RIG_TEMPLATES_REF when the drill was pointed somewhere; else
the pin read from the INSTALLED candidate tree — what actually landed,
never this checkout's copy.
test/drill.sh awk-extracts the harness's decision functions (the
release.sh pattern) and drives them against fixtures: the ref refusal
names both refs, a loud skip never classifies as a pass, the idempotence
verdict is a real diff that goes non-empty when convergence is broken —
demonstrated mechanically on every CI run — and the record emitter
cannot produce a clean-sweep reading over a skipped leg. CI runs it in
the check job.
The tests caught three real harness bugs before any reviewer could:
printf eating a '- '-leading format as options (a silently empty Failed
section — the exact lie the record exists to prevent), tree_of trusting
GNU readlink -f's exit 0 on a dangling final component, and the arg
refusals sitting behind the root check in violation of the repo's own
validated-before-root doctrine.
(ceremony flow: issue #105)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>