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/vole.git

Write over SSH with a registered key:

git clone ssh://git@rodent.software:2222/vole.git

A free and open-source forge that lives in your Git repo.

Phase 7 slice 8: a tag push builds the release

0f442b1c87ca3920f770531c9ad97277d0231f51 · Keenan Nemetz · 17d ago · parent 88e980b0

Phase 7 slice 8: a tag push builds the release

on.push.tags opens CI to tag pushes; release: true on an artifact publishes it to
that tag's release rather than only to the run. The runner commits the same
pointers `vole release attach` writes, so the release page, the ETags, the
immutability rules and the sweep needed no changes: CI gained a trigger, not a
publish path.

Three rules, each from something that went wrong in the first draft:

- a workflow that says tags: runs ONLY on tags. Saying what you trigger on is
  saying it; the first version defaulted to every branch too and queued a doomed
  release build on every commit
- a tag run never counts as a branch's required check. A tag on a branch tip
  produces a second run for the same sha, and ForSHA would have let a release
  build satisfy — or break — the gate for the commit it tags. It now filters to
  refs/heads/%
- a lightweight tag cannot name a release: the build runs and the publish fails
  saying so, because a tag with no message has no notes and no tagger. The server
  resolves annotated-ness at enqueue (the tag object's type) rather than having
  the runner parse ls-remote peeled lines

Tag and annotated-ness ride the run and the job payload beside the release
artifact patterns, pinned at enqueue like everything else.

Measured through the binary: a branch push queued nothing, a tag push queued the
release job, the runner published dist/tool-linux, the release page listed it
with the tag marked annotated, the download returned the bytes, and git log vole
read "release: v1.0.0 artifacts from run 01M5MSX3...".

docs/roadmap.md +20

@@ -519,6 +519,23 @@ the shared push helper was mistaken for a missing *named* remote, so a runner's
519 519 committed locally and reported success — the end-to-end test caught it, because the
520 520 site served a 404 after a "successful" run.)
521 521
522 ✅ **Slice 8 — a tag push builds the release.** `on.push.tags` opens CI to tag pushes,
523 and `release: true` on an artifact publishes it to *that tag's release* rather than only
524 to the run. The runner does exactly what `vole release attach` does — commits the same
525 pointers on `refs/heads/vole` — so the release page, the ETags, the immutability rules
526 and the sweep needed no changes at all. Three rules earned their place. A workflow that
527 says `tags:` runs only on tags: naming your trigger is naming it, and the alternative
528 queued a release build on every commit (measured: a branch push to a tag-only workflow
529 queued zero runs). A tag run never counts as a branch's required check, even when the
530 tag points at that branch's tip — `ForSHA` now filters to `refs/heads/%`, or a release
531 build could satisfy or break the gate for the commit it tags. A lightweight tag cannot
532 name a release: the build runs and the publish fails saying so, because a tag with no
533 message has no notes and no tagger.
534 (Measured through the binary: a branch push queued nothing, a tag push queued the
535 release job, the runner published `dist/tool-linux`, the release page listed it with the
536 tag marked annotated, the download returned the bytes, and `git log vole` read
537 `release: v1.0.0 artifacts from run 01M5MSX3...`.)
538
522 539 Planned:
523 540
524 541 - Identity without ceremony: `vole whoami` resolves the key a person pushes with to an
@@ -693,6 +710,9 @@ Filled in as phases land. Append; don't rewrite.
693 710 | 2026-10-22 | No release record file, and no drafts | The artifact list is the pointer directory and published-ness is the ref itself; a `releases/<tag>.json` would carry a flag that duplicates the tag and an edit history nobody asked for |
694 711 | 2026-10-22 | Attach uploads bytes before it commits the pointer | The reverse order publishes a download that 404s; the hook cannot see bytes that were never in the pack, so the pointer check is the only guard and the GC grace period covers the gap |
695 712 | 2026-10-22 | Artifact downloads are always an attachment, served through `http.ServeContent` | An uploaded `.html` rendered inline is stored XSS on the forge origin; `ServeContent` gives `Range` and conditional requests for free, and the LFS object id is the validator |
713 | 2026-10-23 | Saying `on.push.tags` means tags only | A workflow that named its trigger still firing on every branch queued a doomed release build per commit; "no triggers at all" keeps the existing every-branch default |
714 | 2026-10-23 | A tag run never decides a branch's required checks | A tag on a branch tip produces a second run for the same sha; counting it would let a release build satisfy — or break — the gate for the commit it tags |
715 | 2026-10-23 | CI release artifacts are the same pointers a person commits | The release page, ETags, immutability and the sweep already read that shape, so CI needed no new publish path — only the run id in the commit message |
696 716 | 2026-10-23 | CI deploys the site through the CLI's own snapshot code | A site published by a workflow and one published by a person must be the same commit shape; a second implementation would drift, and the version that drifts is the one nobody runs by hand |
697 717 | 2026-10-23 | A CI site deploy is refused when the run is not the branch tip | Deploy is a snapshot, so a stale build finishing late silently reverts the site; a failed job with a reason is the honest outcome |
698 718 | 2026-10-23 | One job per workflow may publish the site | Two publishers race and the winner is whichever runner finished last, which is not a decision anyone made |