Skip to content

Graphify: validate query-reader compatibility before reporting search available - #1031

Merged
jeffhuber merged 4 commits into
mainfrom
claude/1029-graphify-search-readiness
Sep 18, 2026
Merged

jeffhuber merged 4 commits into
mainfrom
claude/1029-graphify-search-readiness

Conversation

@jeffhuber

Copy link
Copy Markdown
Contributor

Closes #1029

Part of #1027 and #923. Builds on merged #1007 without re-implementing it: doc_ref acceptance, the provider-manifest budget and the frontend test conventions are unchanged.

What changed

  • Search readiness from the query reader. context_graph_query.search_readiness runs read_graph, the same read graph_context uses, over the published generation. build/refresh/status and connection-status report search and a query_reader verdict beside the lifecycle state/usable, which stay the generation's own verdict. status/build exit 0, and connection-status reports authorization: available, only when the generation is usable and the reader can consume it.
  • Known mismatch → upgrade action. A provider node type in KNOWN_PROVIDER_FILE_TYPES (doc_ref1.5.0) that the reader doesn't support raises ReaderIncompatible. An unknown type in a generation built by a provider release outside READER_PROVIDER_VERSIONS (0.9.58) does the same. Both report reason: reader_incompatible with a remediation naming the installed Code Mower, the generation's provider and the required release, plus next_action. graph_context returns the same reason and remediation.
  • Unknown types still fail closed. An unknown type from the reviewed provider raises UnsupportedNodeType (a ContextError), and readiness and query report unreadable. The unknown type's spelling is never echoed.
  • Generation vs query completeness. Query summaries add generation_completeness (from the manifest) and query_completeness (this answer). completeness keeps its meaning. A bounded partial answer stays available/usable and lists provider_has_more, unresolved_entities and document_limit.
  • Docs: lifecycle ("Search readiness is a separate verdict"), queries, setup troubleshooting (marked post-v1.4.2), CHANGELOG Unreleased entry for v1.5.0, and a roadmap note.

Regressions (tests/test_context_graph_connection.py SearchReadinessTests)

  • A complete generation containing doc_ref validates and is queryable.
  • Status, connection-status and the actual query agree (available, incompatible and unreadable cases).
  • An old-reader mismatch (the v1.4.2 reader, simulated by the same code path without doc_ref) produces the upgrade action. The test asserts that no graph labels, targets, ids or local paths appear in the JSON or text output.
  • An unreviewed provider release produces a rebuild-or-upgrade remediation.
  • Unknown node types still fail closed.
  • A bounded partial query stays available/usable and discloses provider_has_more, unresolved_entities and document_limit while generation_completeness stays complete.
  • Fix Graphify inventory limits and frontend test discovery #1007's modules are in both PACKAGE_FILES and code-mower-package-manifest.json. Fix Graphify inventory limits and frontend test discovery #1007's own four compatibility tests are unchanged.

test_the_verbs_address_one_checkout_from_any_directory_inside_it now publishes a real (empty) provider graph document instead of opaque bytes. status now exits 0 only when the reader can consume the generation.

Verification

Every Python interpreter invocation was denied in this lane session ("requires approval"). No suite ran locally, and CI on this head is the executed evidence. No new module was added, so the package manifest is unchanged.

🤖 Generated with Claude Code

…#1029)

build/refresh/status/connection-status now run the query's own read_graph
over the published generation and report `search` plus a metadata-only
`query_reader` verdict beside the lifecycle state. A known provider/reader
mismatch (a doc_ref generation meeting a pre-1.5.0 reader, or an unknown
type from an unreviewed provider release) reports reader_incompatible with
an installed-version/upgrade remediation; unknown node types from the
reviewed release still fail closed as unreadable. graph_context reports the
same reason and remediation, so status, connection-status and a real query
agree. Query summaries now carry generation_completeness and
query_completeness separately, so a bounded partial answer stays available
while disclosing provider_has_more / unresolved_entities / document_limit.

