Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions .claude/board/EPIPHANIES.md

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions crates/lance-graph-contract/src/hhtl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,35 @@ impl NiblePath {
})
}

/// The depth of the **longest common prefix** with `other` — the radix-trie
/// nearest-neighbor measure. Larger ⇒ the two paths share more cascade tiers
/// ⇒ they sit in the same deeper CLAM cluster ⇒ they are nearer.
///
/// This is the operational form of `panCAKES ≡ radix trie ≡ HHTL`
/// (`E-PANCAKES-IS-RADIX-IS-HHTL`): CAKES nearest-neighbor over the cluster
/// tree is *longest-common-prefix ranking* over the HHTL nibble paths — no
/// separate tree to build, the keys ARE the tree. Pure prefix arithmetic on
/// the key; never touches the value slab.
#[must_use]
pub const fn common_prefix_depth(self, other: Self) -> u8 {
let max = if self.depth < other.depth {
self.depth
} else {
other.depth
};
let mut d = 0u8;
// Walk depth-by-depth while the aligned prefixes agree. `prefix(d)` is
// `Some` for every d ≤ depth, so the unwraps below cannot fail.
while d < max {
let next = d + 1;
match (self.prefix(next), other.prefix(next)) {
(Some(a), Some(b)) if a.path == b.path && a.depth == b.depth => d = next,
_ => break,
}
}
d
}

/// Lower a [`NodeGuid`](crate::canonical_node::NodeGuid) prefix to a 16-nibble
/// `NiblePath`, the routing-path counterpart of the GUID's
/// `classid · HEEL · HIP · TWIG` cascade (identity-architecture v1 §3).
Expand Down Expand Up @@ -456,6 +485,24 @@ mod tests {
);
}

#[test]
fn common_prefix_depth_is_the_radix_nn_measure() {
let a = NiblePath::root(1).child(2).child(3);
let b = NiblePath::root(1).child(2).child(4);
let c = NiblePath::root(1).child(2);
let d = NiblePath::root(9);
assert_eq!(a.common_prefix_depth(a), 3, "self ⇒ full depth");
assert_eq!(a.common_prefix_depth(b), 2, "1·2 shared, leaf differs");
assert_eq!(a.common_prefix_depth(c), 2, "ancestor ⇒ min depth");
assert_eq!(a.common_prefix_depth(d), 0, "different basin ⇒ 0");
assert_eq!(
a.common_prefix_depth(b),
b.common_prefix_depth(a),
"symmetric"
);
assert_eq!(NiblePath::EMPTY.common_prefix_depth(a), 0);
}

#[test]
fn is_ancestor_of_is_cheap_prefix_reachability() {
let mammal = NiblePath::root(0x0).child(0x3); // Endurant → …mammal
Expand Down
37 changes: 37 additions & 0 deletions crates/lance-graph-contract/src/soa_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,43 @@ pub trait MailboxSoaView {
None
}

/// The HHTL routing path ([`NiblePath`](crate::hhtl::NiblePath)) of `row`'s
/// GUID key — the `classid·HEEL·HIP·TWIG` cascade lowered to a nibble path.
/// This is the **radix-trie / CLAM cluster address** of the node
/// (`panCAKES ≡ radix trie ≡ HHTL`): containment = `is_ancestor_of`,
/// CAKES nearest = `common_prefix_depth`, both pure key arithmetic, **zero
/// value decode**.
///
/// **Default = `None` (zero-fallback, deferred binding)** — same discipline as
/// [`row_for_local_key`](MailboxSoaView::row_for_local_key): a view that has
/// not materialized a per-row key/HHTL column returns `None`, and a CLAM/CAKES
/// scan over it yields nothing (the consumer falls back to a coarser facet).
/// An owner that carries the GUID key per row overrides this (the canon
/// `NodeRow` already holds `key(16)`, so the override exposes what is there).
#[inline]
fn hhtl_path_at(&self, _row: usize) -> Option<crate::hhtl::NiblePath> {
None
}

/// The 16-byte [`EdgeBlock`](crate::canonical_node::EdgeBlock) of `row` — the
/// node's **explicit typed edges** (12 in-family + 4 out-of-family one-byte
/// slots), bytes 16..32 of the canonical `NodeRow`. This is the edge region,
/// **NOT the value slab** (32..512), so reading it is **zero value decode**.
///
/// How the 16 bytes are *interpreted* is the class's
/// [`EdgeCodecFlavor`](crate::canonical_node::EdgeCodecFlavor)
/// (`CoarseOnly` = 12-family/4-external adjacency, `Pq32x4` = 32×4 turbovec
/// residue) — resolved `classid → ClassView`, never guessed by the query
/// (`E-ADJACENCY-IS-KEY-AND-EDGECODEC`).
///
/// **Default = `None` (zero-fallback, deferred binding)** — a view that has not
/// materialized the edge region returns `None`; an owner that carries the
/// canonical `NodeRow` (which holds `edges(16)`) overrides this.
#[inline]
fn edge_block_at(&self, _row: usize) -> Option<crate::canonical_node::EdgeBlock> {
None
}

// NOTE (follow-up): the qualia column (`QualiaI4_16D`) accessor is intentionally omitted —
// add `fn qualia(&self) -> &[crate::qualia::QualiaI4_16D]` when the first consumer
// (planner strategy selection) needs it; keep the read surface minimal until then.
Expand Down
9 changes: 9 additions & 0 deletions crates/lance-graph/src/graph/graph_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ pub enum Backend {
Blasgraph,
/// Palette-accelerated traversal (Entry 2: bgz17 hot path).
Palette,
/// MailboxSoA traversal (Entry 3, `cypher-kanban-ast-unification-v1` Inc 0):
/// a Cypher `MATCH` routed over the canonical GUID-keyed substrate via the
/// zero-dep `MailboxSoaView` contract — classid prefix-route for node match,
/// `local_key`→row for point lookup. Edge-slot traversal is deferred until the
/// edge-representation boundary is pinned (classid-resolved `EdgeBlock`
/// adjacency vs `CausalEdge64` SPO — they are NOT interchangeable; see
/// `graph::mailbox_scan` and the plan's verdict §4b). See
/// [`crate::graph::mailbox_scan`].
MailboxSoa,
}

/// Classification of a query for routing purposes.
Expand Down
Loading
Loading