ADR-039 โ 4-Lane CI Topology (budget-scoped testing)
Status: Accepted (2026-07-16) Date: 2026-07-16 Related: #4347 (removed PR checks on dev), #3585 (sharded coverage), #1913 (suite sharding) Team guide: docs/guides/ci-cd-4-lanes.md โ the short day-to-day version of this ADR
Contextโ
CI ran the full suite (8 API shards with coverage, frontend build, agent tests, live OpenRouter evals, a macOS-native connector job at 10ร billing) on EVERY push to dev โ 172 runs in the week of 2026-07-08 โ 13,000 Linux-min-equivalents. Measured change locality: 38% of merges touch api/, 33% frontend/, 3% agent/, 6% remote-desktop-agent/, 29% none of them. PRs into dev ran no CI at all (#4347), so compile/type/unit breakage landed on dev routinely.
The team is moving to a weekly release cadence (staging cut Monday 06:00 UTC, prod Wednesday 06:00 UTC). Motto: "keep prod stable; stability derives from proper testing on staging". A release-branch model was considered and DEFERRED โ the dev โ staging โ main branch flow stays.
Decision โ four lanesโ
| Lane | Trigger | Runs | Required? |
|---|---|---|---|
| 1 Fast gate | PR โ dev (pr-checks.yml) | lint, compile, UNIT tests (no containers), migrations check when api/migrations/** or api/src/database/** touched | YES โ PR Gate check |
| 2 Post-merge | push โ dev (ci.yml) | integration suites, PATH-SCOPED to touched services; unit lane re-run; no coverage; no live evals; macOS connector only on remote-desktop-agent/** changes | dev has no protection gate; red = fix forward |
| 3 Promotion | PR โ staging/main (ci.yml) | full unscoped suite + live evals (advisory) + trufflehog secret scan (+ e2e-smoke.yml, unchanged) | YES โ CI Success |
| 4 Nightly | schedule (nightly.yml โ workflow_call ci.yml) | FULL suite with coverage thresholds on merged report + live evals + full connector run | Slack digest on failure |
Manual full run any time: gh workflow run ci.yml --ref <ref> [-f coverage=true] [-f live_evals=true].
Mechanicsโ
- Unit/integration split:
api/jest.classify.jsclassifies specs at config-load time by infrastructure tokens (DataSource, DATABASE_URL, REDIS_URL, ioredis, BullModule.forRoot, .listen(, โฆ) with// @jest-lane unit|integrationoverride markers. Unit lane (jest.config.unit.js) runs parallel workers with NO DATABASE_URL/REDIS_URL โ misclassification fails loudly. Integration lane (jest.config.integration.js) keeps --runInBand + service containers.jest.config.shard.js(full suite + coverage) is now nightly-only. Split at introduction: 482 unit / 152 integration of 634. - Path scoping on pushes:
.github/actions/ci-scopeclassifiesmerge-base(before, sha)..sha; unusable SHAs or merge-base failure fail OPEN to full CI. Any.github/change โ full CI. - Coverage ratchet moved to nightly (accepted trade: a coverage regression is flagged next morning, not at merge).
- Live evals: advisory (continue-on-error, outside
ci-success) โ run on promotion PRs and nightly, never on pushes. - Secret scanning: trufflehog diff scan on promotion PRs (gitleaks was dropped when it went paid). Dependency/CVE scanning is a separate issue.
Consequencesโ
- ~60โ70% billed-minute reduction at current velocity; PR feedback in ~5 min.
- Integration failures in untouched lanes surface at nightly/promotion, not per-merge (accepted; scoping is conservative and .github changes run full).
- New spec files with infrastructure usage MUST be detectable by the token
list or carry
// @jest-lane integrationโ the fail-loud unit lane enforces this automatically. - Branch protection on dev must require the
PR Gatecheck (repo admin, after the first pr-checks.yml run has reported). Read-modify-write the protection settings โ the API PATCH replaces the whole required-checks list.