CODE_MOWER_BUILDER:claude

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber jeffhuber added needs-codex-audit builder:claude Code Mower generated label labels Sep 18, 2026
Comment on lines +163 to +174
repository = Path(state["repository_root"])
report = lifecycle.graph_status(repository, root=root, revision=revision)
# The query's own read, not the lifecycle's verdict alone: a current,
# complete generation this reader cannot consume must not be reported as
# searchable and then fail on the first question asked of it.
readiness = query.search_readiness(lifecycle.GraphStateRoot(repository, root=root), report)
verified = state["state"] == "verified"
search = readiness["search"] if verified else query.SEARCH_UNAVAILABLE
return {**_summary(state, search=search), "graph": report.shareable_summary(),
"query_reader": readiness,
"authorization": "available" if verified and report.usable
and search == query.SEARCH_AVAILABLE else "unavailable"}

@gitar-bot gitar-bot Bot Sep 18, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Performance: connection.status always runs full read_graph even when unverified

In context_graph_connection.py's status() (lines 158-174), query.search_readiness(...) is called unconditionally before checking verified = state["state"] == "verified". search_readiness performs a full read_graph when the generation is usable: opening the tar artifact, extracting and reading up to MAX_GRAPH_BYTES (64 MiB), parsing JSON, and building the whole CodeGraph. When the connection is disconnected, search is forced to SEARCH_UNAVAILABLE regardless (readiness["search"] if verified else query.SEARCH_UNAVAILABLE), so all of that work is thrown away. This is a real, reachable cost on every disconnected connection's status check, not just a corner case. Move the search_readiness call after the verified check so it only runs when its result can matter, or short-circuit it when not verified.

Skip the expensive read_graph-backed readiness check entirely when the connection is not verified, since its result is discarded anyway.:

repository = Path(state["repository_root"])
report = lifecycle.graph_status(repository, root=root, revision=revision)
verified = state["state"] == "verified"
if verified:
    readiness = query.search_readiness(lifecycle.GraphStateRoot(repository, root=root), report)
    search = readiness["search"]
else:
    readiness = {"schema": query.READINESS_SCHEMA, "search": query.SEARCH_UNAVAILABLE,
                 "reader": "not_checked", "reason": "disconnected"}
    search = query.SEARCH_UNAVAILABLE
return {**_summary(state, search=search), "graph": report.shareable_summary(),
        "query_reader": readiness,
        "authorization": "available" if verified and report.usable
        and search == query.SEARCH_AVAILABLE else "unavailable"}

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Sep 18, 2026

Copy link
Copy Markdown

Note

Automatic reviews are paused because your team has used its included automatic processing for this billing period (headroom scales with your seat count). You can still comment "Gitar review" to run one anytime, and automatic reviews resume on their own by October 1. Add seats for more headroom.
Learn more

Code Review 👍 Approved with suggestions 0 closed / 1 findings

🟡 Medium risk

Adds query-reader compatibility validation to report search readiness accurately, with comprehensive test coverage for complete generations, provider version mismatches, and upgrade remediation messaging. Consider deferring the full read_graph call in status() until after verifying the connection state, as it's currently executed unconditionally and discarded for disconnected connections.

💡 Performance: connection.status always runs full read_graph even when unverified

📄 src/code_mower/context_graph_connection.py:163-174

In context_graph_connection.py's status() (lines 158-174), query.search_readiness(...) is called unconditionally before checking verified = state["state"] == "verified". search_readiness performs a full read_graph when the generation is usable: opening the tar artifact, extracting and reading up to MAX_GRAPH_BYTES (64 MiB), parsing JSON, and building the whole CodeGraph. When the connection is disconnected, search is forced to SEARCH_UNAVAILABLE regardless (readiness["search"] if verified else query.SEARCH_UNAVAILABLE), so all of that work is thrown away. This is a real, reachable cost on every disconnected connection's status check, not just a corner case. Move the search_readiness call after the verified check so it only runs when its result can matter, or short-circuit it when not verified.

