Skip to content

Bind graph queries to their source tree - #3139

Open
ascension wants to merge 2 commits into
Graphify-Labs:v8from
ascension:feat/source-identity-status-query-gate
Open

Bind graph queries to their source tree#3139
ascension wants to merge 2 commits into
Graphify-Labs:v8from
ascension:feat/source-identity-status-query-gate

Conversation

@ascension

Copy link
Copy Markdown

What changed

Graph artifacts now carry a source identity with the resolved root, Git revision, canonical supported-file manifest digest, and detector version. A pending marker protects the publication window while source_manifest.json and graph.json advance through atomic replacements.

graphify status <source-root> --json reports stable eligibility reason codes and returns 0 only for a current artifact. graphify query runs the same check before traversal and before it prints a result. Successful queries print the bound identity.

Full extraction, zero-change updates, code rebuilds, watch rebuilds, and cluster-only exports now retain or refresh the identity. Generated runbooks use the same publication path. Legacy and copied graphs stay ineligible until Graphify reconciles them.

This implements the upstream part of Hive issue 2880.

Proof

  • 333 focused tests passed, with 4 platform skips.
  • All five skill generator checks passed for 134 generated artifacts.
  • Ruff and Pyright passed on the changed runtime and test files.
  • Python 3.10 passed the 26 source-identity and query CLI tests.
  • No Comments found no remaining comments, suppressions, or workaround code.
  • The complete local suite reached 5,010 passes and 72 skips. One existing order-sensitive assertion in tests/test_labeling.py:281 failed with concurrent batch completion order [100, 50, 100] instead of [100, 100, 50]; this branch does not change labeling code.

@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.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. 5 change(s) tested, no difference found (not proven).


Graphify review — findings

Adds a source-identity binding step so a graph artifact is only trusted for the source tree it was extracted from. graphify extract now publishes a source_identity (root, Git revision, supported-file manifest) into graph.json once extraction is complete, and both graphify status and graphify query gate on freshness_status before use — status exits non-zero with stable reason codes (wrong_root, revision_mismatch, manifest_mismatch, etc.) and query refuses to traverse or print results when the graph doesn't match the current checkout, directing users to run graphify update .. Committed graphs pulled by teammates no longer work immediately: they must run graphify update . first, which calls begin_reconciliation to rebind the copied graph before queries can use it.

Worth a look

  • query now rejects legacy graphs without source identitygraphify/cli.py:1122 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • query freshness check uses Path.cwd() instead of the graph's rootgraphify/cli.py:1123 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Unexpected indented block after return makes source_identity fail to importgraphify/source_identity.py · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • query validates --graph against cwd instead of graph rootgraphify/cli.py:1122 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Reconciliation marker can be left pending without cleanupgraphify/cli.py:3275 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2768 functions depend on the 1631 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 100 callers, 52 callees
  • new: build_from_json() — 187 callers, 19 callees
  • new: build_merge() — 46 callers, 14 callees
  • new: to_obsidian() — 36 callers, 13 callees
  • new: to_json() — 50 callers, 8 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: _call_claude_cli() — 31 callers, 10 callees
  • new: to_wiki() — 41 callers, 7 callees
  • …and 55 more — each is listed as a finding

Verification — 2768 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: 2616 function(s) in the blast radius were not formally verified this run

Formal verification

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: 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 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: 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)

No difference found (not proven): No behavior difference found in to\_json (not a proof).

The verifier ran both versions of to\_json 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.

No difference found (not proven): No behavior difference found in \_run\_cli (not a proof).

The verifier ran both versions of \_run\_cli 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.

No difference found (not proven): No behavior difference found in \_canonical\_graph\_for\_compare (not a proof).

The verifier ran both versions of \_canonical\_graph\_for\_compare 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.

No difference found (not proven): No behavior difference found in \_canonical\_topology\_for\_compare (not a proof).

The verifier ran both versions of \_canonical\_topology\_for\_compare 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 \_notify\_only.

The verifier did not have enough to check \_notify\_only, 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: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

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: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_topology\_from\_graph (not a proof).

The verifier ran both versions of \_topology\_from\_graph on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: concolic exploration (CrossHair). 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.

· 6 grounded finding(s) anchored inline below; 57 more finding(s) on lines outside this diff (see the check run).

return True


def publish_source_identity(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionpublish_source_identity()

fans out to 10 callees (efferent coupling); 15 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

return SourceManifest.parse(value)


def freshness_status(root: Path, graph_path: Path | None = None) -> FreshnessStatus:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionfreshness_status()

fans out to 8 callees (efferent coupling); 8 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

)


def status_command(args: list[str]) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionstatus_command()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/watch.py
print(f"[graphify watch] Source identity remains pending: {exc}", file=sys.stderr)


