ci: run the gate on amadan too, with the cross-compiles one per shard (#233)
A second home for this repo's gate, on a self-hosted amadan runner. .github/workflows/ci.yml is untouched, stays the required check, and stays the definition of green — where the two disagree, the workflow is the one that gates a merge and the difference is a bug in the new files. 10-gate is CLAUDE.md's build gate verbatim: build, vet, and `go test ./... -count=1 -timeout 30m`. The -count=1 and the timeout are copied from the workflow rather than re-derived, so they change in one place. 20-cross@3 deals the three cross-compile targets one per shard. The @3 is amadan's entire sharding interface — it runs the script three times concurrently and tells each copy which one it is through AMADAN_SHARD and AMADAN_SHARD_COUNT. One target per shard rather than three builds in a row because they are independent, this box has the cores, and a failing target then names itself in the shard that failed instead of being one line of a combined log. That file carries a fence: it refuses to run unless the shard count is exactly 3. The targets are a fixed list and the filename is what deals them out, so renaming it to @2 would otherwise stop compiling one platform while CI stayed green — which is the exact shape of the bug this job exists to catch, arriving through the job that catches it. .amadan/README.md records the two jobs deliberately NOT ported, because a ci.d/ that quietly covers less than the workflow beside it is worth less than none at all. minio-contract needs a Docker service container and these jobs run under bubblewrap with no container runtime. gofmt is scoped to a PR's changed files against its BASE SHA, which is a GitHub concept amadan CI has no equivalent of — and reproducing it against a guessed base would be worse than not running it. Both stay whole on GitHub.
3 files changed,
+107
−0
.amadan/README.md+41 −0.amadan/ci.d/10-gate+20 −0.amadan/ci.d/20-cross@3+46 −0
CI passed — run details
diff --git a/.amadan/README.md b/.amadan/README.md| new file mode 100644 |
| index 000000000..e35f10661 |
| --- /dev/null |
| +++ b/.amadan/README.md |
| @@ -0,0 +1,41 @@ |
| +# platform's CI on amadan |
| + |
| +`ci.d/` is a second home for this repo's gate, running on a self-hosted |
| +amadan runner. **`.github/workflows/ci.yml` remains the required check |
| +and remains the definition of green.** Where the two disagree, the |
| +workflow is the one that gates a merge, and the difference is a bug |
| +here. |
| + |
| +| workflow job | here | | |
| +|---|---|---| |
| +| `gate` | `10-gate` | build · vet · `go test ./... -count=1` | |
| +| `cross` | `20-cross@3` | one target per shard | |
| +| `minio-contract` | **not ported** | see below | |
| +| `gofmt` | **not ported** | see below | |
| + |
| +## What is deliberately missing, and why |
| + |
| +Two jobs are absent. Writing that down is the point of this file: a |
| +`ci.d/` directory that quietly covers less than the workflow next to it |
| +is worth less than no `ci.d/` at all, because it invites people to read |
| +a green run as meaning more than it does. |
| + |
| +**`minio-contract`** starts a MinIO server in Docker and runs |
| +`TestDeliveredContractMinio` against it. amadan CI jobs run under |
| +bubblewrap with the host filesystem read-only and no container runtime; |
| +`-isolate oci` is designed but unbuilt, precisely because "what |
| +container access does a job get, and does that leave any isolation at |
| +all" is unresolved. So this job stays on GitHub, where a service |
| +container is free. It is the only place that S3 contract runs live. |
| + |
| +**`gofmt`** checks only the files a PR changed, deliberately — three |
| +pre-existing unformatted files elsewhere are left alone, and a |
| +repo-wide check would fail on arrival and teach everyone to ignore the |
| +job. That needs the PR's **base SHA**, which is a GitHub concept. amadan |
| +CI hands a job its commit and its branch and has no notion of what it |
| +is a pull request against, so the job cannot be reproduced faithfully |
| +here. Reproducing it *unfaithfully* — repo-wide, or diffed against a |
| +guessed base — is worse than not running it. |
| + |
| +Both are gaps in this runner, not in the gate. The gate is still whole |
| +on GitHub. |
diff --git a/.amadan/ci.d/10-gate b/.amadan/ci.d/10-gate| new file mode 100755 |
| index 000000000..9276c1635 |
| --- /dev/null |
| +++ b/.amadan/ci.d/10-gate |
| @@ -0,0 +1,20 @@ |
| +#!/bin/sh |
| +# The build gate from CLAUDE.md — "go build ./..., go vet ./..., go test |
| +# ./... -count=1 — all clean, every package, before any commit lands" — |
| +# as amadan CI runs it. |
| +# |
| +# A SECOND home for that gate, not a replacement. |
| +# .github/workflows/ci.yml stays the required check and stays the |
| +# definition of green; where the two disagree, the workflow is the one |
| +# that gates a merge and the difference is a bug in this file. |
| +# |
| +# -count=1 is the same rule for the same reason it is there: never |
| +# satisfy the gate from a cached pass. The 30m timeout is above Go's |
| +# 10m default because the root package's e2e tests compile the carlos |
| +# binary many times over — copied from the workflow rather than |
| +# re-derived, so a change there is a change in one place. |
| +set -eu |
| + |
| +go build ./... |
| +go vet ./... |
| +go test ./... -count=1 -timeout 30m |
diff --git a/.amadan/ci.d/20-cross@3 b/.amadan/ci.d/20-cross@3| new file mode 100755 |
| index 000000000..6f79790c5 |
| --- /dev/null |
| +++ b/.amadan/ci.d/20-cross@3 |
| @@ -0,0 +1,46 @@ |
| +#!/bin/sh |
| +# The cross-compile gate, one target per shard. |
| +# |
| +# Why it exists at all is worth keeping next to it: readDiskFreeBytes in |
| +# internal/host/probes.go multiplied an int64 by syscall.Statfs_t.Bsize, |
| +# which is int64 on Linux and uint32 on darwin. That is a compile error |
| +# on macOS for every GOARCH, internal/host is linked into the carlos CLI |
| +# people run on Macs, and it sat in main undetected because everyone who |
| +# built it was on Linux. Cross-compiling catches it in seconds and needs |
| +# no Mac. |
| +# |
| +# The @3 is amadan's whole sharding interface: it runs this script three |
| +# times concurrently and tells each copy which one it is. One target per |
| +# shard rather than three builds in a row, because they are independent |
| +# and this box has the cores — and because a failing target then names |
| +# itself in the shard that failed, instead of being the first line of a |
| +# combined log. |
| +set -eu |
| + |
| +shard=${AMADAN_SHARD:?this step must be run by amadan CI, which sets AMADAN_SHARD} |
| +shards=${AMADAN_SHARD_COUNT:?this step must be run by amadan CI, which sets AMADAN_SHARD_COUNT} |
| + |
| +# The fence. The targets below are a fixed list, and the shard count in |
| +# this file's name is what deals them out — so the two MUST agree. Rename |
| +# this to @2 and, without this check, one platform silently stops being |
| +# compiled while CI stays green. That is the exact shape of the bug the |
| +# job exists to catch, arriving through the job that catches it. |
| +if [ "$shards" -ne 3 ]; then |
| + echo "this step deals 3 fixed targets but was run with $shards shards —" |
| + echo "rename the file back to 20-cross@3, or add a target below" |
| + exit 1 |
| +fi |
| + |
| +case "$shard" in |
| +0) GOOS=darwin GOARCH=arm64 ;; |
| +1) GOOS=darwin GOARCH=amd64 ;; |
| +2) GOOS=linux GOARCH=arm64 ;; |
| +*) echo "no target for shard $shard"; exit 1 ;; |
| +esac |
| + |
| +echo "cross-compiling $GOOS/$GOARCH" |
| + |
| +# CGO_ENABLED=0 is already the default when cross-compiling; explicit |
| +# here so that adding a cgo dependency fails loudly rather than quietly |
| +# changing what this covers. The tree is pure Go today. |
| +GOOS="$GOOS" GOARCH="$GOARCH" CGO_ENABLED=0 go build ./... |