Add ci-runner-register for a general-purpose untagged runner
What
Adds a general-purpose runner recipe, just ci-runner-register, so untagged CI/scan jobs actually run. Closes the runner half of the lab findings: the only bundled recipe (duo-runner-register) is scoped to the gitlab--duo tag with run_untagged: false, so a generic (untagged) job sat pending forever.
Change
ci-runner-registerregisters a general-purpose instance runner (empty tag list,run_untagged: true).- Refactor: the caproni/omnibus executor-registration logic is extracted into a shared
register_runnerhelper, parameterized bytag_list/run_untagged.duo-runner-registerandci-runner-registerare now thin callers.tag_list/run_untaggedare set on the Rails record (authoritative under the token registration flow); the Duo omnibus/caproni register commands are byte-identical to before. - Wired into the justfile, the
simulator.shdispatch, and usage;gce.mdcapability table andseeding.mdrunner caveat updated.
Lab validation (omnibus, one VM, torn down + verified)
Registered both runners, pushed a project with two jobs, triggered one pipeline:
untagged-job : success -> runner: simulation ci runner
duo-tagged-job: success -> runner: simulation duo runner
RESULT: PASSThe untagged job ran on the general-purpose runner and the tagged job on the Duo runner — the direct inverse of the pending-forever finding, and a regression check that the refactor preserves the Duo path (tag scoping intact: neither job crossed to the wrong runner).
Caproni: validated by composition, not an independent run. The caproni branch of register_runner is byte-identical to the already-validated duo-runner-register caproni executor scaffold (podman-docker socket, --docker-extra-hosts); the only difference for the general-purpose variant is tag_list/run_untagged on the Rails record, and the "untagged job runs on a run_untagged: true runner" behavior is GitLab-core and base-independent (validated on omnibus above).
Checks
just ci clean; Vale gate in the MR pipeline.