Build & CI
How a change goes from an editor to a verified build:
devenv the dev shell — owns every toolchain (Go/bun/node/moon + the rest)moon the task graph: what to build/test, caching, affected detectionThe same task graph runs remotely and locally. GitHub Actions schedules
remote runs; moon schedules local ones. The parity gate fails a build when the
toolchains differ. What release.yml publishes, and how a release is cut, is in
Release and images.
Toolchains: devenv/nix
Section titled “Toolchains: devenv/nix”devenv (nix underneath) owns the entire toolchain, so PATH order never decides which copy of a tool wins.
- The language runtimes (Go, bun, node, moon) are pinned in
tools/toolchain/versions/*.nix. - The linters (biome, rumdl), rumdl’s base policy, and the Go analysis tools
(golangci-lint, govulncheck, go-licenses, nilaway) come from the locked
meissainput. Meissa builds them with the consumer’s Go toolchain, through itsdevenvModules.gomodule andlib.goAnalysis. - Everything else, including the contract tooling (buf, protoc, the Go codegen plugins), comes from the pinned nixpkgs revision.
Local: direnv allow puts the toolchain on PATH. devenv injects tools, not a
whole shell, so you keep your own prompt and dotfiles.
CI: nix builds the same derivations from the same pinned sources, through
tools/toolchain/gate-tools.nix, which also yields RUMDL_BASE_CONFIG. Their
bin/ dirs go on PATH. There are no setup-* actions.
Task graph: moon
Section titled “Task graph: moon”moon owns the task graph (deps:), result caching
(inputs/outputs), affected-target detection, and local parallel execution.
moon run <project>:<task> is the interface; moon run :ci runs every
project’s ci task, and moon run :ci --affected runs only what a change
touches.
moon runs go and bun as system tasks: it execs the toolchain on PATH
rather than managing its own. The nix pin files stay the version source.
The contract gate
Section titled “The contract gate”The owned compass.v1 schema is the only door between any UI and the daemon. It is
gated three ways:
buf lint: schema style and consistency.buf breaking: rejects backward-incompatible schema edits.- Drift: regenerate the clients, then
git diff --exit-code. A checked-in client that no longer matches the schema fails the build.
The generated Go and TypeScript clients are checked in, so a normal
go build / bun install needs no codegen step.
.github/workflows/ci.yml runs on every pull request, every push to main, and
a nightly schedule. pr-base-repoint.yml re-runs it when a stacked PR’s base
moves. Branch protection requires one check, rollup. It passes only when every
job it needs succeeded, or skipped because setup marked it unaffected, so jobs
can be added or renamed without touching branch protection.
setupasks moon which projects to run: the affected set on a pull request, every project onmainand nightly. Some special-job flags come from a changed-paths diff instead.tools/ci-matrixturns the answer into themoonmatrix and one affected flag per special job. The workflow lists no projects, so a new project is gated once it is in.moon/workspace.ymlwith aci-group.<name>tag. On a pull request,setupalso fails when the change adds or edits a file underdocs/designs/; design records live in RigelBuild/docs. Deleting one here is allowed.moonruns one leg per concern group, each its own check:moon (go),moon (bun)andmoon (nix). The local gate and thehkpre-push hook run the same task graph.- Special jobs need an environment the
moonlegs lack. On a pull request each runs only when its flag says the change affects it. The Go suites derive their flags from tagged test roots and first-party dependency closures; gate changes and declared owner-task inputs widen selection. Onmainand nightly each always runs.pgtest: the real-Postgres suites (go test -tags pgtest -race ./...) against a Postgres service container.COMPASS_REQUIRE_LIVE=1turns 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 isgo test -tags podman -race -count=1 -p 1 ./e2e/... ./cmd/compass-stack/.... It buildscompass-postgres,compass-serverandcompass-runner; thecompass-stackpackage is tested as an E2E root but is not a built child.microvm,forgeandgtk4cover the microVM suites, the live forge contract and the GTK UI.darwinbuilds the macOS shell and the ad-hoc signed.app/.dmgwithtools/macos-bundleonmacos-14. Its PR flag,darwin_affected, comes from the changed paths.agent-fod-arm64re-runsrenovate:agent-fodonubuntu-24.04-arm.agent-image/entrypoint.nixpins one node_modulesoutputHashper system, and the moon task checks only the host’s. Its PR flag,agent_fod_affected, is set when moon reportsrenovate:agent-fodaffected.refresh-fod-hashes.tsrewrites both pins from any host, since each FOD picks its arch withbun install --cpu.
bun-auditchecks bun dependencies against the live advisory database on every run.
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.
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 it once the change lands.
Which test tier to write at, and how the Go test harness is built: Go testing tiers.
The parity gate
Section titled “The parity gate”Reading a version from a file does not prove the runner got it.
tools/toolchain/parity.ts, scheduled by :ci as toolchain-parity:parity,
resolves the realpath of each command on PATH. It fails the build unless
the path is inside the derivation the pinned sources build. This store-path
check catches an ambient binary of the same version, and it works for tools with
no version flag (go-licenses, nilaway). A tool it cannot check is reported
UNVERIFIABLE and also fails the build; skipping it would make green mean
nothing.
What CI does not gate
Section titled “What CI does not gate”- A live UI↔server path. Every
compass-uitask runs against fixtures. - The native app shell and the microVM boot lane in the moon battery.
compass-app-dev’sbuild/runandcompass-go:test-microvmarerunInCI: false, because they need a GUI toolchain and KVM. Thegtk4andmicrovmjobs cover those paths instead.
Caching
Section titled “Caching”moon task cache — whole-task-output caching, keyed by an inputs hash.