Merge pull request #281 from carlosframework/retire-github-ci
Retire the GitHub workflow: amadan CI is the gate now
7 files changed,
+136
−253
.amadan/README.md+40 −18.amadan/ci.d/10-gate+28 −4.amadan/ci.d/30-minio-contract+37 −25.github/workflows/ci.yml+0 −186AGENTS.md+24 −14internal/host/statfs_unix.go+1 −1internal/objstore/contract_test.go+6 −5
CI passed — run details
diff --git a/.amadan/README.md b/.amadan/README.md| index 2f411b85f..8d81510a1 100644 |
| --- a/.amadan/README.md |
| +++ b/.amadan/README.md |
| @@ -1,12 +1,32 @@ |
| # 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. |
| +`ci.d/` is this repo's gate, running on a self-hosted amadan runner. |
| +**It is the only one.** `.github/workflows/ci.yml` was deleted on |
| +2026-08-24; nothing stands behind these three steps. A green run here |
| +is the whole of what "green" means for this repo, and a red one is not |
| +a discrepancy to reconcile against a more official run somewhere else. |
| -| workflow job | here | | |
| +The workflow went for two reasons that arrived together. The steps here |
| +had caught up with it and then gone past it — the last gap, `gofmt`, |
| +closed by *widening*, so this gate is now strictly the tighter of the |
| +two (see below). And GitHub Actions on the `carlosframework` account |
| +had stopped starting jobs at all over billing, so the "required check" |
| +had been gating nothing for some time before anyone removed it. The |
| +evidence for the first half is the run on `cd47f38`, green on the real |
| +arm64 worker with all four sections in the log: `=== gate ===` |
| +including `gofmt -l .: clean`, `=== cross ===` with all five targets, |
| +and `=== minio-contract ===` with `--- PASS: TestDeliveredContractMinio` |
| +and its five subtests. |
| + |
| +## Where each step came from |
| + |
| +This table is provenance, not a checklist against a live file. Every |
| +step here is shaped the way it is because of the job it descends from, |
| +and dropping that history makes the scripts read as arbitrary choices |
| +rather than as ported ones. The left column names a job in a file that |
| +no longer exists. |
| + |
| +| descended from | here | | |
| |---|---|---| |
| | `gate` | `10-gate` | build · vet · `go test ./... -count=1` | |
| | `cross` | `20-cross@5` | one target per shard, all five | |
| @@ -15,23 +35,25 @@ here. |
| ## `gofmt`: ported by widening it, not by reproducing it |
| -Nothing is missing now. Writing that down is still 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. This file is |
| -the check on that, so it has to stay true. |
| +This section is the reason it was safe to delete the workflow, so it is |
| +worth keeping even now that the comparison is with a dead file. The |
| +whole risk in a `ci.d/` that covers *less* than what it replaces is |
| +that it invites people to read a green run as meaning more than it |
| +does — and that risk did not end when the other gate did. It moved: |
| +there is nothing left to compare against, so a gap here is now a gap |
| +full stop. This file is the check on that, and it has to stay true. |
| **`gofmt`** was the last gap, and it closed by widening rather than by |
| -imitation. The workflow checks only the files a PR changed, because it |
| -needs a **base SHA** — a GitHub concept. amadan CI hands a job its |
| +imitation. The workflow checked only the files a PR changed, because it |
| +needed a **base SHA** — 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 that job cannot be reproduced faithfully here. |
| +against, so that job could not be reproduced faithfully here. |
| -It does not need to be. The scoping existed to protect three |
| +It did not need to be. The scoping existed to protect three |
| pre-existing unformatted files, and those are gone: `gofmt -l .` over |
| the whole tree is clean today. So `10-gate` runs `make test`, whose |
| -recipe already contains a **repo-wide** gofmt check — which is stricter |
| -than the job it replaces, and one definition shared with what a |
| +recipe already contains a **repo-wide** gofmt check — strictly stronger |
| +than the job it replaced, and one definition shared with what a |
| developer runs locally rather than a second list that can drift. |
| The rule that survives from the old scoping is the one worth keeping: |
| @@ -41,7 +63,7 @@ is not a gate. |
| ## `minio-contract`, and why it turned out to be portable |
| -It was listed here as impossible: it starts MinIO in Docker, and amadan |
| +It was listed here as impossible: it started MinIO in Docker, and amadan |
| jobs get no container runtime. That conflated the job with its |
| packaging. MinIO and `mc` are single static Go binaries, and |
| `TestDeliveredContractMinio` only wants an endpoint and credentials over |
diff --git a/.amadan/ci.d/10-gate b/.amadan/ci.d/10-gate| index abb3ada43..434170661 100755 |
| --- a/.amadan/ci.d/10-gate |
| +++ b/.amadan/ci.d/10-gate |
| @@ -3,10 +3,34 @@ |
| # ./... -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. |
| +# THIS IS THE GATE. Not a second home for one, not a copy kept beside a |
| +# more authoritative run somewhere else: the definition of green for this |
| +# repo is what these three steps say it is. |
| +# |
| +# It did not start that way, and the change is recent enough to be worth |
| +# writing down. Until 2026-08-24 the authority was |
| +# .github/workflows/ci.yml, and this file said so in as many words — |
| +# where the two disagreed, the workflow won and the difference was a bug |
| +# here. That workflow has been deleted. Two things made it safe to |
| +# delete, and they arrived together: |
| +# |
| +# 1. This directory caught up, and then went past it. `10-gate`, |
| +# `20-cross@5` and `30-minio-contract` cover every job the workflow |
| +# ran — build/vet/test, all five cross-compile targets, the live |
| +# MinIO contract — and the gofmt check here is STRICTER: `make |
| +# test` runs `gofmt -l .` over the whole tree, where the workflow |
| +# only ever checked the files a PR happened to touch. There is no |
| +# direction in which the deleted file was the tighter gate. |
| +# |
| +# 2. It had stopped running anyway. GitHub Actions on the |
| +# carlosframework account has been refusing to start jobs over |
| +# billing, so the "required check" was gating nothing well before |
| +# anyone removed it — which is the worse of the two failure modes, |
| +# because a gate that is merely absent is at least honest about it. |
| +# |
| +# So: a red step here is not a discrepancy to reconcile against a more |
| +# official run. It is the answer, and nothing behind it is going to catch |
| +# what it misses. Keep it true. |
| # |
| # One definition, not two. Everything past the build is `make test` — the |
| # same target a maintainer runs on a laptop — so CI and the local gate |
diff --git a/.amadan/ci.d/30-minio-contract b/.amadan/ci.d/30-minio-contract| index 48ed815ee..37cac5ee0 100755 |
| --- a/.amadan/ci.d/30-minio-contract |
| +++ b/.amadan/ci.d/30-minio-contract |
| @@ -3,10 +3,17 @@ |
| # TestDeliveredContractMinio, run against a real MinIO, with no container |
| # runtime anywhere in sight. |
| # |
| -# A SECOND home for the workflow's `minio-contract` job, 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. |
| +# THIS IS THE ONLY PLACE THIS TEST RUNS LIVE. It began as a port of the |
| +# `minio-contract` job in .github/workflows/ci.yml, with that workflow |
| +# holding the final say; the workflow has since been deleted (10-gate's |
| +# header records why), so there is no longer a second run of this |
| +# anywhere. If this step does not exercise the contract against a real |
| +# server, nothing does — `TestDeliveredContractMinio` skips itself |
| +# without CARLOS_MINIO_TEST, and a skipped test is a green test. |
| +# |
| +# References to "the workflow" below are provenance: they say where a |
| +# choice came from and where this deliberately diverged from it. They |
| +# describe a file that no longer exists. |
| # |
| # WHY THIS CAN EXIST HERE AT ALL |
| # |
| @@ -20,14 +27,15 @@ |
| # to hand you a MinIO. |
| # |
| # Running the server as a plain process inside the job's existing |
| -# bubblewrap sandbox is, if anything, tighter than the workflow: it binds |
| +# bubblewrap sandbox was, if anything, tighter than the container the |
| +# workflow used, and still is the tighter of the two shapes: it binds |
| # loopback only, its data directory is the job's private /tmp tmpfs, and |
| # it dies with the job's PID namespace. Nothing persists on the box. |
| set -eu |
| # Fail on a missing tool by name rather than by a confusing error forty |
| -# lines down. Both are present on the runner and in the workflow's image; |
| -# this is here so that a stripped-down box says which one it is missing. |
| +# lines down. Both are present on the runner; this is here so that a |
| +# stripped-down box says which one it is missing. |
| for tool in curl sha256sum; do |
| command -v "$tool" >/dev/null 2>&1 || { |
| echo "$tool is required by this step but was not found on PATH" >&2 |
| @@ -38,15 +46,15 @@ done |
| # --------------------------------------------------------------------- |
| # The pins. |
| # |
| -# The workflow pins its MinIO image and its mc download, and says why: |
| +# The workflow pinned its MinIO image and its mc download, and said why: |
| # a floating minio tag has already broken this exact job class once, when |
| -# mc's admin-policy syntax churned across versions. This is a port of |
| -# that pin, not an upgrade — RELEASE.2025-09-07T16-13-09Z is the same |
| -# server release the workflow's image digest resolves to, and the mc |
| -# release is the same one the workflow downloads. |
| +# mc's admin-policy syntax churned across versions. This started as a |
| +# port of that pin, not an upgrade — RELEASE.2025-09-07T16-13-09Z is the |
| +# same server release that image digest resolved to, and the mc release |
| +# is the same one it downloaded. |
| # |
| -# But a version string is a weaker pin than the workflow's, because the |
| -# workflow gets a container digest for free and a plain URL does not: |
| +# But a version string is a weaker pin than a container digest, which the |
| +# workflow got for free and a plain URL does not: |
| # dl.min.io could serve different bytes at the same archive path and |
| # nothing would notice. So each binary carries its sha256, verified after |
| # download and again on every cache hit, and a mismatch refuses to run |
| @@ -60,10 +68,11 @@ done |
| MINIO_RELEASE=RELEASE.2025-09-07T16-13-09Z |
| MC_RELEASE=RELEASE.2025-08-13T08-35-41Z |
| -# Arch-aware on purpose. The amadan runner is arm64 and the workflow's |
| -# ubuntu-latest is amd64, so a hardwired path works in exactly one of the |
| -# two places. The workflow in fact hardwires linux-amd64 for its mc |
| +# Arch-aware on purpose. The amadan runner is arm64; the workflow's |
| +# ubuntu-latest was amd64, and it hardwired linux-amd64 for its mc |
| # download — harmless there, wrong here, and a bug not to carry across. |
| +# The runner fleet is also the kind of thing that changes under you, so |
| +# this reads uname rather than trusting a note about what the box is. |
| case "$(uname -m)" in |
| x86_64 | amd64) |
| arch=amd64 |
| @@ -178,9 +187,10 @@ root_pass=$(head -c 18 /dev/urandom | od -An -tx1 | tr -d ' \n') |
| # --------------------------------------------------------------------- |
| # Start the server. |
| # |
| -# The workflow uses a fixed port 9000 because its MinIO is in a container |
| -# with its own network. amadan jobs deliberately do NOT get --unshare-net |
| -# (they need the Go module proxy), so a job shares the host's network |
| +# The workflow used a fixed port 9000 because its MinIO was in a |
| +# container with its own network. amadan jobs deliberately do NOT get |
| +# --unshare-net (they need the Go module proxy), so a job shares the |
| +# host's network |
| # namespace with the worker and with anything else on it. 9000 is a |
| # popular port; assuming it is free is assuming something this step does |
| # not know. |
| @@ -239,11 +249,13 @@ echo " minio healthy on 127.0.0.1:$port" |
| # --------------------------------------------------------------------- |
| # The fixture: two buckets and a scoped user granted exactly one of them. |
| # |
| -# Kept identical to the workflow's `mint scoped user` step — same bucket |
| -# names, same policy, same user — because the whole value of the test is |
| -# that cross-bucket denial is enforced by real IAM rather than by the |
| -# client politely not asking. Drift between this policy and the |
| -# workflow's is drift in what the two gates mean. |
| +# Carried over verbatim from the workflow's `mint scoped user` step — |
| +# same bucket names, same policy, same user — because the whole value of |
| +# the test is that cross-bucket denial is enforced by real IAM rather |
| +# than by the client politely not asking. Nothing checks this policy |
| +# against a second copy any more, so loosening it here is invisible: |
| +# grant `denied-bucket` by accident and the test still passes while |
| +# proving nothing. Change it only on purpose. |
| "$mc" --quiet alias set local "http://127.0.0.1:$port" "$root_user" "$root_pass" |
| "$mc" --quiet mb local/contract-bucket local/denied-bucket |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml| deleted file mode 100644 |
| index b2d1ff026..000000000 |
| --- a/.github/workflows/ci.yml |
| +++ /dev/null |
| @@ -1,186 +0,0 @@ |
| -# The build gate from CLAUDE.md, enforced by a machine rather than by |
| -# whoever happens to be at a keyboard. The gate itself is not new; what is |
| -# new is that it now runs somewhere other than a maintainer's Linux laptop. |
| -# |
| -# The cross-compile job exists because of a specific bug: 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, and internal/host is imported by the carlos CLI — |
| -# the laptop tool the README's developer story is built around. It sat in |
| -# main undetected because every person who built it was on Linux. A |
| -# `GOOS=darwin go build ./...` costs seconds on a Linux runner and would |
| -# have caught it at the commit that introduced it. |
| -name: CI |
| - |
| -on: |
| - push: |
| - branches: [main] |
| - pull_request: |
| - |
| -permissions: |
| - contents: read |
| - |
| -# A second push to a PR makes the first run's answer irrelevant. |
| -concurrency: |
| - group: ci-${{ github.workflow }}-${{ github.ref }} |
| - cancel-in-progress: true |
| - |
| -jobs: |
| - gate: |
| - name: build · vet · test (linux) |
| - runs-on: ubuntu-latest |
| - steps: |
| - - uses: actions/checkout@v4 |
| - - uses: actions/setup-go@v5 |
| - with: |
| - go-version-file: go.mod |
| - cache: true |
| - - run: go build ./... |
| - - run: go vet ./... |
| - # -count=1 to match CLAUDE.md: never satisfy the gate from a cached |
| - # pass. The explicit timeout is above the 10m default because the |
| - # root package's e2e tests compile the carlos binary many times over. |
| - - run: go test ./... -count=1 -timeout 30m |
| - |
| - cross: |
| - name: compile ${{ matrix.goos }}/${{ matrix.goarch }} |
| - runs-on: ubuntu-latest |
| - strategy: |
| - # One platform breaking must not hide the state of the others. |
| - fail-fast: false |
| - matrix: |
| - include: |
| - - {goos: darwin, goarch: arm64} |
| - - {goos: darwin, goarch: amd64} |
| - - {goos: linux, goarch: arm64} |
| - # Windows is a CLIENT target: `carlos deploy` from a Windows |
| - # workstation against a Linux fleet. The box-side packages still |
| - # have to COMPILE there because the CLI is the same binary as the |
| - # agent, so unix-only syscalls live behind per-OS files |
| - # (internal/edge/purge_listen_*.go, internal/host/{statfs,flock}_*.go). |
| - # This job is what stops the next syscall.Foo from silently |
| - # un-shipping the Windows build. |
| - - {goos: windows, goarch: amd64} |
| - - {goos: windows, goarch: arm64} |
| - steps: |
| - - uses: actions/checkout@v4 |
| - - uses: actions/setup-go@v5 |
| - with: |
| - go-version-file: go.mod |
| - cache: true |
| - # Compile only — these binaries are never run, so no runner for the |
| - # target OS is needed. CGO_ENABLED=0 is already the default when |
| - # cross-compiling; it is explicit here so that adding a cgo dependency |
| - # fails loudly in this job rather than silently changing what is |
| - # covered. The tree is pure Go today (modernc.org/sqlite, no import "C"). |
| - - run: go build ./... |
| - env: |
| - GOOS: ${{ matrix.goos }} |
| - GOARCH: ${{ matrix.goarch }} |
| - CGO_ENABLED: "0" |
| - |
| - minio-contract: |
| - # Exercises internal/objstore's TestDeliveredContractMinio: the S3 |
| - # contract a member app actually gets from delivered grant env (static |
| - # creds, path-style, us-east-1, checksum WhenRequired), against a real |
| - # S3-compatible server plus a scoped user IAM never granted the second |
| - # bucket. This is the only place in the gate that runs that test live — |
| - # see internal/objstore/contract_test.go's doc comment. |
| - name: minio contract |
| - runs-on: ubuntu-latest |
| - steps: |
| - - uses: actions/checkout@v4 |
| - - uses: actions/setup-go@v5 |
| - with: |
| - go-version-file: go.mod |
| - cache: true |
| - - name: start minio |
| - # Pinned rather than :latest — everything else in this file pins |
| - # (go-version-file, action majors), and a floating minio tag has |
| - # already broken this exact job class once (mc's own admin-policy |
| - # syntax churned across versions). Verified live against the quay.io |
| - # registry API on 2026-08-10: RELEASE.2025-09-07T16-13-09Z is the |
| - # image quay.io/minio/minio:latest currently resolves to (identical |
| - # manifest digest sha256:14cea493d9a34af32f524e538b8346cf79f3321eff8e708c1e2960462bd8936e). |
| - # Bump deliberately, not silently — re-verify against |
| - # https://quay.io/api/v1/repository/minio/minio/tag/ before moving it. |
| - run: | |
| - docker run -d --name minio -p 9000:9000 \ |
| - -e MINIO_ROOT_USER=root -e MINIO_ROOT_PASSWORD=rootsecret \ |
| - quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z server /data |
| - for i in $(seq 1 30); do |
| - if curl -sf http://127.0.0.1:9000/minio/health/live; then |
| - break |
| - fi |
| - if [ "$i" -eq 30 ]; then |
| - echo "minio never became healthy after 30s" >&2 |
| - docker logs minio || true |
| - exit 1 |
| - fi |
| - sleep 1 |
| - done |
| - - name: mint scoped user |
| - run: | |
| - # Pinned to the mc release published alongside the minio server |
| - # image above (verified live against the minio/mc GitHub releases |
| - # API and dl.min.io's archive endpoint on 2026-08-10) rather than |
| - # the floating linux-amd64/mc path, for the same reproducibility |
| - # reason. Bump together with the minio image pin above. |
| - curl -sfL https://dl.min.io/client/mc/release/linux-amd64/archive/mc.RELEASE.2025-08-13T08-35-41Z -o mc && chmod +x mc |
| - ./mc --version |
| - ./mc alias set local http://127.0.0.1:9000 root rootsecret |
| - ./mc mb local/contract-bucket local/denied-bucket |
| - cat > /tmp/policy.json <<'EOF' |
| - {"Version": "2012-10-17", "Statement": [ |
| - {"Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": ["arn:aws:s3:::contract-bucket"]}, |
| - {"Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": ["arn:aws:s3:::contract-bucket/*"]} |
| - ]} |
| - EOF |
| - ./mc admin policy create local contract-only /tmp/policy.json |
| - ./mc admin user add local scopeduser scopedsecret1 |
| - ./mc admin policy attach local contract-only --user scopeduser |
| - - name: contract test |
| - env: |
| - CARLOS_MINIO_TEST: "1" |
| - MINIO_ENDPOINT: http://127.0.0.1:9000 |
| - MINIO_SCOPED_KEY: scopeduser |
| - MINIO_SCOPED_SECRET: scopedsecret1 |
| - MINIO_BUCKET: contract-bucket |
| - MINIO_DENIED_BUCKET: denied-bucket |
| - run: go test ./internal/objstore/ -run TestDeliveredContractMinio -count=1 -v |
| - |
| - gofmt: |
| - name: gofmt (changed files) |
| - runs-on: ubuntu-latest |
| - # Only meaningful against a base to diff from. |
| - if: github.event_name == 'pull_request' |
| - steps: |
| - - uses: actions/checkout@v4 |
| - with: |
| - fetch-depth: 0 |
| - - uses: actions/setup-go@v5 |
| - with: |
| - go-version-file: go.mod |
| - # CLAUDE.md's rule is "gofmt -l clean for files you touch", not for the |
| - # tree: three pre-existing unformatted files are deliberately left |
| - # alone, so a repo-wide check would fail on arrival and teach everyone |
| - # to ignore this job. Scoped to the files the PR actually changes. |
| - - name: gofmt changed files |
| - env: |
| - BASE_SHA: ${{ github.event.pull_request.base.sha }} |
| - run: | |
| - set -euo pipefail |
| - files=$(git diff --name-only --diff-filter=ACMR "$BASE_SHA"...HEAD -- '*.go') |
| - if [ -z "$files" ]; then |
| - echo "no Go files changed" |
| - exit 0 |
| - fi |
| - echo "checking:" |
| - echo "$files" | sed 's/^/ /' |
| - unformatted=$(gofmt -l $files) |
| - if [ -n "$unformatted" ]; then |
| - echo |
| - echo "needs gofmt -w:" |
| - echo "$unformatted" | sed 's/^/ /' |
| - exit 1 |
| - fi |
diff --git a/AGENTS.md b/AGENTS.md| index efe0aa0ee..78b2f8c32 100644 |
| --- a/AGENTS.md |
| +++ b/AGENTS.md |
| @@ -74,26 +74,36 @@ Use the judgement the rest of this file asks for: a PR that is still |
| collecting review, that is gated on something (`#62`), or whose diff you |
| would want a second pair of eyes on, waits for a person. |
| -The reason is timing, not ceremony. `.github/workflows/ci.yml` triggers on |
| -`pull_request` *and* on `push` to `main`, so a locally merged change does |
| -still meet the gate — but only **after** it is on `main`, where a red run |
| -is an incident to clean up rather than a merge that simply doesn't happen. |
| -The PR is the one place the gate runs against the merge result while the |
| -change can still be turned away, and the one place the branch's diff is |
| -read as a single piece rather than commit by commit. |
| +The reason is timing, not ceremony. A locally merged change does still |
| +meet the gate — but only **after** it is on `main`, where a red run is an |
| +incident to clean up rather than a merge that simply doesn't happen. The |
| +PR is the one place the branch's diff is read as a single piece rather |
| +than commit by commit, while the change can still be turned away. |
| ## Build & test |
| - `go build ./...`, `go vet ./...`, `go test ./... -count=1` — all clean, |
| every package (21 as of 2026-08-08; the count grows), before any commit |
| lands. |
| -- `gofmt -l` clean for files you touch (three pre-existing unformatted files |
| - elsewhere are left alone). |
| -- `.github/workflows/ci.yml` runs that same gate on every PR, plus a |
| - `GOOS=darwin` compile. **The gate is not Linux-only**: `internal/host` is |
| - linked into the CLI, which people run on Macs, so a Linux-only type |
| - assumption breaks `go build .` for them. Cross-compiling is enough to catch |
| - it — no Mac required. |
| +- `gofmt -l .` clean over the whole tree — `make test` checks it, and the |
| + three pre-existing unformatted files that used to be exempted are gone. |
| +- **`.amadan/ci.d/` is the only CI this repo has.** The GitHub workflow |
| + that used to run this gate was deleted on 2026-08-24, once the amadan |
| + steps covered everything it did and more; `.amadan/README.md` records |
| + what each step descends from, and `.amadan/ci.d/10-gate`'s header |
| + records why the switch was safe. `10-gate` is `go build ./...` then |
| + `make test`, so CI and what you run locally are one definition rather |
| + than two that can drift. |
| +- **CI is triggered by a push to the `amadan` remote, not by GitHub.** |
| + `origin` is still where PRs live and where `main` is source of truth, |
| + but nothing on GitHub runs a build any more. A branch pushed only to |
| + `origin` has been gated by exactly nothing — push it to `amadan` too, |
| + and read the run, before calling it green. |
| +- `20-cross@5` adds `GOOS=darwin`/`windows`/`linux-arm64` compiles, one |
| + per shard. **The gate is not Linux-only**: `internal/host` is linked |
| + into the CLI, which people run on Macs, so a Linux-only type assumption |
| + breaks `go build .` for them. Cross-compiling is enough to catch it — |
| + no Mac required. |
| - Sandbox note: use `GOCACHE=/tmp/claude-1001/gocache GOFLAGS=-mod=mod |
| GOPROXY=off`; go commands may need the sandbox disabled ("read-only file |
| system" from the toolchain is the tell). |
diff --git a/internal/host/statfs_unix.go b/internal/host/statfs_unix.go| index 4c9916994..a57f9ceba 100644 |
| --- a/internal/host/statfs_unix.go |
| +++ b/internal/host/statfs_unix.go |
| @@ -9,7 +9,7 @@ import "syscall" |
| // |
| // It returns BYTES, not the raw statfs fields, and that is the point: Bsize |
| // is int64 on Linux and uint32 on darwin, a difference that already broke |
| -// the CI build once (see .github/workflows/ci.yml's header) because the |
| +// the CI build once (see .amadan/ci.d/20-cross@5's header) because the |
| // multiplication happened at every call site instead of once here. Callers |
| // now get two plain uint64s and no per-OS arithmetic. |
| func diskBytes(dir string) (total, avail uint64, err error) { |
diff --git a/internal/objstore/contract_test.go b/internal/objstore/contract_test.go| index 6a9489a57..ae328d283 100644 |
| --- a/internal/objstore/contract_test.go |
| +++ b/internal/objstore/contract_test.go |
| @@ -28,13 +28,14 @@ import ( |
| // been exercised anywhere in this tree is the actual wire contract a member |
| // app's client.Options() produces from delivered env, against a real |
| // S3-compatible implementation. Skipped by default because it needs a |
| -// running Minio; CI's minio-contract job (.github/workflows/ci.yml) sets |
| -// CARLOS_MINIO_TEST=1 and stands one up. There is no docker in this |
| -// sandbox, so the live path here has only ever been verified in CI — see |
| -// the commit body. |
| +// running Minio; CI's 30-minio-contract step (.amadan/ci.d/30-minio-contract) |
| +// sets CARLOS_MINIO_TEST=1 and stands one up, as a plain process on |
| +// loopback rather than a container. That step is now the only place this |
| +// test runs live anywhere, so treat a change to it as a change to what |
| +// this test is worth. |
| func TestDeliveredContractMinio(t *testing.T) { |
| if os.Getenv("CARLOS_MINIO_TEST") != "1" { |
| - t.Skip("set CARLOS_MINIO_TEST=1 (with a running Minio and the MINIO_* env vars) to run the delivered-contract test; see .github/workflows/ci.yml's minio-contract job") |
| + t.Skip("set CARLOS_MINIO_TEST=1 (with a running Minio and the MINIO_* env vars) to run the delivered-contract test; see .amadan/ci.d/30-minio-contract, which stands one up") |
| } |
| endpoint := requireEnv(t, "MINIO_ENDPOINT") |