kolu's CI is a two-platform pipeline driven by
odu. The executable source of truth is
mod.just: its default recipe defines the DAG, and odu expands
each reachable recipe into Linux and macOS nodes.
Run the pipeline locally on the current platform:
just ciRun one recipe without posting GitHub statuses:
just ci::e2eRun the canonical multi-platform pipeline:
nix run ./ci#odu -- runOnly odu posts GitHub commit statuses. Add its live NDJSON feed when another tool is supervising the run:
nix run ./ci#odu -- run --progress jsonAttach a terminal dashboard to a run already in progress:
nix run ./ci#odu -- attach
# or
just ci::attachEvery reachable recipe runs on:
x86_64-linuxaarch64-darwin
Lane hosts come from $ODU_HOSTS, then ~/.config/odu/hosts.json, with
~/.config/justci/hosts.json accepted as the legacy location. A localhost lane
runs against the current checkout. A remote lane fetches the pushed commit into
its own workspace.
Linux runs lease an idle warm Incus container from the configured venue pool. Odu owns the lease for the life of the run and releases it when the run ends. The coordinator-side maintenance commands are:
just ci::pool-status
just ci::pool-ensureThe default recipe is the executable inventory of required
nodes. Do not copy that membership into documentation: odu expands the recipes
reachable from that root, applies the platform attributes in the same file, and
uses the resulting set for protected statuses.
The DAG groups work by responsibility:
- Nix and packaging recipes build the root flake, independent subflakes, package hashes, smoke targets, and upgrade windows.
- Code-quality recipes own formatting, linting, unit/daemon coverage, and the osfacts checks.
- Browser recipes own end-to-end behavior and its append-only governance ledger.
- Living-documentation and example recipes prove Surface examples and Atlas output remain buildable and synchronized.
For the exact current membership and dependency edges, inspect
ci/mod.just; local entry points and their dependency-bearing
wrappers live in the root justfile.
nix builds every runnable-Kolu flake output for the lane's system and runs
that flake's evaluation gate. The independent website, Surface examples, Solid
Browser example, and Odu flakes are separate nodes. home-manager builds its
own example flake: Darwin checks activation and launchd configuration, while
Linux builds the NixOS configuration and runs its VM tests. Workspace
typechecking is a flake check, so nix build .#default alone is not a type
proof: Vite and tsx transpile without checking types.
The daemon node is separate from unit because it forks real padi and kaval
processes. Keeping it explicit prevents the default-off daemon suites from
silently disappearing from CI.
The large nix build is the shared prerequisite for every later node that
invokes nix build. Their commands remain self-contained, but start from a
warmed store instead of running several Nix builds at once:
┌─ website-nix ─ website-pnpm-hash-fresh
├─ surface-examples-nix
├─ solid-browser-example-nix
├─ odu-nix
setup ─── nix ─────├─ home-manager (Darwin checks / Linux VM tests)
├─ e2e
├─ smoke
└─ pnpm-hash-fresh
This lengthens the critical path but lowers peak CPU, memory, and Nix-store contention.
Edit mod.just. Odu respects just's OS attributes, but a
cross-platform product gate such as home-manager should remain one node and
select its platform-specific proof inside the recipe.
The install node is the single pnpm installation for a lane. Downstream pnpm
consumers must depend on it and invoke pnpm directly; concurrent installs can
corrupt the shared node_modules.
After changing the DAG, inspect the required status contexts for both platforms:
just ci::protect --dry-run