From 0d5a9c431795a1af49ed75eee4eae608855baf22 Mon Sep 17 00:00:00 2001 From: mintaka Date: Sun, 11 Oct 2026 21:53:42 -0400 Subject: [PATCH] docs(ci): describe CI as the job set behind the rollup check (RIG-5280) The CI section described one job with the real-Postgres suites as a step. ci.yml now runs setup, a moon matrix leg per concern group, and peer jobs for pgtest, e2e, microvm, forge, gtk4, darwin and the arm64 pin, behind one required rollup check. The section now says so, with the pgtest and e2e commands, the skip guards, and the affected-vs-full rule. testing-tiers.md links back to it for CI jobs. Co-authored-by: Matt Wilkinson --- docs/architecture/build-and-ci.md | 110 +++++++++++++++++------------ docs/architecture/testing-tiers.md | 2 +- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/docs/architecture/build-and-ci.md b/docs/architecture/build-and-ci.md index 673e2f5b7..ee593eb14 100644 --- a/docs/architecture/build-and-ci.md +++ b/docs/architecture/build-and-ci.md @@ -34,9 +34,9 @@ means PATH order never silently decides which copy of a tool wins. (`inputs`/`outputs`), affected-target detection, and local parallel execution. `moon run :` is the interface; `moon run :ci` runs every project's `ci` task. Affected detection decides which projects a change -actually touches — CI's PR gate drives it through `moon ci :ci` (the -CI-environment form, which reads the base from the provider), while a local -`moon run :ci --affected` is the same detection on demand. +actually touches. In CI, the `setup` job queries moon's affected set and hands +each `moon` leg its targets; a local `moon run :ci --affected` is the same +detection on demand. moon runs `go` and `bun` as **system tasks** — it execs the toolchain on PATH rather than managing its own. moon's graph/caching layer stays @@ -60,31 +60,53 @@ step. ## CI -`.github/workflows/ci.yml` runs on every pull request, every push to `main`, -and a nightly schedule. One job, `CI`, with two parts: - -- **The moon battery** — the whole battery over the moon task graph. It runs - one of two ways by event. On a **pull request** it is `moon ci :ci`, which - runs only the projects the PR affects — unrelated projects are not built. On - a **push to `main`** and on the **nightly schedule** it is the full - `moon run :ci`: every task, every project, no affected filter. Affected - detection trusts each task's `inputs` globs, so the full sweep on everything - that reaches `main` is the backstop — an incomplete glob that let a task be - skipped on a PR is caught the moment the change lands (and re-checked - nightly), named rather than hidden. Either way nothing about the workspace is - enumerated in the workflow, so a new project is gated the moment it is - registered in `.moon/workspace.yml`. This is the same task graph the local - gate and the `hk` pre-push hook run. -- **The real-Postgres suites** — build-tagged `pgtest`, and therefore never - compiled by the moon battery's `go test ./...`. They run as a step in this - same job, unconditionally (no affected filter and no event filter, so they - run on every trigger including the nightly), against a Postgres service - container attached to the job. A step afterwards asserts they actually ran: - the harness *skips* when it finds no database, so a service that never came - up would otherwise pass silently. These suites were once a separate `pgtest` - job, to keep a Postgres-service outage from redding the hermetic gate; they - were folded in so there is one required check to gate `main` on, at the cost - that a service-container flake now reds `CI` (a re-run clears it). +`.github/workflows/ci.yml` runs on every pull request, every push to `main`, and +a nightly schedule, and is re-run automatically (by `pr-base-repoint.yml`) when +a stacked PR's base moves. It is a set of jobs behind one required check, +`rollup`, which passes only when every job it needs succeeded, or skipped +because `setup` marked it unaffected. Branch protection requires only `rollup`, +so jobs can be added or renamed without touching it. + +- **`setup`** asks moon which projects to run: the affected set on a pull + request, every project on `main` and nightly. Some special-job flags come from + a changed-paths diff instead. `tools/ci-matrix` turns that answer into the + matrix for the `moon` job and an affected flag for each special job. Nothing + about the workspace is listed in the workflow, so a new project is gated the + moment it is registered in `.moon/workspace.yml` and tagged with a + `ci-group.` tag. +- **`moon`** runs the moon battery, one leg per concern group, each its own + check, today `moon (go)`, `moon (bun)` and `moon (nix)`. This is the same task + graph the local gate and the `hk` pre-push hook run. +- **Special jobs** need an environment the moon battery does not have. On a + pull request each runs only when its flag says the change affects it, except + `e2e`, which runs on every pull request. On `main` and nightly each always + runs. + - `pgtest`: the real-Postgres suites (`go test -tags pgtest -race ./...`) + against a Postgres service container. `COMPASS_REQUIRE_LIVE=1` turns a + missing database into a failure. + - `e2e`: the podman tier in a privileged container, so rootless podman can + load and run a tree-built agent image. The stack starts its own Postgres. + The command is + `go test -tags podman -race -count=1 -p 1 ./e2e/... ./cmd/compass-stack/...`. + - `microvm`, `forge`, `gtk4`, `darwin` and `agent-fod-arm64` cover the + microVM suites, the live forge contract, the GTK UI, the macOS bundle, and + the arm64 agent dependency pin. +- **`bun-audit`** checks bun dependencies against the live advisory database, + whatever the affected set. + +A tagged suite skips when its dependency is missing, so `pgtest`, `e2e`, +`microvm` and `forge` each end with a step that fails if the suite skipped +instead of running. + +**Affected on pull requests, full on `main`.** Affected detection trusts each +task's `inputs` globs and the affected closure, so an incomplete glob can skip +a task on a pull request. The full run on every push to `main` and the nightly +sweep catch that as soon as the change lands. The design-ledger gate is the one +target added to every pull request whatever the closure says, because no +affected edge reaches it. + +Which test tier to write at, and how the Go test harness is built: +[Go testing tiers](testing-tiers.md). ### Where CI's toolchain comes from @@ -121,8 +143,8 @@ Skipping what it cannot verify would make its green mean nothing. no check exercises the UI against a running server. - **The native app shell and the microVM boot lane.** `compass-app-dev`'s `build`/`run` and `compass-go:test-microvm` are `runInCI: false` — they need a - GUI toolchain and KVM respectively. The gtk4 e2e lane and `ci.yml`'s own - microVM step cover those paths instead, so neither is unguarded; they are just + GUI toolchain and KVM respectively. The `gtk4` and `microvm` jobs in + `ci.yml` cover those paths instead, so neither is unguarded; they are just not gated through the moon battery. ## Publishing the agent image @@ -170,7 +192,7 @@ destination (a registry ref versus `containers-storage:`), so what CI publishes is byte-for-byte what a developer loads locally. **The unified release lane.** Publishing lives in -`.github/workflows/release.yml`, **not** a step in the `CI` gate and **not** a +`.github/workflows/release.yml`, **not** a job in `ci.yml` and **not** a required check. Two jobs share it. The per-push `publish-image` job runs main-only plus `workflow_dispatch`, closure-gated in-job (an in-job changed-path check, not a trigger `paths:` filter — release-pr needs every commit), @@ -178,14 +200,12 @@ with its own concurrency group set to `cancel-in-progress: false` (publishes serialize rather than tear a tag pair mid-push) and a `packages: write` token the gate job never gets; it publishes `:git-` + `:latest`. The release-gated `release-image` job fires only when a `vX.Y.Z` release is cut and -digest-re-tags that image to `:vX.Y.Z`. This is a principled exception to the -[ONE-JOB doctrine](#ci): the doctrine exists to stop a second source of truth -for *what the gate covers*, and this workflow enumerates no moon projects -(`agent-image/` is a standalone devenv, not a moon project) — so it recreates -none of the silent-staleness failure the doctrine guards against. A published -tag is the source of truth; a missing per-push tag (paths filtered it out, or a -superseding push skipped it under the serialized concurrency group) is **not** a -failure — `workflow_dispatch` republishes any HEAD on demand. +digest-re-tags that image to `:vX.Y.Z`. It keeps the [CI](#ci) rule that no +workflow lists moon projects, because a list goes stale silently: release.yml +lists none (`agent-image/` is a standalone devenv, not a moon project). A +published tag is the source of truth; a missing per-push tag (paths filtered it +out, or a superseding push skipped it under the serialized concurrency group) is +**not** a failure — `workflow_dispatch` republishes any HEAD on demand. The same workflow also runs `publish-runner-image`, `publish-guest-image`, and `publish-gateway-image`. The gateway job builds and smoke-tests one local OCI @@ -244,9 +264,9 @@ full class. The Compass agent's compile script derives the SDK's lazy legacy-module registry from installed package exports and supplies it through a local Bun plugin. If the SDK adds a supported root option, Compass can remove this local plugin and use that option instead. The build is heavy — the image closure is the dominant CI cost, the reason -the gate's timeout is 90m — but it is not paid on every PR. `moon ci` runs a -PR's *affected* projects only, and the task's `inputs` scope it to the image -closure: the `agent-image/` tree, `packages/compass-agent/`, the root +the `moon` job's timeout is 90m — but it is not paid on every PR. `setup` +selects a PR's *affected* projects only, and the task's `inputs` scope it to the +image closure: the `agent-image/` tree, `packages/compass-agent/`, the root `package.json` and `bun.lock`, and `tools/toolchain/versions/bun.nix`. A PR that touches none of those never builds the image; every push to main runs it unconditionally in the full sweep. Its `inputs` mirror the closure set the @@ -254,9 +274,9 @@ it unconditionally in the full sweep. Its `inputs` mirror the closure set the trigger carries no `paths:` filter — release-pr needs every commit, so the job self-gates) — the reviewed source of truth for what changes the published artifact — including the bun pin file, since the image now builds bun from that -pinned derivation, so a pin move there changes the output. As a project in the -one-job gate it is a required check: a build break blocks merge, the same -posture as every other required build in the gate. +pinned derivation, so a pin move there changes the output. As a `ci-group.nix` +project it runs in the `moon (nix)` leg behind `rollup`, so a build break +blocks merge, like every other build in CI. ## Release assets and install channels diff --git a/docs/architecture/testing-tiers.md b/docs/architecture/testing-tiers.md index 9fb2ad1fd..1b6121eb5 100644 --- a/docs/architecture/testing-tiers.md +++ b/docs/architecture/testing-tiers.md @@ -98,4 +98,4 @@ stack starts or stops Postgres. ## Related - Design record: `docs/designs/infra/ci/compass-test-harness-tiers/design.md`. -- Toolchains and the moon battery: [Build & CI](build-and-ci.md). +- CI jobs: [Build & CI](build-and-ci.md).