Files
minstrel/ci-requirements.md
T
bvandeusenandClaude Opus 5 fa0827f668 ci: pin the download mirror to v6, not v5 — match on @actions/artifact
The previous pin matched the two actions by their own version numbers, which
is meaningless: upload-artifact and download-artifact release on unrelated
cadences. upload v5 bundles @actions/artifact ^4.0.0; download v5 bundles
^2.3.2. "v5 and v5" was in fact a mismatched pair.

download v6 is the tag that puts ^4.0.0 on both sides — and ^4.0.0 is the
library major just proven against this instance by the upload side
(thoughtsync run 3094: two artifacts listed, downloaded and extracted
intact). ^2.3.2 has never been exercised here.

Not v7: that major is a runner requirement rather than a feature change. It
moves to runs.using: node24 and upstream requires runner >= 2.327.1 for it,
which act_runner does not claim to satisfy. Everything pinned stays node20.

ci-requirements.md now carries the version/runtime table and the reasoning,
so the next person matches on the library instead of the tag number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:37:43 -04:00

7.2 KiB

CI Requirements — Minstrel

Copy of the template. Family-wide policy: ci-runners.md in the FabledRulebook.

Runtime images

Minstrel's four workflows consume two CI images:

git.fabledsword.com/bvandeusen/ci-go:1.26
git.fabledsword.com/bvandeusen/ci-flutter:3.44
  • ci-go:1.26 — Go server tests (.gitea/workflows/test-go.yml), web SPA tests (.gitea/workflows/test-web.yml), and the release container build (.gitea/workflows/release.yml).
  • ci-flutter:3.44 — Flutter client tests + debug/release APK builds (.gitea/workflows/flutter.yml).

Image deps used

From ci-go:1.26

  • Go (1.26 toolchain) — go vet, go test -race, go build, go mod.
  • Node + npmnpm ci and npm test / npm run check in test-web.yml.
  • golangci-lint — lint pass in test-go.yml.
  • docker CLI — bridge-IP discovery of the per-job Postgres service container in test-go.yml integration job (via the runner's shared /var/run/docker.sock).
  • docker buildx — release container build + push in release.yml.
  • curl — release-asset polling / upload in release.yml.

From ci-flutter:3.44

  • Flutter (3.44 stable channel) — flutter pub get, flutter analyze --fatal-infos, flutter test, flutter build apk (debug + signed release).
  • Dartdart run tool/gen_tokens.dart, dart run build_runner build (drift codegen).
  • Android SDK + NDK + cmdline-tools + build-tools — APK assembly + signing.
  • Java 25 — Gradle / Android build.
  • gitactions/checkout@v4 baseline (and any shell git operations).
  • base64 + curl — keystore decode + release-asset upload in the tag-build path.

Per-job tool installs

None.

Notes

  • Label/image split. Workflows keep runs-on: go-ci / runs-on: flutter-ci as the scheduling label per the ci-runners.md "label = scheduling handle, image = container.image" pattern. The labels are intentional handles, not toolchain assertions.

  • Integration-job docker-socket dependency. test-go.yml's integration job uses the runner's shared docker socket (/var/run/docker.sock) to bridge-IP-discover the per-job Postgres service container by name + network intersection — the dev compose's minstrel-postgres-* containers are explicitly skipped as belt-and-suspenders. Depends on act_runner.valid_volumes whitelisting the socket; if that ever stops auto-mounting, integration tests fail at the docker inspect step.

  • Go toolchain pin. go.mod is on go 1.25.0 because golang.org/x/crypto v0.51.0 declares 1.25 as its minimum. ci-go:1.26 satisfies this with headroom. Future x/crypto bumps that move the Go floor should be paired with an image-tag bump in this file + the workflows.

  • In-app update channel polling. release.yml polls Gitea's release-asset API for up to 15 min on tag pushes to fetch the APK that flutter.yml is concurrently attaching to the same release. The asset eventually appears because flutter.yml and release.yml run in parallel on the same tag; if the polling times out, the server image ships without the bundled update channel (graceful degradation, not a build failure).

  • Cache server reachability. test-web.yml does NOT use cache: 'npm' on actions/setup-node — the Gitea Actions cache server isn't reachable from this runner's container network and setup-node was burning ~4m41s on ETIMEDOUT before failing open. With the migration to ci-go:1.26, setup-node is removed entirely (Node is in the image). The cache concern reappears if a future change re-introduces a network-dependent action.

  • Artifacts — use the mirrored actions, never actions/{upload,download}-artifact.

    uses: https://git.fabledsword.com/bvandeusen/upload-artifact@cb8afe72b42edc798abfb8fcb556cf660d894245
    uses: https://git.fabledsword.com/bvandeusen/download-artifact@8d4e9521a5f7e5f8b6351f341f719f9f45a92a3a
    

    Upstream's @v4+ cannot work against this instance and no server-side change will help: isGhes() rejects any hostname that isn't github.com / *.ghe.com / *.localhost and throws before it opens a connection, so the server is never asked what it supports. @v3 is worse — it reports success, and Gitea then serves artifacts back only through the v4 API (content_encoding = application/zip), so a v3 upload is stored but invisible to every retrieval path. A green job producing nothing retrievable; that is how 72 unreachable artifacts accumulated on this repo. Scribe issues 2255 / 2270.

    Both are pull mirrors of the Forgejo project's forks (code.forgejo.org/forgejo/{upload,download}-artifact, one commit on upstream disabling that check), mirrored so CI depends on commits we hold and pinned by SHA because the mirrors auto-sync every 8h — a moved upstream tag would otherwise silently change what runs.

    Match the pins on @actions/artifact, not on the actions' own version numbers. The two actions release on unrelated cadences, so equal version numbers do NOT mean a compatible pair — upload v5 bundles @actions/artifact ^4.0.0 while download v5 bundles ^2.3.2. The pins above are upload v5 and download v6, which is the pairing that puts ^4.0.0 on both sides. This matters because release.yml is a producer/consumer pair — android-release uploads minstrel-apk, image-release downloads it — and a protocol mismatch across it yields an empty listing rather than an error, exactly the silent failure this entry exists to prevent.

    tag @actions/artifact runtime
    upload v4 ^2.1.1 node20
    upload v5 ← pinned ^4.0.0 node20
    download v4 ^2.1.1 node20
    download v5 ^2.3.2 node20
    download v6 ← pinned ^4.0.0 node20
    download v7 ^5.0.0 node24

    The only true protocol break in this history was v3 → v4 (upstream: "Downloading artifacts that were created from actions/upload-artifact@v3 and below are not supported"); v4-and-up are one family. Later majors are mostly ergonomics and runtime — upload v4 forbids re-uploading a name and caps a job at 500 artifacts; download v5 made by-ID extraction match by-name.

    Do not jump the download pin to v7. That major is a runner requirement, not a feature change: it moves to runs.using: node24 and upstream states it "requires a minimum Actions Runner version of 2.327.1 … if you are using self-hosted runners, ensure they are updated before upgrading." act_runner is not GitHub's runner and makes no such version claim, so node24 is unverified here. Everything currently pinned is node20.

    Upload steps set if-no-files-found: error rather than the default warn, so an upload that matches nothing fails its own job instead of failing the consumer later.

    Retrieval: GET /api/v1/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts for the id (global run id, not the repo-scoped run number), then …/actions/artifacts/{id}/zip. The workstation has no unzip — use python3 -m zipfile -e.

  • Friction asks. None pending. The two images cover everything Minstrel needs.