ADR-0002: CI runs every affected project and its dependents¶
Status: Accepted, amended by stackr ADR-0018 Date: 2026-09-30 Deciders: Alex Nodeland
Context¶
RFC-0003 planned CI around moon ci --downstream deep: moon would find the projects a pull request touches, and --downstream deep would add every project that depends on one, so a change to evalr would also run the checks of artifactr, reflexr, relayr, docplan, oncall and stackr. The rehearsal found two gaps, with moon 2.5.6:
--downstream deepfollows the task graph, not the project graph. It reaches a dependent's task only through that task's own dependencies. artifactr'scheckdepends on artifactr'slint,typecheckandtest, not on anything of evalr's, so an evalr-only change ran evalr's checks alone.moon query projects --affectedsees only a project's own files. A change touv.lock, the rootpyproject.tomlor.moon/is in no project's directory, so it affected no package, although every Python task lists the lock and the root's configuration as inputs.
Making every check depend on its dependencies' (^:check) would make the task graph follow the project graph. It would also make moon run artifactr:check run evalr's checks first, every time, on a contributor's machine as in CI.
Decision¶
scripts/affected.py TASKchooses what CI runs. It asks moon which projects the change touches, by their files (moon query projects --affected) and by their tasks' inputs, which include the lock and the root's configuration (moon query tasks --affected). It adds every project that depends on one of them, all the way down, from moon's project graph, andmoon runs the task in each project that has it. When nothing is affected, it runs nothing and succeeds.bddruns in one project after another, each its ownmoon run, rather than all at once. app's and admin's scenarios each start and stop a compose stack, and in CI one stack's network coming down while the other's Chromium navigates failed a scenario withERR_NETWORK_CHANGED. Locally,moon run app:bdd admin:bddstill runs both at once.- The projects tagged
browserrun their task first, in amoon runof their own. ui's, app's and admin's tests run in Chromium, which fails every request it has in flight when a network interface comes or goes, as a container's does when another project's tests start or stop one. In CI that failed a test file's import withERR_NETWORK_CHANGED, a different file each time (#254). Nightly runs their checks first for the same reason. Locally,moon runstill runs them with the rest. - CI's Check, Tests and Images jobs run through it, with the tasks
check,testandimage, andMOON_BASEandMOON_HEADnaming the pull request's change. - Template and Smoke stay on
moon ci(stackr:template,stackr:smoke), and run only when stackr's own inputs change. Until phase 4, the template pins the libraries by revision, so a library's change can't reach what it generates (#3). - The edges are the
dependsOneachmoon.ymlstates, which a root test holds to the project'spyproject.toml, as RFC-0003 planned.
Options considered¶
| Option | An evalr-only change | moon run artifactr:check |
|---|---|---|
scripts/affected.py over moon's queries (chosen) |
Runs evalr's checks and every dependent's | artifactr's checks |
moon ci --downstream deep (RFC-0003) |
Runs evalr's checks alone | artifactr's checks |
^:check in every project's check |
Runs evalr's checks and every dependent's | evalr's checks, then artifactr's |
| Every project's checks on every pull request | Runs everything | artifactr's checks |
Trade-off analysis¶
Running everything on every pull request would be correct and simple, but a change to one package would wait for all of them, and lattice is growing. moon already knows which projects a change touches and what depends on what; the script joins the two answers, and leaves the task graph as narrow as a contributor's own runs need.
Consequences¶
- Easier: a change to a package, or to the lock or configuration that every package reads, runs the checks of every project it can break, and no others.
- Harder: CI's selection is a script of lattice's own, which a moon upgrade could break.
nightly.ymlruns every project'scheckonmain, whatever changed, so a gap in the selection shows there. - Harder: moon counts a variable listed as a task's input as changed whenever it is set, so a variable CI always sets can't be an input without running its task on every pull request. The PostgreSQL URLs aren't inputs of the libraries'
testtasks for that reason (#23); moon's cache then can't tell a run with PostgreSQL from one without it, so locallymoon run <library>:test --forcereruns the tests past a cached run. - Revisit: if moon's
--downstreamcomes to follow the project graph, and its affected projects to include their tasks' root inputs, the script can go.