Summary
A change confined to src/ that breaks an integration target under tests/ — including one that stops it compiling — passes every lane on a PR. Nothing in CI Lite builds integration test targets for a source-only change, so the break lands on main with a fully green PR.
Not hypothetical: found on #6214 today, which was green and mergeable with tests/raw_coverage/inference_agent_raw_coverage_e2e.rs uncompilable.
The three lanes, and why each misses it
| Lane |
Command |
Builds tests/**? |
Rust Quality (fmt, clippy) |
cargo clippy -p openhuman --features "$(…)" -- -D warnings (ci-lite.yml:435, :445) |
No — no --all-targets, so only lib + bins |
Rust Core Coverage |
scripts/ci/rust-coverage-changed.sh (ci-lite.yml:877) |
Only if the changed files select one — see below |
Test (test-reusable.yml, the only workflow that runs cargo test --test <target>) |
reached via test.yml |
Never on a PR — test.yml is on: workflow_dispatch: {} |
Why the coverage lane misses it
rust-coverage-changed.sh is changed-files-driven and correct on its own terms:
tests/raw_coverage/*.rs changed → --test raw_coverage_all (:343-352). Good.
src/** changed → a --lib libtest filter, plus any target named by domain_integration_targets().
And domain_integration_targets() is, in full:
domain_integration_targets() {
case "$1" in
src/openhuman/agent/harness/session/* | src/openhuman/threads/goals/*)
printf '%s\n' agent_turn_overrides_e2e
;;
esac
}
One mapping, two directories, one target. Every other source path in the tree maps to --lib alone. The script's own header already names the class (:10): "integration targets that GUARD a domain but live outside --lib, so a PR that touches only the domain's src/ never runs the gate." The design knows about this; the mitigation is a hand-maintained allowlist that has not kept up, and cannot be expected to.
Worked example (#6214)
- Changed
src/openhuman/agent/orchestration/tools/** — not in the mapping, so scope was --lib only.
- That changed
ArchetypeDelegationTool.agent_id from String to a new DelegationTarget newtype.
- Two call sites in
tests/raw_coverage/inference_agent_raw_coverage_e2e.rs use agent_id: "researcher".into(), which resolved via From<&str> for String.
- No
tests/ file changed, so raw_coverage_all was never selected.
Measured, with cargo check -p openhuman --no-default-features --features "$(bash scripts/ci/product-features.sh)" --test raw_coverage_all:
| Tree |
Result |
upstream/main (5e543a76b) |
exit 0 |
main + 605fc7fa6 (#6214) |
exit 101 — E0277: DelegationTarget: From<&str> is not satisfied |
PR state at that commit: 21 SUCCESS, 0 failing, Rust Core Coverage SUCCESS.
Why this is worse than a coverage gap
Two different things are being conflated by the current setup:
- Which assertions run. Genuinely a judgement call, and what
rust-coverage-changed.sh exists to scope. Running every suite on every PR is not affordable, so scoping is right.
- Whether the tree compiles at all. Not a judgement call. It should not be possible to merge a commit that does not build, and that is not a per-domain decision — it is the same answer for every path in the repo.
The current lanes only answer (1), so (2) goes unasked. And raw_coverage_all is an aggregated target (~76 suites via tests/raw_coverage_all.rs + build.rs), so a single type error in it blocks local verification of every suite inside it, not just the one that broke.
Suggested fix
Add a compile-only gate that is scope-independent:
cargo check -p openhuman --no-default-features \
--features "$(bash scripts/ci/product-features.sh)" --all-targets
- Catches 100% of this class regardless of which files changed — no allowlist to maintain, nothing to keep up to date.
- Compile-only, so far cheaper than running the suites; it is a build the coverage lane largely does anyway.
- Orthogonal to
rust-coverage-changed.sh, which keeps doing the job it is good at. This is not a replacement for scoped coverage.
One wrinkle to handle: naming a target whose required-features are unsatisfied is a hard error, not a skip — rust-coverage-changed.sh:160-190 already solves this with target_features_satisfied() and documents the incident where dropping memory-git from the product set took the lane down. --all-targets selects implicitly, so it skips rather than errors, but this should be confirmed against the current required-features set before the gate is made blocking.
Alternatively, extending domain_integration_targets() would narrow the hole but not close it — it is an allowlist, so it is only ever as good as the last person who remembered to update it, which is the failure mode being reported.
Related
Summary
A change confined to
src/that breaks an integration target undertests/— including one that stops it compiling — passes every lane on a PR. Nothing in CI Lite builds integration test targets for a source-only change, so the break lands onmainwith a fully green PR.Not hypothetical: found on #6214 today, which was green and mergeable with
tests/raw_coverage/inference_agent_raw_coverage_e2e.rsuncompilable.The three lanes, and why each misses it
tests/**?Rust Quality (fmt, clippy)cargo clippy -p openhuman --features "$(…)" -- -D warnings(ci-lite.yml:435,:445)--all-targets, so only lib + binsRust Core Coveragescripts/ci/rust-coverage-changed.sh(ci-lite.yml:877)Test(test-reusable.yml, the only workflow that runscargo test --test <target>)test.ymltest.ymlison: workflow_dispatch: {}Why the coverage lane misses it
rust-coverage-changed.shis changed-files-driven and correct on its own terms:tests/raw_coverage/*.rschanged →--test raw_coverage_all(:343-352). Good.src/**changed → a--liblibtest filter, plus any target named bydomain_integration_targets().And
domain_integration_targets()is, in full:One mapping, two directories, one target. Every other source path in the tree maps to
--libalone. The script's own header already names the class (:10): "integration targets that GUARD a domain but live outside--lib, so a PR that touches only the domain'ssrc/never runs the gate." The design knows about this; the mitigation is a hand-maintained allowlist that has not kept up, and cannot be expected to.Worked example (#6214)
src/openhuman/agent/orchestration/tools/**— not in the mapping, so scope was--libonly.ArchetypeDelegationTool.agent_idfromStringto a newDelegationTargetnewtype.tests/raw_coverage/inference_agent_raw_coverage_e2e.rsuseagent_id: "researcher".into(), which resolved viaFrom<&str> for String.tests/file changed, soraw_coverage_allwas never selected.Measured, with
cargo check -p openhuman --no-default-features --features "$(bash scripts/ci/product-features.sh)" --test raw_coverage_all:upstream/main(5e543a76b)605fc7fa6(#6214)E0277: DelegationTarget: From<&str> is not satisfiedPR state at that commit: 21 SUCCESS, 0 failing,
Rust Core CoverageSUCCESS.Why this is worse than a coverage gap
Two different things are being conflated by the current setup:
rust-coverage-changed.shexists to scope. Running every suite on every PR is not affordable, so scoping is right.The current lanes only answer (1), so (2) goes unasked. And
raw_coverage_allis an aggregated target (~76 suites viatests/raw_coverage_all.rs+build.rs), so a single type error in it blocks local verification of every suite inside it, not just the one that broke.Suggested fix
Add a compile-only gate that is scope-independent:
cargo check -p openhuman --no-default-features \ --features "$(bash scripts/ci/product-features.sh)" --all-targetsrust-coverage-changed.sh, which keeps doing the job it is good at. This is not a replacement for scoped coverage.One wrinkle to handle: naming a target whose
required-featuresare unsatisfied is a hard error, not a skip —rust-coverage-changed.sh:160-190already solves this withtarget_features_satisfied()and documents the incident where droppingmemory-gitfrom the product set took the lane down.--all-targetsselects implicitly, so it skips rather than errors, but this should be confirmed against the currentrequired-featuresset before the gate is made blocking.Alternatively, extending
domain_integration_targets()would narrow the hole but not close it — it is an allowlist, so it is only ever as good as the last person who remembered to update it, which is the failure mode being reported.Related