Why
q2 currently maintains its own GraphSnapshot, GraphNode, GraphEdge, GraphHealth, and NarsInference data structures, plus its own run_nars_deduction(min_confidence, max_hops) (2-hop traversal with f = f1*f2, c = f1*f2*c1*c2) inside crates/cockpit-server/src/graph_engine.rs.
This violates Architectural Commitment #4 in MedCare-rs/CLAUDE.md — "thinking lives only in lance-graph". The local NARS deduction is a duplicate-thinking trap: a temporary local impl that becomes permanent the moment any code path falls back to it.
When A1 (lance-graph#331) lands lance_graph::TripletGraph upstream, q2's local impl must be deleted in the same commit as the switch to upstream — no fallback branch, no parallel impl path, no "we'll clean it up later" PR. The "temporary local impl that becomes permanent" pattern is the highest-debt risk on the Phase 1 critical path.
Deletion atomicity is the contract: either the spine wiring lands and the local impl is gone, or neither lands. A PR that introduces the upstream call while leaving the local NARS code in-tree (even behind a cfg or env flag) is a regression of this issue, not a partial fix.
What
In a single atomic commit in AdaWorldAPI/q2:
File: crates/cockpit-server/src/graph_engine.rs
Structs to delete (replace with re-exports from lance_graph where shape matches, otherwise inline lance_graph::TripletGraph directly):
GraphSnapshot (lines ~30-41: nodes, edges, node_count, edge_count, scene_version, scene_name, health, nars_inferences)
GraphNode (lines ~43-…)
GraphEdge
GraphHealth — health endpoint must report spine state, not local cache state
NarsInference
Function to delete:
run_nars_deduction(min_confidence: f32, max_hops: usize) -> Vec (lines ~196-247) — entire 2-hop body with f = f1*f2, c = f1*f2*c1*c2 is removed; deduction is delegated to lance_graph::TripletGraph calls.
File: crates/cockpit-server/src/main.rs
Fallback branches to delete:
- Line ~154-156:
tracing::warn!(" /api/graph/* → fallback mode (hydration failed: {e})")
- Line ~159:
tracing::warn!(" ... → fallback mode (AIWAR_DATA_PATH not set)")
After this commit, there is no fallback path. If the spine fails to initialize, the server fails to start (or the /api/graph/health endpoint reports unhealthy and the inference/snapshot routes return 503) — but the binary never silently degrades to a local NARS impl.
Architecture
The double-buffer pattern stays, but it wraps lance_graph::TripletGraph directly — not a q2-local newtype:
static LIVE_GRAPH: LazyLock>> = ...;
Do NOT introduce a wrapper newtype "for serde convenience" or "to add q2-specific helpers". Wrappers around upstream graph types are half-twins that drift — within two PRs the wrapper accretes methods, the upstream type evolves, and the wrapper becomes a second source of truth. If lance_graph::TripletGraph doesn't serialize the way q2's HTTP responses need, the fix is upstream (a serde feature on lance-graph), not a q2 wrapper.
Health endpoint contract:
GET /api/graph/health reports the spine's state (last successful TripletGraph load, spine version, triplet count from TripletGraph::len(), last inference latency from spine).
- It does not report a local cache count, local hydration status, or any q2-internal field.
Routes preserved (shape-stable — same JSON schema, same status codes, same headers):
GET /api/graph/snapshot → graph_engine::graph_snapshot_handler
POST /api/graph/infer → graph_engine::nars_infer_handler
GET /api/graph/health → graph_engine::graph_health_handler
Existing API consumers must not observe a behavioral change beyond "data is now sourced from the spine" — same field names, same types, same ordering.
Acceptance criteria
Out of scope
- candle/burn integration — that's E1/E2, separate critical path.
- Cypher last-mile — that's A2, and it lives in
lance-graph, not q2.
- Refactoring of
hydrate_from_aiwar_json beyond what's needed to feed the spine (full hydration pipeline rework is a separate ticket).
- Changes to top-level
/health (line ~944 in main.rs) — that's a different handler.
Dependencies
Blocks on: AdaWorldAPI/lance-graph#331 (A1 — lance_graph::TripletGraph public API land).
This issue cannot start until A1's TripletGraph is published with a stable signature; once A1 lands, this work is unblocked and must complete in a single atomic commit as described.
Why
q2 currently maintains its own
GraphSnapshot,GraphNode,GraphEdge,GraphHealth, andNarsInferencedata structures, plus its ownrun_nars_deduction(min_confidence, max_hops)(2-hop traversal withf = f1*f2,c = f1*f2*c1*c2) insidecrates/cockpit-server/src/graph_engine.rs.This violates Architectural Commitment #4 in
MedCare-rs/CLAUDE.md— "thinking lives only in lance-graph". The local NARS deduction is a duplicate-thinking trap: a temporary local impl that becomes permanent the moment any code path falls back to it.When A1 (lance-graph#331) lands
lance_graph::TripletGraphupstream, q2's local impl must be deleted in the same commit as the switch to upstream — no fallback branch, no parallel impl path, no "we'll clean it up later" PR. The "temporary local impl that becomes permanent" pattern is the highest-debt risk on the Phase 1 critical path.Deletion atomicity is the contract: either the spine wiring lands and the local impl is gone, or neither lands. A PR that introduces the upstream call while leaving the local NARS code in-tree (even behind a
cfgor env flag) is a regression of this issue, not a partial fix.What
In a single atomic commit in
AdaWorldAPI/q2:File:
crates/cockpit-server/src/graph_engine.rsStructs to delete (replace with re-exports from
lance_graphwhere shape matches, otherwise inlinelance_graph::TripletGraphdirectly):GraphSnapshot(lines ~30-41:nodes,edges,node_count,edge_count,scene_version,scene_name,health,nars_inferences)GraphNode(lines ~43-…)GraphEdgeGraphHealth— health endpoint must report spine state, not local cache stateNarsInferenceFunction to delete:
run_nars_deduction(min_confidence: f32, max_hops: usize) -> Vec(lines ~196-247) — entire 2-hop body withf = f1*f2,c = f1*f2*c1*c2is removed; deduction is delegated tolance_graph::TripletGraphcalls.File:
crates/cockpit-server/src/main.rsFallback branches to delete:
tracing::warn!(" /api/graph/* → fallback mode (hydration failed: {e})")tracing::warn!(" ... → fallback mode (AIWAR_DATA_PATH not set)")After this commit, there is no fallback path. If the spine fails to initialize, the server fails to start (or the
/api/graph/healthendpoint reportsunhealthyand the inference/snapshot routes return 503) — but the binary never silently degrades to a local NARS impl.Architecture
The double-buffer pattern stays, but it wraps
lance_graph::TripletGraphdirectly — not a q2-local newtype:Do NOT introduce a wrapper newtype "for serde convenience" or "to add q2-specific helpers". Wrappers around upstream graph types are half-twins that drift — within two PRs the wrapper accretes methods, the upstream type evolves, and the wrapper becomes a second source of truth. If
lance_graph::TripletGraphdoesn't serialize the way q2's HTTP responses need, the fix is upstream (a serde feature on lance-graph), not a q2 wrapper.Health endpoint contract:
GET /api/graph/healthreports the spine's state (last successfulTripletGraphload, spine version, triplet count fromTripletGraph::len(), last inference latency from spine).Routes preserved (shape-stable — same JSON schema, same status codes, same headers):
GET /api/graph/snapshot→graph_engine::graph_snapshot_handlerPOST /api/graph/infer→graph_engine::nars_infer_handlerGET /api/graph/health→graph_engine::graph_health_handlerExisting API consumers must not observe a behavioral change beyond "data is now sourced from the spine" — same field names, same types, same ordering.
Acceptance criteria
lance_graph::TripletGraphare in one commit, not split across two.grep -ri "fallback" crates/cockpit-server/src/returns no graph-engine matches; nocfg(feature = "local-nars"), no env-flagged local impl, no commented-out local code./api/graph/healthbody fields are sourced fromTripletGraphstate; no q2-localGraphHealthstruct exists ingraph_engine.rs./api/graph/snapshot,/api/graph/infer,/api/graph/healthreturn identical JSON shape (golden-file or schema test) before vs after the commit.lance_graph::TripletGraph—LIVE_GRAPHholds the upstream type directly.run_nars_deductionsymbol removed from the crate (cargo doc/rg "fn run_nars_deduction"returns zero hits).cargo build -p cockpit-serversucceeds against the upstreamlance-graphrevision that lands A1.Out of scope
lance-graph, not q2.hydrate_from_aiwar_jsonbeyond what's needed to feed the spine (full hydration pipeline rework is a separate ticket)./health(line ~944 inmain.rs) — that's a different handler.Dependencies
Blocks on:
AdaWorldAPI/lance-graph#331(A1 —lance_graph::TripletGraphpublic API land).This issue cannot start until A1's
TripletGraphis published with a stable signature; once A1 lands, this work is unblocked and must complete in a single atomic commit as described.