Skip the expensive read_graph-backed readiness check entirely when the connection is not verified, since its result is discarded anyway.
repository = Path(state["repository_root"])
report = lifecycle.graph_status(repository, root=root, revision=revision)
verified = state["state"] == "verified"
if verified:
    readiness = query.search_readiness(lifecycle.GraphStateRoot(repository, root=root), report)
    search = readiness["search"]
else:
    readiness = {"schema": query.READINESS_SCHEMA, "search": query.SEARCH_UNAVAILABLE,
                 "reader": "not_checked", "reason": "disconnected"}
    search = query.SEARCH_UNAVAILABLE
return {**_summary(state, search=search), "graph": report.shareable_summary(),
        "query_reader": readiness,
        "authorization": "available" if verified and report.usable
        and search == query.SEARCH_AVAILABLE else "unavailable"}
🤖 Prompt for agents
Code Review: Adds query-reader compatibility validation to report search readiness accurately, with comprehensive test coverage for complete generations, provider version mismatches, and upgrade remediation messaging. Consider deferring the full `read_graph` call in `status()` until after verifying the connection state, as it's currently executed unconditionally and discarded for disconnected connections.

1. 💡 Performance: connection.status always runs full read_graph even when unverified
   Files: src/code_mower/context_graph_connection.py:163-174

   In context_graph_connection.py's status() (lines 158-174), `query.search_readiness(...)` is called unconditionally before checking `verified = state["state"] == "verified"`. `search_readiness` performs a full `read_graph` when the generation is usable: opening the tar artifact, extracting and reading up to `MAX_GRAPH_BYTES` (64 MiB), parsing JSON, and building the whole `CodeGraph`. When the connection is `disconnected`, `search` is forced to `SEARCH_UNAVAILABLE` regardless (`readiness["search"] if verified else query.SEARCH_UNAVAILABLE`), so all of that work is thrown away. This is a real, reachable cost on every disconnected connection's status check, not just a corner case. Move the `search_readiness` call after the `verified` check so it only runs when its result can matter, or short-circuit it when `not verified`.

   Fix (Skip the expensive read_graph-backed readiness check entirely when the connection is not verified, since its result is discarded anyway.):
   repository = Path(state["repository_root"])
   report = lifecycle.graph_status(repository, root=root, revision=revision)
   verified = state["state"] == "verified"
   if verified:
       readiness = query.search_readiness(lifecycle.GraphStateRoot(repository, root=root), report)
       search = readiness["search"]
   else:
       readiness = {"schema": query.READINESS_SCHEMA, "search": query.SEARCH_UNAVAILABLE,
                    "reader": "not_checked", "reason": "disconnected"}
       search = query.SEARCH_UNAVAILABLE
   return {**_summary(state, search=search), "graph": report.shareable_summary(),
           "query_reader": readiness,
           "authorization": "available" if verified and report.usable
           and search == query.SEARCH_AVAILABLE else "unavailable"}

Review coverage

Rules No rules evaluated

Functional validation Not enabled · Set up

Options

Display: compact → Counting what did not apply, without listing it.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@jeffhuber

Copy link
Copy Markdown
Contributor Author

One follow-up before merge (P3 performance): context_graph_connection.status() currently calls the full read_graph-backed search_readiness() before checking whether the connection is verified, then discards that result for disconnected connections. Short-circuit the reader check when the connection is not verified and return a stable non-ready/not-checked query-reader projection. Add a regression proving disconnected status does not open/read the graph artifact. Keep the current exact-head review stale until this same Claude-owned branch is updated and re-audited.

…1029)

