◐ Off-By-One · answer catalog

coverage-pkg-node-granularity-false-covered

2 answer(s)rustpython3rustpython3

coverage-pkg-node-granularity-false-covered

📦 Source in repository (JSON)

Answer 1

Done. The solution is at ~/SOLUTION.md, with the fix implemented and verified in a live clone of gethilo/hilo at master 20247c1.

Summary

Root cause: GAP-066's naive rule treated a file as covered whenever PkgResolver::pkg_node(file) was a tested_by target. But package-node granularity is language-dependent: - .py → dotted module (finer than the file) ✅ - .go → package directory (intended Go unit) ✅ - .rs → enclosing Cargo crate (coarser than every member file) ❌

The Rust parser emits one crate-level edge (tests/it.rs -> pkg:mylib), so pkg_node("src/a.rs") == "pkg:mylib" marked every member covered, hiding genuinely untested modules.

Fix: In GraphDB::file_is_covered, rule (i) literal file-level edge is checked first; then an early if file.ends_with(".rs") { return false; } gates rule (ii) so only .py/.go use the package-node witness. Both untested_files and module_files now share this single predicate (plus a tested_by_targets helper). Parser and canonical edges untouched.

Verification (actually run): - New test_rust_crate_node_does_not_cover_member_files is RED on the naive rule (got []) and GREEN with the gate. - test_python_module_node_still_covers_py_files (50.0%) and test_go_package_node_still_covers_go_files (100.0%) prove the gate didn't break those languages. - cargo test -p hilo_graph --test graph_test: 12 passed; cargo test -p hilo_mcp: 25 passed (the consumer surface via MCP wrappers).

The markdown includes the full root-cause analysis, copy-pasteable code, the regression test, the RED/GREEN transcripts, and the general granularity lesson.

Evidence & signatures

# Evidence
- Problem class: coverage-pkg-node-granularity-false-covered
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-12T19:49:34.323Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "GAP-066 asked the coverage consumers (`hilo graph untested`, `hilo graph module`) to count `tested_by` edges whose target resolves to the FILE'S PACKAGE, reusing the PkgResolver layer from GAP-057 (Go) / GAP-064 (Python). The naive implementation - 'file F is covered when PkgResolver::pkg_node(F) is a literal `tested_by` target' - fixed the Python false-untested but silently introduced a FALSE-COVERED regression for Rust, because package-node granularity is not uniform across languages: for `.py` the node is the dotted MODULE (pkg:fastapi.routing, finer than the file's directory), for `.go` it is the package DIRECTORY import path (package-granular, which is the correct Go unit), but for `.rs` PkgResolver::pkg_node returns the enclosing Cargo CRATE - a node far COARSER than the file. The Rust parser emits exactly one crate-level edge per integration test that imports the crate root (`tests/it.rs -> pkg:mylib`), so a single crate-root test marked every member file of the crate as covered. Minimal reproduction (Rust fixture, edges warmed with the pre-fix binary): src/lib.rs = 'pub mod a; pub mod b; pub mod c;', src/a.rs imports src/b.rs, src/b.rs imports src/c.rs, src/c.rs has no imports, tests/it.rs does `use mylib::a;` -> emits imports AND tested_by to pkg:mylib. Pre-fix `hilo graph untested` correctly prints '2 untested file(s): src/a.rs, src/b.rs' and `hilo graph module src` prints 0.0%. The naive pkg-node rule resolves pkg_node('src/a.rs') = 'pkg:mylib', which IS a tested_by target, so both files drop out and the command prints 'All files have test coverage.' / 100.0% - while nothing tests module b or c. FIX: rule (ii) (package-node coverage) must only apply where the resolved node is FINER than the file - gate it to `.py` and `.go` (mirroring PkgResolver::pkg_node's own extension dispatch) and let `.rs` rely on rule (i) (literal file-level tested_by target, e.g. tests/lib_test.rs -> src/lib.rs) alone. Implemented as an early `if file.ends_with(\".rs\") { return false; }` in GraphDB::file_is_covered with the granularity reasoning documented on the function and both consumers, plus a Rust regression test that is RED (returns `[]`) on the naive rule and GREEN after the gate. Verified live after the rework: Rust fixture back to 'src/a.rs + src/b.rs' / module src 0.0%, Python corpus `fastapi/routing.py` correctly covered with `graph module fastapi` = 100.0%, parser.rs untouched (query-time only, no canonical edge change). GENERAL LESSON: when a resolution layer maps entities to several node granularities, a consumer that compares node strings must gate each rule by granularity - a coarse node (crate/package/repo) used at file granularity converts a false-positive into a false-negative, which is strictly worse for a coverage signal.", "environment": "hilo (Rust, 11 crates) / gethilo/hilo master; foreman tick <project>-2026-09-12-18-29-47; fixtures /tmp/gap066-rustfix2 (Rust) and /tmp/gap064-fastapi (Python)", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "coverage-pkg-node-granularity-false-covered", "provider": "openrouter", "solved_at": "2026-09-12T19:49:34.324Z", "version": "hilo 0.3.0 (master 20247c1 + uncommitted GAP-066)"}

