Worker: A1 (ensemble: ontology spine, Phase 1 entry point)
Mnemonic IDs blocked by this: B1, B2, B3, B4, C1
Why
lance-graph is the obligatory spine (CLAUDE.md, line 4: "The obligatory spine — query engine, codec stack, semantic transformer, and orchestration contract"). The AriGraph tissue (TripletGraph + EpisodicMemory + GraphSensorium + NARS deduction) is the canonical knowledge representation of the spine — but today it lives in crates/lance-graph/src/graph/arigraph/ (the implementation crate), not in crates/lance-graph-contract/ (the contract crate that downstream consumers depend on).
Consequence: downstream consumers (q2, MedCare-rs, smb-office, lance-graph-callcenter) cannot speak the spine's native language without depending on the heavy lance-graph crate. Today this manifests concretely as q2 carrying its own duplicate NARS implementation — the "duplicate-thinking trap" called out in MedCare-rs CLAUDE.md, Architectural Commitment #4: "thinking only in lance-graph."
The contract crate already speaks about arigraph (contract::sensorium::GraphSignals is documented as "Produced by arigraph::sensorium::GraphSensorium::from_graph()") — but the producer types themselves are not on the contract surface. This issue closes that gap.
What
Promote the AriGraph tissue to the contract surface as canonical types and define a Spine trait that downstream crates implement in their own crates.
Concrete moves / re-exports into lance-graph-contract
Confirmed canonical names from crates/lance-graph/src/graph/arigraph/:
| Type |
Currently lives in |
Proposed contract module |
Triplet |
lance-graph::graph::arigraph::triplet_graph |
lance_graph_contract::triplet |
TripletGraph |
lance-graph::graph::arigraph::triplet_graph |
lance_graph_contract::triplet |
Episode |
lance-graph::graph::arigraph::episodic |
lance_graph_contract::episodic |
EpisodicMemory |
lance-graph::graph::arigraph::episodic |
lance_graph_contract::episodic |
GraphSensorium (concrete) |
lance-graph::graph::arigraph::sensorium |
merge into contract::sensorium alongside existing GraphSignals DTO |
NodeId, EdgeRef |
(TBD — likely synthesized; see Open Q below) |
lance_graph_contract::triplet |
Two viable shapes (pick one in design pass, not blocking):
- Full move — types live in contract;
lance-graph re-exports for compat.
- Mirror + From/Into — contract owns wire-stable DTOs;
lance-graph keeps richer impls and round-trips via From/Into. Aligns better with zero-dep contract goal in lance-graph-contract/src/lib.rs.
Define Spine trait
// lance_graph_contract::spine
pub trait Spine {
/// Project this tissue's facts into the canonical TripletGraph spine.
/// Lossless for facts the spine knows how to represent.
fn project_into(&self, target: &mut TripletGraph) -> Result<(), SpineError>;
/// Round-trip self-check: project_into + reconstruct must equal self
/// for the subset of state the contract cares about.
fn verify_roundtrip(&self) -> Result<(), SpineError>;
}
Minimum surface — only those two methods. Per-flesh extensions (e.g. clinical hooks, billing hooks, transcode hooks) are added in the impl crates, not the contract.
SoA DTO surface
Triplet shape is the canonical read shape. Columnar materialization (Arrow RecordBatch-aligned, zero-copy) is the canonical wire. Specifically:
- Default DTO is SoA columnar (subject column, predicate column, object column, truth-frequency column, truth-confidence column, timestamp column).
- No
Vec AoS on the contract surface for bulk reads — that's a perf regression and breaks Arrow zero-copy.
Triplet (struct, AoS) is kept as the single-fact convenience shape; bulk operations go through SoA.
Architecture
┌─── lance-graph-contract ───────────────────────┐
│ TripletGraph, Triplet, NodeId, EdgeRef │
│ EpisodicMemory, Episode │
│ GraphSensorium (concrete) + GraphSignals (DTO)│
│ Spine trait { project_into, verify_roundtrip }│
└──┬──────────────┬──────────────┬───────────────┘
│ │ │
impl Spine for impl Spine for impl Spine for
MedCareSpine SmbOfficeSpine CallcenterSpine
(medcare-rs) (smb-office) (lance-graph-callcenter)
— clinical flesh — invoicing flesh — Supabase realtime flesh
│ │ │
└──────────────┴──────────────┘
│
┌────────▼─────────┐
│ medcare-bridge │ ← B4 (registry+orchestrator,
│ (unified bridge) │ NOT a god crate; contract
└──────────────────┘ only knows Spine impls exist)
The contract knows that Spine impls exist and round-trip. It does not know what flesh is attached. This is the inversion that unblocks every downstream consumer:
- Inner ontology (medcare-rs clinical, smb-office invoicing) →
impl Spine in their own crates.
- Outer ontology (lance-graph-callcenter Supabase realtime transcode) →
impl Spine in its own crate.
- Unified bridge (B4) → registry+orchestrator over
Box<dyn Spine>, not a monolith.
Acceptance criteria
Out of scope
- Actual q2 refactor to drop its local NARS impl → tracked as B1.
impl Spine for MedCareSpine (clinical flesh) → tracked as B2.
impl Spine for SmbOfficeSpine / CallcenterSpine (invoicing / transcode flesh) → tracked as B3.
- Unified bridge crate (
medcare-bridge registry+orchestrator) → tracked as B4.
- Per-consumer wire format / transport (gRPC, JSON, Arrow Flight) → tracked as C1.
Dependencies
- Blocks: B1 (q2 NARS deletion), B2 (medcare Spine impl), B3 (smb-office + callcenter Spine impls), B4 (unified bridge), C1 (wire transport).
- Blocked by: nothing in this ensemble — Phase 1 entry point.
Open questions for reviewers
- NodeId / EdgeRef — these names appear in the architectural brief but I did not find them as free-standing types in
arigraph::triplet_graph (entities are addressed by String name; indices into triplets: Vec<Triplet> serve as EdgeRef-equivalent). Reviewers: should we synthesize first-class NodeId(Fingerprint?)/EdgeRef types as part of this promotion, or leave the contract surface using String + usize as today and revisit in B-phase?
- Full move vs mirror+From/Into —
lance-graph-contract/src/lib.rs advertises itself as "zero-dependency trait crate." A full move of TripletGraph (HashMap-heavy concrete struct) into the contract drags std::collections into the contract, which is fine, but also means every consumer compiles the concrete data structure. Mirror+From/Into keeps contract slim. Recommend the mirror route — but call this out for explicit decision.
- Sensorium overlap —
contract::sensorium::GraphSignals already exists (normalized DTO). Concrete arigraph::sensorium::GraphSensorium has the same fields plus raw counts (active_triplets, total_entities, contradictions). Promote the concrete one and deprecate GraphSignals, or keep both with a From<GraphSensorium> for GraphSignals? Recommend the From impl path.
- Scope of
Spine::project_into semantics — the brief specifies signature only. What does "project" mean for a MedCareSpine whose flesh is mostly not triplets (e.g. structured FHIR observations)? Proposal: project means "emit the triplet view of self" — flesh chooses what becomes a triplet. Reviewers should confirm this informal semantics is enough for the contract surface, or if we need a stricter formal spec.
Worker: A1 (ensemble: ontology spine, Phase 1 entry point)
Mnemonic IDs blocked by this: B1, B2, B3, B4, C1
Why
lance-graphis the obligatory spine (CLAUDE.md, line 4: "The obligatory spine — query engine, codec stack, semantic transformer, and orchestration contract"). The AriGraph tissue (TripletGraph+EpisodicMemory+GraphSensorium+ NARS deduction) is the canonical knowledge representation of the spine — but today it lives incrates/lance-graph/src/graph/arigraph/(the implementation crate), not incrates/lance-graph-contract/(the contract crate that downstream consumers depend on).Consequence: downstream consumers (q2, MedCare-rs, smb-office, lance-graph-callcenter) cannot speak the spine's native language without depending on the heavy
lance-graphcrate. Today this manifests concretely as q2 carrying its own duplicate NARS implementation — the "duplicate-thinking trap" called out in MedCare-rs CLAUDE.md, Architectural Commitment #4: "thinking only in lance-graph."The contract crate already speaks about arigraph (
contract::sensorium::GraphSignalsis documented as "Produced byarigraph::sensorium::GraphSensorium::from_graph()") — but the producer types themselves are not on the contract surface. This issue closes that gap.What
Promote the AriGraph tissue to the contract surface as canonical types and define a
Spinetrait that downstream crates implement in their own crates.Concrete moves / re-exports into
lance-graph-contractConfirmed canonical names from
crates/lance-graph/src/graph/arigraph/:Tripletlance-graph::graph::arigraph::triplet_graphlance_graph_contract::tripletTripletGraphlance-graph::graph::arigraph::triplet_graphlance_graph_contract::tripletEpisodelance-graph::graph::arigraph::episodiclance_graph_contract::episodicEpisodicMemorylance-graph::graph::arigraph::episodiclance_graph_contract::episodicGraphSensorium(concrete)lance-graph::graph::arigraph::sensoriumcontract::sensoriumalongside existingGraphSignalsDTONodeId,EdgeReflance_graph_contract::tripletTwo viable shapes (pick one in design pass, not blocking):
lance-graphre-exports for compat.lance-graphkeeps richer impls and round-trips viaFrom/Into. Aligns better with zero-dep contract goal inlance-graph-contract/src/lib.rs.Define
SpinetraitMinimum surface — only those two methods. Per-flesh extensions (e.g. clinical hooks, billing hooks, transcode hooks) are added in the impl crates, not the contract.
SoA DTO surface
Triplet shape is the canonical read shape. Columnar materialization (Arrow
RecordBatch-aligned, zero-copy) is the canonical wire. Specifically:VecAoS on the contract surface for bulk reads — that's a perf regression and breaks Arrow zero-copy.Triplet(struct, AoS) is kept as the single-fact convenience shape; bulk operations go through SoA.Architecture
The contract knows that
Spineimpls exist and round-trip. It does not know what flesh is attached. This is the inversion that unblocks every downstream consumer:impl Spinein their own crates.impl Spinein its own crate.Box<dyn Spine>, not a monolith.Acceptance criteria
cargo check -p lance-graph-contractpasses; the contract crate exportsTriplet,TripletGraph,Episode,EpisodicMemory,GraphSensorium,Spine.cargo check -p lance-graphpasses after the move/re-export; arigraph tissue continues to compile against the contract types (proves single source of truth).TripletGraphpopulated → projected into a freshTripletGraphviaSpine::project_into→ equality holds modulo documented lossy fields.verify_roundtripreturnsOk(())forTripletGraph's identity impl.Vec<Triplet>in any new bulk-read API.Spinewithout further contract changes.contract::sensorium::GraphSignalsdoc-comment "Produced byarigraph::sensorium::GraphSensorium::from_graph()" is rewritten to point at the now-contractGraphSensorium.Out of scope
impl Spine for MedCareSpine(clinical flesh) → tracked as B2.impl Spine for SmbOfficeSpine/CallcenterSpine(invoicing / transcode flesh) → tracked as B3.medcare-bridgeregistry+orchestrator) → tracked as B4.Dependencies
Open questions for reviewers
arigraph::triplet_graph(entities are addressed byStringname; indices intotriplets: Vec<Triplet>serve asEdgeRef-equivalent). Reviewers: should we synthesize first-classNodeId(Fingerprint?)/EdgeReftypes as part of this promotion, or leave the contract surface usingString+usizeas today and revisit in B-phase?lance-graph-contract/src/lib.rsadvertises itself as "zero-dependency trait crate." A full move ofTripletGraph(HashMap-heavy concrete struct) into the contract dragsstd::collectionsinto the contract, which is fine, but also means every consumer compiles the concrete data structure. Mirror+From/Into keeps contract slim. Recommend the mirror route — but call this out for explicit decision.contract::sensorium::GraphSignalsalready exists (normalized DTO). Concretearigraph::sensorium::GraphSensoriumhas the same fields plus raw counts (active_triplets,total_entities,contradictions). Promote the concrete one and deprecateGraphSignals, or keep both with aFrom<GraphSensorium> for GraphSignals? Recommend theFromimpl path.Spine::project_intosemantics — the brief specifies signature only. What does "project" mean for aMedCareSpinewhose flesh is mostly not triplets (e.g. structured FHIR observations)? Proposal: project means "emit the triplet view of self" — flesh chooses what becomes a triplet. Reviewers should confirm this informal semantics is enough for the contract surface, or if we need a stricter formal spec.