connection.status() no longer runs the read_graph-backed search_readiness
for a disconnected connection; it reports a stable not_checked projection
with reason "disconnected". Regression proves the graph is not read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Before the fresh exact-head audit, merge current origin/main into this same Claude-owned branch so the PR is up to date with merged #1030/#1024. Resolve no unrelated code and rerun the focused Graphify tests after the merge.

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Fix round for the P3 follow-up (performance: disconnected status() read the graph).

Head: 0d1973547329a59bd68706e1822100bb3fdfacc6, PR #1031

  • context_graph_connection.status() now runs search_readiness() (the read_graph-backed check) only when the connection is verified. A disconnected connection returns the stable projection {"schema": "code_mower.contextGraphSearchReadiness.v1", "search": "unavailable", "reader": "not_checked", "reason": "disconnected"} from the new context_graph_query.reader_not_checked(). search_readiness uses the same helper for its unusable-generation branch. search/authorization stay unavailable, and the lifecycle graph verdict is still reported beside it.
  • Regression SearchReadinessTests.test_a_disconnected_status_does_not_read_the_graph: it wraps query.read_graph and checks one call while the connection is verified, then zero calls after disconnect. It also asserts the exact projection and that nothing private leaks.
  • Docs: added the not_checked / reason: disconnected row to the readiness table in docs/context-graph-lifecycle.md.

Tests: a local Python run was blocked in this lane session (interpreter guard hook). Code Mower CI run 35310551868 on this head passed: package_matrix 3.12/3.13/3.14, package, graph containment on Linux and macOS, and board qualification.

Remaining: a Codex re-audit of this head (needs-codex-audit re-applied).

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Exact-head independent review of 0d1973547329a59bd68706e1822100bb3fdfacc6 found two P2 issues. Please fix both on this same Claude-owned branch, add isolated regressions, update docs where necessary, merge current origin/main, rerun focused tests if the lane permits them, push, and re-request the Codex audit on the final head.

  1. Ambiguity-only answers are labeled complete. In src/code_mower/context_graph_query.py near the packet/query completeness calculation, completeness becomes partial only for truncated or provider_partial. A query whose sole omission is unresolved_entities therefore reports packet and query completeness as complete, even though the added contract says unresolved/ambiguous traversal is a partial but usable answer. The current combined regression masks this because it also has truncation. Include unresolved_entities in the completeness predicate and add an isolated ambiguity-only regression asserting: search remains available, dependent work remains usable, generation completeness remains complete, packet/query completeness is partial, and the only applicable omission is unresolved_entities.

  2. Reviewed provider identity is compared by version only. Near the READER_PROVIDER_VERSIONS test, provider.get("version") alone treats any distribution at 0.9.58 as the reviewed provider. The pin accepts and verifies an exact distribution identifier, so other-provider==0.9.58 must not inherit the trust decision for graphifyy==0.9.58. Compare a normalized full provider requirement (distribution plus version) and add same-version/wrong-distribution coverage. An unknown type from that unreviewed provider must produce the bounded reader_incompatible rebuild-or-upgrade action without leaking the type spelling, graph contents, or paths.

Read-only verification otherwise found the doc_ref path, bounded actual-reader readiness, unknown-type fail-closed behavior, privacy, and disconnected no-read regression sound. The focused exact-head query/connection/lifecycle suite passed 450 tests with one skip before these findings.

@github-actions

Copy link
Copy Markdown

Codex audit unavailable

Head SHA: 0d1973547329a59bd68706e1822100bb3fdfacc6
Verdict: UNKNOWN
No merge-authority verdict was published. The local artifact was quarantined, stale, or inconclusive. Check the local runner and requeue this audit.