def _rebuild_code(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_rebuild_code()

fans out to 52 callees (efferent coupling); 100 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread tests/test_cli_export.py
return out


def _make_query_graph(tmp_path: Path) -> Path:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_make_query_graph()

6 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert not (graph_path.parent / PENDING_FILENAME).exists()


def test_failed_atomic_publication_leaves_the_artifact_pending(tmp_path, monkeypatch):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_failed_atomic_publication_leaves_the_artifact_pending()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

@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.

Worth a look — the grounded gate found no coupling regressions or blocking issues, but 5 advisory finding(s) below merit a look before merge.

Formal verification. 5 change(s) tested, no difference found (not proven).


Graphify review — findings

Adds a source-identity gate that binds an exported graph to the source tree it was built from, so a graph copied into a fresh checkout can't be queried until it's re-bound. Introduces graphify status (with --json and stable reason codes like wrong_root, revision_mismatch, manifest_mismatch, and pending_reconciliation) exiting 0 only when the graph matches the requested root, its Git revision, and source_manifest.json; graphify query runs the same freshness_status check before traversal and again before printing, aborting with guidance to run graphify update . if the source is stale. On a completed extract publish_source_identity stamps the graph's identity into the artifact, while --update first calls begin_reconciliation, and code-only runs that omit semantic files on a new graph are left pending rather than bound.

Worth a look

  • query now hard-fails on any pre-existing graph lacking source_identitygraphify/cli.py:1122 · Escalate · high
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • query freshness check assumes CWD is the source rootgraphify/cli.py:1122 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Publish can delete a newer pending markergraphify/source_identity.py · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • Relative detected paths are resolved against the process cwd instead of the source rootgraphify/source_identity.py:302 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
  • coverage_kinds misclassifies unresolved raw paths as absolute relative to CWDgraphify/watch.py:1128 · Escalate · medium
    • agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 2771 functions depend on the 1634 functions this change touches.

Health — this change adds coupling hotspots:

  • new: _rebuild_code() — 100 callers, 52 callees
  • new: build_from_json() — 187 callers, 19 callees
  • new: build_merge() — 46 callers, 14 callees
  • new: to_obsidian() — 36 callers, 13 callees
  • new: to_json() — 50 callers, 8 callees
  • new: extract_files_direct() — 17 callers, 20 callees
  • new: _call_claude_cli() — 31 callers, 10 callees
  • new: to_wiki() — 41 callers, 7 callees
  • …and 55 more — each is listed as a finding

Verification — 2771 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: 2619 function(s) in the blast radius were not formally verified this run

Formal verification

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: 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 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: 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)

No difference found (not proven): No behavior difference found in to\_json (not a proof).

The verifier ran both versions of to\_json 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.

No difference found (not proven): No behavior difference found in \_run\_cli (not a proof).

The verifier ran both versions of \_run\_cli 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.

No difference found (not proven): No behavior difference found in \_canonical\_graph\_for\_compare (not a proof).

The verifier ran both versions of \_canonical\_graph\_for\_compare 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.

No difference found (not proven): No behavior difference found in \_canonical\_topology\_for\_compare (not a proof).

The verifier ran both versions of \_canonical\_topology\_for\_compare 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 \_notify\_only.

The verifier did not have enough to check \_notify\_only, 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: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

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: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

No difference found (not proven): No behavior difference found in \_topology\_from\_graph (not a proof).

The verifier ran both versions of \_topology\_from\_graph on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: concolic exploration (CrossHair). 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 watch.

The verifier did not have enough to check watch, 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: parameter `watch_path` is annotated `Path` — outside the synthesizable primitive/collection set

· 7 grounded finding(s) anchored inline below; 56 more finding(s) on lines outside this diff (see the check run).

return True


def publish_source_identity(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionpublish_source_identity()

fans out to 10 callees (efferent coupling); 15 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

return SourceManifest.parse(value)


def freshness_status(root: Path, graph_path: Path | None = None) -> FreshnessStatus:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionfreshness_status()

fans out to 8 callees (efferent coupling); 10 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

)


def status_command(args: list[str]) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionstatus_command()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/watch.py
print(f"[graphify watch] Source identity remains pending: {exc}", file=sys.stderr)


def _rebuild_code(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_rebuild_code()

fans out to 52 callees (efferent coupling); 100 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread graphify/watch.py
_notify_only(watch_path)


def watch(watch_path: Path, debounce: float = 3.0) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressionwatch()

high coupling complexity (Ca·Ce = 20).

Grounded coupling-delta finding (deterministic), not an LLM guess.

Comment thread tests/test_cli_export.py
return out


def _make_query_graph(tmp_path: Path) -> Path:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_make_query_graph()

6 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

assert "pending_reconciliation" in query.stderr


def test_failed_atomic_publication_leaves_the_artifact_pending(tmp_path, monkeypatch):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regressiontest_failed_atomic_publication_leaves_the_artifact_pending()

fans out to 6 callees (efferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

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