# CI Workflow Design (Plan 4d) **Date:** 2026-04-14 **Scope:** Plan 4d of the Archduino Go rewrite — validate Go code quality, build health, and release config on every change. ## Goal A single Gitea Actions workflow that gives fast, reliable feedback on every push and pull request. It runs unit tests with the race detector, static checks, a build sanity pass, shell-script linting, and validates the goreleaser config — all inside an Arch Linux container for environmental fidelity with the target platform. ## Non-goals - No integration tests that invoke a real `archduino install` against live pacman/AUR mirrors. That requires a privileged container and mirror access; it lives in a future plan if ever needed. - No `golangci-lint`. The `go vet` + `gofmt` baseline is enough for this codebase; revisit only if linting gaps surface. - No coverage reporting or codecov upload. - No scheduled/nightly runs. Trigger is push + PR only. ## Trigger ```yaml on: push: pull_request: ``` Fires on every push to every branch and every pull request. Matches a single-developer workflow where noisy branches aren't a real concern; immediate feedback is worth the runner minutes. ## Runner - `runs-on: ubuntu-latest` — same runner label used by the Plan 4c release workflow. - `container: archlinux:latest` — tests execute inside a fresh Arch userspace each run, matching Archduino's sole supported OS. **Runner infrastructure:** depends on the same Gitea Actions runner registered for Plan 4c. No new infra introduced by this plan. If the runner is offline, CI queues rather than failing; this is acceptable behavior. ## Job steps (single job, sequential) 1. **Install container prereqs.** Before `actions/checkout` can run, the Arch image needs git; the later steps also need `base-devel` (for the C toolchain Go occasionally pulls in for cgo-adjacent deps) and `shellcheck`. Run once up front: ``` pacman -Sy --needed --noconfirm base-devel git shellcheck ``` Note: `go` is intentionally NOT installed here — `actions/setup-go` installs the pinned version. 2. **Checkout code.** `actions/checkout@v4` with `fetch-depth: 1` (CI doesn't need history). 3. **Set up Go.** `actions/setup-go@v5` with: - `go-version-file: go.mod` (pinned, deterministic) - `cache: true` (built-in cache of `$GOMODCACHE` + `$GOCACHE`, keyed on `go.sum`) 4. **`go vet ./...`** — catches obvious correctness issues the compiler misses. 5. **`gofmt` check:** ``` diff=$(gofmt -l .) if [ -n "$diff" ]; then echo "files need gofmt:"; echo "$diff"; exit 1 fi ``` Fails the build if any Go file isn't canonically formatted. 6. **`go build ./...`** — compiles every package. Catches deadcode paths and untested packages that `go test` would otherwise skip. 7. **`go test -race ./...`** — full unit test suite with the race detector. The engine (DAG scheduler) and `privilege.Acquire` (sudo keepalive goroutine) are the concurrency hotspots this covers. 8. **`shellcheck bootstrap.sh`** — lints the user-facing shell installer. `install.sh.legacy` is NOT linted; it's archived code. 9. **Install goreleaser + `goreleaser check`:** ``` go install github.com/goreleaser/goreleaser/v2@latest "$(go env GOPATH)/bin/goreleaser" check ``` Validates `.goreleaser.yaml` on every change. Cheap insurance against config drift. ## Runtime expectations - Arch image pull: ~15–30s (cached on the runner after first run). - `pacman -Sy … base-devel git shellcheck`: ~10–20s. - `actions/setup-go` + cache restore: ~5–10s warm, ~30s cold. - `go build ./...` + `go test -race ./...`: ~30–60s given 37 test files with mostly mocked I/O. - `go vet`, `gofmt`, `shellcheck`, `goreleaser check`: <5s combined. - **Total per run:** ~1–2 min warm cache, ~3 min cold. ## Error handling - Sequential steps; the first failure fails the workflow. No `continue-on-error` anywhere. - `gofmt` check uses an explicit guard (not just `-l`) so a non-empty output is a clear failure. - `setup-go` cache misses silently fall back to a clean install — no workflow-breaking behavior. ## Caching `actions/setup-go@v5` handles Go build/module caching natively via `cache: true`. No extra cache step. The `pacman` packages are re-installed every run; caching them is not worth the complexity for ~15s of savings on a 90s run. ## Files touched - Create: `.gitea/workflows/ci.yml` No other repo changes; the existing tests, `go.mod`, `.goreleaser.yaml`, and `bootstrap.sh` all stay as-is. ## Interaction with Plan 4c The release workflow (`.gitea/workflows/release.yml`) is unchanged. Both workflows share the Gitea Actions runner but never run together: CI fires on push/PR, release fires on tag push. Independent files, independent triggers, zero coupling.