fix: mark virtual workspace Cargo.toml manifests as skipped, not zero-node - #3622
seonghobae wants to merge 1 commit into
Conversation
…-node A Cargo.toml with only [workspace] (no [package]) legitimately declares no package node of its own. extract_package_manifest already returned an empty result for it, but without a "skipped" marker, extract.py's Graphify-Labs#1666 empty-source detector could not tell that apart from an unexplained extraction failure, so every run printed a persistent "produced zero nodes" warning for a manifest that was never supposed to emit one. _parse_cargo now reports workspace_only=True when [workspace] is present with no [package], and extract_package_manifest returns skipped=True for that case only — a manifest missing a name for any other reason still warns. Fixes downstream report at ContextualWisdomLab/fast-mlsirm#1833. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BXYRNTJhYLB2bYveuaFe74
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Formal verification. 1 change(s) alter behavior, breaking input(s) attached.
Behavior changes: \_match\_cited\_file changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_match\_cited\_file behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.
Evidence: On input \{"cited":"'\./'","doc\_file":"'\.mdx'","source\_files":"\{1, 2, 3\}"\}, the old code produced None but the new code produces raises AttributeError. Paste that input straight into a regression test.
Graphify review — findings
Marks Cargo virtual-workspace roots (a [workspace] table with no [package]) as skipped in extract_package_manifest, so their legitimate zero-node result no longer triggers extract.py's empty-source "produced zero nodes" warning on every run. A manifest missing a name for any other reason still returns unskipped and keeps warning, so genuinely malformed manifests aren't hidden.
No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 1552 functions depend on the 39 functions this change touches.
Health — this change adds coupling hotspots:
- new:
extract()— 637 callers, 45 callees - new:
_rebuild_code()— 123 callers, 53 callees - new:
detect()— 112 callers, 15 callees - new:
dispatch_command()— 2 callers, 125 callees - new:
detect_incremental()— 23 callers, 7 callees - new:
_get_extractor()— 26 callers, 6 callees - new:
_is_sensitive()— 39 callers, 4 callees - new:
classify_file()— 43 callers, 3 callees - …and 14 more — each is listed as a finding
Verification — 1552 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 648 function(s) in the blast radius were not formally verified this run
Test selection
Test selection
15 of 283 test file(s) selected (5%) via static blast radius.
tests/test_apm_fallback_version.py— impacttests/test_detect.py— impacttests/test_dotnet.py— impacttests/test_extract.py— impacttests/test_ignore_file_encoding.py— impacttests/test_incremental_mtime_collision.py— impacttests/test_languages.py— impacttests/test_manifest_ingest.py— impact, changed-testtests/test_manifest_tomli_required.py— impacttests/test_mcp_ingest.py— impacttests/test_out_dir_evidence.py— impacttests/test_pipeline.py— impacttests/test_stale_prune.py— impacttests/test_watch.py— impacttests/test_watch_manifest_location.py— impact
Selection is safe under the controlled-regression assumption; always-run tests + a periodic full run are the backstops. Advisory — it never changes the check verdict.
Formal verification
Behavior changes: \_match\_cited\_file changes behavior, here is the input that shows it.
The verifier found a concrete input on which \_match\_cited\_file behaves differently before and after the change. If that change is intended, ship it; if not, this is your bug.
Guarantee: This difference was REPRODUCED, the verifier actually ran both versions on that input and saw them disagree. It is real, not an artifact.
Evidence: On input \{"cited":"'\./'","doc\_file":"'\.mdx'","source\_files":"\{1, 2, 3\}"\}, the old code produced None but the new code produces raises AttributeError. Paste that input straight into a regression test.
Could not verify: Could not verify build\_from\_json.
The verifier did not have enough to check build\_from\_json, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: not verifiable: all 6 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly NameError — names the real obstacle, not a sampling gap)
Could not verify: Could not verify prefix\_graph\_for\_global.
The verifier did not have enough to check prefix\_graph\_for\_global, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: not verifiable: all 200 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous
Could not verify: Could not verify dispatch\_command.
The verifier did not have enough to check dispatch\_command, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: not verifiable: all 23 sampled inputs raised on both versions — the function never executed, so 'no divergence' would be vacuous (mostly SystemExit — names the real obstacle, not a sampling gap)
Could not verify: Could not verify extract\_elixir.
The verifier did not have enough to check extract\_elixir, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
No difference found (not proven): No behavior difference found in \_code\_span\_mention (not a proof).
The verifier ran both versions of \_code\_span\_mention on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify extract\_markdown.
The verifier did not have enough to check extract\_markdown, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_rust.
The verifier did not have enough to check extract\_rust, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify extract\_package\_manifest.
The verifier did not have enough to check extract\_package\_manifest, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_parse\_cargo.
The verifier did not have enough to check \_parse\_cargo, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 2 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
No difference found (not proven): No behavior difference found in resolve\_markdown\_mentions (not a proof).
The verifier ran both versions of resolve\_markdown\_mentions on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.
Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.
Note: An input the sampler did not try could still differ.
Could not verify: Could not verify resolve\_ruby\_member\_calls.
The verifier did not have enough to check resolve\_ruby\_member\_calls, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: non-vacuity: domain too small (only 1 distinct inputs exercised, need 3) — 'no divergence' would be near-vacuous
Could not verify: Could not verify \_rebuild\_code.
The verifier did not have enough to check \_rebuild\_code, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set
Could not verify: Could not verify \_reconcile\_markdown\_links.
The verifier did not have enough to check \_reconcile\_markdown\_links, so it is saying so rather than guessing. No false assurance is the whole point.
Guarantee: No guarantee either way, this is an honest abstention, not a pass.
Note: Reason: no capturable inputs from the test suite; property tier: parameter `project_root` is annotated `Path` — outside the synthesizable primitive/collection set
· 22 more finding(s) on lines outside this diff (see the check run).
Summary
[workspace](no[package]) legitimately declares no package node — this is by design and already handled (_parse_cargoreturnsNone).extract.py's Ruby: stable subset of files yields zero nodes in full-repo runs (0.9.6) — each extracts fine in isolation #1666 empty-source detector cannot distinguish "legitimately declared nothing" from "an extractor exists but silently produced nothing" and prints a persistentwarning: N source file(s) produced zero nodesfor every such manifest, on every run._parse_cargonow reportsworkspace_only=Truewhen[workspace]is present with no[package];extract_package_manifestreturnsskipped=Truein that case only (a manifest missing a name for any other reason still warns, since that IS unexplained).Reproduction (evidence from downstream)
Reported downstream at ContextualWisdomLab/fast-mlsirm#1833: a fresh
extract . --code-only --no-clusterrun against a Cargo workspace root prints:on every run, even though the workspace-only manifest never had a package node to emit (the root only has
[workspace], no[package]).Test plan
test_cargo_virtual_workspace_manifest_is_marked_skipped(RED before the fix, GREEN after) assertingskipped=Truefor a workspace-only manifest andskipped is not Truefor a manifest that is missing a name for any other reason.pytest tests/test_manifest_ingest.py— 14 passed.Related upstream issues referenced by the downstream report: #1264 (Cargo manifest extractor), #2434 (TOML collection).
🤖 Generated with Claude Code