diff --git a/crates/cognitive-shader-driver/src/bindspace.rs b/crates/cognitive-shader-driver/src/bindspace.rs index a33a68f5..4362858a 100644 --- a/crates/cognitive-shader-driver/src/bindspace.rs +++ b/crates/cognitive-shader-driver/src/bindspace.rs @@ -144,6 +144,9 @@ pub struct BindSpace { pub meta: MetaColumn, pub temporal: Box<[u64]>, pub expert: Box<[u16]>, + /// Column H: per-row entity type binding (Foundry Object Type equivalent). + /// 0 = untyped. Non-zero = 1-based index into `Ontology.schemas`. + pub entity_type: Box<[u16]>, } impl BindSpace { @@ -157,6 +160,7 @@ impl BindSpace { meta: MetaColumn::zeros(len), temporal: vec![0u64; len].into_boxed_slice(), expert: vec![0u16; len].into_boxed_slice(), + entity_type: vec![0u16; len].into_boxed_slice(), } } @@ -169,7 +173,8 @@ impl BindSpace { let meta_bytes = self.len * 4; let temporal_bytes = self.len * 8; let expert_bytes = self.len * 2; - content_topic_angle + cycle_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes + let entity_type_bytes = self.len * 2; + content_topic_angle + cycle_bytes + edge_bytes + qualia_bytes + meta_bytes + temporal_bytes + expert_bytes + entity_type_bytes } /// Apply MetaFilter across a row window. Returns a dense Vec of row @@ -220,6 +225,20 @@ impl BindSpaceBuilder { qualia: &[f32; QUALIA_DIMS], temporal: u64, expert: u16, + ) -> Self { + self.push_typed(content, meta, edge, qualia, temporal, expert, 0) + } + + /// Push a row with explicit entity type (Column H). + pub fn push_typed( + mut self, + content: &[u64], + meta: MetaWord, + edge: u64, + qualia: &[f32; QUALIA_DIMS], + temporal: u64, + expert: u16, + entity_type: u16, ) -> Self { let row = self.cursor; self.bs.fingerprints.set_content(row, content); @@ -228,6 +247,7 @@ impl BindSpaceBuilder { self.bs.qualia.set(row, qualia); self.bs.temporal[row] = temporal; self.bs.expert[row] = expert; + self.bs.entity_type[row] = entity_type; self.cursor += 1; self } @@ -254,9 +274,9 @@ mod tests { #[test] fn bindspace_footprint_adds_columns() { let bs = BindSpace::zeros(1); - // 3 × 2048 (content/topic/angle) + 65536 (cycle f32) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert) - // = 6144 + 65536 + 8 + 72 + 4 + 8 + 2 = 71774 - assert_eq!(bs.byte_footprint(), 71774); + // 3 × 2048 (content/topic/angle) + 65536 (cycle f32) + 8 (edge) + 72 (qualia 18×4) + 4 (meta) + 8 (temporal) + 2 (expert) + 2 (entity_type) + // = 6144 + 65536 + 8 + 72 + 4 + 8 + 2 + 2 = 71776 + assert_eq!(bs.byte_footprint(), 71776); } #[test] @@ -298,6 +318,37 @@ mod tests { assert!(bs.fingerprints.cycle_row(0).iter().all(|&v| v == 0.0)); } + #[test] + fn entity_type_defaults_to_untyped() { + let bs = BindSpace::zeros(4); + for row in 0..4 { + assert_eq!(bs.entity_type[row], 0, "default should be untyped (0)"); + } + } + + #[test] + fn entity_type_set_and_get() { + let mut bs = BindSpace::zeros(4); + bs.entity_type[1] = 42; + bs.entity_type[3] = 7; + assert_eq!(bs.entity_type[0], 0); + assert_eq!(bs.entity_type[1], 42); + assert_eq!(bs.entity_type[2], 0); + assert_eq!(bs.entity_type[3], 7); + } + + #[test] + fn builder_push_typed_sets_entity_type() { + let qualia = [0.0f32; QUALIA_DIMS]; + let content = [0u64; WORDS_PER_FP]; + let bs = BindSpaceBuilder::new(2) + .push_typed(&content, MetaWord::new(1, 0, 100, 100, 0), 0, &qualia, 0, 0, 5) + .push(&content, MetaWord::new(2, 0, 200, 200, 0), 0, &qualia, 0, 0) + .build(); + assert_eq!(bs.entity_type[0], 5, "push_typed should set entity_type"); + assert_eq!(bs.entity_type[1], 0, "push should default to 0"); + } + #[test] fn set_cycle_direct_f32() { let mut bs = BindSpace::zeros(2); diff --git a/crates/lance-graph-contract/src/ontology.rs b/crates/lance-graph-contract/src/ontology.rs index c6db3083..d76396b1 100644 --- a/crates/lance-graph-contract/src/ontology.rs +++ b/crates/lance-graph-contract/src/ontology.rs @@ -21,6 +21,27 @@ use crate::property::{ }; use crate::cam::CodecRoute; +// ═══════════════════════════════════════════════════════════════════════════ +// EntityTypeId — Foundry Object Type equivalent (Column H in BindSpace SoA) +// ═══════════════════════════════════════════════════════════════════════════ + +/// Numeric entity type identifier for per-row BindSpace typing. +/// 0 = untyped. Non-zero = index into `Ontology.schemas` (1-based). +/// +/// This is the Palantir Vertex "Object Type" equivalent. Every row in +/// BindSpace can be typed, enabling Object Explorer scrolling, property +/// view selection (LF-22 ObjectView), and type-filtered search (LF-40). +pub type EntityTypeId = u16; + +/// Look up the EntityTypeId for a named entity type within an Ontology. +/// Returns 0 if the name doesn't match any schema. +pub fn entity_type_id(ontology: &Ontology, name: &str) -> EntityTypeId { + ontology.schemas.iter() + .position(|s| s.name == name) + .map(|idx| (idx + 1) as EntityTypeId) + .unwrap_or(0) +} + // ═══════════════════════════════════════════════════════════════════════════ // Ontology — the composed object model // ═══════════════════════════════════════════════════════════════════════════ @@ -371,4 +392,23 @@ mod tests { assert!(PrefetchDepth::Detail < PrefetchDepth::Similar); assert!(PrefetchDepth::Similar < PrefetchDepth::Full); } + + #[test] + fn entity_type_id_returns_1_based_index() { + use crate::property::Schema; + let ont = Ontology { + name: "test", + schemas: vec![ + Schema::builder("Customer").build(), + Schema::builder("Invoice").build(), + Schema::builder("Product").build(), + ], + links: Vec::new(), + actions: Vec::new(), + }; + assert_eq!(entity_type_id(&ont, "Customer"), 1); + assert_eq!(entity_type_id(&ont, "Invoice"), 2); + assert_eq!(entity_type_id(&ont, "Product"), 3); + assert_eq!(entity_type_id(&ont, "Unknown"), 0); + } }