diff --git a/docs/superpowers/specs/2026-04-14-ci-workflow-design.md b/docs/superpowers/specs/2026-04-14-ci-workflow-design.md new file mode 100644 index 0000000..389f0e3 --- /dev/null +++ b/docs/superpowers/specs/2026-04-14-ci-workflow-design.md @@ -0,0 +1,99 @@ +# 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.