fix(serve): MCP orientation refreshes the strict hook's freshness stamp - #3042
fix(serve): MCP orientation refreshes the strict hook's freshness stamp#3042Azeem1985 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 2 advisory finding(s) below merit a look before merge.
Formal verification. No changes could be formally verified in this run.
Graphify review — findings
Makes the MCP orientation tools refresh the strict read hook's "recently oriented" stamp, matching the CLI, so an agent that consults the graph via query_graph, shortest_path, get_node, or get_neighbors isn't treated as blind and denied its next raw file read. The stamp is written after a successful call next to the graph that actually answered — so a project_path query stamps that project's graphify-out/cache — while browsing tools (graph_stats, god_nodes, get_community), PR tools, and failed calls stamp nothing. Stamping is fail-silent, so a stamp write failure never breaks the tool result.
Worth a look
- Post-call stamp reads shared active_graph_path after another concurrent tool call may have rebound it —
graphify/serve.py:2068· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
- Orientation stamp uses shared active_graph_path after tool execution —
graphify/serve.py:2072· 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 — 686 functions depend on the 361 functions this change touches.
Health — this change adds coupling hotspots:
- new:
dispatch_command()— 2 callers, 122 callees - new:
_query_graph_text()— 20 callers, 9 callees - new:
_score_query()— 15 callers, 5 callees - new:
_query_terms()— 20 callers, 3 callees - new:
run_benchmark()— 16 callers, 3 callees - new:
_build_server()— 2 callers, 17 callees - new:
_load_graph()— 9 callers, 3 callees - new:
_query_subgraph_tokens()— 7 callers, 3 callees - …and 14 more — each is listed as a finding
Verification — 686 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: 525 function(s) in the blast radius were not formally verified this run
Formal verification
Could not verify: Could not verify \_build\_server.
The verifier did not have enough to check \_build\_server, 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
· 5 grounded finding(s) anchored inline below; 17 more finding(s) on lines outside this diff (see the check run).
| os.utime(stamp, (1000, 1000)) | ||
|
|
||
|
|
||
| def test_mcp_query_graph_touches_orientation_stamp(tmp_path): |
There was a problem hiding this comment.
test_mcp_query_graph_touches_orientation_stamp()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ("shortest_path", {"source": "Alpha", "target": "Beta"}), | ||
| ], | ||
| ) | ||
| def test_mcp_orientation_tools_refresh_aged_stamp(tmp_path, tool, arguments): |
There was a problem hiding this comment.
test_mcp_orientation_tools_refresh_aged_stamp()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| ("get_community", {"community_id": 0}), | ||
| ], | ||
| ) | ||
| def test_mcp_non_orientation_tools_leave_stamp_alone(tmp_path, tool, arguments): |
There was a problem hiding this comment.
test_mcp_non_orientation_tools_leave_stamp_alone()
fans out to 7 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| assert stamp.stat().st_mtime == 1000, f"{tool} unexpectedly refreshed the stamp" | ||
|
|
||
|
|
||
| def test_mcp_project_path_query_stamps_that_project(tmp_path): |
There was a problem hiding this comment.
test_mcp_project_path_query_stamps_that_project()
fans out to 8 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
| assert not _stamp_path(default_graph).exists() | ||
|
|
||
|
|
||
| def test_mcp_failed_call_does_not_stamp(tmp_path): |
There was a problem hiding this comment.
test_mcp_failed_call_does_not_stamp()
fans out to 6 callees (efferent coupling).
Grounded coupling-delta finding (deterministic), not an LLM guess.
|
Thanks for the grounded review. The flagged fan-out is in the end-to-end test functions — each deliberately exercises the full server lifecycle (spawn, handshake, call, stat the stamp/cache) inline so the test reads as one narrative; happy to factor the shared steps into a fixture if maintainers prefer lower per-test fan-out. No production-code findings noted. — Azeem1985/TelB-io fork 🤖 Generated with Claude Code |
The strict read guard admits an agent only while the 'recently oriented' stamp (graphify-out/cache/last_query_stamp) is fresh. The CLI's query / path / explain each touch it (cli._touch_query_stamp), but their MCP twins never did — an agent that oriented through the server via query_graph was still treated as blind and had its first raw read denied. call_tool now touches the stamp after every successful call to one of the four orientation tools: query_graph (= query), shortest_path (= path), get_node + get_neighbors (the MCP split of explain). The stamp lands next to the graph that answered, so a project_path call stamps THAT project's graphify-out/cache — the graph whose reads the guard gates. Browsing tools (graph_stats, god_nodes, get_community) and the PR tools don't stamp on the CLI and still don't over MCP; a failed call stamps nothing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fd76abc to
4c3c76b
Compare
Fixes #3039.
The strict read guard admits an agent only while the "recently oriented" stamp (
graphify-out/cache/last_query_stamp) is fresh; the CLI'squery/path/explaineach touch it, but their MCP twins never did — so an agent that oriented through the server was still treated as blind and had its first raw read denied (measured on a live deployment; details in the issue).call_toolnow touches the stamp after every successful call to one of the four orientation tools, chosen by strict parity with the CLI's stamping commands:graphify queryquery_graphgraphify pathshortest_pathgraphify explainget_node+get_neighbors(explain's split)graphify god-nodes(no stamp)god_nodesgraph_stats,get_community, PR toolsTwo deliberate choices:
sys.exit(0), not a contract; a no-match answer is still a graph consult, and the guard's premise ("asked the graph first") holds.project_pathcall stamps THAT project'sgraphify-out/cache, the graph whose reads the guard gates. A failed call stamps nothing.The stamp writer is the CLI's own fail-silent
_touch_query_stamp(lazy import;cliimportsserveonly inside handlers, so no cycle).Tests: stamp created by
query_graph; each orientation tool refreshes an aged stamp (parametrized); browsing tools leave an aged stamp untouched;project_pathstamps the serving project, not the default; failed call stamps nothing. serve/serve_http/hook_strict families green on top of v8 @ 0.9.49; ruff clean.🤖 Generated with Claude Code