Summary
The Rust Core Coverage lane compiles the raw_coverage_all integration target only when a tests/raw_coverage/*.rs file is itself in the diff. A src/-only change that breaks one of those ~76 suites therefore lands on main green, and the breakage surfaces later as inherited compile errors in the next unrelated PR that happens to touch a raw-coverage file.
This is not hypothetical — it happened in 3e6f20e29 and cost a full debugging cycle in #5694.
Problem
What happened. 3e6f20e29 ("refactor(memory): bring the host's RPC models home from the engine crate") changed 5 files, all under src/openhuman/memory/:
src/openhuman/memory/direct_engine_refs_tests.rs
src/openhuman/memory/mod.rs
src/openhuman/memory/rpc_models.rs (new, 614 lines)
src/openhuman/memory/rpc_models_tests.rs (new)
src/openhuman/memory/store_golden.rs
Zero test files outside src/. It merged green.
It broke tests/raw_coverage/memory_threads_raw_coverage_e2e.rs with 28 × E0308, which sat undetected on main until #5694 touched a raw-coverage file for unrelated reasons and inherited all 28.
Why the lane missed it. In scripts/ci/rust-coverage-changed.sh:
src/<a>/<b>/….rs → libtest filter <a>::<b> against --lib only.
tests/raw_coverage/*.rs → --test raw_coverage_all (line 281).
domain_integration_targets() (line 116) drags in integration targets that guard a domain. Its entire table is one arm:
domain_integration_targets() {
case "$1" in
src/openhuman/memory/*)
printf '%s\n' memory_golden_fixture_e2e memory_golden_parity_e2e
;;
esac
}
src/openhuman/memory/* maps to the two golden targets — not to raw_coverage_all. So even the one domain that has a mapping does not reach the raw-coverage suites. Every other domain has no mapping at all.
Why the two existing safety gates did not fire. The script header documents both (added in #5593), and neither is aimed at this failure mode:
assert-coverage-presence.sh — fails when a changed source file produced no lcov records. Here the changed src/openhuman/memory/* files were covered by --lib tests, so records existed and the gate passed. It watches source coverage, not test-target compilation.
- Zero-executed-tests → escalate to full suite. The scoped run executed plenty of tests, so this never triggered.
Neither gate can observe "an integration target that was never compiled would fail to compile."
The specific trap. The mechanism is worth naming because it will recur. The commit swapped a re-export for a local module:
-pub use tinymemory_core::rpc_models::*;
+pub mod rpc_models;
+pub use rpc_models::*;
Its own comment reads "Same glob, same paths, same wire bytes." That is true for consumers reaching the types through memory:: — and false for consumers naming tinymemory_core::rpc_models:: directly, which now get a different Rust type with an identical name and identical serde shape. --lib had no such consumer; raw_coverage_all had seven. This is the same tinymemory-api / tinycortex-api near-identical-types hazard already documented in AGENTS.md, reached from the other side.
Steps to reproduce
- Branch from
main.
- Edit any
src/openhuman/<domain>/ file in a way that breaks a signature used by tests/raw_coverage/<something>_raw_coverage_e2e.rs — e.g. re-home a type so a name resolves to a distinct type.
- Touch no file under
tests/.
- Open a PR against
main.
- Observed: all lanes green;
raw_coverage_all is never compiled.
- Confirm locally that it is genuinely broken:
cargo test --features "$(bash scripts/ci/product-features.sh)" \
--test raw_coverage_all --no-run
Platform: CI Lite (ci-lite.yml), Linux, PRs targeting main.
Solution (optional)
The cheapest correct fix is a compile-only check, not a coverage run — the gap is that the target is never built, and building it is a small fraction of running it:
cargo test --features "$PRODUCT_FEATURES" --test raw_coverage_all --no-run
Run it on any PR that changes src/** regardless of scoping. That closes the hole without paying for the ~76 suites on every PR, and preserves the fast lane's whole point.
Options considered, for the record:
- Extend
domain_integration_targets() to map every src/openhuman/* → raw_coverage_all. Correct but expensive: it drags the full raw-coverage run into most PRs, which is close to abandoning the fast lane.
--no-run compile gate (recommended). Catches the entire class — every failure of this shape is a compile failure, since the lane's blind spot is compilation, not assertion outcomes.
- Leave it to CI Full.
main→release does run the full suite, so this is caught eventually — but "eventually" means it is caught by whoever's unrelated PR inherits it, which is exactly the cost incurred here.
Worth noting the same blind spot applies to any tests/ target absent from domain_integration_targets, not only raw_coverage_all. The --no-run approach generalises; the mapping-table approach does not.
Acceptance criteria
Related
Summary
The Rust Core Coverage lane compiles the
raw_coverage_allintegration target only when atests/raw_coverage/*.rsfile is itself in the diff. Asrc/-only change that breaks one of those ~76 suites therefore lands onmaingreen, and the breakage surfaces later as inherited compile errors in the next unrelated PR that happens to touch a raw-coverage file.This is not hypothetical — it happened in
3e6f20e29and cost a full debugging cycle in #5694.Problem
What happened.
3e6f20e29("refactor(memory): bring the host's RPC models home from the engine crate") changed 5 files, all undersrc/openhuman/memory/:Zero test files outside
src/. It merged green.It broke
tests/raw_coverage/memory_threads_raw_coverage_e2e.rswith 28 × E0308, which sat undetected onmainuntil #5694 touched a raw-coverage file for unrelated reasons and inherited all 28.Why the lane missed it. In
scripts/ci/rust-coverage-changed.sh:src/<a>/<b>/….rs→ libtest filter<a>::<b>against--libonly.tests/raw_coverage/*.rs→--test raw_coverage_all(line 281).domain_integration_targets()(line 116) drags in integration targets that guard a domain. Its entire table is one arm:src/openhuman/memory/*maps to the two golden targets — not toraw_coverage_all. So even the one domain that has a mapping does not reach the raw-coverage suites. Every other domain has no mapping at all.Why the two existing safety gates did not fire. The script header documents both (added in #5593), and neither is aimed at this failure mode:
assert-coverage-presence.sh— fails when a changed source file produced no lcov records. Here the changedsrc/openhuman/memory/*files were covered by--libtests, so records existed and the gate passed. It watches source coverage, not test-target compilation.Neither gate can observe "an integration target that was never compiled would fail to compile."
The specific trap. The mechanism is worth naming because it will recur. The commit swapped a re-export for a local module:
Its own comment reads "Same glob, same paths, same wire bytes." That is true for consumers reaching the types through
memory::— and false for consumers namingtinymemory_core::rpc_models::directly, which now get a different Rust type with an identical name and identical serde shape.--libhad no such consumer;raw_coverage_allhad seven. This is the sametinymemory-api/tinycortex-apinear-identical-types hazard already documented inAGENTS.md, reached from the other side.Steps to reproduce
main.src/openhuman/<domain>/file in a way that breaks a signature used bytests/raw_coverage/<something>_raw_coverage_e2e.rs— e.g. re-home a type so a name resolves to a distinct type.tests/.main.raw_coverage_allis never compiled.Platform: CI Lite (
ci-lite.yml), Linux, PRs targetingmain.Solution (optional)
The cheapest correct fix is a compile-only check, not a coverage run — the gap is that the target is never built, and building it is a small fraction of running it:
Run it on any PR that changes
src/**regardless of scoping. That closes the hole without paying for the ~76 suites on every PR, and preserves the fast lane's whole point.Options considered, for the record:
domain_integration_targets()to map everysrc/openhuman/*→raw_coverage_all. Correct but expensive: it drags the full raw-coverage run into most PRs, which is close to abandoning the fast lane.--no-runcompile gate (recommended). Catches the entire class — every failure of this shape is a compile failure, since the lane's blind spot is compilation, not assertion outcomes.main→releasedoes run the full suite, so this is caught eventually — but "eventually" means it is caught by whoever's unrelated PR inherits it, which is exactly the cost incurred here.Worth noting the same blind spot applies to any
tests/target absent fromdomain_integration_targets, not onlyraw_coverage_all. The--no-runapproach generalises; the mapping-table approach does not.Acceptance criteria
src/-only change that breaks atests/raw_coverage/*.rssuite fails CI on its own PR, not on a later unrelated one.src/-only PR is recorded in the PR body; the fast lane must stay fast.raw_coverage_allor every unmappedtests/target is decided and written down.Related
3e6f20e29— the commit that landed the breakage green.69c57e086).scripts/ci/rust-coverage-changed.sh—domain_integration_targets()(L116), raw-coverage mapping (L281).