carlos / platform Public

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.
Paul Campbell pushed by paul@keymail.dev d9a643fd5bd1be82c36048ccdf7b5db8b67a4195 parent 4167d5b
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 ./...