Files
minstrel/ci-requirements.md
T
bvandeusenandClaude Opus 5 8483948f23 docs(ci): true up ci-requirements.md — ci-android replaced ci-flutter
The sheet still described the pre-M8 world: "two CI images: ci-go +
ci-flutter", a ci-flutter dep list, and cross-workflow release polling
against flutter.yml. None of that is true now — flutter.yml is gone,
android.yml and release.yml both pull ci-android:36, and image-release
gates on `needs: [android-release]` instead of polling.

Family rule 39 makes this sheet CI-Runner's decision input for "add a dep
to an image vs. fork a variant", so a stale sheet quietly misinforms that
call: CI-Runner was still carrying ci-flutter for a consumer that no
longer exists, and had no record of ci-android's real consumer.

- Runtime images: ci-flutter:3.44 -> ci-android:36, with a note on why
  ci-flutter is now unconsumed and what would have to change to revive it.
- Image deps: replace the Flutter/Dart/NDK list with the actual
  ci-android surface (JDK 25 + Gradle 9.1 floor, SDK/build-tools 36, no
  NDK, ktlint + detekt).
- Label/image split: record that Android jobs still schedule on the
  flutter-ci label on purpose — it's a scheduling handle, not a toolchain
  assertion.
- Update channel: `needs:` gating, plus the non-tag rebundle path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 10:38:50 -04:00

8.4 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-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. The M8 rewrite replaced the Flutter client with the native Android app and flutter.yml was removed; ci-android took its place. flutter_client/ is still in the tree but nothing builds it. CI-Runner still publishes ci-flutter and will retire it once that directory goes — so if the Flutter client is ever revived, say so there first.

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 — 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.