…er requirement (#1029)

A query whose only omission is unresolved_entities now reports packet and
query completeness as partial. The reviewed-provider check compares the
normalized distribution and version together, so another distribution at
0.9.58 gets the rebuild-or-upgrade remediation instead of the reviewed
release's fail-closed verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Fix round for the two P2 findings from the exact-head review of 0d19735.

Head: e6b623a57e6f93e1470269280932163ff41d514f, PR #1031

  1. Ambiguity-only answers are now partial. build_packet now counts unresolved_entities in the packet/query completeness predicate, alongside truncation and provider_partial. New isolated regression: SearchReadinessTests.test_an_ambiguity_only_answer_is_partial_but_usable. It uses three callers with one AMBIGUOUS edge and the default policy, so no budget, depth or document limit applies. It asserts:
    • search and authorization stay available
    • dependent_work stays usable
    • generation_completeness stays complete
    • packet, summary and query_completeness are partial
    • truncated is false
    • omissions == ["unresolved_entities"] on both the packet and the summary
  2. The reviewed provider is matched on its full requirement. READER_PROVIDER_VERSIONS (version only) is replaced by READER_PROVIDERS = {"graphifyy==0.9.58"}. The new _reviewed_provider() compares the normalized distribution (the lifecycle's _normalized_distribution) and the version together, and fails closed on non-string fields. The remediation key is now reader_providers. New regressions:
    • test_a_same_version_provider_from_another_distribution_is_not_reviewed: an other-provider==0.9.58 generation with an unknown node type reports reader_incompatible with the rebuild-or-upgrade action. That holds for readiness, status (exit 1), connection-status and the query. The report has no required_code_mower/node_type, and no type spelling, graph content or paths (assert_private plus an explicit hologram check).
    • test_the_reviewed_provider_is_matched_by_its_normalized_requirement: covers the normalized spelling, a wrong version, a wrong distribution at the same version, a lookalike name and a missing distribution.
  3. Docs: context-graph-queries.md (what "reviewed" means; unresolved_entities alone makes query_completeness partial), graphify-setup.md troubleshooting and the CHANGELOG Unreleased entry.

Merge with main: origin/main has no commits missing from this branch (the 3877a2e merge already includes #1030/#1024), so no new merge commit was needed.

Tests: local Python runs were blocked again in this lane session (the interpreter guard hook, and explicit-interpreter invocations need approval). The executed evidence is Code Mower CI run 35311612762 on e6b623a, which passed: package_matrix 3.12/3.13/3.14, package, graph containment on Linux and macOS, and board qualification.

Remaining: a Codex re-audit of this head (needs-codex-audit re-applied).

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Exact-head review evidence and owner gate decision

Exact head: e6b623a57e6f93e1470269280932163ff41d514f

  • Independent Codex audit completed PASS with P0=0, P1=0, P2=0, P3=0. Source run: https://github.com/codemower-ai/code-mower/actions/runs/35311619064. The local immutable verdict artifact binds both start and end SHA to this exact head.
  • Full CI passed on this head: https://github.com/codemower-ai/code-mower/actions/runs/35311612762 (Python 3.12/3.13/3.14, package, Linux/macOS graph containment, and Board qualification).
  • Independent installed-dependency test environment ran the focused test_context_graph_* suite from an exact-head archive: 453 tests passed, 1 skipped.
  • The two P2 findings on the prior head were fixed with isolated regressions: ambiguity-only answers are partial-but-usable, and provider review matches normalized distribution plus version. The disconnected no-read regression is also present.

The audit publisher did not write the normal verdict comment after the model verdict completed, consistent with tracked nonblocking publisher issue #1032. I cancelled the stalled source job after preserving the exact-head PASS artifact to free the self-hosted runner. Applying the documented owner gate override to this PR only; this does not weaken or replace the exact-head review, CI, or test evidence above.

@jeffhuber jeffhuber added the gate:override Code Mower generated label label Sep 18, 2026
@jeffhuber
jeffhuber merged commit ea2b775 into main Sep 18, 2026
20 checks passed
@jeffhuber
jeffhuber deleted the claude/1029-graphify-search-readiness branch September 18, 2026 05:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

builder:claude Code Mower generated label gate:override Code Mower generated label needs-codex-audit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Graphify: validate query-reader compatibility before reporting search available

1 participant