Skip to content

fix: mark virtual workspace Cargo.toml manifests as skipped, not zero-node - #3622

Open
seonghobae wants to merge 1 commit into
Graphify-Labs:v8from
seonghobae:fix/1833-workspace-manifest-zero-node
Open

seonghobae wants to merge 1 commit into
Graphify-Labs:v8from
seonghobae:fix/1833-workspace-manifest-zero-node

Conversation

@seonghobae

Copy link
Copy Markdown

Summary

  • A Cargo.toml with only [workspace] (no [package]) legitimately declares no package node — this is by design and already handled (_parse_cargo returns None).
  • However 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 persistent warning: N source file(s) produced zero nodes for every such manifest, on every run.
  • _parse_cargo now reports workspace_only=True when [workspace] is present with no [package]; extract_package_manifest returns skipped=True in 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-cluster run against a Cargo workspace root prints:

warning: 1 source file(s) produced zero nodes and are absent from the graph: Cargo.toml. ...(#1666)

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

  • Added test_cargo_virtual_workspace_manifest_is_marked_skipped (RED before the fix, GREEN after) asserting skipped=True for a workspace-only manifest and skipped is not True for 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

…-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

@graphify-labs graphify-labs Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 — impact
  • tests/test_detect.py — impact
  • tests/test_dotnet.py — impact
  • tests/test_extract.py — impact
  • tests/test_ignore_file_encoding.py — impact
  • tests/test_incremental_mtime_collision.py — impact
  • tests/test_languages.py — impact
  • tests/test_manifest_ingest.py — impact, changed-test
  • tests/test_manifest_tomli_required.py — impact
  • tests/test_mcp_ingest.py — impact
  • tests/test_out_dir_evidence.py — impact
  • tests/test_pipeline.py — impact
  • tests/test_stale_prune.py — impact
  • tests/test_watch.py — impact
  • tests/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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant