Net-new review findings from grok and kimi on !110. Their items 1-3 were
codex's, already fixed in 1933b07; these are the ones only they raised.
grok #4 — the two downloaders would drift. docs/templates/ci-box/install.sh
and the download block in forgejo-runner-install.sh were near-copies, and grok
named the exact consequence with the exact evidence: fail-open survived in BOTH
while a grep for "checksum mismatch" passed against both, because the string it
looked for sat right beside the branch it could not see. The whole policy —
fetch, unreadable, mismatch — is now fetch_and_verify_sha256, byte-identical in
both files and diffed by test/cli.sh. They cannot share a lib: the command
sources commands/lib/, and the template is a registry definition that runs
standalone inside a mint with rig's tree nowhere in reach, which is the same
situation valid_version faces between bin/rig and install.sh. Mutation-checked
by drifting one copy's message and confirming the diff goes red.
kimi #2 — the labeler could not see this family. scope:runner matched
commands/runner-*.sh only, so forgejo-runner-*.sh and the staged ci-box
definition scored no scope at all. Globs extended and the label's description
now says either forge rather than GitHub.
kimi #4 — remove stranded a unit whose user was gone. The missing-user check
exited 0 before the unit was ever looked at, so a deleted account with a
leftover forgejo-runner.service reported "nothing to remove" while the
absence-assert that never ran implied the opposite. The unit is now checked
independently. Auditing that fix surfaced a hazard kimi did not mention: with
the user gone RUNNER_DIR is "", and the later unguarded "$RUNNER_DIR/.rig-labels"
would have expanded to "/.rig-labels" — an rm at the filesystem root, as root.
Every RUNNER_DIR path is now gated, and a test pins that none is unguarded.
kimi #1 — the README handed out a config that breaks rig's own gates.
DEFAULT_ACTIONS_URL is a single fallback and rig's workflows need two origins;
measured: code.forgejo.org serves actions/checkout (200) but not
heavy-duty/ceremony (404), which lives on the Forgejo instance. With the value
the README recommended, all eight ceremony references fail to resolve. The
section now states the conflict with the counts, says which references would
break, and explicitly does NOT pick a side — that is an infra decision, and
rig's CI running on Forgejo is not something rig forgejo-runner depends on.
Asked the maintainer for direction.
746/31/43 pass, shellcheck clean, labeler.yml parses.
forgejo#109
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rig's CI story was GitHub-shaped end to end. This makes it work against a
self-hosted Forgejo, in three pieces.
The registry fetch becomes forge-aware. templates_resolve hardcoded three
github.com archive URLs; RIG_TEMPLATES_HOST now selects the grammar, because
the forges genuinely differ — GitHub serves refs/tags, refs/heads and bare
paths, Forgejo serves exactly one, and emitting the other two there would mean
two guaranteed 404s per fetch and a failure message listing URLs that never
could have worked. Measured against forgejo.heavyduty.builders, not inferred.
The default stays GitHub, so every existing caller is unchanged. install.sh's
snapshot reads the same variable through a byte-identical copy of the builder,
diffed by the tests: a snapshot cached from a forge converge would never fetch
from is worse than no snapshot, and the pin-in-the-name staleness guard cannot
catch a wrong-ORIGIN snapshot, only an old one.
ci-box is a tenant, not a machine role. The topology is a fleet machine
hosting boxes, one of which runs CI — a '-box' guest by rig's own family rule.
That also deletes the docker-in-docker layer the usual setup needs:
bootstrap-tenant.sh already installs Docker and adds the tenant user to the
group, and the isolation a privileged dind sidecar buys is already paid for by
a box that is network-isolated, inbound-less and disposable. rig runner
install refuses Docker for good reason — it converges a MACHINE, where the
blast radius is the machine. Here it is a guest that gets thrown away.
rig forgejo-runner is a new family beside rig runner, which is untouched.
Forgejo registers against an INSTANCE and the token carries the scope, so
there is no --repo to converge toward and nothing to compare; folding that
into one command would make every guard bimodal to share a flag name while the
contract underneath differs. assert_runner_instance asks the same
trust-boundary question about the axis Forgejo actually has. There is no
repoint and no --local, and both absences are explained where an operator
arriving from the GitHub sibling will hit them.
Forgejo's .runner holds the runner's own long-lived token, unlike GitHub's, so
it is installed 0600 and the mode is re-asserted on every converge — a mode
that drifted leaks the secret silently, since nothing fails and the runner
keeps working. status reports it and never prints the token.
Both downloads verify the published .sha256 before installing: this binary
lands as root and is executed by a systemd unit.
bootstrap --undo learns the guard for the same hazard on the other forge, and
it matters more here — Forgejo has no deregistration endpoint, so the ghost it
would strand has to be deleted by hand.
Known prerequisite, documented rather than assumed: the fetch is
unauthenticated by contract, and a Forgejo with REQUIRE_SIGNIN_VIEW=true
answers 404 for repos it reports as public. Hosting a registry there needs
FORGEJO__service__REQUIRE_SIGNIN_VIEW=false. The refusal names that case,
because it is indistinguishable from a wrong ref.
forgejo#109
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>