vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
Phase 4: a run record names the machine, not the toolchain
379dad1e7417b9153a4913402cfc97bb51ce1bb4 · Keenan Nemetz · 16d ago · parent 72797dcd
Phase 4: a run record names the machine, not the toolchain A finished attempt now says which operating system and architecture ran it, in record.md beside the exit code and the signer, so the two fields ride the signature that is the attestation. A result a clone carries says what kind of machine produced it, not only which job it was — which is what turns "one macOS runner in a Linux pool" into a question somebody can answer after the fact, and the first half of the targeting work that remains. The roadmap also asked for the runner's Go version and that was dropped deliberately: runtime.Version() names the toolchain the *runner binary* was compiled with, which is not the Go that builds anything in a job. A step running `go build` uses whatever is on PATH in the checkout, usually a different install, so in a signed record the field would be a confident answer to a question nobody asked. OS and architecture survive the same objection because they describe the machine, which is the fact this was for. The keys are optional and an absent machine writes nothing at all rather than an empty line, because a record is never rewritten and history that predates a field has to stay readable. runs.db mirrors the pair the way it mirrors the log and the artifacts — only while the runner still holds the lease, so an overtaken runner leaves no claim behind — and `vole index rebuild` restores it from the branch. Measured: TestRunRecordIsPortable carries the machine through the record, the row, the rebuild and the page; TestRecordWithoutMachineStillReads and TestMachineIsRecordedOnlyWhileTheLeaseIsHeld pin the two edges.
cmd/vole/runner.go +5
| @@ -12,6 +12,7 @@ import ( | |||
| 12 | 12 | "os/exec" | |
| 13 | 13 | "path" | |
| 14 | 14 | "path/filepath" | |
| 15 | "runtime" | ||
| 15 | 16 | "slices" | |
| 16 | 17 | "sort" | |
| 17 | 18 | "strings" | |
| @@ -508,6 +509,10 @@ func (s *runnerSession) finish(remote, work string, job *runnerJob, log *strings | |||
| 508 | 509 | Workflow: job.Workflow, WorkflowSHA: job.WorkflowSHA, Job: job.Job, | |
| 509 | 510 | StepsSHA: job.StepsSHA, Exit: exit, Finished: time.Now().UTC(), | |
| 510 | 511 | Signer: s.account, Key: s.fingerprint, Artifacts: artifacts, | |
| 512 | // The machine this attempt ran on. The runner binary's own build target, | ||
| 513 | // which is the honest thing it can say: a live probe would add a subprocess | ||
| 514 | // and a platform quirk for a fact the binary already knows. | ||
| 515 | OS: runtime.GOOS, Arch: runtime.GOARCH, | ||
| 511 | 516 | } | |
| 512 | 517 | if oid != "" { | |
| 513 | 518 | rec.Log = &lfs.Pointer{OID: oid, Size: size} | |