Context and Problem Statement
The per-module CI selector (scripts/ci/gate_selection.py, fed by ci-router.yml)
keyed which module shards run on a push to source roots — a change under
src/specify_cli/<x>/** selected the <x> module — with no model of which
source a given test exercises. Two blind spots followed:
- Cross-cutting / unenrolled suites were silently skipped on
mainpushes. Whole test directories (tests/agent,tests/specify_cli/live_work,tests/review,tests/upgrade, and theexecution_contextfamily) were effectively never selected on ordinarymainpushes, so a regression in code they cover could ship to a greenmainundetected. integration_tests_nextwas a dead tier — declared in the registry with rootstests/integration/**+tests/next/**but invoked by no workflow, so those suites ran nowhere.
This was root-caused in #5034 (epic #4437) after it repeatedly cost maintainer landing passes: PRs #5029 and #5032 each un-masked a backlog of pre-existing reds the moment their diff touched a path that finally selected one of those shards. A green check no longer meant the tests had run.
Decision
Make "green ⇒ the tests actually ran" an enforced property, in four parts:
- Coverage-honesty guards (
scripts/ci/coverage_guard_lib.py, enforced bytests/architectural/test_foreign_coverage_guard.pyandtest_src_reachability_guard.py) — shrink-only ratchets over a measured baseline (.github/ci-foreign-coverage-baseline.json). foreign-coverage requires every registry row's test dirs to exercise its ownroots:; src-reachability pins atruly-darkset (a package imported by no test anywhere) and anin-matrix-darkset (tested only nightly). They fail only when coverage regresses, never vacuously. - Enrol the dark suites into
.github/ci-module-registry.yml(agent_utils,live_work,config,calibration,tasks_authoring,bootstrap) with re-measured host shard timings, and fix the phantom-mirror authority bug intest_gate_selection_authority(it validated a non-existenttests/agent_utils). - A nightly run-all
integration-nextlane inci-nightly.ymlrunningpytest tests/integration tests/nextregardless of paths, with red → deduped-P0 escalation (scripts/ci/nightly_escalation.py): onepriority:P0issue per suite key, opened/updated on red and closed on green, fail-closed and token-redacted when the token/API is absent. This retires the deadintegration_tests_nexttier (#4729). - Release gated on a green nightly (
scripts/ci/release_nightly_gate.py, wired inrelease.yml):build-release/publish-pypiare blocked unless the nightly for the exact release SHA is green; fail-closed on a missing, stale, red, or in-progress nightly.
Consequences
- A green nightly now means the full suite ran; a masked per-push red cannot reach a published release, because release gates on that nightly.
- Operator prerequisite:
release.ymldispatches the nightly viaworkflow_dispatch, which the defaultGITHUB_TOKENcannot trigger. A repo secretRELEASE_NIGHTLY_DISPATCH_TOKEN(a PAT or GitHub App token) is required; without it the release gate fails closed and nothing publishes. This is intentional fail-closed behavior.release.yml(Publish Release) is the live release workflow, so the secret must be provisioned before the next tag release — seeRELEASE_CHECKLIST.md. - Per-push selection is still path-filtered (unchanged); this ADR closes the
honesty gap (nothing is untested-by-construction, and release can't ship a
masked red), not the latency gap. Promoting
tests/integrationto a per-PR lane is deferred to #5037. - The
in-matrix-darkbaseline records remaining nightly-only debt (e.g.diagnostics, imported only bytests/e2e) as an explicit, shrink-only ledger rather than a silent gap.
Alternatives Considered
- Run every module shard on every push. Rejected: prohibitively slow/expensive
for the common case, and it does not address
truly-darkpackages that no test exercises at all (the guards do). - Leave per-push selection as-is and rely on maintainers noticing. Rejected — that is the status quo #5034 documents as failing: reds accumulated invisibly and surfaced only as landing-pass tax on unrelated PRs.