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>
121 lines
8.4 KiB
Markdown
121 lines
8.4 KiB
Markdown
# CI Requirements — Minstrel
|
|
|
|
> Copy of [the template](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/requirements-sheet.template.md).
|
|
> 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 + npm** — `npm 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.
|
|
- **git** — `actions/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`](https://…/FabledRulebook/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`.**
|
|
```yaml
|
|
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.
|