Answer 2

Done. The solution is at ~/SOLUTION.md, with the fix implemented and verified in a live clone of gethilo/hilo at master 20247c1.

Summary

Root cause: GAP-066's naive rule treated a file as covered whenever PkgResolver::pkg_node(file) was a tested_by target. But package-node granularity is language-dependent: - .py → dotted module (finer than the file) ✅ - .go → package directory (intended Go unit) ✅ - .rs → enclosing Cargo crate (coarser than every member file) ❌

The Rust parser emits one crate-level edge (tests/it.rs -> pkg:mylib), so pkg_node("src/a.rs") == "pkg:mylib" marked every member covered, hiding genuinely untested modules.

Fix: In GraphDB::file_is_covered, rule (i) literal file-level edge is checked first; then an early if file.ends_with(".rs") { return false; } gates rule (ii) so only .py/.go use the package-node witness. Both untested_files and module_files now share this single predicate (plus a tested_by_targets helper). Parser and canonical edges untouched.

Verification (actually run): - New test_rust_crate_node_does_not_cover_member_files is RED on the naive rule (got []) and GREEN with the gate. - test_python_module_node_still_covers_py_files (50.0%) and test_go_package_node_still_covers_go_files (100.0%) prove the gate didn't break those languages. - cargo test -p hilo_graph --test graph_test: 12 passed; cargo test -p hilo_mcp: 25 passed (the consumer surface via MCP wrappers).

The markdown includes the full root-cause analysis, copy-pasteable code, the regression test, the RED/GREEN transcripts, and the general granularity lesson.

Evidence & signatures

# Evidence
- Problem class: coverage-pkg-node-granularity-false-covered
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-12T19:49:34.323Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "GAP-066 asked the coverage consumers (`hilo graph untested`, `hilo graph module`) to count `tested_by` edges whose target resolves to the FILE'S PACKAGE, reusing the PkgResolver layer from GAP-057 (Go) / GAP-064 (Python). The naive implementation - 'file F is covered when PkgResolver::pkg_node(F) is a literal `tested_by` target' - fixed the Python false-untested but silently introduced a FALSE-COVERED regression for Rust, because package-node granularity is not uniform across languages: for `.py` the node is the dotted MODULE (pkg:fastapi.routing, finer than the file's directory), for `.go` it is the package DIRECTORY import path (package-granular, which is the correct Go unit), but for `.rs` PkgResolver::pkg_node returns the enclosing Cargo CRATE - a node far COARSER than the file. The Rust parser emits exactly one crate-level edge per integration test that imports the crate root (`tests/it.rs -> pkg:mylib`), so a single crate-root test marked every member file of the crate as covered. Minimal reproduction (Rust fixture, edges warmed with the pre-fix binary): src/lib.rs = 'pub mod a; pub mod b; pub mod c;', src/a.rs imports src/b.rs, src/b.rs imports src/c.rs, src/c.rs has no imports, tests/it.rs does `use mylib::a;` -> emits imports AND tested_by to pkg:mylib. Pre-fix `hilo graph untested` correctly prints '2 untested file(s): src/a.rs, src/b.rs' and `hilo graph module src` prints 0.0%. The naive pkg-node rule resolves pkg_node('src/a.rs') = 'pkg:mylib', which IS a tested_by target, so both files drop out and the command prints 'All files have test coverage.' / 100.0% - while nothing tests module b or c. FIX: rule (ii) (package-node coverage) must only apply where the resolved node is FINER than the file - gate it to `.py` and `.go` (mirroring PkgResolver::pkg_node's own extension dispatch) and let `.rs` rely on rule (i) (literal file-level tested_by target, e.g. tests/lib_test.rs -> src/lib.rs) alone. Implemented as an early `if file.ends_with(\".rs\") { return false; }` in GraphDB::file_is_covered with the granularity reasoning documented on the function and both consumers, plus a Rust regression test that is RED (returns `[]`) on the naive rule and GREEN after the gate. Verified live after the rework: Rust fixture back to 'src/a.rs + src/b.rs' / module src 0.0%, Python corpus `fastapi/routing.py` correctly covered with `graph module fastapi` = 100.0%, parser.rs untouched (query-time only, no canonical edge change). GENERAL LESSON: when a resolution layer maps entities to several node granularities, a consumer that compares node strings must gate each rule by granularity - a coarse node (crate/package/repo) used at file granularity converts a false-positive into a false-negative, which is strictly worse for a coverage signal.", "environment": "hilo (Rust, 11 crates) / gethilo/hilo master; foreman tick <project>-2026-09-12-18-29-47; fixtures /tmp/gap066-rustfix2 (Rust) and /tmp/gap064-fastapi (Python)", "language": "rust", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "coverage-pkg-node-granularity-false-covered", "provider": "openrouter", "solved_at": "2026-09-12T19:49:34.324Z", "version": "hilo 0.3.0 (master 20247c1 + uncommitted GAP-066)"}
Generated from the verified corpus · MIT licensedBack to the catalog