Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

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 7 slice 6: a job declares its outputs, the runner publishes them

213f4d6dc6707cc9199f97acdacd617740bfb84d · Keenan Nemetz · 17d ago · parent 4c0f2aef

Phase 7 slice 6: a job declares its outputs, the runner publishes them

Workflow artifacts are a declaration, not a CLI call in a step. A workflow is
validated at the push that introduces it, so a bad pattern fails the push; a
shell command calling vole would fail at run time, need the binary on a runner's
PATH, and get idempotency wrong under retries.

- workflow: artifacts: [{path: dist/*}] on a job, validated at push — relative,
  no '..', no '**', no trailing slash, a glob that parses. Patterns are pinned
  into the queued run beside the pinned steps
- runner: expands the patterns in its checkout after the steps pass (one segment
  at a time, .git excluded), uploads through the same SSH object transfer git-lfs
  uses, and reports the list with its finish. A pattern matching nothing fails the
  job: a silent empty artifact is the failure found a week later
- server: the artifact list rides the finish message, so a run's outputs and its
  result are one fact; nothing is recorded unless the lease is still held, and
  every oid must be in the store at the stated size, or the finish is refused
- storage: run_artifacts rows point at objects in the existing store. A build
  output is machine-written state, so it does not touch the metadata branch; the
  queue is its only index, and gc marks it from there the way it marks logs
- web: a run page (status, job, sha, log, artifacts) and
  /r/<repo>/runs/<id>/artifacts/<name> served from the store, ETag = oid, always
  an attachment, nosniff, Range. The CI table now links the run rather than only
  its log

Measured through the binary on a real instance: a two-file dist/* declaration ran
on a real runner, both files uploaded over SSH, the run page served them with a
matching sha256, and vole gc --dry-run reported 3 objects, 3 reachable roots (the
log and both artifacts), removing nothing.

internal/server/server.go +2

@@ -153,7 +153,9 @@ func (s *Server) routes() *http.ServeMux {
153 153 mux.HandleFunc("GET /r/{name}/releases/{tag}/archive.tar", w.ReleaseTar)
154 154 mux.HandleFunc("GET /r/{name}/releases/{tag}/archive.tar.gz", w.ReleaseTarGz)
155 155 mux.HandleFunc("GET /r/{name}/releases/{tag}/artifacts/{artifact}", w.ReleaseArtifact)
156 mux.HandleFunc("GET /r/{name}/runs/{id}", w.RunPage)
156 157 mux.HandleFunc("GET /r/{name}/runs/{id}/log", w.RunLog)
158 mux.HandleFunc("GET /r/{name}/runs/{id}/artifacts/{artifact}", w.RunArtifact)
157 159
158 160 mux.HandleFunc("GET /healthz", func(rw http.ResponseWriter, r *http.Request) {
159 161 rw.Header().Set("Content-Type", "text/plain")