Files
minstrel/ci-requirements.md
T
bvandeusenandClaude Opus 5 8f4b76a638
test-go / test (push) Successful in 1m4s
test-go / integration (push) Successful in 3m54s
android / Build + lint + test (push) Successful in 4m58s
release / Build signed APK (releases and dev) (push) Successful in 5m11s
release / Build + push container image (push) Successful in 1m52s
release / Verify release artifacts (tag releases only) (push) Skipped
fix(ci): artifacts move to stock upload-artifact@v7 / download-artifact@v8
android.yml's debug upload and release.yml's minstrel-apk pair went
through the bvandeusen fork mirrors, with comments saying stock actions
refuse this hostname, that the pair had to be matched on the bundled
@actions/artifact major, and that download v7 was off-limits for node24.
None of that holds on gitea/runner 3.x: the runner edits the GHES refusal
out of the action bundles, every download major v4-v8 reads every upload
major v4-v7 (Scribe spike #3843, CI-runner run 6312), and every CI image
carries Node 24. The mirror pair itself was last verified at tag run 6286.

Same artifact names, paths and if-no-files-found. ci-requirements.md
drops the pairing table and keeps what is still true: @v3 is invisible.

Scribe snippet #2271, milestone 395.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwoKYuw3qJmUUYsJeNherB
2026-09-10 17:25:10 -04:00

7.0 KiB
Raw Blame History

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-android:36
  • 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 (release.yml's image-release job).
  • ci-android:36 — native Kotlin/Compose client: ktlint + detekt + unit tests + debug APK (.gitea/workflows/android.yml), and the signed release APK (release.yml's android-release job).

ci-flutter is no longer consumed, and ci-flutter can now be retired. The M8 rewrite replaced the Flutter client with the native Android app and flutter.yml was removed; ci-android took its place. flutter_client/ itself was deleted on 2026-08-16, which was the condition CI-Runner was waiting on before dropping the image — nothing in this repo needs a Flutter toolchain any more.

Note this does not mean the flutter-ci runner label goes: the Android jobs still schedule on it while pulling ci-android:36, per the label/image split below. The label is a scheduling handle, not a toolchain assertion.

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-android:36

  • JDK 25 — Gradle launcher + Android build. Requires Gradle 9.1.0+ in android/gradle/wrapper; older Gradle rejects JDK 25 with an opaque "25.0.3" error. The workflows also set JAVA_TOOL_OPTIONS=--enable-native-access=ALL-UNNAMED to silence Gradle's launcher-JVM restricted-method warning.
  • Android SDK + cmdline-tools + build-tools 36.0.0 — APK assembly + signing. No NDK: the native client has no C/C++ sources (this is why it isn't on ci-flutter).
  • ktlint + detekt./gradlew ktlintCheck and ./gradlew detekt in android.yml. Image pins track android/gradle/libs.versions.toml so local and CI checks agree.
  • gitactions/checkout@v4 baseline (and any shell git operations).
  • base64 + curl — keystore decode + release-asset upload in release.yml's android-release job.

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 — which is why the Android jobs still schedule on flutter-ci while pulling ci-android:36. Switch them to android-ci if that runner label is ever registered; nothing breaks either way.

  • 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 — needs:, not polling. release.yml's image-release job declares needs: [android-release], so on tag pushes the signed APK is guaranteed present before the image build starts — no polling window, no race. (The old cross-workflow polling against flutter.yml is gone with that workflow.) On non-tag main pushes android-release is skipped and image-release instead pulls the most recent release's APK and reconstructs its exact versionName, so :latest never ships without an update channel. It degrades to an empty client/ — never a wrong version — if no release, asset, or tag commit-count can be resolved.

  • 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 — stock actions/upload-artifact@v7 and actions/download-artifact@v8; never @v3.

    uses: actions/upload-artifact@v7
    uses: actions/download-artifact@v8
    

    Stock works on this forge since the runner moved to gitea/runner 3.x, which edits the actions' client-side isGhes() refusal out of their bundles. Proven on 2026-09-10 for upload v4v7 and download v4v8 (Scribe spike #3843). Until then this repo pinned SHA mirrors of the Forgejo project's forks, because upstream threw on the hostname before it opened a connection (Scribe 2255).

    @v3 is still broken: it reports success, and Gitea serves artifacts back only through the v4 API (content_encoding = application/zip), so a v3 upload is stored but invisible to every retrieval path. That is how 72 unreachable artifacts accumulated on this repo (Scribe 2270).

    Pairing no longer needs managing. This entry used to pin upload v5 against download v6 so both bundled @actions/artifact ^4.0.0, warning that a mismatch across release.yml's producer/consumer pair would list empty. Tested, and not true on this instance: every download major v4v8 read the artifacts of every upload major v4v7, by name and by pattern (CI-runner run 6312). The only real protocol break is v3 → v4. node24 is no longer a concern either — every CI-runner image carries Node 24 and the runner runs actions with the image's node.

    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.