1use std::fmt;
2use std::sync::atomic::{AtomicU64, Ordering};
3use std::sync::Arc;
4
5use sha2::{Digest, Sha256};
6use xlog_core::{symbol, RelId, ScalarType, Schema, XlogError};
7
8use crate::launch::LaunchEnqueueError;
9use crate::memory::TrackedCudaSlice;
10use crate::provider::resident_schedule::{validate_execution_domain, ResidentExecutionDomain};
11use crate::semantic_transition::Identity256;
12use crate::{
13 CudaFunction, CudaKernelProvider, CudaStream, DeviceRepr, DriverError, LaunchAsync,
14 LaunchConfig,
15};
16
17const MODULE: &str = "xlog_semantic_hypergraph";
18const KERNEL: &str = "semantic_hypergraph_execute";
19const COMMAND_WORDS: usize = 32;
20const RECEIPT_WORDS: usize = 42;
21const HYPERGRAPH_ABI_GENERATION: u64 = 7;
23
24const CONTROL_WORDS: u64 = 16;
25const ROOT_WORDS: u64 = 16;
26const CANDIDATE_WORDS: u64 = 16;
27const STATEMENT_WORDS: u64 = 8;
28const SUPPORT_WORDS: u64 = 17;
29const VERSION_WORDS: u64 = 16;
30
31const OP_INITIALIZE: u64 = 1;
32const OP_FORK: u64 = 2;
33const OP_INSERT_SUPPORT: u64 = 3;
34const OP_DISCARD: u64 = 4;
35const OP_SEAL: u64 = 5;
36const OP_SNAPSHOT: u64 = 6;
37const OP_TRUTH: u64 = 7;
38const OP_INSPECT_STATEMENT: u64 = 8;
39const OP_INSPECT_SUPPORT: u64 = 9;
40const OP_INSPECT_VERSION: u64 = 10;
41#[cfg(test)]
42const OP_PREFLIGHT_TRANSITION: u64 = 11;
43
44const HOST_COMMAND_ADMISSION: u64 = 0;
45const RESIDENT_EMPTY_ROOT_HANDLE_ADMISSION: u64 = 1;
46const RESIDENT_FORK_ADMISSION: u64 = 2;
47const RESIDENT_INSERT_SUPPORT_ADMISSION: u64 = 3;
48const RESIDENT_FORK_TRUTH_ADMISSION: u64 = 4;
49const RESIDENT_SEAL_ADMISSION: u64 = 5;
50const RESIDENT_CONSUME_TRUTH_ADMISSION: u64 = 6;
51const RESIDENT_MATERIALIZE_DECODED_ADMISSION: u64 = 7;
52const RESIDENT_PREFLIGHT_TRANSITION_ADMISSION: u64 = 8;
53const RESIDENT_ROOT_TRUTH_ADMISSION: u64 = 9;
54
55const STATUS_OK: u64 = 0;
56const STATUS_FOREIGN_OWNER: u64 = 1;
57const STATUS_SLOT_OUT_OF_RANGE: u64 = 2;
58const STATUS_STALE_GENERATION: u64 = 3;
59const STATUS_INACTIVE: u64 = 4;
60const STATUS_NOT_REACHABLE: u64 = 5;
61const STATUS_STATEMENT_CAPACITY: u64 = 6;
62const STATUS_SUPPORT_CAPACITY: u64 = 7;
63const STATUS_VERSION_CAPACITY: u64 = 8;
64const STATUS_ROOT_CAPACITY: u64 = 9;
65const STATUS_GENERATION_EXHAUSTED: u64 = 10;
66const STATUS_CORRUPT_LINEAGE: u64 = 11;
67const STATUS_INVALID_COMMAND: u64 = 12;
68const STATUS_ARENA_MISMATCH: u64 = 13;
69
70const OUTCOME_INSERTED: u64 = 1;
71const OUTCOME_UNCHANGED: u64 = 2;
72const INSERT_NEW_STATEMENT: u64 = 1;
73const INSERT_PREVIOUS_TRUTH_SHIFT: u32 = 1;
74const INSERT_PREVIOUS_TRUTH_MASK: u64 = 3 << INSERT_PREVIOUS_TRUTH_SHIFT;
75
76static NEXT_OWNER_ID: AtomicU64 = AtomicU64::new(1);
77
78#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
80pub enum SemanticTruth {
81 Neither,
83 True,
85 False,
87 Both,
89}
90
91impl SemanticTruth {
92 pub const fn from_presence(pro: bool, contra: bool) -> Self {
94 match (pro, contra) {
95 (false, false) => Self::Neither,
96 (true, false) => Self::True,
97 (false, true) => Self::False,
98 (true, true) => Self::Both,
99 }
100 }
101
102 pub const fn presence(self) -> (bool, bool) {
104 match self {
105 Self::Neither => (false, false),
106 Self::True => (true, false),
107 Self::False => (false, true),
108 Self::Both => (true, true),
109 }
110 }
111
112 pub(crate) fn from_bits(bits: u64) -> Result<Self, SemanticHypergraphError> {
113 match bits {
114 0 => Ok(Self::Neither),
115 1 => Ok(Self::True),
116 2 => Ok(Self::False),
117 3 => Ok(Self::Both),
118 _ => Err(SemanticHypergraphError::CorruptLineage {
119 detail: format!("device returned invalid semantic bits {bits}"),
120 }),
121 }
122 }
123}
124
125#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
127pub enum SemanticPolarity {
128 Pro,
130 Contra,
132}
133
134impl SemanticPolarity {
135 const fn code(self) -> u64 {
136 match self {
137 Self::Pro => 1,
138 Self::Contra => 2,
139 }
140 }
141}
142
143#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub enum SemanticArgument {
146 U32(u32),
147 U64(u64),
148 I32(i32),
149 I64(i64),
150 F32Bits(u32),
151 F64Bits(u64),
152 Bool(bool),
153 Symbol(u32),
154}
155
156#[derive(Clone, Copy, Debug, Eq, PartialEq)]
158pub enum SemanticRecordRole {
159 Statement,
160 Qualifier,
161 Provenance,
162 Source,
163 Context,
164 Scope,
165}
166
167impl SemanticRecordRole {
168 fn code(self) -> u8 {
169 match self {
170 Self::Statement => 1,
171 Self::Qualifier => 2,
172 Self::Provenance => 3,
173 Self::Source => 4,
174 Self::Context => 5,
175 Self::Scope => 6,
176 }
177 }
178}
179
180#[derive(Clone, Debug, Eq, PartialEq)]
182pub struct SemanticPredicateRecord {
183 pub predicate: RelId,
184 pub role: SemanticRecordRole,
185 pub schema: Schema,
186}
187
188#[derive(Clone, Debug, Eq, PartialEq)]
193pub struct SemanticTypedRecord {
194 pub predicate: RelId,
195 pub arguments: Vec<SemanticArgument>,
196 pub qualifiers: Vec<u32>,
197}
198
199#[derive(Clone, Debug, Eq, PartialEq)]
201pub struct SemanticSupportRecord {
202 pub statement: u32,
203 pub polarity: SemanticPolarity,
204 pub provenance: u32,
205 pub source: u32,
206 pub context: u32,
207 pub scope: u32,
208}
209
210#[derive(Clone, Debug, Eq, PartialEq)]
212pub struct SemanticAdmissionRecords {
213 pub predicates: Vec<SemanticPredicateRecord>,
214 pub records: Vec<SemanticTypedRecord>,
215 pub supports: Vec<SemanticSupportRecord>,
216}
217
218#[derive(Clone, Copy, Debug)]
220pub struct SemanticAdmissionLimits {
221 pub max_records: u32,
223 pub max_terms: u32,
225 pub max_references: u32,
227 pub max_utf8_bytes: usize,
229}
230
231#[derive(Clone, Debug, Eq, PartialEq)]
234pub(crate) struct SemanticRootMaterial {
235 pub(crate) records: SemanticAdmissionRecords,
236 pub(crate) symbols: Vec<String>,
237 pub(crate) insertions: Vec<SemanticRootInsertion>,
238 pub(crate) digest: [u8; 32],
239 pub(crate) extents: [u32; 3],
240 pub(crate) admission_base_digest: [u8; 32],
241 pub(crate) admission_base_extents: [u32; 3],
242}
243
244#[derive(Clone, Debug, Eq, PartialEq)]
245pub(crate) struct SemanticRootInsertion {
246 pub(crate) statement: Option<u32>,
248 pub(crate) reconstruction: [u32; 10],
251 pub(crate) support: u32,
252 pub(crate) version: [u8; 32],
253}
254
255pub(crate) struct SemanticMaterialReader<'a> {
257 remaining: &'a [u8],
258}
259
260impl<'a> SemanticMaterialReader<'a> {
261 pub(crate) fn new(bytes: &'a [u8]) -> Self {
262 Self { remaining: bytes }
263 }
264
265 pub(crate) fn take(&mut self, len: usize) -> Result<&'a [u8], SemanticHypergraphError> {
266 if len > self.remaining.len() {
267 return Err(admission_error("truncated execution material"));
268 }
269 let (value, rest) = self.remaining.split_at(len);
270 self.remaining = rest;
271 Ok(value)
272 }
273
274 pub(crate) fn u8(&mut self) -> Result<u8, SemanticHypergraphError> {
275 Ok(self.take(1)?[0])
276 }
277 pub(crate) fn u32(&mut self) -> Result<u32, SemanticHypergraphError> {
278 Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
279 }
280 pub(crate) fn u64(&mut self) -> Result<u64, SemanticHypergraphError> {
281 Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
282 }
283 pub(crate) fn count(
284 &mut self,
285 min_bytes_per_item: usize,
286 ) -> Result<usize, SemanticHypergraphError> {
287 let count = self.u32()? as usize;
288 if min_bytes_per_item == 0 || count > self.remaining.len() / min_bytes_per_item {
289 return Err(admission_error(
290 "execution material count exceeds remaining bytes",
291 ));
292 }
293 Ok(count)
294 }
295 pub(crate) fn bytes(&mut self) -> Result<&'a [u8], SemanticHypergraphError> {
296 let len = self.count(1)?;
297 self.take(len)
298 }
299 pub(crate) fn take_rest(&mut self) -> &'a [u8] {
300 let value = self.remaining;
301 self.remaining = &[];
302 value
303 }
304 pub(crate) fn finish(self) -> Result<(), SemanticHypergraphError> {
305 if self.remaining.is_empty() {
306 Ok(())
307 } else {
308 Err(admission_error("trailing execution material bytes"))
309 }
310 }
311}
312
313pub(crate) fn material_u32(output: &mut Vec<u8>, value: u32) {
314 output.extend_from_slice(&value.to_le_bytes());
315}
316pub(crate) fn material_u64(output: &mut Vec<u8>, value: u64) {
317 output.extend_from_slice(&value.to_le_bytes());
318}
319pub(crate) fn material_bytes(
320 output: &mut Vec<u8>,
321 bytes: &[u8],
322) -> Result<(), SemanticHypergraphError> {
323 material_count(output, bytes.len())?;
324 output.extend_from_slice(bytes);
325 Ok(())
326}
327fn material_count(output: &mut Vec<u8>, count: usize) -> Result<(), SemanticHypergraphError> {
328 material_u32(
329 output,
330 u32::try_from(count).map_err(|_| admission_error("material count exceeds u32"))?,
331 );
332 Ok(())
333}
334fn material_budget(remaining: &mut usize, count: usize) -> Result<(), SemanticHypergraphError> {
335 *remaining = remaining
336 .checked_sub(count)
337 .ok_or_else(|| admission_error("execution material exceeds admission bound"))?;
338 Ok(())
339}
340fn material_string(
341 reader: &mut SemanticMaterialReader<'_>,
342 budget: &mut usize,
343) -> Result<String, SemanticHypergraphError> {
344 let bytes = reader.bytes()?;
345 material_budget(budget, bytes.len())?;
346 Ok(std::str::from_utf8(bytes)
347 .map_err(|_| admission_error("execution material text is not UTF-8"))?
348 .to_owned())
349}
350
351impl SemanticRootMaterial {
352 fn validate_symbol_indices(&self) -> Result<(), SemanticHypergraphError> {
353 let mut occurrence = 0usize;
354 for record in &self.records.records {
355 for argument in &record.arguments {
356 if let SemanticArgument::Symbol(index) = argument {
357 if *index as usize != occurrence || occurrence >= self.symbols.len() {
358 return Err(admission_error(
359 "material symbols are not in canonical occurrence order",
360 ));
361 }
362 occurrence += 1;
363 }
364 }
365 }
366 if occurrence != self.symbols.len() {
367 return Err(admission_error(
368 "material contains unreferenced symbol text",
369 ));
370 }
371 Ok(())
372 }
373
374 pub(crate) fn admission_records(
376 &self,
377 ) -> Result<SemanticAdmissionRecords, SemanticHypergraphError> {
378 self.validate_symbol_indices()?;
379 let mut records = self.records.clone();
380 for record in &mut records.records {
381 for argument in &mut record.arguments {
382 if let SemanticArgument::Symbol(index) = argument {
383 *index = symbol::intern(&self.symbols[*index as usize]);
384 }
385 }
386 }
387 Ok(records)
388 }
389
390 pub(crate) fn encode(&self) -> Result<Vec<u8>, SemanticHypergraphError> {
391 self.validate_symbol_indices()?;
392 let mut out = Self::encode_admission(&self.records, &self.symbols)?;
393 material_count(&mut out, self.insertions.len())?;
394 for insertion in &self.insertions {
395 if insertion.statement.is_some() && insertion.reconstruction != [0; 10] {
396 return Err(admission_error(
397 "original material target has derived reconstruction references",
398 ));
399 }
400 out.push(u8::from(insertion.statement.is_some()));
401 if let Some(statement) = insertion.statement {
402 material_u32(&mut out, statement);
403 }
404 for reference in insertion.reconstruction {
405 material_u32(&mut out, reference);
406 }
407 material_u32(&mut out, insertion.support);
408 out.extend_from_slice(&insertion.version);
409 }
410 out.extend_from_slice(&self.digest);
411 for extent in self.extents {
412 material_u32(&mut out, extent);
413 }
414 out.extend_from_slice(&self.admission_base_digest);
415 for extent in self.admission_base_extents {
416 material_u32(&mut out, extent);
417 }
418 Ok(out)
419 }
420
421 fn encode_admission(
422 records: &SemanticAdmissionRecords,
423 symbols: &[String],
424 ) -> Result<Vec<u8>, SemanticHypergraphError> {
425 let mut out = b"XLOGROOT".to_vec();
426 material_u32(&mut out, 2);
427 material_count(&mut out, records.predicates.len())?;
428 for predicate in &records.predicates {
429 material_u32(&mut out, predicate.predicate.0);
430 out.push(predicate.role.code());
431 material_count(&mut out, predicate.schema.columns.len())?;
432 for ((name, scalar), label) in predicate
433 .schema
434 .columns
435 .iter()
436 .zip(predicate.schema.sort_labels())
437 {
438 material_bytes(&mut out, name.as_bytes())?;
439 out.push(scalar.to_code());
440 material_bytes(&mut out, label.as_bytes())?;
441 }
442 material_count(&mut out, predicate.schema.key_columns.len())?;
443 for &column in &predicate.schema.key_columns {
444 material_count(&mut out, column)?;
445 }
446 }
447 material_count(&mut out, records.records.len())?;
448 for record in &records.records {
449 material_u32(&mut out, record.predicate.0);
450 material_count(&mut out, record.arguments.len())?;
451 for argument in &record.arguments {
452 out.push(argument_type(*argument).to_code());
453 match argument {
454 SemanticArgument::U32(value)
455 | SemanticArgument::F32Bits(value)
456 | SemanticArgument::Symbol(value) => material_u32(&mut out, *value),
457 SemanticArgument::U64(value) | SemanticArgument::F64Bits(value) => {
458 material_u64(&mut out, *value)
459 }
460 SemanticArgument::I32(value) => material_u32(&mut out, *value as u32),
461 SemanticArgument::I64(value) => material_u64(&mut out, *value as u64),
462 SemanticArgument::Bool(value) => out.push(u8::from(*value)),
463 }
464 }
465 material_count(&mut out, record.qualifiers.len())?;
466 for &qualifier in &record.qualifiers {
467 material_u32(&mut out, qualifier);
468 }
469 }
470 material_count(&mut out, records.supports.len())?;
471 for support in &records.supports {
472 material_u32(&mut out, support.statement);
473 out.push(support.polarity.code() as u8);
474 for value in [
475 support.provenance,
476 support.source,
477 support.context,
478 support.scope,
479 ] {
480 material_u32(&mut out, value);
481 }
482 }
483 material_count(&mut out, symbols.len())?;
484 for symbol in symbols {
485 material_bytes(&mut out, symbol.as_bytes())?;
486 }
487 Ok(out)
488 }
489
490 pub(crate) fn decode(
491 bytes: &[u8],
492 limits: SemanticAdmissionLimits,
493 ) -> Result<Self, SemanticHypergraphError> {
494 let mut reader = SemanticMaterialReader::new(bytes);
495 if reader.take(8)? != b"XLOGROOT" || reader.u32()? != 2 {
496 return Err(admission_error(
497 "unsupported semantic root material encoding",
498 ));
499 }
500 let mut record_budget = limits.max_records as usize;
501 let mut term_budget = limits.max_terms as usize;
502 let mut reference_budget = limits.max_references as usize;
503 let mut text_budget = limits.max_utf8_bytes;
504 let count = reader.count(13)?;
505 material_budget(&mut record_budget, count)?;
506 let mut predicates = Vec::with_capacity(count);
507 for _ in 0..count {
508 let predicate = RelId(reader.u32()?);
509 let role = match reader.u8()? {
510 1 => SemanticRecordRole::Statement,
511 2 => SemanticRecordRole::Qualifier,
512 3 => SemanticRecordRole::Provenance,
513 4 => SemanticRecordRole::Source,
514 5 => SemanticRecordRole::Context,
515 6 => SemanticRecordRole::Scope,
516 _ => return Err(admission_error("invalid material record role")),
517 };
518 let count = reader.count(9)?;
519 material_budget(&mut term_budget, count)?;
520 let mut columns = Vec::with_capacity(count);
521 let mut labels = Vec::with_capacity(count);
522 for _ in 0..count {
523 let name = material_string(&mut reader, &mut text_budget)?;
524 let scalar = ScalarType::from_code(reader.u8()?)
525 .ok_or_else(|| admission_error("invalid material scalar code"))?;
526 let label = material_string(&mut reader, &mut text_budget)?;
527 if label.trim().is_empty() {
528 return Err(admission_error("empty material sort label"));
529 }
530 columns.push((name, scalar));
531 labels.push(label);
532 }
533 let mut schema = Schema::new(columns)
534 .with_sort_labels(labels)
535 .map_err(admission_error)?;
536 let count = reader.count(4)?;
537 material_budget(&mut term_budget, count)?;
538 schema.key_columns.clear();
539 for _ in 0..count {
540 schema.key_columns.push(reader.u32()? as usize);
541 }
542 predicates.push(SemanticPredicateRecord {
543 predicate,
544 role,
545 schema,
546 });
547 }
548 let count = reader.count(12)?;
549 material_budget(&mut record_budget, count)?;
550 let mut records = Vec::with_capacity(count);
551 for _ in 0..count {
552 let predicate = RelId(reader.u32()?);
553 let count = reader.count(2)?;
554 material_budget(&mut term_budget, count)?;
555 let mut arguments = Vec::with_capacity(count);
556 for _ in 0..count {
557 let scalar = ScalarType::from_code(reader.u8()?)
558 .ok_or_else(|| admission_error("invalid material scalar code"))?;
559 arguments.push(match scalar {
560 ScalarType::U32 => SemanticArgument::U32(reader.u32()?),
561 ScalarType::U64 => SemanticArgument::U64(reader.u64()?),
562 ScalarType::I32 => SemanticArgument::I32(reader.u32()? as i32),
563 ScalarType::I64 => SemanticArgument::I64(reader.u64()? as i64),
564 ScalarType::F32 => SemanticArgument::F32Bits(reader.u32()?),
565 ScalarType::F64 => SemanticArgument::F64Bits(reader.u64()?),
566 ScalarType::Symbol => SemanticArgument::Symbol(reader.u32()?),
567 ScalarType::Bool => SemanticArgument::Bool(match reader.u8()? {
568 0 => false,
569 1 => true,
570 _ => return Err(admission_error("noncanonical material boolean")),
571 }),
572 });
573 }
574 let count = reader.count(4)?;
575 material_budget(&mut reference_budget, count)?;
576 let mut qualifiers = Vec::with_capacity(count);
577 for _ in 0..count {
578 qualifiers.push(reader.u32()?);
579 }
580 records.push(SemanticTypedRecord {
581 predicate,
582 arguments,
583 qualifiers,
584 });
585 }
586 let count = reader.count(21)?;
587 material_budget(&mut record_budget, count)?;
588 material_budget(
589 &mut reference_budget,
590 count.checked_mul(5).ok_or_else(size_overflow)?,
591 )?;
592 let mut supports = Vec::with_capacity(count);
593 for _ in 0..count {
594 let statement = reader.u32()?;
595 let polarity = match reader.u8()? {
596 1 => SemanticPolarity::Pro,
597 2 => SemanticPolarity::Contra,
598 _ => return Err(admission_error("invalid material polarity")),
599 };
600 supports.push(SemanticSupportRecord {
601 statement,
602 polarity,
603 provenance: reader.u32()?,
604 source: reader.u32()?,
605 context: reader.u32()?,
606 scope: reader.u32()?,
607 });
608 }
609 let count = reader.count(4)?;
610 if count > limits.max_terms as usize {
611 return Err(admission_error(
612 "material symbol count exceeds admission bound",
613 ));
614 }
615 let mut symbols = Vec::with_capacity(count);
616 for _ in 0..count {
617 symbols.push(material_string(&mut reader, &mut text_budget)?);
618 }
619 let count = reader.count(77)?;
620 let mut insertions = Vec::with_capacity(count);
621 for _ in 0..count {
622 let statement = match reader.u8()? {
623 0 => None,
624 1 => Some(reader.u32()?),
625 _ => return Err(admission_error("invalid material target origin")),
626 };
627 let mut reconstruction = [0; 10];
628 for reference in &mut reconstruction {
629 *reference = reader.u32()?;
630 }
631 if statement.is_some() && reconstruction != [0; 10] {
632 return Err(admission_error(
633 "original material target has derived reconstruction references",
634 ));
635 }
636 insertions.push(SemanticRootInsertion {
637 statement,
638 reconstruction,
639 support: reader.u32()?,
640 version: reader.take(32)?.try_into().unwrap(),
641 });
642 }
643 let digest = reader.take(32)?.try_into().unwrap();
644 let extents = [reader.u32()?, reader.u32()?, reader.u32()?];
645 let admission_base_digest = reader.take(32)?.try_into().unwrap();
646 let admission_base_extents = [reader.u32()?, reader.u32()?, reader.u32()?];
647 reader.finish()?;
648 let material = Self {
649 records: SemanticAdmissionRecords {
650 predicates,
651 records,
652 supports,
653 },
654 symbols,
655 insertions,
656 digest,
657 extents,
658 admission_base_digest,
659 admission_base_extents,
660 };
661 material.validate_symbol_indices()?;
662 Ok(material)
663 }
664}
665
666fn normalized_material_admission(
667 admission: &SemanticAdmission,
668) -> Result<(SemanticAdmissionRecords, Vec<String>), SemanticHypergraphError> {
669 let mut records = admission.records.clone();
670 let mut symbols = Vec::with_capacity(admission.symbols.entries().len());
671 for record in &mut records.records {
672 for argument in &mut record.arguments {
673 if let SemanticArgument::Symbol(index) = argument {
674 let (accepted, text) = admission
675 .symbols
676 .entries()
677 .get(symbols.len())
678 .ok_or_else(|| admission_error("retained admission symbol is missing"))?;
679 if *index != *accepted {
680 return Err(admission_error("retained admission symbol order differs"));
681 }
682 *index = u32::try_from(symbols.len()).map_err(|_| size_overflow())?;
683 symbols.push(text.to_string());
684 }
685 }
686 }
687 if symbols.len() != admission.symbols.entries().len() {
688 return Err(admission_error("retained admission has excess symbols"));
689 }
690 Ok((records, symbols))
691}
692
693fn material_root_digest(previous: [u8; 32], version: [u8; 32], extents: [u32; 3]) -> [u8; 32] {
694 let mut bytes = [0u8; 108];
695 bytes[..22].copy_from_slice(b"xlog.semantic.root.v1\0");
696 bytes[32..64].copy_from_slice(&previous);
697 bytes[64..96].copy_from_slice(&version);
698 for (chunk, extent) in bytes[96..].as_chunks_mut::<4>().0.iter_mut().zip(extents) {
699 chunk.copy_from_slice(&extent.to_le_bytes());
700 }
701 Sha256::digest(bytes).into()
702}
703
704fn material_version_digest(previous: [u8; 32], support: [u8; 32], truth: u64) -> [u8; 32] {
705 let mut bytes = [0u8; 104];
706 bytes[..25].copy_from_slice(b"xlog.semantic.version.v1\0");
707 bytes[32..64].copy_from_slice(&previous);
708 bytes[64..96].copy_from_slice(&support);
709 bytes[96..].copy_from_slice(&truth.to_le_bytes());
710 Sha256::digest(bytes).into()
711}
712
713fn record_encoding_prefix(schema: Identity256, predicate: RelId, arity: usize) -> Vec<u8> {
714 let mut bytes = b"xlog.semantic.record.v2\0".to_vec();
715 bytes.extend_from_slice(schema.as_bytes());
716 bytes.extend_from_slice(&predicate.0.to_le_bytes());
717 bytes.extend_from_slice(&(arity as u32).to_le_bytes());
718 bytes
719}
720
721fn qualified_statement_identity(
722 atom: [u8; 32],
723 qualifiers: impl ExactSizeIterator<Item = [u8; 32]>,
724) -> SemanticStatementIdentity {
725 let mut hash = Sha256::new();
726 hash.update(b"xlog.semantic.statement.v2\0");
727 hash.update(atom);
728 hash.update((qualifiers.len() as u32).to_le_bytes());
729 for qualifier in qualifiers {
730 hash.update(qualifier);
731 }
732 SemanticStatementIdentity(hash.finalize().into())
733}
734
735pub(crate) fn material_statement_key(
738 admission: &SemanticAdmission,
739 original: Option<u32>,
740 reconstruction: &[u32; 10],
741) -> Result<SemanticStatementKey, SemanticHypergraphError> {
742 if let Some(record) = original {
743 if *reconstruction != [0; 10] {
744 return Err(admission_error(
745 "original material target has derived reconstruction references",
746 ));
747 }
748 return admission.statement_key(record);
749 }
750 let predicates = &admission.records.predicates;
751 let target = predicates
752 .iter()
753 .find(|entry| entry.predicate.0 == reconstruction[0])
754 .ok_or_else(|| {
755 admission_error("derived target predicate is outside the retained admission")
756 })?;
757 let arity = target.schema.arity();
758 if target.role != SemanticRecordRole::Statement || arity > 4 {
759 return Err(admission_error(
760 "derived target requires an admitted statement schema of at most four arguments",
761 ));
762 }
763 let mut bytes = record_encoding_prefix(admission.schema_generation, target.predicate, arity);
764 for argument in 0..4 {
765 let record_index = reconstruction[1 + 2 * argument] as usize;
766 let argument_index = reconstruction[2 + 2 * argument] as usize;
767 if argument >= arity {
768 if record_index != 0 || argument_index != 0 {
769 return Err(admission_error(
770 "derived target has nonzero inactive argument references",
771 ));
772 }
773 continue;
774 }
775 let record = admission.records.records.get(record_index).ok_or_else(|| {
776 admission_error("derived argument record is outside the retained admission")
777 })?;
778 let source = predicates
779 .iter()
780 .find(|entry| entry.predicate == record.predicate)
781 .ok_or_else(|| {
782 admission_error("derived argument predicate is outside the retained admission")
783 })?;
784 let column = source
785 .schema
786 .columns
787 .get(argument_index)
788 .ok_or_else(|| admission_error("derived argument is outside its source schema"))?;
789 if column.1 != target.schema.columns[argument].1
790 || source.schema.sort_labels().get(argument_index)
791 != target.schema.sort_labels().get(argument)
792 {
793 return Err(admission_error(
794 "derived argument type or sort differs from its target schema",
795 ));
796 }
797 let encoding = &admission.encoded_records[record_index];
798 let span = encoding.arguments.get(argument_index).ok_or_else(|| {
799 admission_error("derived argument lacks its retained canonical bytes")
800 })?;
801 bytes.extend_from_slice(&encoding.bytes[span.clone()]);
802 }
803 let qualifiers = if reconstruction[9] == u32::MAX {
804 &[][..]
805 } else {
806 admission.statement_key(reconstruction[9])?;
807 &admission.records.records[reconstruction[9] as usize].qualifiers
808 };
809 let identity = qualified_statement_identity(
810 Sha256::digest(bytes).into(),
811 qualifiers
812 .iter()
813 .map(|&index| Sha256::digest(&admission.encoded_records[index as usize].bytes).into()),
814 );
815 Ok(SemanticStatementKey {
816 identity,
817 owner: admission.base.owner,
818 record: u32::MAX,
819 })
820}
821
822fn encode_support_insertion(
825 command: &mut DeviceCommand,
826 fork: SemanticForkHandle,
827 statement: &SemanticStatementKey,
828 event: &SemanticSupportEvent,
829 reconstruction: &[u32; 10],
830) {
831 command.words[0] = OP_INSERT_SUPPORT;
832 command.words[1] = fork.owner;
833 command.words[8] = u64::from(fork.slot);
834 command.words[9] = fork.generation;
835 command.words[12] = event.polarity.code();
836 command.words[13] = u64::from(statement.record);
837 command.words[14] = u64::from(event.record);
838 command.words[16..20].copy_from_slice(&identity_words(statement.identity.0));
839 command.words[20..24].copy_from_slice(&identity_words(event.identity(statement.identity).0));
840 for word in 0..5 {
841 command.words[24 + word] =
842 u64::from(reconstruction[2 * word]) | (u64::from(reconstruction[2 * word + 1]) << 32);
843 }
844}
845
846impl SemanticRootMaterial {
847 pub(crate) fn task_observation_roots(
851 &self,
852 admission: &SemanticAdmission,
853 query_records: [u32; 3],
854 ) -> Result<crate::semantic_transition::SemanticTaskObservationRoots, SemanticHypergraphError>
855 {
856 self.validate_lineage(admission)?;
857 let queries = query_records.map(|record| admission.statement_key(record));
858 let mut contributors = Vec::new();
859 for (ordinal, query) in queries.into_iter().enumerate() {
860 let query = query?;
861 for insertion in &self.insertions {
862 let key = material_statement_key(
863 admission,
864 insertion.statement,
865 &insertion.reconstruction,
866 )?;
867 if key.identity == query.identity {
868 contributors.push((ordinal as u32, insertion.statement, insertion.support));
869 }
870 }
871 }
872 Ok(crate::semantic_transition::SemanticTaskObservationRoots {
873 root_digest: crate::semantic_transition::Identity256::from_bytes(self.digest),
874 root_extents: self.extents,
875 query_records,
876 contributors,
877 })
878 }
879
880 fn validate_lineage(
881 &self,
882 admission: &SemanticAdmission,
883 ) -> Result<(), SemanticHypergraphError> {
884 let (records, symbols) = normalized_material_admission(admission)?;
885 if self.records != records || self.symbols != symbols {
886 return Err(admission_error(
887 "root material differs from the owner's complete typed admission",
888 ));
889 }
890 let mut heads = std::collections::BTreeMap::<[u8; 32], ([u8; 32], u64)>::new();
891 let mut supports = std::collections::BTreeSet::new();
892 let mut digest = material_root_digest([0; 32], [0; 32], [0; 3]);
893 let mut extents = [0u32; 3];
894 let mut found_base =
895 self.admission_base_extents == extents && self.admission_base_digest == digest;
896 for insertion in &self.insertions {
897 let key =
898 material_statement_key(admission, insertion.statement, &insertion.reconstruction)?;
899 let event = admission.support_event(insertion.support)?;
900 let support = event.identity(key.identity).0;
901 if !supports.insert((key.identity.0, support)) {
902 return Err(admission_error(
903 "material repeats an unchanged support insertion",
904 ));
905 }
906 let previous = heads.get(&key.identity.0).copied().unwrap_or(([0; 32], 0));
907 let truth = previous.1 | event.polarity.code();
908 let version = material_version_digest(previous.0, support, truth);
909 if version != insertion.version {
910 return Err(admission_error(
911 "material version identity does not match typed insertion history",
912 ));
913 }
914 heads.insert(key.identity.0, (version, truth));
915 extents = [
916 u32::try_from(heads.len()).map_err(|_| size_overflow())?,
917 extents[1].checked_add(1).ok_or_else(size_overflow)?,
918 extents[2].checked_add(1).ok_or_else(size_overflow)?,
919 ];
920 digest = material_root_digest(digest, version, extents);
921 if extents == self.admission_base_extents && digest == self.admission_base_digest {
922 found_base = true;
923 }
924 }
925 if digest != self.digest || extents != self.extents || !found_base {
926 return Err(admission_error(
927 "material root or original admission base differs from insertion history",
928 ));
929 }
930 Ok(())
931 }
932}
933
934fn material_from_arena(
937 arena: &[u64],
938 capacities: SemanticHypergraphCapacities,
939 root: SemanticRootHandle,
940 snapshot: SemanticRootSnapshot,
941 admission: &SemanticAdmission,
942) -> Result<SemanticRootMaterial, SemanticHypergraphError> {
943 let corrupt = || SemanticHypergraphError::CorruptLineage {
944 detail: "root material contains invalid native reachability or generation".into(),
945 };
946 if arena.len() as u64 != checked_arena_words(capacities)? || root.slot >= capacities.roots {
947 return Err(corrupt());
948 }
949 let statements = CONTROL_WORDS as usize
950 + capacities.roots as usize * ROOT_WORDS as usize
951 + CANDIDATE_WORDS as usize;
952 let supports = statements + capacities.statements as usize * STATEMENT_WORDS as usize;
953 let versions = supports + capacities.supports as usize * SUPPORT_WORDS as usize;
954 let heads = versions
955 + capacities.versions as usize * VERSION_WORDS as usize
956 + root.slot as usize * capacities.statements as usize;
957 let root_offset = CONTROL_WORDS as usize + root.slot as usize * ROOT_WORDS as usize;
958 let native_root = &arena[root_offset..root_offset + ROOT_WORDS as usize];
959 if arena[1] != root.owner
960 || native_root[0] != 3
961 || native_root[1] != root.generation
962 || root.generation == 0
963 {
964 return Err(corrupt());
965 }
966 let identity = |words: &[u64]| -> [u8; 32] {
967 let mut bytes = [0; 32];
968 for (chunk, word) in bytes.as_chunks_mut::<8>().0.iter_mut().zip(words) {
969 chunk.copy_from_slice(&word.to_le_bytes());
970 }
971 bytes
972 };
973 if identity(&native_root[6..10]) != snapshot.digest.0
974 || native_root[3..6]
975 != [
976 u64::from(snapshot.extents.statements),
977 u64::from(snapshot.extents.supports),
978 u64::from(snapshot.extents.versions),
979 ]
980 {
981 return Err(corrupt());
982 }
983 let mut seen_versions = std::collections::BTreeSet::new();
984 let mut seen_supports = std::collections::BTreeSet::new();
985 let mut ordered = Vec::new();
986 let mut statement_count = 0u32;
987 for statement_slot in 0..capacities.statements as usize {
988 let mut encoded = arena[heads + statement_slot];
989 if encoded == 0 {
990 continue;
991 }
992 statement_count += 1;
993 let offset = statements + statement_slot * STATEMENT_WORDS as usize;
994 let statement = &arena[offset..offset + STATEMENT_WORDS as usize];
995 if statement[0] != 3 || statement[1] == 0 {
996 return Err(corrupt());
997 }
998 let statement_identity = identity(&statement[3..7]);
999 let mut newer_ordinal = u64::MAX;
1000 while encoded != 0 {
1001 let version_slot = usize::try_from(encoded - 1).map_err(|_| corrupt())?;
1002 if version_slot >= capacities.versions as usize || !seen_versions.insert(version_slot) {
1003 return Err(corrupt());
1004 }
1005 let offset = versions + version_slot * VERSION_WORDS as usize;
1006 let version = &arena[offset..offset + VERSION_WORDS as usize];
1007 if version[0] != 3
1008 || version[1] == 0
1009 || version[3] != statement_slot as u64
1010 || version[4] != statement[1]
1011 || version[13] == 0
1012 || version[13] >= newer_ordinal
1013 {
1014 return Err(corrupt());
1015 }
1016 newer_ordinal = version[13];
1017 let support_slot = usize::try_from(version[5]).map_err(|_| corrupt())?;
1018 if support_slot >= capacities.supports as usize || !seen_supports.insert(support_slot) {
1019 return Err(corrupt());
1020 }
1021 let offset = supports + support_slot * SUPPORT_WORDS as usize;
1022 let support = &arena[offset..offset + SUPPORT_WORDS as usize];
1023 if support[0] != 3
1024 || support[1] == 0
1025 || support[1] != version[6]
1026 || support[3] != statement_slot as u64
1027 || support[4] != statement[1]
1028 || !matches!(support[5], 1 | 2)
1029 {
1030 return Err(corrupt());
1031 }
1032 let support_identity = identity(&support[6..10]);
1033 let statement_index = u32::try_from(support[10]).map_err(|_| corrupt())?;
1034 let statement_index = (statement_index != u32::MAX).then_some(statement_index);
1035 let support_index = u32::try_from(support[11]).map_err(|_| corrupt())?;
1036 let reconstruction =
1037 std::array::from_fn(|word| (support[12 + word / 2] >> (32 * (word % 2))) as u32);
1038 let key = material_statement_key(admission, statement_index, &reconstruction)?;
1039 let event = admission.support_event(support_index)?;
1040 if key.identity.0 != statement_identity
1041 || event.polarity.code() != support[5]
1042 || event.identity(key.identity).0 != support_identity
1043 {
1044 return Err(admission_error(
1045 "reachable insertion differs from its original typed occurrences",
1046 ));
1047 }
1048 let (previous, previous_truth) = if version[7] == 0 {
1049 ([0; 32], 0)
1050 } else {
1051 let slot = usize::try_from(version[7] - 1).map_err(|_| corrupt())?;
1052 if slot >= capacities.versions as usize {
1053 return Err(corrupt());
1054 }
1055 let offset = versions + slot * VERSION_WORDS as usize;
1056 (identity(&arena[offset + 9..offset + 13]), arena[offset + 8])
1057 };
1058 let digest = identity(&version[9..13]);
1059 if previous_truth > 3
1060 || version[8] != previous_truth | support[5]
1061 || digest != material_version_digest(previous, support_identity, version[8])
1062 {
1063 return Err(corrupt());
1064 }
1065 ordered.push((
1066 version[13],
1067 SemanticRootInsertion {
1068 statement: statement_index,
1069 reconstruction,
1070 support: support_index,
1071 version: digest,
1072 },
1073 ));
1074 encoded = version[7];
1075 }
1076 }
1077 ordered.sort_unstable_by_key(|(ordinal, _)| *ordinal);
1078 if ordered
1079 .iter()
1080 .enumerate()
1081 .any(|(index, (ordinal, _))| *ordinal != index as u64 + 1)
1082 || statement_count != snapshot.extents.statements
1083 || seen_supports.len() != snapshot.extents.supports as usize
1084 || seen_versions.len() != snapshot.extents.versions as usize
1085 {
1086 return Err(corrupt());
1087 }
1088 let (records, symbols) = normalized_material_admission(admission)?;
1089 let material = SemanticRootMaterial {
1090 records,
1091 symbols,
1092 insertions: ordered
1093 .into_iter()
1094 .map(|(_, insertion)| insertion)
1095 .collect(),
1096 digest: snapshot.digest.0,
1097 extents: [
1098 snapshot.extents.statements,
1099 snapshot.extents.supports,
1100 snapshot.extents.versions,
1101 ],
1102 admission_base_digest: admission.base_snapshot.digest.0,
1103 admission_base_extents: [
1104 admission.base_snapshot.extents.statements,
1105 admission.base_snapshot.extents.supports,
1106 admission.base_snapshot.extents.versions,
1107 ],
1108 };
1109 material.validate_lineage(admission)?;
1110 Ok(material)
1111}
1112
1113pub struct SemanticAdmission {
1120 records: SemanticAdmissionRecords,
1121 symbols: symbol::SymbolSnapshot,
1122 schema_bytes: Vec<u8>,
1123 schema_generation: Identity256,
1124 identity: Identity256,
1125 base: SemanticRootHandle,
1126 base_snapshot: SemanticRootSnapshot,
1127 statement_keys: Vec<Option<SemanticStatementKey>>,
1128 support_events: Vec<SemanticSupportEvent>,
1129 pub(crate) encoded_records: Vec<SemanticRecordEncoding>,
1130}
1131
1132pub(crate) struct SemanticRecordEncoding {
1135 pub bytes: Vec<u8>,
1136 pub arguments: Vec<std::ops::Range<usize>>,
1137}
1138
1139impl SemanticAdmission {
1140 pub const fn encoding_generation(&self) -> u32 {
1142 2
1143 }
1144 pub fn records(&self) -> &SemanticAdmissionRecords {
1145 &self.records
1146 }
1147 pub fn symbols(&self) -> &symbol::SymbolSnapshot {
1148 &self.symbols
1149 }
1150 pub const fn schema_generation(&self) -> Identity256 {
1151 self.schema_generation
1152 }
1153 pub fn schema_bytes(&self) -> &[u8] {
1155 &self.schema_bytes
1156 }
1157 pub const fn identity(&self) -> Identity256 {
1158 self.identity
1159 }
1160 pub const fn base(&self) -> SemanticRootHandle {
1161 self.base
1162 }
1163 pub const fn base_snapshot(&self) -> &SemanticRootSnapshot {
1164 &self.base_snapshot
1165 }
1166
1167 pub fn statement_key(
1169 &self,
1170 record: u32,
1171 ) -> Result<SemanticStatementKey, SemanticHypergraphError> {
1172 self.statement_keys
1173 .get(record as usize)
1174 .copied()
1175 .flatten()
1176 .ok_or_else(|| admission_error("record is not an admitted statement"))
1177 }
1178
1179 pub fn support_event(
1183 &self,
1184 index: u32,
1185 ) -> Result<SemanticSupportEvent, SemanticHypergraphError> {
1186 self.support_events
1187 .get(index as usize)
1188 .copied()
1189 .ok_or_else(|| admission_error("support event is outside the admitted records"))
1190 }
1191}
1192
1193fn admission_error(detail: impl Into<String>) -> SemanticHypergraphError {
1194 SemanticHypergraphError::InvalidInput {
1195 detail: detail.into(),
1196 }
1197}
1198
1199fn consume_admission_bound(
1200 remaining: &mut usize,
1201 count: usize,
1202 name: &str,
1203) -> Result<(), SemanticHypergraphError> {
1204 *remaining = remaining
1205 .checked_sub(count)
1206 .ok_or_else(|| admission_error(format!("semantic admission exceeds {name} bound")))?;
1207 Ok(())
1208}
1209
1210fn argument_type(argument: SemanticArgument) -> ScalarType {
1211 match argument {
1212 SemanticArgument::U32(_) => ScalarType::U32,
1213 SemanticArgument::U64(_) => ScalarType::U64,
1214 SemanticArgument::I32(_) => ScalarType::I32,
1215 SemanticArgument::I64(_) => ScalarType::I64,
1216 SemanticArgument::F32Bits(_) => ScalarType::F32,
1217 SemanticArgument::F64Bits(_) => ScalarType::F64,
1218 SemanticArgument::Bool(_) => ScalarType::Bool,
1219 SemanticArgument::Symbol(_) => ScalarType::Symbol,
1220 }
1221}
1222
1223fn admit_semantic_records(
1226 records: SemanticAdmissionRecords,
1227 limits: SemanticAdmissionLimits,
1228 base: SemanticRootHandle,
1229 observe_base: impl FnOnce() -> Result<SemanticRootSnapshot, SemanticHypergraphError>,
1230) -> Result<SemanticAdmission, SemanticHypergraphError> {
1231 let mut record_budget = limits.max_records as usize;
1232 for count in [
1233 records.predicates.len(),
1234 records.records.len(),
1235 records.supports.len(),
1236 ] {
1237 consume_admission_bound(&mut record_budget, count, "record count")?;
1238 }
1239 let mut term_budget = limits.max_terms as usize;
1240 let mut reference_budget = limits.max_references as usize;
1241 let mut byte_budget = limits.max_utf8_bytes;
1242 for predicate in &records.predicates {
1243 let schema = &predicate.schema;
1244 consume_admission_bound(&mut term_budget, schema.columns.len(), "term count")?;
1245 consume_admission_bound(&mut term_budget, schema.key_columns.len(), "term count")?;
1246 if !schema.has_authoritative_sort_labels() {
1247 return Err(admission_error("schema has missing or invalid sort labels"));
1248 }
1249 for (name, _) in &schema.columns {
1250 consume_admission_bound(&mut byte_budget, name.len(), "UTF-8 bytes")?;
1251 }
1252 for label in schema.sort_labels() {
1253 consume_admission_bound(&mut byte_budget, label.len(), "UTF-8 bytes")?;
1254 }
1255 let mut keys = std::collections::BTreeSet::new();
1256 for &key in &schema.key_columns {
1257 if key >= schema.arity() || !keys.insert(key) {
1258 return Err(admission_error(
1259 "schema key column is out of range or repeated",
1260 ));
1261 }
1262 }
1263 }
1264 for record in &records.records {
1265 consume_admission_bound(&mut term_budget, record.arguments.len(), "term count")?;
1266 consume_admission_bound(
1267 &mut reference_budget,
1268 record.qualifiers.len(),
1269 "reference count",
1270 )?;
1271 }
1272 for _ in &records.supports {
1273 consume_admission_bound(&mut reference_budget, 5, "reference count")?;
1274 }
1275 let mut predicates = std::collections::BTreeMap::new();
1276 for predicate in &records.predicates {
1277 if predicates
1278 .insert(predicate.predicate.0, predicate)
1279 .is_some()
1280 {
1281 return Err(admission_error("predicate schema is repeated"));
1282 }
1283 }
1284 let mut roles = Vec::with_capacity(records.records.len());
1285 let mut symbol_ids = Vec::new();
1286 for record in &records.records {
1287 let predicate = predicates
1288 .get(&record.predicate.0)
1289 .ok_or_else(|| admission_error("typed record references an unknown predicate"))?;
1290 if record.arguments.len() != predicate.schema.arity() {
1291 return Err(admission_error(
1292 "typed record arity differs from its predicate schema",
1293 ));
1294 }
1295 for (argument, (_, expected)) in record.arguments.iter().zip(&predicate.schema.columns) {
1296 if argument_type(*argument) != *expected {
1297 return Err(admission_error(
1298 "typed argument differs from its schema column type",
1299 ));
1300 }
1301 if let SemanticArgument::Symbol(id) = argument {
1302 symbol_ids.push(*id);
1303 }
1304 }
1305 if predicate.role != SemanticRecordRole::Statement && !record.qualifiers.is_empty() {
1306 return Err(admission_error(
1307 "only statements may carry identity-bearing qualifiers",
1308 ));
1309 }
1310 roles.push(predicate.role);
1311 }
1312 let require_role = |index: u32, role| {
1313 if roles.get(index as usize) == Some(&role) {
1314 Ok(())
1315 } else {
1316 Err(admission_error(format!(
1317 "record reference {index} is not an admitted {role:?}"
1318 )))
1319 }
1320 };
1321 for record in &records.records {
1322 for &index in &record.qualifiers {
1323 require_role(index, SemanticRecordRole::Qualifier)?;
1324 }
1325 }
1326 for support in &records.supports {
1327 for (index, role) in [
1328 (support.statement, SemanticRecordRole::Statement),
1329 (support.provenance, SemanticRecordRole::Provenance),
1330 (support.source, SemanticRecordRole::Source),
1331 (support.context, SemanticRecordRole::Context),
1332 (support.scope, SemanticRecordRole::Scope),
1333 ] {
1334 require_role(index, role)?;
1335 }
1336 }
1337 let symbols = symbol::snapshot_checked(&symbol_ids, limits.max_terms as usize, byte_budget)
1338 .map_err(|error| admission_error(error.to_string()))?;
1339 let mut schema_bytes = Vec::new();
1340 schema_bytes.extend_from_slice(b"xlog.semantic.schema.v2\0");
1341 schema_bytes.extend_from_slice(&(predicates.len() as u32).to_le_bytes());
1342 for predicate in predicates.values() {
1343 schema_bytes.extend_from_slice(&predicate.predicate.0.to_le_bytes());
1344 schema_bytes.push(predicate.role.code());
1345 schema_bytes.extend_from_slice(&(predicate.schema.arity() as u32).to_le_bytes());
1346 for (name, ty) in &predicate.schema.columns {
1347 schema_bytes.extend_from_slice(&(name.len() as u64).to_le_bytes());
1348 schema_bytes.extend_from_slice(name.as_bytes());
1349 schema_bytes.push(ty.to_code());
1350 }
1351 schema_bytes.extend_from_slice(&(predicate.schema.key_columns.len() as u32).to_le_bytes());
1352 for &key in &predicate.schema.key_columns {
1353 schema_bytes.extend_from_slice(&(key as u32).to_le_bytes());
1354 }
1355 for label in predicate.schema.sort_labels() {
1356 schema_bytes.extend_from_slice(&(label.len() as u64).to_le_bytes());
1357 schema_bytes.extend_from_slice(label.as_bytes());
1358 }
1359 }
1360 let schema_generation = Identity256::from_bytes(Sha256::digest(&schema_bytes).into());
1361 let mut symbol_text = symbols.entries().iter();
1362 let mut atoms = Vec::<[u8; 32]>::with_capacity(records.records.len());
1363 let mut encoded_records = Vec::with_capacity(records.records.len());
1364 for record in &records.records {
1365 let mut bytes =
1366 record_encoding_prefix(schema_generation, record.predicate, record.arguments.len());
1367 let mut arguments = Vec::with_capacity(record.arguments.len());
1368 for &argument in &record.arguments {
1369 let start = bytes.len();
1370 bytes.push(argument_type(argument).to_code() + 1);
1371 match argument {
1372 SemanticArgument::U32(value) | SemanticArgument::F32Bits(value) => {
1373 bytes.extend_from_slice(&value.to_le_bytes())
1374 }
1375 SemanticArgument::U64(value) | SemanticArgument::F64Bits(value) => {
1376 bytes.extend_from_slice(&value.to_le_bytes())
1377 }
1378 SemanticArgument::I32(value) => bytes.extend_from_slice(&value.to_le_bytes()),
1379 SemanticArgument::I64(value) => bytes.extend_from_slice(&value.to_le_bytes()),
1380 SemanticArgument::Bool(value) => bytes.push(u8::from(value)),
1381 SemanticArgument::Symbol(id) => {
1382 let (accepted_id, text) = symbol_text
1383 .next()
1384 .expect("validated complete symbol snapshot");
1385 debug_assert_eq!(*accepted_id, id);
1386 bytes.extend_from_slice(&(text.len() as u64).to_le_bytes());
1388 bytes.extend_from_slice(text.as_bytes());
1389 }
1390 }
1391 arguments.push(start..bytes.len());
1392 }
1393 atoms.push(Sha256::digest(&bytes).into());
1394 encoded_records.push(SemanticRecordEncoding { bytes, arguments });
1395 }
1396 let statement_keys: Vec<_> = records
1397 .records
1398 .iter()
1399 .enumerate()
1400 .map(|(index, record)| {
1401 if roles[index] != SemanticRecordRole::Statement {
1402 return None;
1403 }
1404 Some(SemanticStatementKey {
1405 identity: qualified_statement_identity(
1406 atoms[index],
1407 record
1408 .qualifiers
1409 .iter()
1410 .map(|&qualifier| atoms[qualifier as usize]),
1411 ),
1412 owner: base.owner,
1413 record: index as u32,
1414 })
1415 })
1416 .collect();
1417 let support_events: Vec<_> = records
1418 .supports
1419 .iter()
1420 .enumerate()
1421 .map(|(index, record)| SemanticSupportEvent {
1422 owner: base.owner,
1423 record: index as u32,
1424 polarity: record.polarity,
1425 provenance: Identity256::from_bytes(atoms[record.provenance as usize]),
1426 source: Identity256::from_bytes(atoms[record.source as usize]),
1427 context: Identity256::from_bytes(atoms[record.context as usize]),
1428 scope: Identity256::from_bytes(atoms[record.scope as usize]),
1429 })
1430 .collect();
1431 let base_snapshot = observe_base()?;
1432 let identity = derive_admission_identity(
1433 &records,
1434 schema_generation,
1435 &encoded_records,
1436 &statement_keys,
1437 &support_events,
1438 &base_snapshot,
1439 );
1440 Ok(SemanticAdmission {
1441 records,
1442 symbols,
1443 schema_bytes,
1444 schema_generation,
1445 identity,
1446 base,
1447 base_snapshot,
1448 statement_keys,
1449 support_events,
1450 encoded_records,
1451 })
1452}
1453
1454fn derive_admission_identity(
1455 records: &SemanticAdmissionRecords,
1456 schema_generation: Identity256,
1457 encoded_records: &[SemanticRecordEncoding],
1458 statement_keys: &[Option<SemanticStatementKey>],
1459 support_events: &[SemanticSupportEvent],
1460 base_snapshot: &SemanticRootSnapshot,
1461) -> Identity256 {
1462 let mut hash = Sha256::new();
1463 hash.update(b"xlog.semantic.admission.v2\0");
1464 hash.update(base_snapshot.canonical_bytes());
1465 hash.update(schema_generation.as_bytes());
1466 for predicate in &records.predicates {
1467 hash.update(predicate.predicate.0.to_le_bytes());
1468 }
1469 hash.update((encoded_records.len() as u32).to_le_bytes());
1470 for ((encoded, key), record) in encoded_records
1471 .iter()
1472 .zip(statement_keys)
1473 .zip(&records.records)
1474 {
1475 hash.update(Sha256::digest(&encoded.bytes));
1476 hash.update((record.qualifiers.len() as u32).to_le_bytes());
1477 for &reference in &record.qualifiers {
1478 hash.update(reference.to_le_bytes());
1479 }
1480 hash.update([u8::from(key.is_some())]);
1481 if let Some(key) = key {
1482 hash.update(key.identity.as_bytes());
1483 }
1484 }
1485 let symbol_ids: Vec<_> = records
1486 .records
1487 .iter()
1488 .flat_map(|record| &record.arguments)
1489 .filter_map(|argument| match argument {
1490 SemanticArgument::Symbol(id) => Some(*id),
1491 _ => None,
1492 })
1493 .collect();
1494 hash.update((symbol_ids.len() as u32).to_le_bytes());
1495 for id in symbol_ids {
1496 hash.update(id.to_le_bytes());
1497 }
1498 hash.update((support_events.len() as u32).to_le_bytes());
1499 for (event, record) in support_events.iter().zip(&records.supports) {
1500 for index in [
1501 record.statement,
1502 record.provenance,
1503 record.source,
1504 record.context,
1505 record.scope,
1506 ] {
1507 hash.update(index.to_le_bytes());
1508 }
1509 let source_statement = statement_keys[record.statement as usize]
1510 .expect("validated source statement reference")
1511 .identity;
1512 hash.update(event.identity(source_statement).as_bytes());
1513 }
1514 Identity256::from_bytes(hash.finalize().into())
1515}
1516
1517#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1519pub struct SemanticStatementIdentity([u8; 32]);
1520
1521impl SemanticStatementIdentity {
1522 pub const fn as_bytes(&self) -> &[u8; 32] {
1524 &self.0
1525 }
1526}
1527
1528#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1530pub struct SemanticSupportIdentity([u8; 32]);
1531
1532impl SemanticSupportIdentity {
1533 pub const fn as_bytes(&self) -> &[u8; 32] {
1535 &self.0
1536 }
1537}
1538
1539#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1541pub struct SemanticVersionIdentity([u8; 32]);
1542
1543impl SemanticVersionIdentity {
1544 pub const fn as_bytes(&self) -> &[u8; 32] {
1546 &self.0
1547 }
1548}
1549
1550#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1552pub struct SemanticRootDigest([u8; 32]);
1553
1554impl SemanticRootDigest {
1555 pub const fn as_bytes(&self) -> &[u8; 32] {
1557 &self.0
1558 }
1559}
1560
1561#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1563pub struct SemanticStatementKey {
1564 identity: SemanticStatementIdentity,
1565 owner: u64,
1566 record: u32,
1567}
1568
1569impl SemanticStatementKey {
1570 pub const fn identity(&self) -> SemanticStatementIdentity {
1572 self.identity
1573 }
1574}
1575
1576#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1578pub struct SemanticSupportEvent {
1579 owner: u64,
1580 record: u32,
1581 polarity: SemanticPolarity,
1582 provenance: Identity256,
1583 source: Identity256,
1584 context: Identity256,
1585 scope: Identity256,
1586}
1587
1588impl SemanticSupportEvent {
1589 pub(crate) fn identity(self, statement: SemanticStatementIdentity) -> SemanticSupportIdentity {
1590 let mut hash = Sha256::new();
1591 hash.update(b"xlog.semantic.support.v1\0");
1592 hash.update(statement.as_bytes());
1593 hash.update((self.polarity.code() as u32).to_le_bytes());
1594 hash.update(1u32.to_le_bytes());
1595 hash.update(self.provenance.as_bytes());
1596 hash.update(self.source.as_bytes());
1597 hash.update(self.context.as_bytes());
1598 hash.update(self.scope.as_bytes());
1599 SemanticSupportIdentity(hash.finalize().into())
1600 }
1601}
1602
1603#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1605pub enum SemanticHandleKind {
1606 Root,
1607 Fork,
1608 Statement,
1609 Support,
1610 Version,
1611}
1612
1613macro_rules! semantic_handle {
1614 ($name:ident) => {
1615 #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1616 pub struct $name {
1617 owner: u64,
1618 slot: u32,
1619 generation: u64,
1620 }
1621
1622 impl $name {
1623 const fn new(owner: u64, slot: u32, generation: u64) -> Self {
1624 Self {
1625 owner,
1626 slot,
1627 generation,
1628 }
1629 }
1630
1631 pub const fn slot(self) -> u32 {
1633 self.slot
1634 }
1635
1636 pub const fn generation(self) -> u64 {
1638 self.generation
1639 }
1640 }
1641 };
1642}
1643
1644semantic_handle!(SemanticRootHandle);
1645semantic_handle!(SemanticForkHandle);
1646semantic_handle!(SemanticStatementHandle);
1647semantic_handle!(SemanticSupportHandle);
1648semantic_handle!(SemanticVersionHandle);
1649
1650#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1652pub enum SemanticView {
1653 Root(SemanticRootHandle),
1654 Fork(SemanticForkHandle),
1655}
1656
1657#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
1659pub struct SemanticExtents {
1660 statements: u32,
1661 supports: u32,
1662 versions: u32,
1663}
1664
1665impl SemanticExtents {
1666 pub const fn new(statements: u32, supports: u32, versions: u32) -> Self {
1667 Self {
1668 statements,
1669 supports,
1670 versions,
1671 }
1672 }
1673
1674 pub const fn statements(self) -> u32 {
1675 self.statements
1676 }
1677
1678 pub const fn supports(self) -> u32 {
1679 self.supports
1680 }
1681
1682 pub const fn versions(self) -> u32 {
1683 self.versions
1684 }
1685}
1686
1687#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1689pub struct SemanticRootSnapshot {
1690 digest: SemanticRootDigest,
1691 extents: SemanticExtents,
1692 canonical_bytes: [u8; 64],
1693}
1694
1695impl SemanticRootSnapshot {
1696 fn new(digest: SemanticRootDigest, extents: SemanticExtents) -> Self {
1697 let mut canonical_bytes = [0u8; 64];
1698 canonical_bytes[..8].copy_from_slice(b"XLOGSHG1");
1699 canonical_bytes[8..12].copy_from_slice(&1u32.to_le_bytes());
1700 canonical_bytes[12..16].copy_from_slice(&extents.statements.to_le_bytes());
1701 canonical_bytes[16..20].copy_from_slice(&extents.supports.to_le_bytes());
1702 canonical_bytes[20..24].copy_from_slice(&extents.versions.to_le_bytes());
1703 canonical_bytes[24..56].copy_from_slice(digest.as_bytes());
1704 Self {
1705 digest,
1706 extents,
1707 canonical_bytes,
1708 }
1709 }
1710
1711 pub const fn digest(&self) -> &SemanticRootDigest {
1712 &self.digest
1713 }
1714
1715 pub const fn extents(&self) -> SemanticExtents {
1716 self.extents
1717 }
1718
1719 pub const fn canonical_bytes(&self) -> &[u8; 64] {
1720 &self.canonical_bytes
1721 }
1722}
1723
1724#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1726pub struct SemanticHypergraphCapacities {
1727 roots: u32,
1728 statements: u32,
1729 supports: u32,
1730 versions: u32,
1731}
1732
1733impl SemanticHypergraphCapacities {
1734 pub fn try_new(
1735 roots: u32,
1736 statements: u32,
1737 supports: u32,
1738 versions: u32,
1739 ) -> Result<Self, SemanticHypergraphError> {
1740 for (name, value) in [
1741 ("root", roots),
1742 ("statement", statements),
1743 ("support", supports),
1744 ("version", versions),
1745 ] {
1746 if value == 0 {
1747 return Err(SemanticHypergraphError::InvalidCapacity { kind: name, value });
1748 }
1749 }
1750 Ok(Self {
1751 roots,
1752 statements,
1753 supports,
1754 versions,
1755 })
1756 }
1757}
1758
1759#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1761pub struct SemanticStatementRef {
1762 handle: SemanticStatementHandle,
1763 identity: SemanticStatementIdentity,
1764}
1765
1766impl SemanticStatementRef {
1767 pub const fn handle(self) -> SemanticStatementHandle {
1768 self.handle
1769 }
1770
1771 pub const fn identity(self) -> SemanticStatementIdentity {
1772 self.identity
1773 }
1774}
1775
1776#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1778pub struct SemanticSupportRef {
1779 handle: SemanticSupportHandle,
1780 identity: SemanticSupportIdentity,
1781}
1782
1783impl SemanticSupportRef {
1784 pub const fn handle(self) -> SemanticSupportHandle {
1785 self.handle
1786 }
1787
1788 pub const fn identity(self) -> SemanticSupportIdentity {
1789 self.identity
1790 }
1791}
1792
1793#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1795pub struct SemanticVersionRef {
1796 handle: SemanticVersionHandle,
1797 identity: SemanticVersionIdentity,
1798 truth: SemanticTruth,
1799}
1800
1801impl SemanticVersionRef {
1802 pub const fn handle(self) -> SemanticVersionHandle {
1803 self.handle
1804 }
1805
1806 pub const fn identity(self) -> SemanticVersionIdentity {
1807 self.identity
1808 }
1809
1810 pub const fn truth(self) -> SemanticTruth {
1811 self.truth
1812 }
1813}
1814
1815#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1817pub struct SemanticInsertedSupport {
1818 statement: SemanticStatementRef,
1819 support: SemanticSupportRef,
1820 version: SemanticVersionRef,
1821 previous_truth: SemanticTruth,
1822}
1823
1824impl SemanticInsertedSupport {
1825 pub const fn statement(self) -> SemanticStatementRef {
1826 self.statement
1827 }
1828
1829 pub const fn support(self) -> SemanticSupportRef {
1830 self.support
1831 }
1832
1833 pub const fn version(self) -> SemanticVersionRef {
1834 self.version
1835 }
1836
1837 pub const fn previous_truth(self) -> SemanticTruth {
1839 self.previous_truth
1840 }
1841
1842 pub fn changes_defined_truth(self) -> bool {
1845 self.previous_truth != SemanticTruth::Neither && self.previous_truth != self.version.truth
1846 }
1847}
1848
1849#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1851pub enum SemanticInsertOutcome {
1852 Inserted(SemanticInsertedSupport),
1853 Unchanged(SemanticVersionRef),
1854}
1855
1856#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1858pub struct SemanticHypergraphExecutionStats {
1859 cuda_kernel_launches: u64,
1860}
1861
1862impl SemanticHypergraphExecutionStats {
1863 pub const fn cuda_kernel_launches(self) -> u64 {
1864 self.cuda_kernel_launches
1865 }
1866}
1867
1868#[non_exhaustive]
1870#[derive(Clone, Debug, PartialEq, Eq)]
1871pub enum SemanticHypergraphError {
1872 InvalidCapacity {
1873 kind: &'static str,
1874 value: u32,
1875 },
1876 InvalidInput {
1877 detail: String,
1878 },
1879 ForeignHandle {
1880 kind: SemanticHandleKind,
1881 },
1882 SlotOutOfRange {
1883 kind: SemanticHandleKind,
1884 slot: u32,
1885 capacity: u32,
1886 },
1887 StaleGeneration {
1888 kind: SemanticHandleKind,
1889 slot: u32,
1890 presented: u64,
1891 current: u64,
1892 },
1893 InactiveHandle {
1894 kind: SemanticHandleKind,
1895 slot: u32,
1896 },
1897 NotReachable {
1898 kind: SemanticHandleKind,
1899 slot: u32,
1900 },
1901 CapacityExceeded {
1902 kind: SemanticHandleKind,
1903 capacity: u32,
1904 },
1905 GenerationExhausted {
1906 kind: SemanticHandleKind,
1907 slot: u32,
1908 },
1909 CorruptLineage {
1910 detail: String,
1911 },
1912 KernelUnavailable,
1913 Runtime {
1914 operation: &'static str,
1915 detail: String,
1916 },
1917 DeviceControlled,
1918 Poisoned,
1919}
1920
1921impl fmt::Display for SemanticHypergraphError {
1922 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1923 match self {
1924 Self::InvalidCapacity { kind, value } => {
1925 write!(
1926 formatter,
1927 "semantic {kind} capacity must be positive, got {value}"
1928 )
1929 }
1930 Self::InvalidInput { detail } => write!(formatter, "invalid semantic input: {detail}"),
1931 Self::ForeignHandle { kind } => write!(formatter, "foreign {kind:?} handle"),
1932 Self::SlotOutOfRange {
1933 kind,
1934 slot,
1935 capacity,
1936 } => write!(
1937 formatter,
1938 "semantic {kind:?} slot {slot} exceeds capacity {capacity}"
1939 ),
1940 Self::StaleGeneration {
1941 kind,
1942 slot,
1943 presented,
1944 current,
1945 } => write!(
1946 formatter,
1947 "stale semantic {kind:?} generation at slot {slot}: {presented}, current {current}"
1948 ),
1949 Self::InactiveHandle { kind, slot } => {
1950 write!(formatter, "inactive semantic {kind:?} slot {slot}")
1951 }
1952 Self::NotReachable { kind, slot } => {
1953 write!(formatter, "semantic {kind:?} slot {slot} is not reachable")
1954 }
1955 Self::CapacityExceeded { kind, capacity } => {
1956 write!(
1957 formatter,
1958 "semantic {kind:?} capacity {capacity} is exhausted"
1959 )
1960 }
1961 Self::GenerationExhausted { kind, slot } => {
1962 write!(
1963 formatter,
1964 "semantic {kind:?} generation exhausted at slot {slot}"
1965 )
1966 }
1967 Self::CorruptLineage { detail } => {
1968 write!(formatter, "corrupt semantic device lineage: {detail}")
1969 }
1970 Self::KernelUnavailable => {
1971 write!(formatter, "semantic hypergraph CUDA kernel unavailable")
1972 }
1973 Self::Runtime { operation, detail } => {
1974 write!(formatter, "semantic {operation} failed: {detail}")
1975 }
1976 Self::DeviceControlled => write!(
1977 formatter,
1978 "semantic hypergraph is device-controlled after resident admission"
1979 ),
1980 Self::Poisoned => write!(
1981 formatter,
1982 "semantic hypergraph is poisoned after runtime or integrity failure"
1983 ),
1984 }
1985 }
1986}
1987
1988impl std::error::Error for SemanticHypergraphError {}
1989
1990#[repr(C)]
1991#[derive(Clone, Copy)]
1992pub(crate) struct DeviceCommand {
1993 words: [u64; COMMAND_WORDS],
1994}
1995
1996impl Default for DeviceCommand {
1997 fn default() -> Self {
1998 Self {
1999 words: [0; COMMAND_WORDS],
2000 }
2001 }
2002}
2003
2004unsafe impl DeviceRepr for DeviceCommand {}
2006
2007#[repr(C)]
2008#[derive(Clone, Copy)]
2009pub(crate) struct SemanticResidentReceiptRecord {
2010 words: [u64; RECEIPT_WORDS],
2016}
2017
2018impl Default for SemanticResidentReceiptRecord {
2019 fn default() -> Self {
2020 Self {
2021 words: [0; RECEIPT_WORDS],
2022 }
2023 }
2024}
2025
2026unsafe impl DeviceRepr for SemanticResidentReceiptRecord {}
2028
2029type DeviceReceipt = SemanticResidentReceiptRecord;
2030
2031#[repr(C)]
2033#[derive(Clone, Copy)]
2034pub(crate) struct SemanticResidentDecodedStatement {
2035 identity_words: [u32; 8],
2036 record: u32,
2037 reconstruction: [u32; 10],
2038}
2039
2040unsafe impl DeviceRepr for SemanticResidentDecodedStatement {}
2042
2043#[repr(C)]
2045#[derive(Clone, Copy)]
2046pub(crate) struct SemanticResidentDecodedSupport {
2047 polarity: u32,
2048 provenance_words: [u32; 8],
2049 source_words: [u32; 8],
2050 context_words: [u32; 8],
2051 scope_words: [u32; 8],
2052 record: u32,
2053}
2054
2055unsafe impl DeviceRepr for SemanticResidentDecodedSupport {}
2057
2058#[repr(C)]
2060#[derive(Clone, Copy)]
2061pub(crate) struct SemanticResidentDecodedInput {
2062 statement_identity_words: [u32; 8],
2063 polarity: u32,
2064 provenance_words: [u32; 8],
2065 source_words: [u32; 8],
2066 context_words: [u32; 8],
2067 scope_words: [u32; 8],
2068 statement_record: u32,
2069 support_record: u32,
2070 reconstruction: [u32; 10],
2071}
2072
2073unsafe impl DeviceRepr for SemanticResidentDecodedInput {}
2075
2076#[repr(C)]
2077#[derive(Clone, Copy, Default)]
2078pub(crate) struct SemanticResidentTruthValue {
2079 status: u64,
2080 truth: u64,
2081 owner: u64,
2082 reserved: u64,
2083}
2084
2085unsafe impl DeviceRepr for SemanticResidentTruthValue {}
2087
2088#[repr(C)]
2090#[derive(Clone, Copy)]
2091pub(crate) struct SemanticResidentHandleRecord {
2092 owner: u64,
2093 kind: u64,
2094 slot: u64,
2095 generation: u64,
2096}
2097
2098unsafe impl DeviceRepr for SemanticResidentHandleRecord {}
2100
2101const _: () = assert!(std::mem::size_of::<DeviceCommand>() == COMMAND_WORDS * 8);
2102const _: () = assert!(std::mem::align_of::<DeviceCommand>() == 8);
2103const _: () = assert!(std::mem::size_of::<DeviceReceipt>() == RECEIPT_WORDS * 8);
2104const _: () = assert!(std::mem::align_of::<DeviceReceipt>() == 8);
2105const _: () = assert!(std::mem::size_of::<SemanticResidentDecodedStatement>() == 76);
2106const _: () = assert!(std::mem::align_of::<SemanticResidentDecodedStatement>() == 4);
2107const _: () = assert!(std::mem::size_of::<SemanticResidentDecodedSupport>() == 136);
2108const _: () = assert!(std::mem::align_of::<SemanticResidentDecodedSupport>() == 4);
2109const _: () = assert!(std::mem::size_of::<SemanticResidentDecodedInput>() == 212);
2110const _: () = assert!(std::mem::align_of::<SemanticResidentDecodedInput>() == 4);
2111const _: () = assert!(std::mem::size_of::<SemanticResidentTruthValue>() == 32);
2112const _: () = assert!(std::mem::align_of::<SemanticResidentTruthValue>() == 8);
2113const _: () = assert!(std::mem::size_of::<SemanticResidentHandleRecord>() == 32);
2114const _: () = assert!(std::mem::align_of::<SemanticResidentHandleRecord>() == 8);
2115
2116pub(crate) struct SemanticResidentHandleSlot<'a> {
2118 allocation: &'a TrackedCudaSlice<SemanticResidentHandleRecord>,
2119 index: u32,
2120}
2121
2122impl<'a> SemanticResidentHandleSlot<'a> {
2123 #[cfg_attr(
2124 not(test),
2125 expect(
2126 dead_code,
2127 reason = "constructed by crate-internal device-resident pipelines"
2128 )
2129 )]
2130 pub(crate) fn new(
2131 allocation: &'a TrackedCudaSlice<SemanticResidentHandleRecord>,
2132 index: u32,
2133 ) -> Result<Self, SemanticHypergraphError> {
2134 validate_resident_bank_index(index, allocation.len(), "handle output")?;
2135 Ok(Self { allocation, index })
2136 }
2137}
2138
2139pub(crate) struct SemanticResidentRootHandleBank<'a> {
2141 allocation: &'a TrackedCudaSlice<SemanticResidentHandleRecord>,
2142 index: u32,
2143}
2144
2145impl SemanticResidentRootHandleBank<'_> {
2146 #[cfg_attr(
2147 not(test),
2148 expect(
2149 dead_code,
2150 reason = "consumed by crate-internal device-resident pipelines"
2151 )
2152 )]
2153 pub(crate) fn handle(&self) -> SemanticResidentRootHandle<'_> {
2154 SemanticResidentRootHandle::Bank {
2155 allocation: self.allocation,
2156 index: self.index,
2157 }
2158 }
2159}
2160
2161pub(crate) struct SemanticResidentStatementBank<'a> {
2163 allocation: &'a TrackedCudaSlice<SemanticResidentDecodedStatement>,
2164 index: u32,
2165}
2166
2167pub(crate) struct SemanticResidentDecodedInputBank<'a> {
2169 allocation: &'a TrackedCudaSlice<SemanticResidentDecodedInput>,
2170 index: u32,
2171}
2172
2173impl<'a> SemanticResidentDecodedInputBank<'a> {
2174 #[cfg_attr(
2175 not(test),
2176 expect(
2177 dead_code,
2178 reason = "constructed by crate-internal device-resident pipelines"
2179 )
2180 )]
2181 pub(crate) fn new(
2182 allocation: &'a TrackedCudaSlice<SemanticResidentDecodedInput>,
2183 index: u32,
2184 ) -> Result<Self, SemanticHypergraphError> {
2185 validate_resident_bank_index(index, allocation.len(), "decoded input")?;
2186 Ok(Self { allocation, index })
2187 }
2188}
2189
2190impl<'a> SemanticResidentStatementBank<'a> {
2191 #[cfg_attr(
2192 not(test),
2193 expect(
2194 dead_code,
2195 reason = "constructed by crate-internal device-resident pipelines"
2196 )
2197 )]
2198 pub(crate) fn new(
2199 allocation: &'a TrackedCudaSlice<SemanticResidentDecodedStatement>,
2200 index: u32,
2201 ) -> Result<Self, SemanticHypergraphError> {
2202 validate_resident_bank_index(index, allocation.len(), "decoded statement")?;
2203 Ok(Self { allocation, index })
2204 }
2205}
2206
2207pub(crate) struct SemanticResidentSupportBank<'a> {
2209 allocation: &'a TrackedCudaSlice<SemanticResidentDecodedSupport>,
2210 index: u32,
2211}
2212
2213impl<'a> SemanticResidentSupportBank<'a> {
2214 #[cfg_attr(
2215 not(test),
2216 expect(
2217 dead_code,
2218 reason = "constructed by crate-internal device-resident pipelines"
2219 )
2220 )]
2221 pub(crate) fn new(
2222 allocation: &'a TrackedCudaSlice<SemanticResidentDecodedSupport>,
2223 index: u32,
2224 ) -> Result<Self, SemanticHypergraphError> {
2225 validate_resident_bank_index(index, allocation.len(), "decoded support")?;
2226 Ok(Self { allocation, index })
2227 }
2228}
2229
2230pub(crate) struct SemanticResidentReceiptSlot<'a> {
2232 allocation: &'a TrackedCudaSlice<SemanticResidentReceiptRecord>,
2233 index: u32,
2234}
2235
2236impl<'a> SemanticResidentReceiptSlot<'a> {
2237 #[cfg_attr(
2238 not(test),
2239 expect(
2240 dead_code,
2241 reason = "constructed by crate-internal device-resident pipelines"
2242 )
2243 )]
2244 pub(crate) fn new(
2245 allocation: &'a TrackedCudaSlice<SemanticResidentReceiptRecord>,
2246 index: u32,
2247 ) -> Result<Self, SemanticHypergraphError> {
2248 validate_resident_bank_index(index, allocation.len(), "receipt")?;
2249 Ok(Self { allocation, index })
2250 }
2251}
2252
2253pub(crate) struct SemanticResidentReceiptView<'a> {
2255 allocation: &'a TrackedCudaSlice<SemanticResidentReceiptRecord>,
2256 index: u32,
2257}
2258
2259pub(crate) struct SemanticResidentTruthView<'a> {
2261 receipt: SemanticResidentReceiptView<'a>,
2262}
2263
2264pub(crate) struct SemanticResidentTruthSlot<'a> {
2266 allocation: &'a TrackedCudaSlice<SemanticResidentTruthValue>,
2267 index: u32,
2268}
2269
2270impl<'a> SemanticResidentTruthSlot<'a> {
2271 #[cfg_attr(
2272 not(test),
2273 expect(
2274 dead_code,
2275 reason = "constructed by crate-internal device-resident pipelines"
2276 )
2277 )]
2278 pub(crate) fn new(
2279 allocation: &'a TrackedCudaSlice<SemanticResidentTruthValue>,
2280 index: u32,
2281 ) -> Result<Self, SemanticHypergraphError> {
2282 validate_resident_bank_index(index, allocation.len(), "truth output")?;
2283 Ok(Self { allocation, index })
2284 }
2285}
2286
2287impl SemanticResidentReceiptView<'_> {
2288 pub(crate) fn record_read(&self, recorder: &mut crate::launch::LaunchRecorder) {
2289 recorder.read(self.allocation);
2290 }
2291}
2292
2293pub(crate) enum SemanticResidentRootHandle<'a> {
2295 Bank {
2296 allocation: &'a TrackedCudaSlice<SemanticResidentHandleRecord>,
2297 index: u32,
2298 },
2299 Receipt(SemanticResidentReceiptView<'a>),
2300}
2301
2302impl SemanticResidentRootHandle<'_> {
2303 fn record_input(
2304 self,
2305 descriptor: &mut DeviceLaunchDescriptor,
2306 recorder: &mut crate::launch::LaunchRecorder,
2307 ) {
2308 match self {
2309 Self::Bank { allocation, index } => {
2310 recorder.read(allocation);
2311 descriptor.handle_ptr = allocation.device_ptr_value();
2312 descriptor.handle_index = u64::from(index);
2313 }
2314 Self::Receipt(receipt) => {
2315 receipt.record_read(recorder);
2316 descriptor.source_receipt_ptr = receipt.allocation.device_ptr_value();
2317 descriptor.source_receipt_index = u64::from(receipt.index);
2318 }
2319 }
2320 }
2321}
2322
2323pub(crate) struct SemanticResidentCandidateHandle<'a> {
2325 receipt: SemanticResidentReceiptView<'a>,
2326}
2327
2328#[cfg_attr(
2330 not(test),
2331 expect(
2332 dead_code,
2333 reason = "root and fork construction is exercised by the CUDA resident-query qualification"
2334 )
2335)]
2336pub(crate) enum SemanticResidentView<'a> {
2337 Root(SemanticResidentRootHandle<'a>),
2338 Fork(SemanticResidentCandidateHandle<'a>),
2339}
2340
2341pub(crate) struct SemanticResidentMutationReceipt<'a> {
2343 receipt: SemanticResidentReceiptView<'a>,
2344}
2345
2346impl SemanticResidentMutationReceipt<'_> {
2347 #[cfg_attr(
2348 not(test),
2349 expect(
2350 dead_code,
2351 reason = "consumed by crate-internal device-resident pipelines"
2352 )
2353 )]
2354 pub(crate) fn candidate_handle(&self) -> SemanticResidentCandidateHandle<'_> {
2355 SemanticResidentCandidateHandle {
2356 receipt: SemanticResidentReceiptView {
2357 allocation: self.receipt.allocation,
2358 index: self.receipt.index,
2359 },
2360 }
2361 }
2362}
2363
2364pub(crate) struct SemanticResidentSealReceipt<'a> {
2366 receipt: SemanticResidentReceiptView<'a>,
2367}
2368
2369impl SemanticResidentSealReceipt<'_> {
2370 #[cfg_attr(
2371 not(test),
2372 expect(
2373 dead_code,
2374 reason = "consumed by crate-internal device-resident pipelines"
2375 )
2376 )]
2377 pub(crate) fn root_handle(&self) -> SemanticResidentRootHandle<'_> {
2378 SemanticResidentRootHandle::Receipt(SemanticResidentReceiptView {
2379 allocation: self.receipt.allocation,
2380 index: self.receipt.index,
2381 })
2382 }
2383}
2384
2385pub(crate) struct SemanticResidentTruthReceipt<'a> {
2387 receipt: SemanticResidentReceiptView<'a>,
2388}
2389
2390impl SemanticResidentTruthReceipt<'_> {
2391 #[cfg_attr(
2392 not(test),
2393 expect(
2394 dead_code,
2395 reason = "consumed by crate-internal device-resident pipelines"
2396 )
2397 )]
2398 pub(crate) fn device_view(&self) -> SemanticResidentTruthView<'_> {
2399 SemanticResidentTruthView {
2400 receipt: SemanticResidentReceiptView {
2401 allocation: self.receipt.allocation,
2402 index: self.receipt.index,
2403 },
2404 }
2405 }
2406}
2407
2408#[derive(Clone, Copy)]
2409struct SlotLedger {
2410 generation: u64,
2411 live: bool,
2412}
2413
2414impl SlotLedger {
2415 const FREE: Self = Self {
2416 generation: 1,
2417 live: false,
2418 };
2419}
2420
2421struct CurrentFork {
2422 handle: SemanticForkHandle,
2423 staged_statements: Vec<u32>,
2424 staged_supports: Vec<u32>,
2425 staged_versions: Vec<u32>,
2426}
2427
2428#[derive(Clone, Copy)]
2429enum ArenaAccess {
2430 Read,
2431 ReadWrite,
2432}
2433
2434struct SemanticKernelLaunchSpec<'a> {
2435 domain: &'a ResidentExecutionDomain,
2436 execute: &'a CudaFunction,
2437 owner: u64,
2438 capacities: SemanticHypergraphCapacities,
2439 arena_words: u64,
2440}
2441
2442#[repr(C)]
2443#[derive(Clone, Copy)]
2444struct DeviceLaunchDescriptor {
2445 expected_owner: u64,
2446 root_capacity: u64,
2447 statement_capacity: u64,
2448 support_capacity: u64,
2449 version_capacity: u64,
2450 arena_words: u64,
2451 command_ptr: u64,
2452 command_index: u64,
2453 handle_ptr: u64,
2454 handle_index: u64,
2455 source_receipt_ptr: u64,
2456 source_receipt_index: u64,
2457 decoded_input_ptr: u64,
2458 decoded_input_index: u64,
2459 decoded_statement_ptr: u64,
2460 decoded_statement_index: u64,
2461 decoded_support_ptr: u64,
2462 decoded_support_index: u64,
2463 output_ptr: u64,
2464 output_index: u64,
2465 receipt_ptr: u64,
2466 receipt_index: u64,
2467 admission: u64,
2468 abi_generation: u64,
2469}
2470
2471const _: () = assert!(std::mem::size_of::<DeviceLaunchDescriptor>() == 192);
2472const _: () = assert!(std::mem::align_of::<DeviceLaunchDescriptor>() == 8);
2473
2474unsafe impl DeviceRepr for DeviceLaunchDescriptor {}
2476
2477struct DeviceLaunchDescriptorParam(DeviceLaunchDescriptor);
2478
2479impl crate::cuda_compat::KernelParamStorage for DeviceLaunchDescriptorParam {
2480 fn as_kernel_param(&self) -> *mut std::ffi::c_void {
2481 (&self.0 as *const DeviceLaunchDescriptor).cast_mut().cast()
2482 }
2483}
2484
2485impl crate::cuda_compat::IntoKernelParamStorage for DeviceLaunchDescriptor {
2486 type Storage = DeviceLaunchDescriptorParam;
2487
2488 fn into_kernel_param_storage(self) -> Self::Storage {
2489 DeviceLaunchDescriptorParam(self)
2490 }
2491}
2492
2493enum SemanticKernelInput<'a> {
2494 HostCommand {
2495 commands: &'a TrackedCudaSlice<DeviceCommand>,
2496 index: u32,
2497 },
2498 ResidentEmptyRootHandle {
2499 handle: &'a TrackedCudaSlice<SemanticResidentHandleRecord>,
2500 index: u32,
2501 },
2502 ResidentFork {
2503 root: SemanticResidentRootHandle<'a>,
2504 },
2505 ResidentPreflightTransition {
2506 root: SemanticResidentRootHandle<'a>,
2507 },
2508 ResidentInsertSupport {
2509 candidate: SemanticResidentCandidateHandle<'a>,
2510 statement: SemanticResidentStatementBank<'a>,
2511 support: SemanticResidentSupportBank<'a>,
2512 },
2513 ResidentSeal {
2514 candidate: SemanticResidentCandidateHandle<'a>,
2515 },
2516 ResidentTruth {
2517 view: SemanticResidentView<'a>,
2518 statement: SemanticResidentStatementBank<'a>,
2519 },
2520 ResidentConsumeTruth {
2521 truth: SemanticResidentTruthView<'a>,
2522 output: SemanticResidentTruthSlot<'a>,
2523 },
2524 ResidentMaterializeDecoded {
2525 input: SemanticResidentDecodedInputBank<'a>,
2526 statement: SemanticResidentStatementBank<'a>,
2527 support: SemanticResidentSupportBank<'a>,
2528 },
2529}
2530
2531struct SemanticKernelLaunchIo<'a> {
2532 arena: &'a mut TrackedCudaSlice<u64>,
2533 arena_access: ArenaAccess,
2534 input: SemanticKernelInput<'a>,
2535 receipts: &'a TrackedCudaSlice<DeviceReceipt>,
2536 receipt_index: u32,
2537}
2538
2539pub struct SemanticHypergraph {
2541 domain: ResidentExecutionDomain,
2542 stream: Arc<CudaStream>,
2543 execute: CudaFunction,
2544 arena: TrackedCudaSlice<u64>,
2545 command: TrackedCudaSlice<DeviceCommand>,
2546 receipt: TrackedCudaSlice<DeviceReceipt>,
2547 arena_words: u64,
2548 owner: u64,
2549 capacities: SemanticHypergraphCapacities,
2550 roots: Vec<SlotLedger>,
2551 fork: SlotLedger,
2552 statements: Vec<SlotLedger>,
2553 supports: Vec<SlotLedger>,
2554 versions: Vec<SlotLedger>,
2555 current_fork: Option<CurrentFork>,
2556 empty_root: SemanticRootHandle,
2557 admission: Option<SemanticAdmission>,
2558 stats: SemanticHypergraphExecutionStats,
2559 device_controlled: bool,
2560 poisoned: bool,
2561 provider: Arc<CudaKernelProvider>,
2563}
2564
2565impl SemanticHypergraph {
2566 pub(crate) fn transition_owner(
2567 &self,
2568 ) -> Result<(Arc<CudaKernelProvider>, ResidentExecutionDomain), SemanticHypergraphError> {
2569 self.ensure_host_facade_available()?;
2570 if self.current_fork.is_some() || self.admission.is_none() {
2571 return Err(admission_error(
2572 "transition requires admitted records and no live candidate",
2573 ));
2574 }
2575 Ok((Arc::clone(&self.provider), self.domain.clone()))
2576 }
2577
2578 pub(crate) fn record_transition(&self, recorder: &mut crate::launch::LaunchRecorder) {
2581 recorder.read_write(&self.arena);
2582 }
2583
2584 pub(crate) fn transition_arena(&self) -> [u64; 7] {
2585 [
2586 self.arena.device_ptr_value(),
2587 self.owner,
2588 self.capacities.roots as u64,
2589 self.capacities.statements as u64,
2590 self.capacities.supports as u64,
2591 self.capacities.versions as u64,
2592 self.arena_words,
2593 ]
2594 }
2595
2596 pub(crate) fn transition_arena_view(&self) -> crate::memory::DeviceMemoryView<u64> {
2597 self.arena.view()
2598 }
2599
2600 #[cfg(feature = "semantic-policy")]
2603 pub(crate) fn export_retained_transition_root(
2604 &self,
2605 arena: &[u64],
2606 owner: u64,
2607 slot: u64,
2608 generation: u64,
2609 digest: [u8; 32],
2610 extents: [u64; 3],
2611 ) -> Result<SemanticRootMaterial, SemanticHypergraphError> {
2612 self.ensure_not_poisoned()?;
2613 if owner != self.owner {
2614 return Err(SemanticHypergraphError::ForeignHandle {
2615 kind: SemanticHandleKind::Root,
2616 });
2617 }
2618 let extents = extents.map(u32::try_from);
2619 let [statements, supports, versions] = extents;
2620 let snapshot = SemanticRootSnapshot::new(
2621 SemanticRootDigest(digest),
2622 SemanticExtents::new(
2623 statements.map_err(|_| size_overflow())?,
2624 supports.map_err(|_| size_overflow())?,
2625 versions.map_err(|_| size_overflow())?,
2626 ),
2627 );
2628 let slot = checked_receipt_slot(slot, SemanticHandleKind::Root, self.capacities.roots)?;
2629 material_from_arena(
2630 arena,
2631 self.capacities,
2632 SemanticRootHandle::new(owner, slot, generation),
2633 snapshot,
2634 self.admission
2635 .as_ref()
2636 .ok_or_else(|| admission_error("retained root requires typed admission"))?,
2637 )
2638 }
2639
2640 #[cfg(feature = "semantic-policy")]
2643 pub(crate) fn transition_root_material_capacity(
2644 &self,
2645 ) -> Result<usize, SemanticHypergraphError> {
2646 let admission = self
2647 .admission
2648 .as_ref()
2649 .ok_or_else(|| admission_error("root capacity requires typed admission"))?;
2650 let (records, symbols) = normalized_material_admission(admission)?;
2651 let prefix = SemanticRootMaterial::encode_admission(&records, &symbols)?.len();
2652 (self.capacities.versions as usize)
2653 .checked_mul(1 + 4 + 10 * 4 + 4 + 32)
2654 .and_then(|bytes| bytes.checked_add(prefix))
2655 .and_then(|bytes| bytes.checked_add(4 + 2 * 32 + 2 * 3 * 4))
2656 .ok_or_else(size_overflow)
2657 }
2658
2659 pub(crate) fn enter_transition(&mut self) {
2660 self.device_controlled = true;
2661 }
2662
2663 pub(crate) fn observe_transition_root(
2664 &mut self,
2665 words: [u64; 42],
2666 ) -> Result<(SemanticRootHandle, SemanticRootSnapshot), SemanticHypergraphError> {
2667 let receipt = DeviceReceipt { words };
2668 self.expect_success(&receipt)?;
2669 let result = (|| {
2670 let slot =
2671 checked_receipt_slot(words[3], SemanticHandleKind::Root, self.capacities.roots)?;
2672 if words[39] != self.owner || words[4] == 0 {
2673 return self.corrupt("transition root receipt has invalid owner or generation");
2674 }
2675 Ok((
2676 SemanticRootHandle::new(self.owner, slot, words[4]),
2677 SemanticRootSnapshot::new(
2678 SemanticRootDigest(receipt_identity(&receipt, 16)),
2679 receipt_extents(&receipt)?,
2680 ),
2681 ))
2682 })();
2683 poison_after_reconciliation_error(&mut self.poisoned, result)
2684 }
2685
2686 pub(crate) fn observe_transition_edit(
2687 &mut self,
2688 words: [u64; 42],
2689 ) -> Result<Option<SemanticInsertOutcome>, SemanticHypergraphError> {
2690 let receipt = DeviceReceipt { words };
2691 self.expect_success(&receipt)?;
2692 let result = (|| {
2693 if words[39] != self.owner
2694 || (words[1] == OUTCOME_INSERTED
2695 && (words[8] == 0 || words[10] == 0 || words[12] == 0))
2696 || (words[1] == OUTCOME_UNCHANGED && words[12] == 0)
2697 {
2698 return self.corrupt("transition edit receipt has invalid owner or generation");
2699 }
2700 match words[1] {
2701 0 => Ok(None),
2702 OUTCOME_UNCHANGED => Ok(Some(SemanticInsertOutcome::Unchanged(
2703 self.version_ref(&receipt)?,
2704 ))),
2705 OUTCOME_INSERTED => Ok(Some(SemanticInsertOutcome::Inserted(
2706 SemanticInsertedSupport {
2707 statement: self.statement_ref(&receipt)?,
2708 support: self.support_ref(&receipt)?,
2709 version: self.version_ref(&receipt)?,
2710 previous_truth: inserted_support_previous_truth(&receipt)?,
2711 },
2712 ))),
2713 _ => self.corrupt("invalid transition edit outcome"),
2714 }
2715 })();
2716 poison_after_reconciliation_error(&mut self.poisoned, result)
2717 }
2718}
2719
2720impl CudaKernelProvider {
2721 pub fn allocate_semantic_hypergraph(
2723 self: &Arc<Self>,
2724 domain: &ResidentExecutionDomain,
2725 capacities: SemanticHypergraphCapacities,
2726 ) -> Result<SemanticHypergraph, SemanticHypergraphError> {
2727 validate_execution_domain(self, domain)
2728 .map_err(|error| runtime_error("domain validation", error))?;
2729 let execute = self
2730 .device()
2731 .inner()
2732 .get_func(MODULE, KERNEL)
2733 .ok_or(SemanticHypergraphError::KernelUnavailable)?;
2734 let arena_words = checked_arena_words(capacities)?;
2735 let arena_bytes =
2736 arena_words
2737 .checked_mul(8)
2738 .ok_or_else(|| SemanticHypergraphError::InvalidInput {
2739 detail: "semantic arena byte size overflow".into(),
2740 })?;
2741 let total_bytes = arena_bytes
2742 .checked_add(std::mem::size_of::<DeviceCommand>() as u64)
2743 .and_then(|bytes| bytes.checked_add(std::mem::size_of::<DeviceReceipt>() as u64))
2744 .ok_or_else(|| SemanticHypergraphError::InvalidInput {
2745 detail: "semantic allocation byte size overflow".into(),
2746 })?;
2747 let arena_len =
2748 usize::try_from(arena_words).map_err(|_| SemanticHypergraphError::InvalidInput {
2749 detail: "semantic arena exceeds platform usize".into(),
2750 })?;
2751 let mut reservation = self
2752 .memory()
2753 .reserve_bytes(total_bytes)
2754 .map_err(|error| runtime_error("reservation", error))?;
2755 let arena = reservation
2756 .alloc::<u64>(arena_len)
2757 .map_err(|error| runtime_error("arena allocation", error))?;
2758 let command = reservation
2759 .alloc::<DeviceCommand>(1)
2760 .map_err(|error| runtime_error("command allocation", error))?;
2761 let receipt = reservation
2762 .alloc::<DeviceReceipt>(1)
2763 .map_err(|error| runtime_error("receipt allocation", error))?;
2764 if reservation.remaining_bytes() != 0 {
2765 return Err(SemanticHypergraphError::CorruptLineage {
2766 detail: format!(
2767 "{} reserved semantic bytes were not materialized",
2768 reservation.remaining_bytes()
2769 ),
2770 });
2771 }
2772 let runtime = self
2773 .memory()
2774 .runtime()
2775 .ok_or_else(|| SemanticHypergraphError::Runtime {
2776 operation: "stream resolution",
2777 detail: "provider has no device runtime".into(),
2778 })?;
2779 let stream = runtime
2780 .stream_pool()
2781 .resolve(domain.stream_id())
2782 .ok_or_else(|| SemanticHypergraphError::Runtime {
2783 operation: "stream resolution",
2784 detail: "resident stream id is not live in provider runtime".into(),
2785 })?;
2786 let owner = NEXT_OWNER_ID
2787 .try_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
2788 value.checked_add(1).filter(|next| *next != 0)
2789 })
2790 .map_err(|_| SemanticHypergraphError::GenerationExhausted {
2791 kind: SemanticHandleKind::Root,
2792 slot: 0,
2793 })?;
2794 let mut graph = SemanticHypergraph {
2795 provider: Arc::clone(self),
2796 domain: domain.clone(),
2797 stream,
2798 execute,
2799 arena,
2800 command,
2801 receipt,
2802 arena_words,
2803 owner,
2804 capacities,
2805 roots: vec![SlotLedger::FREE; capacities.roots as usize],
2806 fork: SlotLedger::FREE,
2807 statements: vec![SlotLedger::FREE; capacities.statements as usize],
2808 supports: vec![SlotLedger::FREE; capacities.supports as usize],
2809 versions: vec![SlotLedger::FREE; capacities.versions as usize],
2810 current_fork: None,
2811 empty_root: SemanticRootHandle::new(owner, 0, 1),
2812 admission: None,
2813 stats: SemanticHypergraphExecutionStats::default(),
2814 device_controlled: false,
2815 poisoned: false,
2816 };
2817 let command = graph.command_for(OP_INITIALIZE);
2818 let receipt = graph.run(command, ArenaAccess::ReadWrite)?;
2819 graph.expect_success(&receipt)?;
2820 if receipt.words[3] != 0 || receipt.words[4] != 1 {
2821 return Err(SemanticHypergraphError::CorruptLineage {
2822 detail: "CUDA initialization did not publish empty root slot 0 generation 1".into(),
2823 });
2824 }
2825 graph.roots[0].live = true;
2826 Ok(graph)
2827 }
2828}
2829
2830impl SemanticHypergraph {
2831 pub fn admit_initial_records(
2843 mut self,
2844 records: SemanticAdmissionRecords,
2845 initial_supports: &[u32],
2846 limits: SemanticAdmissionLimits,
2847 ) -> Result<Self, SemanticHypergraphError> {
2848 self.ensure_host_facade_available()?;
2849 if initial_supports.len() > limits.max_records as usize {
2850 return Err(admission_error(
2851 "initial support count exceeds admission bound",
2852 ));
2853 }
2854 if initial_supports
2855 .iter()
2856 .any(|&index| records.supports.get(index as usize).is_none())
2857 {
2858 return Err(admission_error(
2859 "initial support is outside the declared records",
2860 ));
2861 }
2862 let empty = self.empty_root();
2863 self.admit_records(empty, records, limits)?;
2864 if initial_supports.is_empty() {
2865 return Ok(self);
2866 }
2867 let fork = self.fork(empty)?;
2868 for &index in initial_supports {
2869 let admission = self.admission.as_ref().expect("validated cold admission");
2870 let statement = admission.records.supports[index as usize].statement;
2871 let key = admission.statement_key(statement)?;
2872 let event = admission.support_event(index)?;
2873 self.insert_support(fork, &key, &event)?;
2874 }
2875 let base = self.seal(fork)?;
2876 let base_snapshot = self.snapshot(SemanticView::Root(base))?;
2877 let admission = self.admission.as_mut().expect("validated cold admission");
2878 let identity = derive_admission_identity(
2879 &admission.records,
2880 admission.schema_generation,
2881 &admission.encoded_records,
2882 &admission.statement_keys,
2883 &admission.support_events,
2884 &base_snapshot,
2885 );
2886 admission.base = base;
2887 admission.base_snapshot = base_snapshot;
2888 admission.identity = identity;
2889 Ok(self)
2890 }
2891
2892 pub fn admit_records(
2898 &mut self,
2899 base: SemanticRootHandle,
2900 records: SemanticAdmissionRecords,
2901 limits: SemanticAdmissionLimits,
2902 ) -> Result<&SemanticAdmission, SemanticHypergraphError> {
2903 self.ensure_host_facade_available()?;
2904 self.validate_root(base)?;
2905 if self.admission.is_some() || self.current_fork.is_some() {
2906 return Err(admission_error(
2907 "typed admission requires an unbound owner with no live fork",
2908 ));
2909 }
2910 let admission = admit_semantic_records(records, limits, base, || {
2911 self.snapshot(SemanticView::Root(base))
2912 })?;
2913 self.admission = Some(admission);
2914 Ok(self
2915 .admission
2916 .as_ref()
2917 .expect("just published complete admission"))
2918 }
2919
2920 pub fn admission(&self) -> Option<&SemanticAdmission> {
2922 self.admission.as_ref()
2923 }
2924
2925 pub(crate) fn export_transition_root_parts(
2928 &mut self,
2929 owner: u64,
2930 slot: u64,
2931 generation: u64,
2932 ) -> Result<SemanticRootMaterial, SemanticHypergraphError> {
2933 self.ensure_not_poisoned()?;
2934 if owner != self.owner {
2935 return Err(SemanticHypergraphError::ForeignHandle {
2936 kind: SemanticHandleKind::Root,
2937 });
2938 }
2939 let slot = checked_receipt_slot(slot, SemanticHandleKind::Root, self.capacities.roots)?;
2940 self.export_transition_root(SemanticRootHandle::new(owner, slot, generation))
2941 }
2942
2943 pub(crate) fn export_transition_root(
2945 &mut self,
2946 root: SemanticRootHandle,
2947 ) -> Result<SemanticRootMaterial, SemanticHypergraphError> {
2948 self.ensure_not_poisoned()?;
2949 if root.owner != self.owner {
2950 return Err(SemanticHypergraphError::ForeignHandle {
2951 kind: SemanticHandleKind::Root,
2952 });
2953 }
2954 if self.admission.is_none() {
2955 return Err(admission_error(
2956 "root material export requires typed admission",
2957 ));
2958 }
2959 let mut command = self.command_for(OP_SNAPSHOT);
2960 write_view(&mut command, SemanticView::Root(root));
2961 let receipt = self.run(command, ArenaAccess::Read)?;
2962 self.expect_success(&receipt)?;
2963 let result = (|| {
2964 let snapshot = SemanticRootSnapshot::new(
2965 SemanticRootDigest(receipt_identity(&receipt, 16)),
2966 receipt_extents(&receipt)?,
2967 );
2968 let arena_words = usize::try_from(self.arena_words).map_err(|_| size_overflow())?;
2969 let mut arena = vec![0; arena_words];
2970 self.provider
2971 .dtoh_sync_copy_into_tracked(&self.arena, &mut arena)
2972 .map_err(|error| runtime_error("root material arena read", error))?;
2973 material_from_arena(
2974 &arena,
2975 self.capacities,
2976 root,
2977 snapshot,
2978 self.admission.as_ref().expect("checked typed admission"),
2979 )
2980 })();
2981 poison_after_reconciliation_error(&mut self.poisoned, result)
2982 }
2983
2984 pub(crate) fn restore_root(
2987 &mut self,
2988 material: &SemanticRootMaterial,
2989 ) -> Result<SemanticRootHandle, SemanticHypergraphError> {
2990 self.ensure_host_facade_available()?;
2991 let admission = self
2992 .admission
2993 .as_ref()
2994 .ok_or_else(|| admission_error("root restoration requires typed admission"))?;
2995 if admission.base != self.empty_root
2996 || self.current_fork.is_some()
2997 || self.fork.generation != 1
2998 || self.roots.iter().filter(|slot| slot.live).count() != 1
2999 || self.statements.iter().any(|slot| slot.live)
3000 || self.supports.iter().any(|slot| slot.live)
3001 || self.versions.iter().any(|slot| slot.live)
3002 {
3003 return Err(admission_error(
3004 "root restoration requires a fresh empty native owner",
3005 ));
3006 }
3007 material.validate_lineage(admission)?;
3008 let base_versions = material.admission_base_extents[2] as usize;
3009 let needed_roots = 1
3010 + u32::from(!material.insertions.is_empty())
3011 + u32::from(base_versions > 0 && base_versions < material.insertions.len());
3012 if material.extents[0] > self.capacities.statements
3013 || material.extents[1] > self.capacities.supports
3014 || material.extents[2] > self.capacities.versions
3015 || self.capacities.roots < needed_roots
3016 {
3017 return Err(admission_error(
3018 "root material exceeds fresh native capacities",
3019 ));
3020 }
3021 let result = (|| {
3022 let mut admission_root = self.empty_root;
3023 let root = if material.insertions.is_empty() {
3024 self.empty_root
3025 } else {
3026 let mut fork = self.fork(self.empty_root)?;
3027 for (index, insertion) in material.insertions.iter().enumerate() {
3028 let admission = self.admission.as_ref().expect("checked typed admission");
3029 let key = material_statement_key(
3030 admission,
3031 insertion.statement,
3032 &insertion.reconstruction,
3033 )?;
3034 let event = admission.support_event(insertion.support)?;
3035 match self.insert_support_reconstructed(
3036 fork,
3037 &key,
3038 &event,
3039 &insertion.reconstruction,
3040 )? {
3041 SemanticInsertOutcome::Inserted(inserted)
3042 if inserted.version.identity.0 == insertion.version => {}
3043 _ => {
3044 return self
3045 .corrupt("restored insertion differs from canonical root material")
3046 }
3047 }
3048 if index + 1 == base_versions && base_versions < material.insertions.len() {
3049 admission_root = self.seal(fork)?;
3050 fork = self.fork(admission_root)?;
3051 }
3052 }
3053 self.seal(fork)?
3054 };
3055 if base_versions == material.insertions.len() {
3056 admission_root = root;
3057 }
3058 let snapshot = self.snapshot(SemanticView::Root(root))?;
3059 if snapshot.digest.0 != material.digest
3060 || snapshot.extents
3061 != SemanticExtents::new(
3062 material.extents[0],
3063 material.extents[1],
3064 material.extents[2],
3065 )
3066 {
3067 return self.corrupt(
3068 "fresh native root differs from restored digest or reachable extents",
3069 );
3070 }
3071 let base_snapshot = if admission_root == root {
3072 snapshot
3073 } else {
3074 self.snapshot(SemanticView::Root(admission_root))?
3075 };
3076 if base_snapshot.digest.0 != material.admission_base_digest
3077 || base_snapshot.extents
3078 != SemanticExtents::new(
3079 material.admission_base_extents[0],
3080 material.admission_base_extents[1],
3081 material.admission_base_extents[2],
3082 )
3083 {
3084 return self.corrupt(
3085 "fresh native admission base differs from its retained original binding",
3086 );
3087 }
3088 let admission = self.admission.as_mut().expect("checked typed admission");
3089 admission.base = admission_root;
3090 admission.base_snapshot = base_snapshot;
3091 admission.identity = derive_admission_identity(
3092 &admission.records,
3093 admission.schema_generation,
3094 &admission.encoded_records,
3095 &admission.statement_keys,
3096 &admission.support_events,
3097 &base_snapshot,
3098 );
3099 Ok(root)
3100 })();
3101 poison_after_reconciliation_error(&mut self.poisoned, result)
3102 }
3103
3104 fn validate_statement_key(
3105 &self,
3106 key: &SemanticStatementKey,
3107 ) -> Result<(), SemanticHypergraphError> {
3108 if key.owner != self.owner
3109 || self
3110 .admission
3111 .as_ref()
3112 .and_then(|admission| admission.statement_keys.get(key.record as usize))
3113 != Some(&Some(*key))
3114 {
3115 return Err(admission_error(
3116 "statement key is not from this owner's typed admission",
3117 ));
3118 }
3119 Ok(())
3120 }
3121
3122 pub const fn empty_root(&self) -> SemanticRootHandle {
3123 self.empty_root
3124 }
3125
3126 pub const fn execution_stats(&self) -> SemanticHypergraphExecutionStats {
3127 self.stats
3128 }
3129
3130 pub fn fork(
3131 &mut self,
3132 base: SemanticRootHandle,
3133 ) -> Result<SemanticForkHandle, SemanticHypergraphError> {
3134 self.ensure_host_facade_available()?;
3135 self.validate_root(base)?;
3136 if self.current_fork.is_some() {
3137 return Err(SemanticHypergraphError::InactiveHandle {
3138 kind: SemanticHandleKind::Fork,
3139 slot: 0,
3140 });
3141 }
3142 let mut command = self.command_for(OP_FORK);
3143 command.words[1] = base.owner;
3144 command.words[8] = u64::from(base.slot);
3145 command.words[9] = base.generation;
3146 let receipt = self.run(command, ArenaAccess::ReadWrite)?;
3147 self.expect_success(&receipt)?;
3148 let result = (|| {
3149 let slot = checked_receipt_slot(receipt.words[5], SemanticHandleKind::Fork, 1)?;
3150 let generation = receipt.words[6];
3151 if slot != 0 || generation != self.fork.generation {
3152 return self.corrupt("fork receipt disagrees with host physical generation");
3153 }
3154 self.fork.live = true;
3155 let handle = SemanticForkHandle::new(self.owner, slot, generation);
3156 self.current_fork = Some(CurrentFork {
3157 handle,
3158 staged_statements: Vec::new(),
3159 staged_supports: Vec::new(),
3160 staged_versions: Vec::new(),
3161 });
3162 Ok(handle)
3163 })();
3164 poison_after_reconciliation_error(&mut self.poisoned, result)
3165 }
3166
3167 pub fn insert_support(
3168 &mut self,
3169 fork: SemanticForkHandle,
3170 statement: &SemanticStatementKey,
3171 event: &SemanticSupportEvent,
3172 ) -> Result<SemanticInsertOutcome, SemanticHypergraphError> {
3173 self.insert_support_reconstructed(fork, statement, event, &[0; 10])
3174 }
3175
3176 fn insert_support_reconstructed(
3177 &mut self,
3178 fork: SemanticForkHandle,
3179 statement: &SemanticStatementKey,
3180 event: &SemanticSupportEvent,
3181 reconstruction: &[u32; 10],
3182 ) -> Result<SemanticInsertOutcome, SemanticHypergraphError> {
3183 self.ensure_host_facade_available()?;
3184 self.validate_fork(fork)?;
3185 if statement.record == u32::MAX {
3186 let admission = self.admission.as_ref().ok_or_else(|| {
3187 admission_error("derived insertion requires retained typed admission")
3188 })?;
3189 if material_statement_key(admission, None, reconstruction)? != *statement {
3190 return Err(admission_error(
3191 "derived insertion key differs from its typed reconstruction",
3192 ));
3193 }
3194 } else {
3195 if *reconstruction != [0; 10] {
3196 return Err(admission_error(
3197 "original insertion has derived reconstruction references",
3198 ));
3199 }
3200 self.validate_statement_key(statement)?;
3201 }
3202 if event.owner != self.owner {
3203 return Err(admission_error(
3204 "support event does not belong to this owner's typed admission",
3205 ));
3206 }
3207 let mut command = self.command_for(OP_INSERT_SUPPORT);
3208 encode_support_insertion(&mut command, fork, statement, event, reconstruction);
3209 let receipt = self.run(command, ArenaAccess::ReadWrite)?;
3210 self.expect_success(&receipt)?;
3211 let result = (|| {
3212 let statement_ref = self.statement_ref(&receipt)?;
3213 let version_ref = self.version_ref(&receipt)?;
3214 match receipt.words[1] {
3215 OUTCOME_UNCHANGED => Ok(SemanticInsertOutcome::Unchanged(version_ref)),
3216 OUTCOME_INSERTED => {
3217 let previous_truth = inserted_support_previous_truth(&receipt)?;
3218 let support_ref = self.support_ref(&receipt)?;
3219 let flags = receipt.words[32];
3220 if flags & INSERT_NEW_STATEMENT != 0 {
3221 self.publish_statement(statement_ref.handle)?;
3222 self.current_fork
3223 .as_mut()
3224 .expect("validated live fork")
3225 .staged_statements
3226 .push(statement_ref.handle.slot);
3227 } else {
3228 self.validate_statement(statement_ref.handle)?;
3229 }
3230 self.publish_support(support_ref.handle)?;
3231 self.publish_version(version_ref.handle)?;
3232 let current = self.current_fork.as_mut().expect("validated live fork");
3233 current.staged_supports.push(support_ref.handle.slot);
3234 current.staged_versions.push(version_ref.handle.slot);
3235 Ok(SemanticInsertOutcome::Inserted(SemanticInsertedSupport {
3236 statement: statement_ref,
3237 support: support_ref,
3238 version: version_ref,
3239 previous_truth,
3240 }))
3241 }
3242 other => self.corrupt(format!("invalid insertion outcome {other}")),
3243 }
3244 })();
3245 poison_after_reconciliation_error(&mut self.poisoned, result)
3246 }
3247
3248 pub fn discard(&mut self, fork: SemanticForkHandle) -> Result<(), SemanticHypergraphError> {
3249 self.ensure_host_facade_available()?;
3250 self.validate_fork(fork)?;
3251 let mut command = self.command_for(OP_DISCARD);
3252 command.words[1] = fork.owner;
3253 command.words[8] = u64::from(fork.slot);
3254 command.words[9] = fork.generation;
3255 let receipt = self.run(command, ArenaAccess::ReadWrite)?;
3256 self.expect_success(&receipt)?;
3257 let result = (|| {
3258 let current = self.current_fork.take().expect("validated live fork");
3259 for slot in current.staged_statements {
3260 retire_slot(
3261 &mut self.statements[slot as usize],
3262 SemanticHandleKind::Statement,
3263 slot,
3264 )?;
3265 }
3266 for slot in current.staged_supports {
3267 retire_slot(
3268 &mut self.supports[slot as usize],
3269 SemanticHandleKind::Support,
3270 slot,
3271 )?;
3272 }
3273 for slot in current.staged_versions {
3274 retire_slot(
3275 &mut self.versions[slot as usize],
3276 SemanticHandleKind::Version,
3277 slot,
3278 )?;
3279 }
3280 retire_slot(&mut self.fork, SemanticHandleKind::Fork, 0)?;
3281 if receipt.words[6] != self.fork.generation {
3282 return self.corrupt("discard receipt disagrees with advanced fork generation");
3283 }
3284 Ok(())
3285 })();
3286 poison_after_reconciliation_error(&mut self.poisoned, result)
3287 }
3288
3289 pub fn seal(
3290 &mut self,
3291 fork: SemanticForkHandle,
3292 ) -> Result<SemanticRootHandle, SemanticHypergraphError> {
3293 self.ensure_host_facade_available()?;
3294 self.validate_fork(fork)?;
3295 let mut command = self.command_for(OP_SEAL);
3296 command.words[1] = fork.owner;
3297 command.words[8] = u64::from(fork.slot);
3298 command.words[9] = fork.generation;
3299 let receipt = self.run(command, ArenaAccess::ReadWrite)?;
3300 self.expect_success(&receipt)?;
3301 let result = (|| {
3302 let slot = checked_receipt_slot(
3303 receipt.words[3],
3304 SemanticHandleKind::Root,
3305 self.capacities.roots,
3306 )?;
3307 let generation = receipt.words[4];
3308 let unchanged = match receipt.words[1] {
3309 OUTCOME_INSERTED => false,
3310 OUTCOME_UNCHANGED => true,
3311 _ => return self.corrupt("seal receipt has an invalid outcome"),
3312 };
3313 if unchanged {
3314 let current = self.current_fork.as_ref().expect("validated live fork");
3315 if !current.staged_statements.is_empty()
3316 || !current.staged_supports.is_empty()
3317 || !current.staged_versions.is_empty()
3318 {
3319 return self.corrupt("unchanged seal discarded staged insertions");
3320 }
3321 }
3322 let root_ledger = &mut self.roots[slot as usize];
3323 if root_ledger.live != unchanged || root_ledger.generation != generation {
3324 return self.corrupt("seal receipt disagrees with root physical generation");
3325 }
3326 root_ledger.live = true;
3327 self.current_fork.take().expect("validated live fork");
3328 retire_slot(&mut self.fork, SemanticHandleKind::Fork, 0)?;
3329 if receipt.words[6] != self.fork.generation {
3330 return self.corrupt("seal receipt disagrees with advanced fork generation");
3331 }
3332 Ok(SemanticRootHandle::new(self.owner, slot, generation))
3333 })();
3334 poison_after_reconciliation_error(&mut self.poisoned, result)
3335 }
3336
3337 pub fn snapshot(
3338 &mut self,
3339 view: SemanticView,
3340 ) -> Result<SemanticRootSnapshot, SemanticHypergraphError> {
3341 self.ensure_host_facade_available()?;
3342 self.validate_view(view)?;
3343 let mut command = self.command_for(OP_SNAPSHOT);
3344 write_view(&mut command, view);
3345 let receipt = self.run(command, ArenaAccess::Read)?;
3346 self.expect_success(&receipt)?;
3347 let result = (|| {
3348 let extents = receipt_extents(&receipt)?;
3349 let digest = SemanticRootDigest(receipt_identity(&receipt, 16));
3350 Ok(SemanticRootSnapshot::new(digest, extents))
3351 })();
3352 poison_after_reconciliation_error(&mut self.poisoned, result)
3353 }
3354
3355 pub fn truth(
3356 &mut self,
3357 view: SemanticView,
3358 statement: &SemanticStatementKey,
3359 ) -> Result<SemanticTruth, SemanticHypergraphError> {
3360 self.ensure_host_facade_available()?;
3361 self.validate_view(view)?;
3362 self.validate_statement_key(statement)?;
3363 let mut command = self.command_for(OP_TRUTH);
3364 write_view(&mut command, view);
3365 command.words[16..20].copy_from_slice(&identity_words(statement.identity.0));
3366 let receipt = self.run(command, ArenaAccess::Read)?;
3367 self.expect_success(&receipt)?;
3368 let result = SemanticTruth::from_bits(receipt.words[2]);
3369 poison_after_reconciliation_error(&mut self.poisoned, result)
3370 }
3371
3372 pub fn inspect_statement(
3373 &mut self,
3374 view: SemanticView,
3375 handle: SemanticStatementHandle,
3376 ) -> Result<SemanticStatementRef, SemanticHypergraphError> {
3377 self.ensure_host_facade_available()?;
3378 self.validate_view(view)?;
3379 self.validate_statement(handle)?;
3380 let mut command = self.command_for(OP_INSPECT_STATEMENT);
3381 write_view(&mut command, view);
3382 command.words[10] = u64::from(handle.slot);
3383 command.words[11] = handle.generation;
3384 let receipt = self.run(command, ArenaAccess::Read)?;
3385 self.expect_success(&receipt)?;
3386 let result = self.statement_ref(&receipt);
3387 poison_after_reconciliation_error(&mut self.poisoned, result)
3388 }
3389
3390 pub fn inspect_support(
3391 &mut self,
3392 view: SemanticView,
3393 handle: SemanticSupportHandle,
3394 ) -> Result<SemanticSupportRef, SemanticHypergraphError> {
3395 self.ensure_host_facade_available()?;
3396 self.validate_view(view)?;
3397 self.validate_support(handle)?;
3398 let mut command = self.command_for(OP_INSPECT_SUPPORT);
3399 write_view(&mut command, view);
3400 command.words[10] = u64::from(handle.slot);
3401 command.words[11] = handle.generation;
3402 let receipt = self.run(command, ArenaAccess::Read)?;
3403 self.expect_success(&receipt)?;
3404 let result = self.support_ref(&receipt);
3405 poison_after_reconciliation_error(&mut self.poisoned, result)
3406 }
3407
3408 pub fn inspect_version(
3409 &mut self,
3410 view: SemanticView,
3411 handle: SemanticVersionHandle,
3412 ) -> Result<SemanticVersionRef, SemanticHypergraphError> {
3413 self.ensure_host_facade_available()?;
3414 self.validate_view(view)?;
3415 self.validate_version(handle)?;
3416 let mut command = self.command_for(OP_INSPECT_VERSION);
3417 write_view(&mut command, view);
3418 command.words[10] = u64::from(handle.slot);
3419 command.words[11] = handle.generation;
3420 let receipt = self.run(command, ArenaAccess::Read)?;
3421 self.expect_success(&receipt)?;
3422 let result = self.version_ref(&receipt);
3423 poison_after_reconciliation_error(&mut self.poisoned, result)
3424 }
3425
3426 fn command_for(&self, operation: u64) -> DeviceCommand {
3427 let mut command = DeviceCommand::default();
3428 command.words[0] = operation;
3429 command.words[1] = self.owner;
3430 command.words[2] = u64::from(self.capacities.roots);
3431 command.words[3] = u64::from(self.capacities.statements);
3432 command.words[4] = u64::from(self.capacities.supports);
3433 command.words[5] = u64::from(self.capacities.versions);
3434 command.words[6] = self.arena_words;
3435 command
3436 }
3437
3438 #[cfg_attr(
3440 not(test),
3441 expect(
3442 dead_code,
3443 reason = "reserved for crate-internal device-resident pipelines"
3444 )
3445 )]
3446 pub(crate) fn enqueue_resident_empty_root_handle<'handle, 'receipt>(
3447 &mut self,
3448 handle: SemanticResidentHandleSlot<'handle>,
3449 receipt: SemanticResidentReceiptSlot<'receipt>,
3450 ) -> Result<SemanticResidentRootHandleBank<'handle>, SemanticHypergraphError> {
3451 let SemanticResidentHandleSlot {
3452 allocation: handles,
3453 index: handle_index,
3454 } = handle;
3455 self.enqueue_resident_input(
3456 SemanticKernelInput::ResidentEmptyRootHandle {
3457 handle: handles,
3458 index: handle_index,
3459 },
3460 ArenaAccess::Read,
3461 false,
3462 receipt,
3463 )?;
3464 Ok(SemanticResidentRootHandleBank {
3465 allocation: handles,
3466 index: handle_index,
3467 })
3468 }
3469
3470 #[cfg_attr(
3474 not(test),
3475 expect(dead_code, reason = "consumed by the resident transition owner")
3476 )]
3477 pub(crate) fn enqueue_resident_preflight_transition<'input, 'receipt>(
3478 &mut self,
3479 root: SemanticResidentRootHandle<'input>,
3480 receipt: SemanticResidentReceiptSlot<'receipt>,
3481 ) -> Result<SemanticResidentRootHandle<'receipt>, SemanticHypergraphError> {
3482 self.enqueue_resident_input(
3483 SemanticKernelInput::ResidentPreflightTransition { root },
3484 ArenaAccess::Read,
3485 false,
3486 receipt,
3487 )
3488 .map(SemanticResidentRootHandle::Receipt)
3489 }
3490
3491 #[cfg_attr(
3493 not(test),
3494 expect(
3495 dead_code,
3496 reason = "reserved for crate-internal device-resident pipelines"
3497 )
3498 )]
3499 pub(crate) fn enqueue_resident_fork<'input, 'receipt>(
3500 &mut self,
3501 root: SemanticResidentRootHandle<'input>,
3502 receipt: SemanticResidentReceiptSlot<'receipt>,
3503 ) -> Result<SemanticResidentMutationReceipt<'receipt>, SemanticHypergraphError> {
3504 let receipt = self.enqueue_resident_input(
3505 SemanticKernelInput::ResidentFork { root },
3506 ArenaAccess::ReadWrite,
3507 true,
3508 receipt,
3509 )?;
3510 Ok(SemanticResidentMutationReceipt { receipt })
3511 }
3512
3513 #[cfg_attr(
3515 not(test),
3516 expect(
3517 dead_code,
3518 reason = "reserved for crate-internal device-resident pipelines"
3519 )
3520 )]
3521 pub(crate) fn enqueue_resident_insert_support<'input, 'receipt>(
3522 &mut self,
3523 candidate: SemanticResidentCandidateHandle<'input>,
3524 statement: SemanticResidentStatementBank<'input>,
3525 support: SemanticResidentSupportBank<'input>,
3526 receipt: SemanticResidentReceiptSlot<'receipt>,
3527 ) -> Result<SemanticResidentMutationReceipt<'receipt>, SemanticHypergraphError> {
3528 let receipt = self.enqueue_resident_input(
3529 SemanticKernelInput::ResidentInsertSupport {
3530 candidate,
3531 statement,
3532 support,
3533 },
3534 ArenaAccess::ReadWrite,
3535 true,
3536 receipt,
3537 )?;
3538 Ok(SemanticResidentMutationReceipt { receipt })
3539 }
3540
3541 #[cfg_attr(
3543 not(test),
3544 expect(
3545 dead_code,
3546 reason = "reserved for crate-internal device-resident pipelines"
3547 )
3548 )]
3549 pub(crate) fn enqueue_resident_seal<'input, 'receipt>(
3550 &mut self,
3551 candidate: SemanticResidentCandidateHandle<'input>,
3552 receipt: SemanticResidentReceiptSlot<'receipt>,
3553 ) -> Result<SemanticResidentSealReceipt<'receipt>, SemanticHypergraphError> {
3554 let receipt = self.enqueue_resident_input(
3555 SemanticKernelInput::ResidentSeal { candidate },
3556 ArenaAccess::ReadWrite,
3557 true,
3558 receipt,
3559 )?;
3560 Ok(SemanticResidentSealReceipt { receipt })
3561 }
3562
3563 #[cfg_attr(
3565 not(test),
3566 expect(
3567 dead_code,
3568 reason = "reserved for crate-internal device-resident pipelines"
3569 )
3570 )]
3571 pub(crate) fn enqueue_resident_truth<'input, 'receipt>(
3572 &mut self,
3573 view: SemanticResidentView<'input>,
3574 statement: SemanticResidentStatementBank<'input>,
3575 receipt: SemanticResidentReceiptSlot<'receipt>,
3576 ) -> Result<SemanticResidentTruthReceipt<'receipt>, SemanticHypergraphError> {
3577 let receipt = self.enqueue_resident_input(
3578 SemanticKernelInput::ResidentTruth { view, statement },
3579 ArenaAccess::Read,
3580 false,
3581 receipt,
3582 )?;
3583 Ok(SemanticResidentTruthReceipt { receipt })
3584 }
3585
3586 #[cfg_attr(
3588 not(test),
3589 expect(
3590 dead_code,
3591 reason = "reserved for crate-internal device-resident pipelines"
3592 )
3593 )]
3594 pub(crate) fn enqueue_resident_truth_consumer<'input, 'output, 'receipt>(
3595 &mut self,
3596 truth: SemanticResidentTruthView<'input>,
3597 output: SemanticResidentTruthSlot<'output>,
3598 receipt: SemanticResidentReceiptSlot<'receipt>,
3599 ) -> Result<SemanticResidentReceiptView<'receipt>, SemanticHypergraphError> {
3600 self.enqueue_resident_input(
3601 SemanticKernelInput::ResidentConsumeTruth { truth, output },
3602 ArenaAccess::Read,
3603 false,
3604 receipt,
3605 )
3606 }
3607
3608 #[cfg_attr(
3610 not(test),
3611 expect(
3612 dead_code,
3613 reason = "reserved for crate-internal device-resident pipelines"
3614 )
3615 )]
3616 pub(crate) fn enqueue_resident_materialize_decoded<'input, 'receipt>(
3617 &mut self,
3618 input: SemanticResidentDecodedInputBank<'input>,
3619 statement: SemanticResidentStatementBank<'input>,
3620 support: SemanticResidentSupportBank<'input>,
3621 receipt: SemanticResidentReceiptSlot<'receipt>,
3622 ) -> Result<SemanticResidentReceiptView<'receipt>, SemanticHypergraphError> {
3623 self.enqueue_resident_input(
3624 SemanticKernelInput::ResidentMaterializeDecoded {
3625 input,
3626 statement,
3627 support,
3628 },
3629 ArenaAccess::Read,
3630 false,
3631 receipt,
3632 )
3633 }
3634
3635 fn enqueue_resident_input<'receipt>(
3636 &mut self,
3637 input: SemanticKernelInput<'_>,
3638 arena_access: ArenaAccess,
3639 device_mutation: bool,
3640 receipt: SemanticResidentReceiptSlot<'receipt>,
3641 ) -> Result<SemanticResidentReceiptView<'receipt>, SemanticHypergraphError> {
3642 self.ensure_not_poisoned()?;
3643 let next_launches = self.next_launch_count()?;
3644 let SemanticResidentReceiptSlot {
3645 allocation: receipts,
3646 index: receipt_index,
3647 } = receipt;
3648 enqueue_device_command(
3649 SemanticKernelLaunchSpec {
3650 domain: &self.domain,
3651 execute: &self.execute,
3652 owner: self.owner,
3653 capacities: self.capacities,
3654 arena_words: self.arena_words,
3655 },
3656 SemanticKernelLaunchIo {
3657 arena: &mut self.arena,
3658 arena_access,
3659 input,
3660 receipts,
3661 receipt_index,
3662 },
3663 &mut self.poisoned,
3664 )?;
3665 self.stats.cuda_kernel_launches = next_launches;
3666 self.device_controlled |= device_mutation;
3667 Ok(SemanticResidentReceiptView {
3668 allocation: receipts,
3669 index: receipt_index,
3670 })
3671 }
3672
3673 fn run(
3674 &mut self,
3675 command: DeviceCommand,
3676 arena_access: ArenaAccess,
3677 ) -> Result<DeviceReceipt, SemanticHypergraphError> {
3678 let next_launches = self.next_launch_count()?;
3679 self.provider
3680 .htod_launch_metadata_sync_copy_into(&[command], &mut self.command)
3681 .map_err(|error| runtime_error("command upload", error))?;
3682 enqueue_device_command(
3683 SemanticKernelLaunchSpec {
3684 domain: &self.domain,
3685 execute: &self.execute,
3686 owner: self.owner,
3687 capacities: self.capacities,
3688 arena_words: self.arena_words,
3689 },
3690 SemanticKernelLaunchIo {
3691 arena: &mut self.arena,
3692 arena_access,
3693 input: SemanticKernelInput::HostCommand {
3694 commands: &self.command,
3695 index: 0,
3696 },
3697 receipts: &mut self.receipt,
3698 receipt_index: 0,
3699 },
3700 &mut self.poisoned,
3701 )?;
3702 self.stats.cuda_kernel_launches = next_launches;
3703 if let Err(error) = self.stream.synchronize() {
3704 self.poisoned = true;
3705 return Err(SemanticHypergraphError::Runtime {
3706 operation: "stream synchronization",
3707 detail: error.to_string(),
3708 });
3709 }
3710 let mut receipts = match self
3711 .provider
3712 .dtoh_small_metadata_untracked(&self.receipt, 1)
3713 {
3714 Ok(receipts) => receipts,
3715 Err(error) => {
3716 self.poisoned = true;
3717 return Err(runtime_error("receipt read", error));
3718 }
3719 };
3720 receipts.pop().ok_or_else(|| {
3721 self.poisoned = true;
3722 SemanticHypergraphError::CorruptLineage {
3723 detail: "CUDA receipt read returned no record".into(),
3724 }
3725 })
3726 }
3727
3728 fn next_launch_count(&self) -> Result<u64, SemanticHypergraphError> {
3729 self.stats.cuda_kernel_launches.checked_add(1).ok_or(
3730 SemanticHypergraphError::GenerationExhausted {
3731 kind: SemanticHandleKind::Root,
3732 slot: 0,
3733 },
3734 )
3735 }
3736
3737 fn expect_success(&mut self, receipt: &DeviceReceipt) -> Result<(), SemanticHypergraphError> {
3738 let status = receipt.words[0];
3739 if status == STATUS_OK {
3740 return Ok(());
3741 }
3742 let kind = decode_kind(receipt.words[33]);
3743 let slot = u32::try_from(receipt.words[34]).unwrap_or(u32::MAX);
3744 let result = match status {
3745 STATUS_FOREIGN_OWNER => Err(SemanticHypergraphError::ForeignHandle { kind }),
3746 STATUS_SLOT_OUT_OF_RANGE => Err(SemanticHypergraphError::SlotOutOfRange {
3747 kind,
3748 slot,
3749 capacity: u32::try_from(receipt.words[37]).unwrap_or(u32::MAX),
3750 }),
3751 STATUS_STALE_GENERATION => Err(SemanticHypergraphError::StaleGeneration {
3752 kind,
3753 slot,
3754 presented: receipt.words[35],
3755 current: receipt.words[36],
3756 }),
3757 STATUS_INACTIVE => Err(SemanticHypergraphError::InactiveHandle { kind, slot }),
3758 STATUS_NOT_REACHABLE => Err(SemanticHypergraphError::NotReachable { kind, slot }),
3759 STATUS_STATEMENT_CAPACITY => Err(SemanticHypergraphError::CapacityExceeded {
3760 kind: SemanticHandleKind::Statement,
3761 capacity: self.capacities.statements,
3762 }),
3763 STATUS_SUPPORT_CAPACITY => Err(SemanticHypergraphError::CapacityExceeded {
3764 kind: SemanticHandleKind::Support,
3765 capacity: self.capacities.supports,
3766 }),
3767 STATUS_VERSION_CAPACITY => Err(SemanticHypergraphError::CapacityExceeded {
3768 kind: SemanticHandleKind::Version,
3769 capacity: self.capacities.versions,
3770 }),
3771 STATUS_ROOT_CAPACITY => Err(SemanticHypergraphError::CapacityExceeded {
3772 kind: SemanticHandleKind::Root,
3773 capacity: self.capacities.roots,
3774 }),
3775 STATUS_GENERATION_EXHAUSTED => {
3776 Err(SemanticHypergraphError::GenerationExhausted { kind, slot })
3777 }
3778 STATUS_CORRUPT_LINEAGE => Err(SemanticHypergraphError::CorruptLineage {
3779 detail: format!("device validation code {}", receipt.words[38]),
3780 }),
3781 STATUS_INVALID_COMMAND => Err(SemanticHypergraphError::CorruptLineage {
3782 detail: "device rejected an internal operation code".into(),
3783 }),
3784 STATUS_ARENA_MISMATCH => Err(SemanticHypergraphError::CorruptLineage {
3785 detail: "device and host semantic arena layouts disagree".into(),
3786 }),
3787 _ => Err(SemanticHypergraphError::CorruptLineage {
3788 detail: format!("unknown device status {status}"),
3789 }),
3790 };
3791 if device_status_is_integrity_failure(status) {
3792 self.poisoned = true;
3793 }
3794 result
3795 }
3796
3797 fn statement_ref(
3798 &self,
3799 receipt: &DeviceReceipt,
3800 ) -> Result<SemanticStatementRef, SemanticHypergraphError> {
3801 let slot = checked_receipt_slot(
3802 receipt.words[7],
3803 SemanticHandleKind::Statement,
3804 self.capacities.statements,
3805 )?;
3806 Ok(SemanticStatementRef {
3807 handle: SemanticStatementHandle::new(self.owner, slot, receipt.words[8]),
3808 identity: SemanticStatementIdentity(receipt_identity(receipt, 20)),
3809 })
3810 }
3811
3812 fn support_ref(
3813 &self,
3814 receipt: &DeviceReceipt,
3815 ) -> Result<SemanticSupportRef, SemanticHypergraphError> {
3816 let slot = checked_receipt_slot(
3817 receipt.words[9],
3818 SemanticHandleKind::Support,
3819 self.capacities.supports,
3820 )?;
3821 Ok(SemanticSupportRef {
3822 handle: SemanticSupportHandle::new(self.owner, slot, receipt.words[10]),
3823 identity: SemanticSupportIdentity(receipt_identity(receipt, 24)),
3824 })
3825 }
3826
3827 fn version_ref(
3828 &self,
3829 receipt: &DeviceReceipt,
3830 ) -> Result<SemanticVersionRef, SemanticHypergraphError> {
3831 let slot = checked_receipt_slot(
3832 receipt.words[11],
3833 SemanticHandleKind::Version,
3834 self.capacities.versions,
3835 )?;
3836 Ok(SemanticVersionRef {
3837 handle: SemanticVersionHandle::new(self.owner, slot, receipt.words[12]),
3838 identity: SemanticVersionIdentity(receipt_identity(receipt, 28)),
3839 truth: SemanticTruth::from_bits(receipt.words[2])?,
3840 })
3841 }
3842
3843 fn publish_statement(
3844 &mut self,
3845 handle: SemanticStatementHandle,
3846 ) -> Result<(), SemanticHypergraphError> {
3847 publish_slot(
3848 &mut self.statements,
3849 handle.slot,
3850 handle.generation,
3851 SemanticHandleKind::Statement,
3852 )
3853 }
3854
3855 fn publish_support(
3856 &mut self,
3857 handle: SemanticSupportHandle,
3858 ) -> Result<(), SemanticHypergraphError> {
3859 publish_slot(
3860 &mut self.supports,
3861 handle.slot,
3862 handle.generation,
3863 SemanticHandleKind::Support,
3864 )
3865 }
3866
3867 fn publish_version(
3868 &mut self,
3869 handle: SemanticVersionHandle,
3870 ) -> Result<(), SemanticHypergraphError> {
3871 publish_slot(
3872 &mut self.versions,
3873 handle.slot,
3874 handle.generation,
3875 SemanticHandleKind::Version,
3876 )
3877 }
3878
3879 fn validate_view(&self, view: SemanticView) -> Result<(), SemanticHypergraphError> {
3880 match view {
3881 SemanticView::Root(handle) => self.validate_root(handle),
3882 SemanticView::Fork(handle) => self.validate_fork(handle),
3883 }
3884 }
3885
3886 fn validate_root(&self, handle: SemanticRootHandle) -> Result<(), SemanticHypergraphError> {
3887 validate_slot(
3888 self.owner,
3889 handle.owner,
3890 handle.slot,
3891 handle.generation,
3892 &self.roots,
3893 SemanticHandleKind::Root,
3894 )
3895 }
3896
3897 fn validate_fork(&self, handle: SemanticForkHandle) -> Result<(), SemanticHypergraphError> {
3898 if handle.owner != self.owner {
3899 return Err(SemanticHypergraphError::ForeignHandle {
3900 kind: SemanticHandleKind::Fork,
3901 });
3902 }
3903 if handle.slot != 0 {
3904 return Err(SemanticHypergraphError::SlotOutOfRange {
3905 kind: SemanticHandleKind::Fork,
3906 slot: handle.slot,
3907 capacity: 1,
3908 });
3909 }
3910 if handle.generation != self.fork.generation {
3911 return Err(SemanticHypergraphError::StaleGeneration {
3912 kind: SemanticHandleKind::Fork,
3913 slot: 0,
3914 presented: handle.generation,
3915 current: self.fork.generation,
3916 });
3917 }
3918 if !self.fork.live
3919 || self
3920 .current_fork
3921 .as_ref()
3922 .is_none_or(|current| current.handle != handle)
3923 {
3924 return Err(SemanticHypergraphError::InactiveHandle {
3925 kind: SemanticHandleKind::Fork,
3926 slot: 0,
3927 });
3928 }
3929 Ok(())
3930 }
3931
3932 fn validate_statement(
3933 &self,
3934 handle: SemanticStatementHandle,
3935 ) -> Result<(), SemanticHypergraphError> {
3936 validate_slot(
3937 self.owner,
3938 handle.owner,
3939 handle.slot,
3940 handle.generation,
3941 &self.statements,
3942 SemanticHandleKind::Statement,
3943 )
3944 }
3945
3946 fn validate_support(
3947 &self,
3948 handle: SemanticSupportHandle,
3949 ) -> Result<(), SemanticHypergraphError> {
3950 validate_slot(
3951 self.owner,
3952 handle.owner,
3953 handle.slot,
3954 handle.generation,
3955 &self.supports,
3956 SemanticHandleKind::Support,
3957 )
3958 }
3959
3960 fn validate_version(
3961 &self,
3962 handle: SemanticVersionHandle,
3963 ) -> Result<(), SemanticHypergraphError> {
3964 validate_slot(
3965 self.owner,
3966 handle.owner,
3967 handle.slot,
3968 handle.generation,
3969 &self.versions,
3970 SemanticHandleKind::Version,
3971 )
3972 }
3973
3974 pub(crate) fn ensure_not_poisoned(&self) -> Result<(), SemanticHypergraphError> {
3975 if self.poisoned {
3976 Err(SemanticHypergraphError::Poisoned)
3977 } else {
3978 Ok(())
3979 }
3980 }
3981
3982 fn ensure_host_facade_available(&self) -> Result<(), SemanticHypergraphError> {
3983 self.ensure_not_poisoned()?;
3984 if self.device_controlled {
3985 return Err(SemanticHypergraphError::DeviceControlled);
3986 }
3987 Ok(())
3988 }
3989
3990 fn corrupt<T>(&self, detail: impl Into<String>) -> Result<T, SemanticHypergraphError> {
3991 Err(SemanticHypergraphError::CorruptLineage {
3992 detail: detail.into(),
3993 })
3994 }
3995}
3996
3997fn device_status_is_integrity_failure(status: u64) -> bool {
3998 !matches!(
3999 status,
4000 STATUS_OK
4001 | STATUS_NOT_REACHABLE
4002 | STATUS_STATEMENT_CAPACITY
4003 | STATUS_SUPPORT_CAPACITY
4004 | STATUS_VERSION_CAPACITY
4005 | STATUS_ROOT_CAPACITY
4006 | STATUS_GENERATION_EXHAUSTED
4007 )
4008}
4009
4010fn poison_after_reconciliation_error<T>(
4011 poisoned: &mut bool,
4012 result: Result<T, SemanticHypergraphError>,
4013) -> Result<T, SemanticHypergraphError> {
4014 if result.is_err() {
4015 *poisoned = true;
4016 }
4017 result
4018}
4019
4020fn checked_arena_words(
4021 capacities: SemanticHypergraphCapacities,
4022) -> Result<u64, SemanticHypergraphError> {
4023 let roots = u64::from(capacities.roots);
4024 let statements = u64::from(capacities.statements);
4025 let supports = u64::from(capacities.supports);
4026 let versions = u64::from(capacities.versions);
4027 CONTROL_WORDS
4028 .checked_add(roots.checked_mul(ROOT_WORDS).ok_or_else(size_overflow)?)
4029 .and_then(|words| words.checked_add(CANDIDATE_WORDS))
4030 .and_then(|words| words.checked_add(statements.checked_mul(STATEMENT_WORDS)?))
4031 .and_then(|words| words.checked_add(supports.checked_mul(SUPPORT_WORDS)?))
4032 .and_then(|words| words.checked_add(versions.checked_mul(VERSION_WORDS)?))
4033 .and_then(|words| words.checked_add(roots.checked_mul(statements)?))
4034 .and_then(|words| words.checked_add(statements))
4035 .ok_or_else(size_overflow)
4036}
4037
4038fn size_overflow() -> SemanticHypergraphError {
4039 SemanticHypergraphError::InvalidInput {
4040 detail: "semantic arena size overflow".into(),
4041 }
4042}
4043
4044fn validate_slot(
4045 expected_owner: u64,
4046 presented_owner: u64,
4047 slot: u32,
4048 generation: u64,
4049 ledger: &[SlotLedger],
4050 kind: SemanticHandleKind,
4051) -> Result<(), SemanticHypergraphError> {
4052 if presented_owner != expected_owner {
4053 return Err(SemanticHypergraphError::ForeignHandle { kind });
4054 }
4055 let entry = ledger
4056 .get(slot as usize)
4057 .ok_or(SemanticHypergraphError::SlotOutOfRange {
4058 kind,
4059 slot,
4060 capacity: u32::try_from(ledger.len()).unwrap_or(u32::MAX),
4061 })?;
4062 if generation != entry.generation {
4063 return Err(SemanticHypergraphError::StaleGeneration {
4064 kind,
4065 slot,
4066 presented: generation,
4067 current: entry.generation,
4068 });
4069 }
4070 if !entry.live {
4071 return Err(SemanticHypergraphError::InactiveHandle { kind, slot });
4072 }
4073 Ok(())
4074}
4075
4076fn publish_slot(
4077 ledger: &mut [SlotLedger],
4078 slot: u32,
4079 generation: u64,
4080 kind: SemanticHandleKind,
4081) -> Result<(), SemanticHypergraphError> {
4082 let capacity = u32::try_from(ledger.len()).unwrap_or(u32::MAX);
4083 let entry = ledger
4084 .get_mut(slot as usize)
4085 .ok_or(SemanticHypergraphError::SlotOutOfRange {
4086 kind,
4087 slot,
4088 capacity,
4089 })?;
4090 if entry.live || entry.generation != generation {
4091 return Err(SemanticHypergraphError::CorruptLineage {
4092 detail: format!("device published inconsistent {kind:?} slot {slot}"),
4093 });
4094 }
4095 entry.live = true;
4096 Ok(())
4097}
4098
4099fn retire_slot(
4100 slot: &mut SlotLedger,
4101 kind: SemanticHandleKind,
4102 index: u32,
4103) -> Result<(), SemanticHypergraphError> {
4104 slot.generation = slot
4105 .generation
4106 .checked_add(1)
4107 .ok_or(SemanticHypergraphError::GenerationExhausted { kind, slot: index })?;
4108 slot.live = false;
4109 Ok(())
4110}
4111
4112fn enqueue_device_command(
4113 spec: SemanticKernelLaunchSpec<'_>,
4114 io: SemanticKernelLaunchIo<'_>,
4115 poisoned: &mut bool,
4116) -> Result<(), SemanticHypergraphError> {
4117 let SemanticKernelLaunchIo {
4118 arena,
4119 arena_access,
4120 input,
4121 receipts,
4122 receipt_index,
4123 } = io;
4124 let mut recorder = spec.domain.new_strict_recorder();
4125 match arena_access {
4126 ArenaAccess::Read => {
4127 recorder.read(arena);
4128 }
4129 ArenaAccess::ReadWrite => {
4130 recorder.read_write(arena);
4131 }
4132 }
4133 recorder.write(receipts);
4134 let mut descriptor = DeviceLaunchDescriptor {
4135 expected_owner: spec.owner,
4136 root_capacity: u64::from(spec.capacities.roots),
4137 statement_capacity: u64::from(spec.capacities.statements),
4138 support_capacity: u64::from(spec.capacities.supports),
4139 version_capacity: u64::from(spec.capacities.versions),
4140 arena_words: spec.arena_words,
4141 command_ptr: 0,
4142 command_index: 0,
4143 handle_ptr: 0,
4144 handle_index: 0,
4145 source_receipt_ptr: 0,
4146 source_receipt_index: 0,
4147 decoded_input_ptr: 0,
4148 decoded_input_index: 0,
4149 decoded_statement_ptr: 0,
4150 decoded_statement_index: 0,
4151 decoded_support_ptr: 0,
4152 decoded_support_index: 0,
4153 output_ptr: 0,
4154 output_index: 0,
4155 receipt_ptr: receipts.device_ptr_value(),
4156 receipt_index: u64::from(receipt_index),
4157 admission: HOST_COMMAND_ADMISSION,
4158 abi_generation: HYPERGRAPH_ABI_GENERATION,
4159 };
4160 let preflight = matches!(
4161 &input,
4162 SemanticKernelInput::ResidentPreflightTransition { .. }
4163 );
4164 match input {
4165 SemanticKernelInput::HostCommand { commands, index } => {
4166 recorder.read(commands);
4167 descriptor.command_ptr = commands.device_ptr_value();
4168 descriptor.command_index = u64::from(index);
4169 }
4170 SemanticKernelInput::ResidentEmptyRootHandle { handle, index } => {
4171 recorder.write(handle);
4172 descriptor.handle_ptr = handle.device_ptr_value();
4173 descriptor.handle_index = u64::from(index);
4174 descriptor.admission = RESIDENT_EMPTY_ROOT_HANDLE_ADMISSION;
4175 }
4176 SemanticKernelInput::ResidentFork { root }
4177 | SemanticKernelInput::ResidentPreflightTransition { root } => {
4178 root.record_input(&mut descriptor, &mut recorder);
4179 descriptor.admission = if preflight {
4180 RESIDENT_PREFLIGHT_TRANSITION_ADMISSION
4181 } else {
4182 RESIDENT_FORK_ADMISSION
4183 };
4184 }
4185 SemanticKernelInput::ResidentInsertSupport {
4186 candidate,
4187 statement,
4188 support,
4189 } => {
4190 candidate.receipt.record_read(&mut recorder);
4191 descriptor.source_receipt_ptr = candidate.receipt.allocation.device_ptr_value();
4192 descriptor.source_receipt_index = u64::from(candidate.receipt.index);
4193 recorder.read(statement.allocation);
4194 recorder.read(support.allocation);
4195 descriptor.decoded_statement_ptr = statement.allocation.device_ptr_value();
4196 descriptor.decoded_statement_index = u64::from(statement.index);
4197 descriptor.decoded_support_ptr = support.allocation.device_ptr_value();
4198 descriptor.decoded_support_index = u64::from(support.index);
4199 descriptor.admission = RESIDENT_INSERT_SUPPORT_ADMISSION;
4200 }
4201 SemanticKernelInput::ResidentSeal { candidate } => {
4202 candidate.receipt.record_read(&mut recorder);
4203 descriptor.source_receipt_ptr = candidate.receipt.allocation.device_ptr_value();
4204 descriptor.source_receipt_index = u64::from(candidate.receipt.index);
4205 descriptor.admission = RESIDENT_SEAL_ADMISSION;
4206 }
4207 SemanticKernelInput::ResidentTruth { view, statement } => {
4208 match view {
4209 SemanticResidentView::Root(root) => {
4210 root.record_input(&mut descriptor, &mut recorder);
4211 descriptor.admission = RESIDENT_ROOT_TRUTH_ADMISSION;
4212 }
4213 SemanticResidentView::Fork(candidate) => {
4214 candidate.receipt.record_read(&mut recorder);
4215 descriptor.source_receipt_ptr = candidate.receipt.allocation.device_ptr_value();
4216 descriptor.source_receipt_index = u64::from(candidate.receipt.index);
4217 descriptor.admission = RESIDENT_FORK_TRUTH_ADMISSION;
4218 }
4219 }
4220 recorder.read(statement.allocation);
4221 descriptor.decoded_statement_ptr = statement.allocation.device_ptr_value();
4222 descriptor.decoded_statement_index = u64::from(statement.index);
4223 }
4224 SemanticKernelInput::ResidentConsumeTruth { truth, output } => {
4225 truth.receipt.record_read(&mut recorder);
4226 recorder.write(output.allocation);
4227 descriptor.source_receipt_ptr = truth.receipt.allocation.device_ptr_value();
4228 descriptor.source_receipt_index = u64::from(truth.receipt.index);
4229 descriptor.output_ptr = output.allocation.device_ptr_value();
4230 descriptor.output_index = u64::from(output.index);
4231 descriptor.admission = RESIDENT_CONSUME_TRUTH_ADMISSION;
4232 }
4233 SemanticKernelInput::ResidentMaterializeDecoded {
4234 input,
4235 statement,
4236 support,
4237 } => {
4238 recorder.read(input.allocation);
4239 recorder.write(statement.allocation);
4240 recorder.write(support.allocation);
4241 descriptor.decoded_input_ptr = input.allocation.device_ptr_value();
4242 descriptor.decoded_input_index = u64::from(input.index);
4243 descriptor.decoded_statement_ptr = statement.allocation.device_ptr_value();
4244 descriptor.decoded_statement_index = u64::from(statement.index);
4245 descriptor.decoded_support_ptr = support.allocation.device_ptr_value();
4246 descriptor.decoded_support_index = u64::from(support.index);
4247 descriptor.admission = RESIDENT_MATERIALIZE_DECODED_ADMISSION;
4248 }
4249 }
4250 let execute = spec.execute.clone();
4251 let enqueued = match unsafe {
4252 spec.domain.enqueue(recorder, |stream| {
4253 execute.clone().launch_in(
4254 stream,
4255 LaunchConfig {
4256 grid_dim: (1, 1, 1),
4257 block_dim: (1, 1, 1),
4258 shared_mem_bytes: 0,
4259 },
4260 (arena, descriptor),
4261 )
4262 })
4263 } {
4264 Ok(enqueued) => enqueued,
4265 Err(error) => {
4266 if matches!(
4267 &error,
4268 LaunchEnqueueError::Operation(_) | LaunchEnqueueError::OperationAndCleanup { .. }
4269 ) {
4270 *poisoned = true;
4271 }
4272 return Err(map_enqueue_error(error));
4273 }
4274 };
4275 if let Err(error) = enqueued.commit() {
4276 *poisoned = true;
4277 return Err(runtime_error("launch commit", error));
4278 }
4279 Ok(())
4280}
4281
4282fn runtime_error(operation: &'static str, error: XlogError) -> SemanticHypergraphError {
4283 SemanticHypergraphError::Runtime {
4284 operation,
4285 detail: error.to_string(),
4286 }
4287}
4288
4289fn validate_resident_bank_index(
4290 index: u32,
4291 len: usize,
4292 role: &'static str,
4293) -> Result<(), SemanticHypergraphError> {
4294 if (index as usize) < len {
4295 return Ok(());
4296 }
4297 Err(SemanticHypergraphError::InvalidInput {
4298 detail: format!("resident semantic {role} index {index} exceeds bank length {len}"),
4299 })
4300}
4301
4302fn map_enqueue_error(error: LaunchEnqueueError<DriverError>) -> SemanticHypergraphError {
4303 let operation = match &error {
4304 LaunchEnqueueError::Preparation(_) => "launch preparation",
4305 LaunchEnqueueError::PreparationAndCleanup { .. } => "launch preparation and cleanup",
4306 LaunchEnqueueError::Operation(_) => "kernel enqueue",
4307 LaunchEnqueueError::OperationAndCleanup { .. } => "kernel enqueue and cleanup",
4308 };
4309 SemanticHypergraphError::Runtime {
4310 operation,
4311 detail: error.to_string(),
4312 }
4313}
4314
4315fn identity_words(bytes: [u8; 32]) -> [u64; 4] {
4316 core::array::from_fn(|index| {
4317 let start = index * 8;
4318 u64::from_le_bytes(
4319 bytes[start..start + 8]
4320 .try_into()
4321 .expect("fixed identity chunk"),
4322 )
4323 })
4324}
4325
4326fn receipt_identity(receipt: &DeviceReceipt, start: usize) -> [u8; 32] {
4327 let mut bytes = [0u8; 32];
4328 for (index, word) in receipt.words[start..start + 4].iter().enumerate() {
4329 bytes[index * 8..index * 8 + 8].copy_from_slice(&word.to_le_bytes());
4330 }
4331 bytes
4332}
4333
4334fn inserted_support_previous_truth(
4335 receipt: &DeviceReceipt,
4336) -> Result<SemanticTruth, SemanticHypergraphError> {
4337 let metadata = receipt.words[32];
4338 let previous = (metadata & INSERT_PREVIOUS_TRUTH_MASK) >> INSERT_PREVIOUS_TRUTH_SHIFT;
4339 let resulting = receipt.words[2];
4340 let new_statement = metadata & INSERT_NEW_STATEMENT != 0;
4341 if receipt.words[0] != STATUS_OK
4342 || receipt.words[1] != OUTCOME_INSERTED
4343 || metadata & !(INSERT_NEW_STATEMENT | INSERT_PREVIOUS_TRUTH_MASK) != 0
4344 || new_statement != (previous == 0)
4345 || !(1..=3).contains(&resulting)
4346 || previous & resulting != previous
4347 || (previous ^ resulting).count_ones() > 1
4348 {
4349 return Err(SemanticHypergraphError::CorruptLineage {
4350 detail: "insertion receipt has invalid previous or resulting truth".into(),
4351 });
4352 }
4353 SemanticTruth::from_bits(previous)
4354}
4355
4356fn receipt_extents(receipt: &DeviceReceipt) -> Result<SemanticExtents, SemanticHypergraphError> {
4357 Ok(SemanticExtents::new(
4358 u32::try_from(receipt.words[13]).map_err(|_| SemanticHypergraphError::CorruptLineage {
4359 detail: "statement extent exceeds u32".into(),
4360 })?,
4361 u32::try_from(receipt.words[14]).map_err(|_| SemanticHypergraphError::CorruptLineage {
4362 detail: "support extent exceeds u32".into(),
4363 })?,
4364 u32::try_from(receipt.words[15]).map_err(|_| SemanticHypergraphError::CorruptLineage {
4365 detail: "version extent exceeds u32".into(),
4366 })?,
4367 ))
4368}
4369
4370fn checked_receipt_slot(
4371 value: u64,
4372 kind: SemanticHandleKind,
4373 capacity: u32,
4374) -> Result<u32, SemanticHypergraphError> {
4375 let slot = u32::try_from(value).map_err(|_| SemanticHypergraphError::CorruptLineage {
4376 detail: format!("device returned non-u32 {kind:?} slot {value}"),
4377 })?;
4378 if slot >= capacity {
4379 return Err(SemanticHypergraphError::CorruptLineage {
4380 detail: format!("device returned out-of-range {kind:?} slot {slot}"),
4381 });
4382 }
4383 Ok(slot)
4384}
4385
4386fn write_view(command: &mut DeviceCommand, view: SemanticView) {
4387 match view {
4388 SemanticView::Root(handle) => {
4389 command.words[1] = handle.owner;
4390 command.words[7] = 1;
4391 command.words[8] = u64::from(handle.slot);
4392 command.words[9] = handle.generation;
4393 }
4394 SemanticView::Fork(handle) => {
4395 command.words[1] = handle.owner;
4396 command.words[7] = 2;
4397 command.words[8] = u64::from(handle.slot);
4398 command.words[9] = handle.generation;
4399 }
4400 }
4401}
4402
4403fn decode_kind(code: u64) -> SemanticHandleKind {
4404 match code {
4405 1 => SemanticHandleKind::Root,
4406 2 => SemanticHandleKind::Fork,
4407 3 => SemanticHandleKind::Statement,
4408 4 => SemanticHandleKind::Support,
4409 5 => SemanticHandleKind::Version,
4410 _ => SemanticHandleKind::Root,
4411 }
4412}
4413
4414#[cfg(test)]
4415pub(crate) mod tests {
4416 use super::*;
4417 use crate::cuda_graph::CapturedCudaGraph;
4418 use crate::CudaProviderBuilder;
4419 use xlog_core::MemoryBudget;
4420
4421 const OP_RETIRE_ROOT: u64 = 12;
4422
4423 fn kernel_error(error: impl fmt::Display) -> XlogError {
4424 XlogError::Kernel(error.to_string())
4425 }
4426
4427 fn download_test_arena(provider: &CudaKernelProvider, graph: &SemanticHypergraph) -> Vec<u64> {
4428 let mut arena = vec![0; graph.arena_words as usize];
4429 provider
4430 .dtoh_sync_copy_into_tracked(&graph.arena, &mut arena)
4431 .unwrap();
4432 arena
4433 }
4434
4435 fn retirement_test_graph() -> Option<SemanticHypergraph> {
4436 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
4437 eprintln!("Skipping: set XLOG_REQUIRE_CUDA=1 to run this real-CUDA contract");
4438 return None;
4439 }
4440 let provider = Arc::new(
4441 CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
4442 .with_stream_capacity(1)
4443 .build()
4444 .unwrap(),
4445 );
4446 let runtime = Arc::clone(provider.memory().runtime().unwrap());
4447 let stream_id = runtime.stream_pool().acquire().unwrap();
4448 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
4449 let domain = provider
4450 .bind_resident_execution_domain(runtime, stream_id, stream)
4451 .unwrap();
4452 let mut graph = provider
4453 .allocate_semantic_hypergraph(
4454 &domain,
4455 SemanticHypergraphCapacities::try_new(8, 8, 16, 16).unwrap(),
4456 )
4457 .unwrap();
4458 graph.enter_transition();
4459 Some(graph)
4460 }
4461
4462 fn retirement_command(
4463 graph: &mut SemanticHypergraph,
4464 operation: u64,
4465 fields: &[(usize, u64)],
4466 ) -> DeviceReceipt {
4467 let mut command = graph.command_for(operation);
4468 for &(index, value) in fields {
4469 command.words[index] = value;
4470 }
4471 graph.run(command, ArenaAccess::ReadWrite).unwrap()
4472 }
4473
4474 fn retirement_root(
4475 graph: &mut SemanticHypergraph,
4476 root: SemanticRootHandle,
4477 protected_base: SemanticRootHandle,
4478 ) -> DeviceReceipt {
4479 retirement_command(
4480 graph,
4481 OP_RETIRE_ROOT,
4482 &[
4483 (8, root.slot as u64),
4484 (9, root.generation),
4485 (10, protected_base.slot as u64),
4486 (11, protected_base.generation),
4487 ],
4488 )
4489 }
4490
4491 fn retirement_insert_root(
4492 graph: &mut SemanticHypergraph,
4493 base: SemanticRootHandle,
4494 statement: u64,
4495 support: u64,
4496 ) -> (SemanticRootHandle, DeviceReceipt) {
4497 let fork = retirement_command(
4498 graph,
4499 OP_FORK,
4500 &[(8, base.slot as u64), (9, base.generation)],
4501 );
4502 assert_eq!(fork.words[0], STATUS_OK);
4503 let edit = retirement_command(
4504 graph,
4505 OP_INSERT_SUPPORT,
4506 &[(9, fork.words[6]), (12, 1), (16, statement), (20, support)],
4507 );
4508 assert_eq!(edit.words[0], STATUS_OK);
4509 let sealed = retirement_command(graph, OP_SEAL, &[(9, fork.words[6])]);
4510 assert_eq!(sealed.words[0], STATUS_OK);
4511 (
4512 SemanticRootHandle::new(graph.owner, sealed.words[3] as u32, sealed.words[4]),
4513 edit,
4514 )
4515 }
4516
4517 #[test]
4518 fn sealed_root_retirement_preserves_descendants_and_reuses_generations() {
4519 let Some(mut graph) = retirement_test_graph() else {
4520 return;
4521 };
4522 let provider = Arc::clone(&graph.provider);
4523 let empty = graph.empty_root();
4524 let (parent, inherited) = retirement_insert_root(&mut graph, empty, 100, 200);
4525 let (loser, exclusive) = retirement_insert_root(&mut graph, parent, 300, 400);
4526 let (descendant, _) = retirement_insert_root(&mut graph, parent, 100, 201);
4527 let parent_before = retirement_command(
4528 &mut graph,
4529 OP_SNAPSHOT,
4530 &[(7, 1), (8, parent.slot as u64), (9, parent.generation)],
4531 );
4532 let retired = retirement_root(&mut graph, loser, parent);
4533 assert_eq!(retired.words[0], STATUS_OK);
4534 assert_eq!(retired.words[3], loser.slot as u64);
4535 assert_eq!(retired.words[4], loser.generation + 1);
4536 assert_eq!(retired.words[39], graph.owner);
4537 let parent_after = retirement_command(
4538 &mut graph,
4539 OP_SNAPSHOT,
4540 &[(7, 1), (8, parent.slot as u64), (9, parent.generation)],
4541 );
4542 assert_eq!(&parent_after.words[13..20], &parent_before.words[13..20]);
4543 let retained = download_test_arena(&provider, &graph);
4544 let repeated = retirement_command(
4545 &mut graph,
4546 OP_RETIRE_ROOT,
4547 &[
4548 (8, loser.slot as u64),
4549 (9, loser.generation),
4550 (10, parent.slot as u64),
4551 (11, parent.generation),
4552 ],
4553 );
4554 assert_eq!(repeated.words[0], STATUS_STALE_GENERATION);
4555 assert_eq!(download_test_arena(&provider, &graph), retained);
4556 let (reused, replacement) = retirement_insert_root(&mut graph, parent, 301, 401);
4557 assert_eq!(reused.slot, loser.slot);
4558 assert_eq!(reused.generation, loser.generation + 1);
4559 for (slot, generation) in [(7, 8), (9, 10), (11, 12)] {
4560 assert_eq!(replacement.words[slot], exclusive.words[slot]);
4561 assert_eq!(
4562 replacement.words[generation],
4563 exclusive.words[generation] + 1
4564 );
4565 }
4566 let candidate = retirement_command(
4569 &mut graph,
4570 OP_FORK,
4571 &[(8, descendant.slot as u64), (9, descendant.generation)],
4572 );
4573 assert_eq!(candidate.words[0], STATUS_OK);
4574 let staged = retirement_command(
4575 &mut graph,
4576 OP_INSERT_SUPPORT,
4577 &[(9, candidate.words[6]), (12, 2), (16, 100), (20, 202)],
4578 );
4579 assert_eq!(staged.words[0], STATUS_OK);
4580 assert_eq!(
4581 retirement_root(&mut graph, parent, empty).words[0],
4582 STATUS_OK
4583 );
4584 let candidate_truth = retirement_command(
4585 &mut graph,
4586 OP_TRUTH,
4587 &[(7, 2), (9, candidate.words[6]), (16, 100)],
4588 );
4589 assert_eq!(candidate_truth.words[0], STATUS_OK);
4590 assert_eq!(candidate_truth.words[2], 3);
4591 assert_eq!(
4592 retirement_command(&mut graph, OP_SEAL, &[(9, candidate.words[6])]).words[0],
4593 STATUS_OK
4594 );
4595 for (operation, slot, generation) in [
4596 (OP_INSPECT_STATEMENT, 7, 8),
4597 (OP_INSPECT_SUPPORT, 9, 10),
4598 (OP_INSPECT_VERSION, 11, 12),
4599 ] {
4600 let inspected = retirement_command(
4601 &mut graph,
4602 operation,
4603 &[
4604 (7, 1),
4605 (8, descendant.slot as u64),
4606 (9, descendant.generation),
4607 (10, inherited.words[slot]),
4608 (11, inherited.words[generation]),
4609 ],
4610 );
4611 assert_eq!(inspected.words[0], STATUS_OK);
4612 assert_eq!(inspected.words[slot], inherited.words[slot]);
4613 assert_eq!(inspected.words[generation], inherited.words[generation]);
4614 }
4615 let truth = retirement_command(
4616 &mut graph,
4617 OP_TRUTH,
4618 &[
4619 (7, 1),
4620 (8, descendant.slot as u64),
4621 (9, descendant.generation),
4622 (16, 100),
4623 ],
4624 );
4625 assert_eq!(truth.words[0], STATUS_OK);
4626 assert_eq!(truth.words[2], 1);
4627 let stale = retirement_root(&mut graph, loser, empty);
4628 assert_eq!(stale.words[0], STATUS_STALE_GENERATION);
4629 }
4630
4631 #[test]
4632 fn sealed_root_retirement_refuses_without_partial_reclamation() {
4633 let Some(mut graph) = retirement_test_graph() else {
4634 return;
4635 };
4636 let provider = Arc::clone(&graph.provider);
4637 let empty = graph.empty_root();
4638 let (target, target_edit) = retirement_insert_root(&mut graph, empty, 100, 200);
4639 let (other, other_edit) = retirement_insert_root(&mut graph, empty, 300, 400);
4640 let read_arena = |graph: &SemanticHypergraph| download_test_arena(&provider, graph);
4641 for (root, base) in [(empty, empty), (target, target)] {
4642 let before = read_arena(&graph);
4643 let refused = retirement_command(
4644 &mut graph,
4645 OP_RETIRE_ROOT,
4646 &[
4647 (8, root.slot as u64),
4648 (9, root.generation),
4649 (10, base.slot as u64),
4650 (11, base.generation),
4651 ],
4652 );
4653 assert_eq!(refused.words[0], STATUS_INACTIVE);
4654 assert_eq!(read_arena(&graph), before);
4655 }
4656 let fork = retirement_command(
4657 &mut graph,
4658 OP_FORK,
4659 &[(8, target.slot as u64), (9, target.generation)],
4660 );
4661 assert_eq!(fork.words[0], STATUS_OK);
4662 let before = read_arena(&graph);
4663 let refused = retirement_command(
4664 &mut graph,
4665 OP_RETIRE_ROOT,
4666 &[
4667 (8, target.slot as u64),
4668 (9, target.generation),
4669 (11, empty.generation),
4670 ],
4671 );
4672 assert_eq!(refused.words[0], STATUS_INACTIVE);
4673 assert_eq!(read_arena(&graph), before);
4674 assert_eq!(
4675 retirement_command(&mut graph, OP_DISCARD, &[(9, fork.words[6])]).words[0],
4676 STATUS_OK
4677 );
4678 let version_start = CONTROL_WORDS
4679 + 8 * ROOT_WORDS
4680 + CANDIDATE_WORDS
4681 + 8 * STATEMENT_WORDS
4682 + 16 * SUPPORT_WORDS;
4683 let clean = read_arena(&graph);
4684 for (offset, value, expected_status) in [
4685 (
4686 version_start + target_edit.words[11] * VERSION_WORDS + 1,
4687 u64::MAX,
4688 STATUS_GENERATION_EXHAUSTED,
4689 ),
4690 (
4691 version_start + other_edit.words[11] * VERSION_WORDS + 7,
4692 17,
4693 STATUS_CORRUPT_LINEAGE,
4694 ),
4695 ] {
4696 let mut arena = clean.clone();
4697 arena[offset as usize] = value;
4698 provider
4699 .htod_sync_copy_into_tracked(&arena, &mut graph.arena)
4700 .unwrap();
4701 let refused = retirement_command(
4702 &mut graph,
4703 OP_RETIRE_ROOT,
4704 &[
4705 (8, target.slot as u64),
4706 (9, target.generation),
4707 (11, empty.generation),
4708 ],
4709 );
4710 assert_eq!(refused.words[0], expected_status);
4711 assert_eq!(read_arena(&graph), arena);
4712 }
4713 provider
4714 .htod_sync_copy_into_tracked(&clean, &mut graph.arena)
4715 .unwrap();
4716 assert_eq!(
4717 retirement_root(&mut graph, target, empty).words[0],
4718 STATUS_OK
4719 );
4720 let truth = retirement_command(
4721 &mut graph,
4722 OP_TRUTH,
4723 &[
4724 (7, 1),
4725 (8, other.slot as u64),
4726 (9, other.generation),
4727 (16, 300),
4728 ],
4729 );
4730 assert_eq!(truth.words[0], STATUS_OK);
4731 assert_eq!(truth.words[2], 1);
4732 }
4733
4734 #[test]
4735 fn insertion_receipt_retains_previous_truth_for_actual_support_effects() {
4736 for (previous, resulting, new_statement) in [
4737 (0, 1, true),
4738 (0, 2, true),
4739 (1, 1, false),
4740 (2, 2, false),
4741 (1, 3, false),
4742 (2, 3, false),
4743 (3, 3, false),
4744 ] {
4745 let mut receipt = DeviceReceipt::default();
4746 receipt.words[1] = OUTCOME_INSERTED;
4747 receipt.words[2] = resulting;
4748 receipt.words[32] = u64::from(new_statement) | (previous << 1);
4749 assert_eq!(
4750 inserted_support_previous_truth(&receipt).unwrap(),
4751 SemanticTruth::from_bits(previous).unwrap(),
4752 );
4753 }
4754 }
4755
4756 #[test]
4757 fn resident_discard_retires_refused_acquisition_and_rejects_stale_replay() {
4758 let Some(mut graph) = retirement_test_graph() else {
4759 return;
4760 };
4761 let provider = Arc::clone(&graph.provider);
4762 let bytes = std::mem::size_of::<SemanticResidentDecodedStatement>()
4763 + 2 * std::mem::size_of::<SemanticResidentDecodedSupport>()
4764 + std::mem::size_of::<SemanticResidentHandleRecord>()
4765 + 5 * std::mem::size_of::<SemanticResidentReceiptRecord>();
4766 let mut reservation = provider.memory().reserve_bytes(bytes as u64).unwrap();
4767 let mut statements = reservation
4768 .alloc::<SemanticResidentDecodedStatement>(1)
4769 .unwrap();
4770 let mut supports = reservation
4771 .alloc::<SemanticResidentDecodedSupport>(2)
4772 .unwrap();
4773 let handles = reservation
4774 .alloc::<SemanticResidentHandleRecord>(1)
4775 .unwrap();
4776 let receipts = reservation
4777 .alloc::<SemanticResidentReceiptRecord>(5)
4778 .unwrap();
4779 provider
4780 .htod_sync_copy_into_tracked(
4781 &[SemanticResidentDecodedStatement {
4782 identity_words: [17; 8],
4783 record: 0,
4784 reconstruction: [0; 10],
4785 }],
4786 &mut statements,
4787 )
4788 .unwrap();
4789 provider
4790 .htod_sync_copy_into_tracked(
4791 &[1, 0].map(|polarity| SemanticResidentDecodedSupport {
4792 polarity,
4793 provenance_words: [polarity; 8],
4794 source_words: [3; 8],
4795 context_words: [4; 8],
4796 scope_words: [5; 8],
4797 record: polarity,
4798 }),
4799 &mut supports,
4800 )
4801 .unwrap();
4802 let mut setup = graph.domain.new_strict_recorder();
4803 setup.read_write(&statements);
4804 setup.read_write(&supports);
4805 setup.write(&handles);
4806 setup.write(&receipts);
4807 unsafe { graph.domain.enqueue(setup, |_| Ok::<(), XlogError>(())) }
4808 .unwrap()
4809 .commit()
4810 .unwrap();
4811 let slot = |index| SemanticResidentReceiptSlot::new(&receipts, index).unwrap();
4812 let statement = || SemanticResidentStatementBank::new(&statements, 0).unwrap();
4813 let support = |index| SemanticResidentSupportBank::new(&supports, index).unwrap();
4814 graph
4815 .enqueue_resident_empty_root_handle(
4816 SemanticResidentHandleSlot::new(&handles, 0).unwrap(),
4817 slot(0),
4818 )
4819 .unwrap();
4820 let base = || SemanticResidentRootHandle::Bank {
4821 allocation: &handles,
4822 index: 0,
4823 };
4824 let fork = graph.enqueue_resident_fork(base(), slot(1)).unwrap();
4825 let inserted = graph
4826 .enqueue_resident_insert_support(
4827 fork.candidate_handle(),
4828 statement(),
4829 support(0),
4830 slot(2),
4831 )
4832 .unwrap();
4833 graph
4834 .enqueue_resident_insert_support(
4835 inserted.candidate_handle(),
4836 statement(),
4837 support(1),
4838 slot(3),
4839 )
4840 .unwrap();
4841 graph.stream.synchronize().unwrap();
4842 let before = provider
4843 .dtoh_small_metadata_untracked(&receipts, receipts.len())
4844 .unwrap();
4845 assert_eq!(before[2].words[1], OUTCOME_INSERTED);
4846 assert_eq!(before[3].words[0], STATUS_INVALID_COMMAND);
4847 assert_ne!(before[3].words[41], 0);
4848
4849 let discard = |graph: &SemanticHypergraph| {
4850 let descriptor = DeviceLaunchDescriptor {
4851 expected_owner: graph.owner,
4852 root_capacity: graph.capacities.roots as u64,
4853 statement_capacity: graph.capacities.statements as u64,
4854 support_capacity: graph.capacities.supports as u64,
4855 version_capacity: graph.capacities.versions as u64,
4856 arena_words: graph.arena_words,
4857 command_ptr: 0,
4858 command_index: 0,
4859 handle_ptr: 0,
4860 handle_index: 0,
4861 source_receipt_ptr: receipts.device_ptr_value(),
4862 source_receipt_index: 3,
4863 decoded_input_ptr: 0,
4864 decoded_input_index: 0,
4865 decoded_statement_ptr: 0,
4866 decoded_statement_index: 0,
4867 decoded_support_ptr: 0,
4868 decoded_support_index: 0,
4869 output_ptr: 0,
4870 output_index: 0,
4871 receipt_ptr: receipts.device_ptr_value(),
4872 receipt_index: 4,
4873 admission: 10,
4874 abi_generation: HYPERGRAPH_ABI_GENERATION,
4875 };
4876 let mut recorder = graph.domain.new_strict_recorder();
4877 recorder.read_write(&graph.arena);
4878 recorder.read_write(&receipts);
4879 unsafe {
4880 graph.domain.enqueue(recorder, |stream| {
4881 graph.execute.clone().launch_in(
4882 stream,
4883 LaunchConfig {
4884 grid_dim: (1, 1, 1),
4885 block_dim: (1, 1, 1),
4886 shared_mem_bytes: 0,
4887 },
4888 (graph.arena.device_ptr_value(), descriptor),
4889 )
4890 })
4891 }
4892 .unwrap()
4893 .commit()
4894 .unwrap();
4895 graph.stream.synchronize().unwrap();
4896 provider
4897 .dtoh_small_metadata_untracked(&receipts, receipts.len())
4898 .unwrap()
4899 };
4900 let cleaned = discard(&graph);
4901 assert_eq!(cleaned[4].words[0], STATUS_OK);
4902 assert_eq!(&cleaned[4].words[40..42], &[0, 0]);
4903 assert_eq!(cleaned[3].words, before[3].words);
4904 let next = graph.enqueue_resident_fork(base(), slot(1)).unwrap();
4905 graph.stream.synchronize().unwrap();
4906 let arena = download_test_arena(&provider, &graph);
4907 let stale = discard(&graph);
4908 assert_eq!(stale[4].words[0], STATUS_STALE_GENERATION);
4909 assert_eq!(stale[3].words, before[3].words);
4910 assert_eq!(download_test_arena(&provider, &graph), arena);
4911 let reused = graph
4912 .enqueue_resident_insert_support(
4913 next.candidate_handle(),
4914 statement(),
4915 support(0),
4916 slot(2),
4917 )
4918 .unwrap();
4919 graph
4920 .enqueue_resident_seal(reused.candidate_handle(), slot(3))
4921 .unwrap();
4922 graph.stream.synchronize().unwrap();
4923 let sealed_arena = download_test_arena(&provider, &graph);
4924 let sealed = discard(&graph);
4925 assert_eq!(sealed[3].words[0], STATUS_OK);
4926 assert_eq!(sealed[2].words[1], OUTCOME_INSERTED);
4927 assert_eq!(sealed[2].words[9], before[2].words[9]);
4928 assert_eq!(sealed[2].words[10], before[2].words[10] + 1);
4929 assert_eq!(sealed[4].words[0], STATUS_INVALID_COMMAND);
4930 assert_eq!(download_test_arena(&provider, &graph), sealed_arena);
4931 }
4932
4933 #[test]
4934 fn insertion_receipt_rejects_refused_duplicate_and_impossible_truth_effects() {
4935 for (status, outcome, resulting, metadata) in [
4936 (STATUS_SUPPORT_CAPACITY, OUTCOME_INSERTED, 1, 1),
4937 (STATUS_OK, OUTCOME_UNCHANGED, 1, 2),
4938 (STATUS_OK, 0, 1, 1),
4939 (STATUS_OK, OUTCOME_INSERTED, 0, 1),
4940 (STATUS_OK, OUTCOME_INSERTED, 4, 1),
4941 (STATUS_OK, OUTCOME_INSERTED, 3, 1),
4942 (STATUS_OK, OUTCOME_INSERTED, 1, 0),
4943 (STATUS_OK, OUTCOME_INSERTED, 1, 3),
4944 (STATUS_OK, OUTCOME_INSERTED, 1, 4),
4945 (STATUS_OK, OUTCOME_INSERTED, 1, 6),
4946 (STATUS_OK, OUTCOME_INSERTED, 1, 9),
4947 ] {
4948 let mut receipt = DeviceReceipt::default();
4949 receipt.words[0] = status;
4950 receipt.words[1] = outcome;
4951 receipt.words[2] = resulting;
4952 receipt.words[32] = metadata;
4953 assert!(
4954 matches!(
4955 inserted_support_previous_truth(&receipt),
4956 Err(SemanticHypergraphError::CorruptLineage { .. }),
4957 ),
4958 "accepted invalid insertion receipt: {status}/{outcome}/{resulting}/{metadata}",
4959 );
4960 }
4961 }
4962
4963 fn identity_bytes_from_dwords(words: [u32; 8]) -> [u8; 32] {
4964 let mut bytes = [0; 32];
4965 for (chunk, word) in bytes.as_chunks_mut::<4>().0.iter_mut().zip(words) {
4966 chunk.copy_from_slice(&word.to_le_bytes());
4967 }
4968 bytes
4969 }
4970
4971 #[test]
4972 fn root_material_codec_preserves_typed_bits_and_rejects_truncation() {
4973 let material = SemanticRootMaterial {
4974 records: numeric_records(),
4975 symbols: vec![],
4976 insertions: vec![],
4977 digest: [0; 32],
4978 extents: [0; 3],
4979 admission_base_digest: material_root_digest([0; 32], [0; 32], [0; 3]),
4980 admission_base_extents: [0; 3],
4981 };
4982 let limits = SemanticAdmissionLimits {
4983 max_records: 5,
4984 max_terms: 25,
4985 max_references: 2,
4986 max_utf8_bytes: 1024,
4987 };
4988 let encoded = material.encode().unwrap();
4989 assert_eq!(
4990 SemanticRootMaterial::decode(&encoded, limits).unwrap(),
4991 material
4992 );
4993 for len in 0..encoded.len() {
4994 assert!(SemanticRootMaterial::decode(&encoded[..len], limits).is_err());
4995 }
4996 let mut trailing = encoded.clone();
4997 trailing.push(0);
4998 assert!(SemanticRootMaterial::decode(&trailing, limits).is_err());
4999 let tight = SemanticAdmissionLimits {
5000 max_terms: 24,
5001 ..limits
5002 };
5003 assert!(SemanticRootMaterial::decode(&encoded, tight).is_err());
5004 let mut reader = SemanticMaterialReader::new(&[255; 4]);
5005 assert!(reader.count(1).is_err());
5006 }
5007
5008 pub(crate) fn root_material_records() -> SemanticAdmissionRecords {
5009 let roles = [
5010 SemanticRecordRole::Statement,
5011 SemanticRecordRole::Provenance,
5012 SemanticRecordRole::Source,
5013 SemanticRecordRole::Context,
5014 SemanticRecordRole::Scope,
5015 ];
5016 let predicates = roles
5017 .into_iter()
5018 .enumerate()
5019 .map(|(index, role)| SemanticPredicateRecord {
5020 predicate: RelId(index as u32),
5021 role,
5022 schema: Schema::new(vec![("value".into(), ScalarType::U32)]),
5023 })
5024 .collect();
5025 let records = [0, 0, 1, 2, 3, 4, 2]
5026 .into_iter()
5027 .enumerate()
5028 .map(|(index, predicate)| SemanticTypedRecord {
5029 predicate: RelId(predicate),
5030 arguments: vec![SemanticArgument::U32(index as u32)],
5031 qualifiers: vec![],
5032 })
5033 .collect();
5034 let mut supports: Vec<_> = [SemanticPolarity::Pro, SemanticPolarity::Contra]
5035 .into_iter()
5036 .map(|polarity| SemanticSupportRecord {
5037 statement: 0,
5038 polarity,
5039 provenance: 2,
5040 source: 3,
5041 context: 4,
5042 scope: 5,
5043 })
5044 .collect();
5045 supports.push(SemanticSupportRecord {
5046 statement: 0,
5047 polarity: SemanticPolarity::Pro,
5048 provenance: 2,
5049 source: 6,
5050 context: 4,
5051 scope: 5,
5052 });
5053 SemanticAdmissionRecords {
5054 predicates,
5055 records,
5056 supports,
5057 }
5058 }
5059
5060 pub(crate) fn root_material_limits() -> SemanticAdmissionLimits {
5061 SemanticAdmissionLimits {
5062 max_records: 32,
5063 max_terms: 64,
5064 max_references: 32,
5065 max_utf8_bytes: 1024,
5066 }
5067 }
5068
5069 pub(crate) fn admit_material_records(records: SemanticAdmissionRecords) -> SemanticAdmission {
5070 admit_semantic_records(
5071 records,
5072 root_material_limits(),
5073 SemanticRootHandle::new(1, 0, 1),
5074 || {
5075 Ok(SemanticRootSnapshot::new(
5076 SemanticRootDigest(material_root_digest([0; 32], [0; 32], [0; 3])),
5077 SemanticExtents::default(),
5078 ))
5079 },
5080 )
5081 .unwrap()
5082 }
5083
5084 pub(crate) fn reconstructed_material_statement(
5085 admission: &SemanticAdmission,
5086 original: Option<u32>,
5087 reconstruction: &[u32; 10],
5088 ) -> Result<SemanticStatementKey, SemanticHypergraphError> {
5089 material_statement_key(admission, original, reconstruction)
5090 }
5091
5092 fn verify_native_material_reconstruction(
5093 executable: &std::path::Path,
5094 directory: &std::path::Path,
5095 run: &impl Fn(&mut std::process::Command),
5096 ) {
5097 let mut records = root_material_records();
5098 records.predicates[0].schema = Schema::new(vec![
5099 ("left".into(), ScalarType::U32),
5100 ("right".into(), ScalarType::U32),
5101 ])
5102 .with_sort_labels(vec!["bit".into(), "bit".into()])
5103 .unwrap();
5104 records.records[0].arguments = vec![SemanticArgument::U32(0), SemanticArgument::U32(0)];
5105 records.records[1].arguments = vec![SemanticArgument::U32(1), SemanticArgument::U32(1)];
5106 records.supports.push(records.supports[0].clone());
5107 let empty_digest = material_root_digest([0; 32], [0; 32], [0; 3]);
5108 let admission = admit_semantic_records(
5109 records,
5110 root_material_limits(),
5111 SemanticRootHandle::new(91, 0, 1),
5112 || {
5113 Ok(SemanticRootSnapshot::new(
5114 SemanticRootDigest(empty_digest),
5115 SemanticExtents::default(),
5116 ))
5117 },
5118 )
5119 .unwrap();
5120 let (records, symbols) = normalized_material_admission(&admission).unwrap();
5121 let derived_equal = [0, 0, 0, 0, 1, 0, 0, 0, 0, u32::MAX];
5122 let derived_new = [0, 0, 0, 1, 1, 0, 0, 0, 0, u32::MAX];
5123 let original = material_statement_key(&admission, Some(0), &[0; 10]).unwrap();
5124 assert_eq!(
5125 original.identity,
5126 material_statement_key(&admission, None, &derived_equal)
5127 .unwrap()
5128 .identity
5129 );
5130 let new_key = material_statement_key(&admission, None, &derived_new).unwrap();
5131 assert!(admission
5132 .statement_keys
5133 .iter()
5134 .flatten()
5135 .all(|key| key.identity != new_key.identity));
5136 let mut encodings = Vec::new();
5137 for (case, (statement, reconstruction)) in [
5138 (Some(0), [0; 10]),
5139 (None, derived_equal),
5140 (None, derived_new),
5141 ]
5142 .into_iter()
5143 .enumerate()
5144 {
5145 let key = material_statement_key(&admission, statement, &reconstruction).unwrap();
5146 let event = admission.support_event(3).unwrap();
5147 let version = material_version_digest(
5148 [0; 32],
5149 event.identity(key.identity).0,
5150 event.polarity.code(),
5151 );
5152 let material = SemanticRootMaterial {
5153 records: records.clone(),
5154 symbols: symbols.clone(),
5155 insertions: vec![SemanticRootInsertion {
5156 statement,
5157 reconstruction,
5158 support: 3,
5159 version,
5160 }],
5161 digest: material_root_digest(empty_digest, version, [1; 3]),
5162 extents: [1; 3],
5163 admission_base_digest: empty_digest,
5164 admission_base_extents: [0; 3],
5165 };
5166 material.validate_lineage(&admission).unwrap();
5167 let encoded = material.encode().unwrap();
5168 let decoded = SemanticRootMaterial::decode(&encoded, root_material_limits()).unwrap();
5169 assert_eq!(decoded, material);
5170 let mut old_encoding = encoded.clone();
5171 old_encoding[8..12].copy_from_slice(&1u32.to_le_bytes());
5172 assert!(SemanticRootMaterial::decode(&old_encoding, root_material_limits()).is_err());
5173 encodings.push(encoded);
5174 for reuse in [false, true] {
5175 let mut commands = vec![DeviceCommand::default()];
5176 commands[0].words[0] = OP_INITIALIZE;
5177 let fork = || {
5178 let mut command = DeviceCommand::default();
5179 command.words[0] = OP_FORK;
5180 command.words[9] = 1;
5181 command
5182 };
5183 if reuse {
5184 commands.push(fork());
5185 let mut insert = DeviceCommand::default();
5186 encode_support_insertion(
5187 &mut insert,
5188 SemanticForkHandle::new(91, 0, 1),
5189 &key,
5190 &event,
5191 &reconstruction,
5192 );
5193 commands.push(insert);
5194 let mut discard = DeviceCommand::default();
5195 discard.words[0] = OP_DISCARD;
5196 discard.words[9] = 1;
5197 commands.push(discard);
5198 }
5199 commands.push(fork());
5200 let insertion = &decoded.insertions[0];
5201 let restored_key = material_statement_key(
5202 &admission,
5203 insertion.statement,
5204 &insertion.reconstruction,
5205 )
5206 .unwrap();
5207 let mut insert = DeviceCommand::default();
5208 encode_support_insertion(
5209 &mut insert,
5210 SemanticForkHandle::new(91, 0, 1 + u64::from(reuse)),
5211 &restored_key,
5212 &admission.support_event(insertion.support).unwrap(),
5213 &insertion.reconstruction,
5214 );
5215 commands.push(insert);
5216 let mut seal = DeviceCommand::default();
5217 seal.words[0] = OP_SEAL;
5218 seal.words[9] = 1 + u64::from(reuse);
5219 commands.push(seal);
5220 let command_path = directory.join(format!("material-{case}-{reuse}.commands"));
5221 let output_path = directory.join(format!("material-{case}-{reuse}.arena"));
5222 let bytes: Vec<_> = commands
5223 .iter()
5224 .flat_map(|command| command.words.iter().flat_map(|word| word.to_ne_bytes()))
5225 .collect();
5226 std::fs::write(&command_path, bytes).unwrap();
5227 run(std::process::Command::new(executable)
5228 .arg("--commands")
5229 .arg(&command_path)
5230 .arg(&output_path));
5231 let bytes = std::fs::read(&output_path).unwrap();
5232 assert_eq!(bytes.len() % 8, 0);
5233 let words: Vec<_> = bytes
5234 .as_chunks::<8>()
5235 .0
5236 .iter()
5237 .map(|word| u64::from_ne_bytes(*word))
5238 .collect();
5239 let root = SemanticRootHandle::new(91, words[3] as u32, words[4]);
5240 let digest =
5241 std::array::from_fn(|byte| (words[16 + byte / 8] >> (8 * (byte % 8))) as u8);
5242 let snapshot = SemanticRootSnapshot::new(
5243 SemanticRootDigest(digest),
5244 SemanticExtents::new(words[13] as u32, words[14] as u32, words[15] as u32),
5245 );
5246 let capacities = SemanticHypergraphCapacities::try_new(4, 4, 8, 8).unwrap();
5247 let arena = &words[RECEIPT_WORDS..];
5248 let observed =
5249 material_from_arena(arena, capacities, root, snapshot, &admission).unwrap();
5250 assert_eq!(
5251 observed, decoded,
5252 "native restoration changed original/derived target or support occurrence"
5253 );
5254 let support =
5255 (CONTROL_WORDS + 4 * ROOT_WORDS + CANDIDATE_WORDS + 4 * STATEMENT_WORDS)
5256 as usize;
5257 assert_eq!(arena[support + 1], 1 + u64::from(reuse));
5258 std::fs::remove_file(command_path).unwrap();
5259 std::fs::remove_file(output_path).unwrap();
5260 }
5261 let mut changed = material.clone();
5262 changed.insertions[0].support = 2;
5263 assert!(changed.validate_lineage(&admission).is_err());
5264 if statement.is_none() {
5265 for (field, value) in [(0, 1), (1, u32::MAX), (1, 2), (5, 1), (9, 2)] {
5266 let mut changed = material.clone();
5267 changed.insertions[0].reconstruction[field] = value;
5268 assert!(changed.validate_lineage(&admission).is_err());
5269 }
5270 changed = material.clone();
5271 changed.insertions[0].statement = Some(0);
5272 assert!(changed.validate_lineage(&admission).is_err());
5273 assert!(changed.encode().is_err());
5274 }
5275 }
5276 assert_ne!(
5277 encodings[0], encodings[1],
5278 "derived bytes acquired original record zero"
5279 );
5280 assert_ne!(Sha256::digest(&encodings[0]), Sha256::digest(&encodings[1]));
5281 }
5282
5283 #[test]
5284 fn native_truth_receipt_producer_preserves_view_and_head_fields() {
5285 use std::process::Command;
5286 use std::time::{Duration, Instant};
5287 let mut nonce = [0; 8];
5288 getrandom::fill(&mut nonce).unwrap();
5289 let directory = std::env::temp_dir().join(format!(
5290 "xlog-native-truth-{}-{}",
5291 std::process::id(),
5292 u64::from_le_bytes(nonce)
5293 ));
5294 std::fs::create_dir(&directory).unwrap();
5295 let deadline = Instant::now() + Duration::from_secs(120);
5296 let run = |command: &mut Command| {
5297 let invocation = format!("{command:?}");
5298 let mut child = command
5299 .current_dir(&directory)
5300 .spawn()
5301 .expect("native truth regression command must start; no silent compiler skip");
5302 loop {
5303 if let Some(status) = child
5304 .try_wait()
5305 .expect("native truth regression process status")
5306 {
5307 #[cfg(unix)]
5308 let termination = {
5309 use std::os::unix::process::ExitStatusExt;
5310 format!(
5311 "code={:?}, signal={:?}, core_dumped={}",
5312 status.code(),
5313 status.signal(),
5314 status.core_dumped()
5315 )
5316 };
5317 #[cfg(not(unix))]
5318 let termination = format!("code={:?}", status.code());
5319 assert!(
5320 status.success(),
5321 "native truth regression failed: {termination}; command {invocation}; output retained in {}",
5322 directory.display()
5323 );
5324 break;
5325 }
5326 if Instant::now() >= deadline {
5327 child
5328 .kill()
5329 .expect("stop timed-out native truth regression");
5330 child
5331 .wait()
5332 .expect("reap timed-out native truth regression");
5333 panic!(
5334 "native truth regression exceeded two minutes; output retained in {}",
5335 directory.display()
5336 );
5337 }
5338 std::thread::sleep(Duration::from_millis(10));
5339 }
5340 };
5341 for name in ["semantic_truth_receipt", "semantic_feedback_lineage"] {
5342 let executable = directory.join(name);
5343 let source =
5344 std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(format!("tests/{name}.cpp"));
5345 let mut compiler =
5346 Command::new(std::env::var_os("CXX").unwrap_or_else(|| "c++".into()));
5347 compiler.args([
5348 "-std=c++20",
5349 "-O0",
5350 "-g",
5351 "-fno-omit-frame-pointer",
5352 "-rdynamic",
5353 "-Wall",
5354 "-Wextra",
5355 "-I",
5356 env!("OUT_DIR"),
5357 ]);
5358 if cfg!(feature = "semantic-policy") {
5359 compiler.arg("-DXLOG_SEMANTIC_POLICY");
5360 }
5361 run(compiler.arg(source).arg("-o").arg(&executable));
5362 run(&mut Command::new(&executable));
5363 if name == "semantic_truth_receipt" {
5364 verify_native_material_reconstruction(&executable, &directory, &run);
5365 } else {
5366 crate::semantic_transition::task_binding_tests::verify_native_decoded_reconstruction(
5367 &executable, &directory, &run,
5368 );
5369 }
5370 std::fs::remove_file(&executable).unwrap();
5371 }
5372 std::fs::remove_dir(&directory).unwrap();
5373 }
5374
5375 #[test]
5376 fn task_selection_identity_retains_exact_admitted_occurrences() {
5377 use crate::semantic_transition::{
5378 task_binding_tests::{arithmetic_observation, task_spec},
5379 TaskEvaluationBinding,
5380 };
5381 let mut records = root_material_records();
5382 records.predicates[0].schema = Schema::new(vec![
5383 ("left".into(), ScalarType::U32),
5384 ("right".into(), ScalarType::U32),
5385 ])
5386 .with_sort_labels(vec!["bit".into(), "bit".into()])
5387 .unwrap();
5388 records.records[0].arguments = vec![SemanticArgument::U32(1), SemanticArgument::U32(1)];
5389 records.records[1].arguments = vec![SemanticArgument::U32(0), SemanticArgument::U32(1)];
5390 records.records.push(records.records[0].clone());
5391 records.supports.push(records.supports[0].clone());
5392 let admission = admit_material_records(records);
5393 let bind = |statement_records, allowed_support_records: Vec<u32>| {
5394 let binding = TaskEvaluationBinding::bind(
5395 &admission,
5396 task_spec(statement_records, allowed_support_records.clone()),
5397 arithmetic_observation(),
5398 )
5399 .unwrap();
5400 assert_eq!(binding.spec().statement_records, statement_records);
5401 assert_eq!(
5402 binding.spec().allowed_support_records,
5403 allowed_support_records
5404 );
5405 binding
5406 };
5407 let first = bind([0, 1, 1], vec![0]).words(1);
5408 let equal_occurrence = bind([0, 1, 1], vec![3]).words(1);
5409 assert_ne!(
5410 first[6..],
5411 equal_occurrence[6..],
5412 "resident task bank collapsed equal support occurrences"
5413 );
5414 assert_eq!(first[22..25], [0, 1, 1]);
5415 assert_eq!(first[34], 0);
5416 assert_eq!(equal_occurrence[34], 3);
5417 let both = bind([0, 1, 1], vec![0, 3]).words(1);
5418 assert_eq!(both[21], 2);
5419 assert_eq!([both[34], both[39]], [0, 3]);
5420 for (left, right) in [
5421 (bind([0, 1, 1], vec![]), bind([7, 1, 1], vec![])),
5422 (bind([0, 1, 1], vec![0]), bind([0, 1, 1], vec![3])),
5423 (bind([0, 1, 1], vec![0, 3]), bind([0, 1, 1], vec![3, 0])),
5424 (bind([0, 1, 1], vec![0, 0]), bind([0, 1, 1], vec![0])),
5425 ] {
5426 assert_ne!(
5429 left.identity(),
5430 right.identity(),
5431 "task identity lost original record selections"
5432 );
5433 }
5434 }
5435
5436 #[test]
5437 fn root_material_lineage_binds_cross_statement_order_and_original_support() {
5438 let admission = admit_semantic_records(
5439 root_material_records(),
5440 root_material_limits(),
5441 SemanticRootHandle::new(1, 0, 1),
5442 || {
5443 Ok(SemanticRootSnapshot::new(
5444 SemanticRootDigest(material_root_digest([0; 32], [0; 32], [0; 3])),
5445 SemanticExtents::default(),
5446 ))
5447 },
5448 )
5449 .unwrap();
5450 let (records, symbols) = normalized_material_admission(&admission).unwrap();
5451 let mut material = SemanticRootMaterial {
5452 records,
5453 symbols,
5454 insertions: vec![],
5455 digest: material_root_digest([0; 32], [0; 32], [0; 3]),
5456 extents: [0; 3],
5457 admission_base_digest: material_root_digest([0; 32], [0; 32], [0; 3]),
5458 admission_base_extents: [0; 3],
5459 };
5460 for (ordinal, (statement, support)) in [(1, 0), (0, 1)].into_iter().enumerate() {
5461 let key = admission.statement_key(statement).unwrap();
5462 let event = admission.support_event(support).unwrap();
5463 let version = material_version_digest(
5464 [0; 32],
5465 event.identity(key.identity).0,
5466 event.polarity.code(),
5467 );
5468 material.insertions.push(SemanticRootInsertion {
5469 statement: Some(statement),
5470 reconstruction: [0; 10],
5471 support,
5472 version,
5473 });
5474 material.extents = [(ordinal + 1) as u32; 3];
5475 material.digest = material_root_digest(material.digest, version, material.extents);
5476 }
5477 material.validate_lineage(&admission).unwrap();
5478 let first = &material.insertions[0];
5481 material.admission_base_digest = material_root_digest(
5482 material_root_digest([0; 32], [0; 32], [0; 3]),
5483 first.version,
5484 [1; 3],
5485 );
5486 material.admission_base_extents = [1; 3];
5487 material.validate_lineage(&admission).unwrap();
5488 let mut wrong_base = material.clone();
5489 wrong_base.admission_base_digest[0] ^= 1;
5490 assert!(wrong_base.validate_lineage(&admission).is_err());
5491 let decoded =
5492 SemanticRootMaterial::decode(&material.encode().unwrap(), root_material_limits())
5493 .unwrap();
5494 assert_eq!(decoded, material);
5495 assert_eq!(decoded.records.supports[0].statement, 0);
5496 assert_eq!(decoded.insertions[0].statement, Some(1));
5497 let mut reversed = material.clone();
5498 reversed.insertions.reverse();
5499 assert!(reversed.validate_lineage(&admission).is_err());
5500 let mut duplicate = material.clone();
5501 duplicate.insertions.push(duplicate.insertions[0].clone());
5502 assert!(duplicate.validate_lineage(&admission).is_err());
5503 let mut altered = material;
5504 altered.insertions[0].version[0] ^= 1;
5505 assert!(altered.validate_lineage(&admission).is_err());
5506 }
5507
5508 #[test]
5509 fn root_material_observation_roots_preserve_actual_aliases_and_both_polarities() {
5510 let mut declared = root_material_records();
5511 declared.records.push(declared.records[0].clone());
5512 let mut other_query = declared.records[1].clone();
5513 other_query.arguments = vec![SemanticArgument::U32(8)];
5514 declared.records.push(other_query);
5515 let admission = admit_material_records(declared);
5516 let (records, symbols) = normalized_material_admission(&admission).unwrap();
5517 let empty = material_root_digest([0; 32], [0; 32], [0; 3]);
5518 let mut material = SemanticRootMaterial {
5519 records,
5520 symbols,
5521 insertions: vec![],
5522 digest: empty,
5523 extents: [0; 3],
5524 admission_base_digest: empty,
5525 admission_base_extents: [0; 3],
5526 };
5527 let mut previous = [0; 32];
5528 for (statement, support, extents, truth) in [
5529 (7, 0, [1, 1, 1], 1),
5530 (7, 1, [1, 2, 2], 3),
5531 (1, 2, [2, 3, 3], 1),
5532 ] {
5533 let key = admission.statement_key(statement).unwrap();
5534 let event = admission.support_event(support).unwrap();
5535 let version = material_version_digest(
5536 if statement == 7 { previous } else { [0; 32] },
5537 event.identity(key.identity).0,
5538 truth,
5539 );
5540 material.insertions.push(SemanticRootInsertion {
5541 statement: Some(statement),
5542 reconstruction: [0; 10],
5543 support,
5544 version,
5545 });
5546 material.extents = extents;
5547 material.digest = material_root_digest(material.digest, version, extents);
5548 previous = version;
5549 if support == 0 {
5550 material.admission_base_digest = material.digest;
5551 material.admission_base_extents = extents;
5552 }
5553 }
5554 let material =
5555 SemanticRootMaterial::decode(&material.encode().unwrap(), root_material_limits())
5556 .unwrap();
5557 let roots = material
5558 .task_observation_roots(&admission, [0, 8, 8])
5559 .unwrap();
5560 assert_eq!(roots.query_records, [0, 8, 8]);
5561 assert_eq!(roots.root_digest.as_bytes(), &material.digest);
5562 assert_eq!(roots.root_extents, [2, 3, 3]);
5563 assert_eq!(roots.contributors, [(0, Some(7), 0), (0, Some(7), 1)]);
5564 let reverse = material
5565 .task_observation_roots(&admission, [8, 0, 8])
5566 .unwrap();
5567 assert_eq!(reverse.contributors, [(1, Some(7), 0), (1, Some(7), 1)]);
5568 let mut corrupt = material.clone();
5569 corrupt.insertions[0].version[0] ^= 1;
5570 assert!(corrupt
5571 .task_observation_roots(&admission, [0, 8, 8])
5572 .is_err());
5573 assert!(material
5574 .task_observation_roots(&admission, [0, 2, 2])
5575 .is_err());
5576 let mut derived = material;
5577 derived.insertions[0].statement = None;
5578 derived.insertions[0].reconstruction = [0; 10];
5579 derived.insertions[0].reconstruction[9] = u32::MAX;
5580 let roots = derived
5581 .task_observation_roots(&admission, [0, 8, 8])
5582 .unwrap();
5583 assert_eq!(roots.contributors, [(0, None, 0), (0, Some(7), 1)]);
5584 }
5585
5586 #[test]
5587 fn root_material_symbols_retain_occurrence_order_and_enforce_byte_budget() {
5588 let mut records = root_material_records();
5589 records.predicates[0].schema = Schema::new(vec![("value".into(), ScalarType::Symbol)]);
5590 records.records[0].arguments =
5591 vec![SemanticArgument::Symbol(symbol::intern("root-material-a"))];
5592 records.records[1].arguments =
5593 vec![SemanticArgument::Symbol(symbol::intern("root-material-b"))];
5594 let admission = admit_semantic_records(
5595 records,
5596 root_material_limits(),
5597 SemanticRootHandle::new(1, 0, 1),
5598 || {
5599 Ok(SemanticRootSnapshot::new(
5600 SemanticRootDigest([0; 32]),
5601 SemanticExtents::default(),
5602 ))
5603 },
5604 )
5605 .unwrap();
5606 let (records, symbols) = normalized_material_admission(&admission).unwrap();
5607 let mut material = SemanticRootMaterial {
5608 records,
5609 symbols,
5610 insertions: vec![],
5611 digest: [0; 32],
5612 extents: [0; 3],
5613 admission_base_digest: material_root_digest([0; 32], [0; 32], [0; 3]),
5614 admission_base_extents: [0; 3],
5615 };
5616 assert_eq!(
5617 material.records.records[0].arguments,
5618 [SemanticArgument::Symbol(0)]
5619 );
5620 assert_eq!(
5621 material.records.records[1].arguments,
5622 [SemanticArgument::Symbol(1)]
5623 );
5624 let bytes = material.encode().unwrap();
5625 assert_eq!(
5626 SemanticRootMaterial::decode(&bytes, root_material_limits()).unwrap(),
5627 material
5628 );
5629 let records = material.admission_records().unwrap();
5630 assert_eq!(records, admission.records);
5631 assert!(SemanticRootMaterial::decode(
5632 &bytes,
5633 SemanticAdmissionLimits {
5634 max_utf8_bytes: 1,
5635 ..root_material_limits()
5636 }
5637 )
5638 .is_err());
5639 material.records.records[1].arguments = vec![SemanticArgument::Symbol(0)];
5640 assert!(material.encode().is_err());
5641 }
5642
5643 #[test]
5644 fn root_material_capture_uses_ordinals_and_rejects_invalid_reachable_generations() {
5645 let root = SemanticRootHandle::new(17, 1, 4);
5646 let mut records = root_material_records();
5647 records.records.push(records.records[0].clone());
5648 records.supports.push(records.supports[1].clone());
5649 let admission = admit_semantic_records(
5650 records,
5651 root_material_limits(),
5652 SemanticRootHandle::new(17, 0, 1),
5653 || {
5654 Ok(SemanticRootSnapshot::new(
5655 SemanticRootDigest(material_root_digest([0; 32], [0; 32], [0; 3])),
5656 SemanticExtents::default(),
5657 ))
5658 },
5659 )
5660 .unwrap();
5661 let capacities = SemanticHypergraphCapacities::try_new(2, 3, 3, 3).unwrap();
5662 let mut arena = vec![0u64; checked_arena_words(capacities).unwrap() as usize];
5665 arena[1] = root.owner;
5666 let statements = (CONTROL_WORDS + 2 * ROOT_WORDS + CANDIDATE_WORDS) as usize;
5667 let supports = statements + 3 * STATEMENT_WORDS as usize;
5668 let versions = supports + 3 * SUPPORT_WORDS as usize;
5669 let heads = versions + 3 * VERSION_WORDS as usize + 3;
5670 let root_offset = (CONTROL_WORDS + ROOT_WORDS) as usize;
5671 arena[root_offset] = 3;
5672 arena[root_offset + 1] = root.generation;
5673 arena[root_offset + 3..root_offset + 6].copy_from_slice(&[2, 2, 2]);
5674 arena[CONTROL_WORDS as usize] = 2;
5677 arena[(CONTROL_WORDS + 2 * ROOT_WORDS) as usize] = 1;
5678 arena[versions + VERSION_WORDS as usize] = 99;
5679 let mut digest = material_root_digest([0; 32], [0; 32], [0; 3]);
5680 for (index, (statement_index, support_index, statement_slot, slot)) in
5681 [(1u32, 0u32, 1usize, 2usize), (7, 3, 0, 0)]
5682 .into_iter()
5683 .enumerate()
5684 {
5685 let key = admission.statement_key(statement_index).unwrap();
5686 let event = admission.support_event(support_index).unwrap();
5687 let support_digest = event.identity(key.identity).0;
5688 let version_digest =
5689 material_version_digest([0; 32], support_digest, event.polarity.code());
5690 let statement = statements + statement_slot * STATEMENT_WORDS as usize;
5691 arena[statement] = 3;
5692 arena[statement + 1] = 8;
5693 arena[statement + 3..statement + 7].copy_from_slice(&identity_words(key.identity.0));
5694 let support = supports + slot * SUPPORT_WORDS as usize;
5695 arena[support..support + 6].copy_from_slice(&[
5696 3,
5697 9,
5698 0,
5699 statement_slot as u64,
5700 8,
5701 event.polarity.code(),
5702 ]);
5703 arena[support + 6..support + 10].copy_from_slice(&identity_words(support_digest));
5704 arena[support + 10] = u64::from(statement_index);
5705 arena[support + 11] = u64::from(support_index);
5706 let version = versions + slot * VERSION_WORDS as usize;
5707 arena[version..version + 9].copy_from_slice(&[
5708 3,
5709 10,
5710 0,
5711 statement_slot as u64,
5712 8,
5713 slot as u64,
5714 9,
5715 0,
5716 event.polarity.code(),
5717 ]);
5718 arena[version + 9..version + 13].copy_from_slice(&identity_words(version_digest));
5719 arena[version + 13] = index as u64 + 1;
5720 arena[heads + statement_slot] = slot as u64 + 1;
5721 digest = material_root_digest(digest, version_digest, [(index + 1) as u32; 3]);
5722 }
5723 arena[root_offset + 6..root_offset + 10].copy_from_slice(&identity_words(digest));
5724 let snapshot =
5725 SemanticRootSnapshot::new(SemanticRootDigest(digest), SemanticExtents::new(2, 2, 2));
5726 let material = material_from_arena(&arena, capacities, root, snapshot, &admission).unwrap();
5727 assert_eq!(
5728 material
5729 .insertions
5730 .iter()
5731 .map(|insertion| insertion.statement)
5732 .collect::<Vec<_>>(),
5733 [Some(1), Some(7)]
5734 );
5735 assert_eq!(
5736 material
5737 .insertions
5738 .iter()
5739 .map(|insertion| insertion.support)
5740 .collect::<Vec<_>>(),
5741 [0, 3]
5742 );
5743 assert_eq!(material.extents, [2; 3]);
5744 for (offset, value) in [
5745 (versions + 13, 0),
5746 (versions + 13, 1),
5747 (versions + 6, 8),
5748 (versions + 7, 1),
5749 (heads, 4),
5750 (root_offset + 1, 3),
5751 (supports + 10, 1),
5752 (supports + 10, u64::MAX),
5753 (supports + 11, 0),
5754 (supports + 11, u64::MAX),
5755 ] {
5756 let mut damaged = arena.clone();
5757 damaged[offset] = value;
5758 assert!(material_from_arena(&damaged, capacities, root, snapshot, &admission).is_err());
5759 }
5760 }
5761
5762 #[test]
5763 fn root_material_codec_rejects_noncanonical_boolean_and_unknown_scalar() {
5764 let mut records = root_material_records();
5765 records.predicates[0].schema = Schema::new(vec![("value".into(), ScalarType::Bool)]);
5766 records.records[0].arguments = vec![SemanticArgument::Bool(false)];
5767 records.records[1].arguments = vec![SemanticArgument::Bool(true)];
5768 let material = SemanticRootMaterial {
5769 records,
5770 symbols: vec![],
5771 insertions: vec![],
5772 digest: [0; 32],
5773 extents: [0; 3],
5774 admission_base_digest: material_root_digest([0; 32], [0; 32], [0; 3]),
5775 admission_base_extents: [0; 3],
5776 };
5777 let encoded = material.encode().unwrap();
5778 let mut reader = SemanticMaterialReader::new(&encoded);
5779 reader.take(12).unwrap();
5780 for _ in 0..reader.u32().unwrap() {
5781 reader.take(5).unwrap();
5782 for _ in 0..reader.u32().unwrap() {
5783 reader.bytes().unwrap();
5784 reader.u8().unwrap();
5785 reader.bytes().unwrap();
5786 }
5787 let keys = reader.u32().unwrap() as usize;
5788 reader.take(keys * 4).unwrap();
5789 }
5790 assert_eq!(reader.u32().unwrap(), material.records.records.len() as u32);
5791 reader.u32().unwrap();
5792 assert_eq!(reader.u32().unwrap(), 1);
5793 let scalar_offset = encoded.len() - reader.remaining.len();
5794 assert_eq!(reader.u8().unwrap(), ScalarType::Bool.to_code());
5795 let mut invalid = encoded.clone();
5796 invalid[scalar_offset + 1] = 2;
5797 assert!(SemanticRootMaterial::decode(&invalid, root_material_limits()).is_err());
5798 invalid = encoded;
5799 invalid[scalar_offset] = u8::MAX;
5800 assert!(SemanticRootMaterial::decode(&invalid, root_material_limits()).is_err());
5801 }
5802
5803 #[test]
5804 #[ignore = "requires explicitly authorized CUDA execution"]
5805 fn root_material_native_restore_preserves_history_after_retirement_and_slot_reuse() {
5806 let mut graph = retirement_test_graph().expect("XLOG_REQUIRE_CUDA=1 is required");
5807 graph.device_controlled = false;
5808 graph = graph
5809 .admit_initial_records(root_material_records(), &[0], root_material_limits())
5810 .unwrap();
5811 graph.enter_transition();
5812 let insert = |graph: &mut SemanticHypergraph,
5813 base: SemanticRootHandle,
5814 statement: u32,
5815 support: u32| {
5816 let admission = graph.admission.as_ref().unwrap();
5817 let key = admission.statement_key(statement).unwrap();
5818 let event = admission.support_event(support).unwrap();
5819 let fork = retirement_command(
5820 graph,
5821 OP_FORK,
5822 &[(8, base.slot as u64), (9, base.generation)],
5823 );
5824 assert_eq!(fork.words[0], STATUS_OK);
5825 let mut command = graph.command_for(OP_INSERT_SUPPORT);
5826 command.words[9] = fork.words[6];
5827 command.words[12] = event.polarity.code();
5828 command.words[13] = u64::from(key.record);
5829 command.words[14] = u64::from(event.record);
5830 command.words[16..20].copy_from_slice(&identity_words(key.identity.0));
5831 command.words[20..24].copy_from_slice(&identity_words(event.identity(key.identity).0));
5832 assert_eq!(
5833 graph.run(command, ArenaAccess::ReadWrite).unwrap().words[0],
5834 STATUS_OK
5835 );
5836 let sealed = retirement_command(graph, OP_SEAL, &[(9, fork.words[6])]);
5837 assert_eq!(sealed.words[0], STATUS_OK);
5838 SemanticRootHandle::new(graph.owner, sealed.words[3] as u32, sealed.words[4])
5839 };
5840 let original_base = graph.admission().unwrap().base();
5841 let original_base_snapshot = *graph.admission().unwrap().base_snapshot();
5842 let unrelated = insert(&mut graph, original_base, 1, 1);
5843 let first = insert(&mut graph, original_base, 1, 0);
5844 assert_eq!(
5845 retirement_root(&mut graph, unrelated, first).words[0],
5846 STATUS_OK
5847 );
5848 let second = insert(&mut graph, first, 0, 1);
5849 let root = insert(&mut graph, second, 1, 1);
5850 assert_eq!(
5851 retirement_root(&mut graph, original_base, root).words[0],
5852 STATUS_OK
5853 );
5854 let material = graph.export_transition_root(root).unwrap();
5855 assert_eq!(
5856 material
5857 .insertions
5858 .iter()
5859 .map(|insertion| (insertion.statement, insertion.support))
5860 .collect::<Vec<_>>(),
5861 [(Some(0), 0), (Some(1), 0), (Some(0), 1), (Some(1), 1)]
5862 );
5863 assert_eq!(
5864 material.admission_base_digest,
5865 original_base_snapshot.digest.0
5866 );
5867 assert_eq!(material.admission_base_extents, [1; 3]);
5868 let pending = retirement_command(
5869 &mut graph,
5870 OP_FORK,
5871 &[(8, root.slot as u64), (9, root.generation)],
5872 );
5873 assert_eq!(pending.words[0], STATUS_OK);
5874 let key = graph.admission().unwrap().statement_key(0).unwrap();
5875 let event = graph.admission().unwrap().support_event(2).unwrap();
5876 let mut pending_insert = graph.command_for(OP_INSERT_SUPPORT);
5877 pending_insert.words[9] = pending.words[6];
5878 pending_insert.words[12] = event.polarity.code();
5879 pending_insert.words[13] = u64::from(key.record);
5880 pending_insert.words[14] = u64::from(event.record);
5881 pending_insert.words[16..20].copy_from_slice(&identity_words(key.identity.0));
5882 pending_insert.words[20..24]
5883 .copy_from_slice(&identity_words(event.identity(key.identity).0));
5884 let pending_inserted = graph.run(pending_insert, ArenaAccess::ReadWrite).unwrap();
5885 assert_eq!(pending_inserted.words[0], STATUS_OK);
5886 assert_eq!(pending_inserted.words[1], OUTCOME_INSERTED);
5887 assert_eq!(graph.export_transition_root(root).unwrap(), material);
5888 let decoded =
5889 SemanticRootMaterial::decode(&material.encode().unwrap(), root_material_limits())
5890 .unwrap();
5891 let mut too_small = graph
5892 .provider
5893 .allocate_semantic_hypergraph(
5894 &graph.domain,
5895 SemanticHypergraphCapacities::try_new(2, 8, 16, 16).unwrap(),
5896 )
5897 .unwrap();
5898 too_small
5899 .admit_records(
5900 too_small.empty_root,
5901 decoded.admission_records().unwrap(),
5902 root_material_limits(),
5903 )
5904 .unwrap();
5905 let before = too_small.execution_stats();
5906 assert!(too_small.restore_root(&decoded).is_err());
5907 assert_eq!(too_small.execution_stats(), before);
5908 let mut restored = graph
5909 .provider
5910 .allocate_semantic_hypergraph(&graph.domain, graph.capacities)
5911 .unwrap();
5912 restored
5913 .admit_records(
5914 restored.empty_root,
5915 decoded.admission_records().unwrap(),
5916 root_material_limits(),
5917 )
5918 .unwrap();
5919 let before = restored.execution_stats();
5920 let mut damaged = decoded.clone();
5921 damaged.digest[0] ^= 1;
5922 assert!(restored.restore_root(&damaged).is_err());
5923 assert_eq!(restored.execution_stats(), before);
5924 let fresh = restored.restore_root(&decoded).unwrap();
5925 assert_ne!(fresh.owner, root.owner);
5926 assert_ne!(restored.admission().unwrap().base(), fresh);
5927 assert_eq!(
5928 *restored.admission().unwrap().base_snapshot(),
5929 original_base_snapshot
5930 );
5931 assert_eq!(restored.export_transition_root(fresh).unwrap(), decoded);
5932 assert!(restored.restore_root(&decoded).is_err());
5933 assert!(matches!(
5934 restored.export_transition_root(root),
5935 Err(SemanticHypergraphError::ForeignHandle { .. })
5936 ));
5937 assert!(graph.export_transition_root(unrelated).is_err());
5938 }
5939
5940 fn numeric_records() -> SemanticAdmissionRecords {
5941 let types = [
5942 ScalarType::U32,
5943 ScalarType::U64,
5944 ScalarType::I32,
5945 ScalarType::I64,
5946 ScalarType::F32,
5947 ScalarType::F64,
5948 ScalarType::Bool,
5949 ];
5950 SemanticAdmissionRecords {
5951 predicates: vec![
5952 SemanticPredicateRecord {
5953 predicate: RelId(7),
5954 role: SemanticRecordRole::Statement,
5955 schema: Schema::new(
5956 types
5957 .into_iter()
5958 .enumerate()
5959 .map(|(i, ty)| (format!("column_{i}"), ty))
5960 .collect(),
5961 ),
5962 },
5963 SemanticPredicateRecord {
5964 predicate: RelId(8),
5965 role: SemanticRecordRole::Qualifier,
5966 schema: Schema::new(vec![("qualifier".into(), ScalarType::U64)]),
5967 },
5968 ],
5969 records: vec![
5970 SemanticTypedRecord {
5971 predicate: RelId(7),
5972 arguments: vec![
5973 SemanticArgument::U32(u32::MAX),
5974 SemanticArgument::U64(u64::MAX),
5975 SemanticArgument::I32(i32::MIN),
5976 SemanticArgument::I64(i64::MIN),
5977 SemanticArgument::F32Bits(0x7fc0_0001),
5978 SemanticArgument::F64Bits(0x8000_0000_0000_0000),
5979 SemanticArgument::Bool(false),
5980 ],
5981 qualifiers: vec![1, 2],
5982 },
5983 SemanticTypedRecord {
5984 predicate: RelId(8),
5985 arguments: vec![SemanticArgument::U64(1)],
5986 qualifiers: vec![],
5987 },
5988 SemanticTypedRecord {
5989 predicate: RelId(8),
5990 arguments: vec![SemanticArgument::U64(2)],
5991 qualifiers: vec![],
5992 },
5993 ],
5994 supports: vec![],
5995 }
5996 }
5997
5998 pub(crate) fn admit_numeric(records: SemanticAdmissionRecords) -> SemanticAdmission {
5999 admit_semantic_records(
6000 records,
6001 SemanticAdmissionLimits {
6002 max_records: 5,
6003 max_terms: 25,
6004 max_references: 2,
6005 max_utf8_bytes: 1024,
6006 },
6007 SemanticRootHandle::new(1, 0, 1),
6008 || {
6009 Ok(SemanticRootSnapshot::new(
6010 SemanticRootDigest([0; 32]),
6011 SemanticExtents::default(),
6012 ))
6013 },
6014 )
6015 .unwrap()
6016 }
6017
6018 #[test]
6019 fn admitted_schema_bytes_are_the_generation_preimage() {
6020 let records = numeric_records();
6021 let baseline = admit_numeric(records.clone());
6022 assert!(!baseline.schema_bytes().is_empty());
6023 assert_eq!(
6024 Sha256::digest(baseline.schema_bytes()).as_slice(),
6025 baseline.schema_generation().as_bytes()
6026 );
6027
6028 let mut different_fact = records.clone();
6029 different_fact.records[0].arguments[0] = SemanticArgument::U32(0);
6030 assert_eq!(
6031 admit_numeric(different_fact).schema_bytes(),
6032 baseline.schema_bytes()
6033 );
6034
6035 let mut different_schema = records;
6036 different_schema.predicates[0].schema.key_columns.reverse();
6037 assert_ne!(
6038 admit_numeric(different_schema).schema_bytes(),
6039 baseline.schema_bytes()
6040 );
6041 }
6042
6043 #[test]
6044 fn typed_admission_identity_preserves_numeric_bits_schema_and_qualifier_order() {
6045 let records = numeric_records();
6046 let baseline = admit_numeric(records.clone());
6047 let original = baseline.statement_key(0).unwrap().identity();
6048 for (column, value) in [
6049 (0, SemanticArgument::U32(0)),
6050 (1, SemanticArgument::U64(0)),
6051 (2, SemanticArgument::I32(-1)),
6052 (3, SemanticArgument::I64(-1)),
6053 (4, SemanticArgument::F32Bits(0x7fc0_0002)), (5, SemanticArgument::F64Bits(0)), (6, SemanticArgument::Bool(true)),
6056 ] {
6057 let mut changed = records.clone();
6058 changed.records[0].arguments[column] = value;
6059 assert_ne!(
6060 admit_numeric(changed).statement_key(0).unwrap().identity(),
6061 original
6062 );
6063 }
6064 let mut changed = records.clone();
6065 changed.records[0].qualifiers.reverse();
6066 assert_ne!(
6067 admit_numeric(changed).statement_key(0).unwrap().identity(),
6068 original
6069 );
6070 let mut changed = records.clone();
6071 changed.records[0].qualifiers.clear();
6072 assert_ne!(
6073 admit_numeric(changed).statement_key(0).unwrap().identity(),
6074 original
6075 );
6076 let mut changed = records.clone();
6077 changed.predicates[0].schema.key_columns.reverse();
6078 let changed = admit_numeric(changed);
6079 assert_ne!(changed.schema_generation(), baseline.schema_generation());
6080 assert_ne!(changed.statement_key(0).unwrap().identity(), original);
6081 let mut changed = records.clone();
6082 changed.predicates[0].schema = changed.predicates[0]
6083 .schema
6084 .clone()
6085 .with_sort_labels((0..7).map(|i| format!("sort_{i}")).collect())
6086 .unwrap();
6087 assert_ne!(
6088 admit_numeric(changed).statement_key(0).unwrap().identity(),
6089 original
6090 );
6091 let mut changed = records.clone();
6092 changed.predicates[0].schema.columns[0].0 = "renamed column".into();
6093 assert_ne!(
6094 admit_numeric(changed).statement_key(0).unwrap().identity(),
6095 original
6096 );
6097 assert_eq!(
6098 baseline.records(),
6099 &records,
6100 "the accepted schema and typed bytes are retained"
6101 );
6102 }
6103
6104 #[test]
6105 fn typed_admission_checks_complete_input_before_observing_base() {
6106 let mut records = numeric_records();
6107 records.records[0].arguments[0] = SemanticArgument::I32(0);
6108 let result = admit_semantic_records(
6109 records,
6110 SemanticAdmissionLimits {
6111 max_records: 5,
6112 max_terms: 25,
6113 max_references: 2,
6114 max_utf8_bytes: 1024,
6115 },
6116 SemanticRootHandle::new(1, 0, 1),
6117 || panic!("invalid input must not observe CUDA"),
6118 );
6119 assert!(matches!(
6120 result,
6121 Err(SemanticHypergraphError::InvalidInput { .. })
6122 ));
6123 }
6124
6125 #[test]
6126 fn typed_admission_identity_rebinds_the_base_without_reencoding_records() {
6127 let admission = admit_numeric(numeric_records());
6128 let identity_for = |snapshot| {
6129 derive_admission_identity(
6130 &admission.records,
6131 admission.schema_generation,
6132 &admission.encoded_records,
6133 &admission.statement_keys,
6134 &admission.support_events,
6135 snapshot,
6136 )
6137 };
6138 assert_eq!(identity_for(&admission.base_snapshot), admission.identity);
6139 let populated =
6140 SemanticRootSnapshot::new(SemanticRootDigest([17; 32]), SemanticExtents::new(1, 2, 2));
6141 assert_ne!(identity_for(&populated), admission.identity);
6142 let changed_extents =
6143 SemanticRootSnapshot::new(*populated.digest(), SemanticExtents::new(1, 2, 3));
6144 assert_ne!(identity_for(&populated), identity_for(&changed_extents));
6145 let changed_digest =
6146 SemanticRootSnapshot::new(SemanticRootDigest([18; 32]), populated.extents());
6147 assert_ne!(identity_for(&populated), identity_for(&changed_digest));
6148 assert_eq!(identity_for(&admission.base_snapshot), admission.identity);
6149 }
6150
6151 #[test]
6152 fn host_device_divergence_statuses_are_integrity_failures() {
6153 for status in [
6154 STATUS_FOREIGN_OWNER,
6155 STATUS_SLOT_OUT_OF_RANGE,
6156 STATUS_STALE_GENERATION,
6157 STATUS_INACTIVE,
6158 STATUS_CORRUPT_LINEAGE,
6159 STATUS_INVALID_COMMAND,
6160 STATUS_ARENA_MISMATCH,
6161 u64::MAX,
6162 ] {
6163 assert!(device_status_is_integrity_failure(status));
6164 }
6165 for status in [
6166 STATUS_OK,
6167 STATUS_NOT_REACHABLE,
6168 STATUS_STATEMENT_CAPACITY,
6169 STATUS_SUPPORT_CAPACITY,
6170 STATUS_VERSION_CAPACITY,
6171 STATUS_ROOT_CAPACITY,
6172 STATUS_GENERATION_EXHAUSTED,
6173 ] {
6174 assert!(!device_status_is_integrity_failure(status));
6175 }
6176 }
6177
6178 #[test]
6179 fn reconciliation_failure_poisoning_is_sticky() {
6180 let mut poisoned = false;
6181 let failure = Err::<(), _>(SemanticHypergraphError::CorruptLineage {
6182 detail: "test reconciliation failure".into(),
6183 });
6184 assert!(poison_after_reconciliation_error(&mut poisoned, failure).is_err());
6185 assert!(poisoned);
6186 assert!(poison_after_reconciliation_error(&mut poisoned, Ok(())).is_ok());
6187 assert!(poisoned);
6188 }
6189
6190 #[test]
6191 fn resident_refusal_retires_only_its_acquired_candidate() {
6192 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
6193 eprintln!("Skipping: set XLOG_REQUIRE_CUDA=1 to run this real-CUDA contract");
6194 return;
6195 }
6196 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
6197 .with_stream_capacity(1)
6198 .build()
6199 .unwrap();
6200 let provider = Arc::new(provider);
6201 let runtime = Arc::clone(provider.memory().runtime().unwrap());
6202 let stream_id = runtime.stream_pool().acquire().unwrap();
6203 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
6204 let domain = provider
6205 .bind_resident_execution_domain(runtime, stream_id, Arc::clone(&stream))
6206 .unwrap();
6207 let mut graph = provider
6208 .allocate_semantic_hypergraph(
6209 &domain,
6210 SemanticHypergraphCapacities::try_new(2, 1, 1, 1).unwrap(),
6211 )
6212 .unwrap();
6213 let bytes = std::mem::size_of::<SemanticResidentDecodedStatement>()
6214 + 2 * std::mem::size_of::<SemanticResidentDecodedSupport>()
6215 + std::mem::size_of::<SemanticResidentHandleRecord>()
6216 + 11 * std::mem::size_of::<SemanticResidentReceiptRecord>();
6217 let mut reservation = provider.memory().reserve_bytes(bytes as u64).unwrap();
6218 let mut statements = reservation
6219 .alloc::<SemanticResidentDecodedStatement>(1)
6220 .unwrap();
6221 let mut supports = reservation
6222 .alloc::<SemanticResidentDecodedSupport>(2)
6223 .unwrap();
6224 let handles = reservation
6225 .alloc::<SemanticResidentHandleRecord>(1)
6226 .unwrap();
6227 let receipts = reservation
6228 .alloc::<SemanticResidentReceiptRecord>(11)
6229 .unwrap();
6230 assert_eq!(reservation.remaining_bytes(), 0);
6231 provider
6232 .htod_sync_copy_into_tracked(
6233 &[SemanticResidentDecodedStatement {
6234 identity_words: [17; 8],
6235 record: 0,
6236 reconstruction: [0; 10],
6237 }],
6238 &mut statements,
6239 )
6240 .unwrap();
6241 let events = [1, 2].map(|value| SemanticResidentDecodedSupport {
6242 polarity: 1,
6243 provenance_words: [value; 8],
6244 source_words: [3; 8],
6245 context_words: [4; 8],
6246 scope_words: [5; 8],
6247 record: value - 1,
6248 });
6249 provider
6250 .htod_sync_copy_into_tracked(&events, &mut supports)
6251 .unwrap();
6252 let mut setup = domain.new_strict_recorder();
6253 setup.read_write(&statements);
6254 setup.read_write(&supports);
6255 setup.write(&handles);
6256 setup.write(&receipts);
6257 unsafe { domain.enqueue(setup, |_| Ok::<(), XlogError>(())) }
6258 .unwrap()
6259 .commit()
6260 .unwrap();
6261 stream.synchronize().unwrap();
6262 let before = provider.host_transfer_stats();
6263 let metadata_before = provider.host_launch_metadata_transfer_stats();
6264 let untracked_before = provider.untracked_metadata_dtoh_count();
6265 let captured = CapturedCudaGraph::capture_on_stream(&stream, || {
6266 let slot =
6267 |index| SemanticResidentReceiptSlot::new(&receipts, index).map_err(kernel_error);
6268 let statement =
6269 || SemanticResidentStatementBank::new(&statements, 0).map_err(kernel_error);
6270 let support =
6271 |index| SemanticResidentSupportBank::new(&supports, index).map_err(kernel_error);
6272 let base = graph
6273 .enqueue_resident_empty_root_handle(
6274 SemanticResidentHandleSlot::new(&handles, 0).map_err(kernel_error)?,
6275 slot(0)?,
6276 )
6277 .map_err(kernel_error)?;
6278 let first = graph
6279 .enqueue_resident_fork(base.handle(), slot(1)?)
6280 .map_err(kernel_error)?;
6281 let inserted = graph
6282 .enqueue_resident_insert_support(
6283 first.candidate_handle(),
6284 statement()?,
6285 support(0)?,
6286 slot(2)?,
6287 )
6288 .map_err(kernel_error)?;
6289 let refused = graph
6290 .enqueue_resident_insert_support(
6291 inserted.candidate_handle(),
6292 statement()?,
6293 support(1)?,
6294 slot(3)?,
6295 )
6296 .map_err(kernel_error)?;
6297 graph
6298 .enqueue_resident_seal(refused.candidate_handle(), slot(4)?)
6299 .map_err(kernel_error)?;
6300 let second = graph
6301 .enqueue_resident_fork(base.handle(), slot(5)?)
6302 .map_err(kernel_error)?;
6303 let denied = graph
6304 .enqueue_resident_fork(base.handle(), slot(6)?)
6305 .map_err(kernel_error)?;
6306 graph
6307 .enqueue_resident_seal(denied.candidate_handle(), slot(7)?)
6308 .map_err(kernel_error)?;
6309 let inserted = graph
6310 .enqueue_resident_insert_support(
6311 second.candidate_handle(),
6312 statement()?,
6313 support(1)?,
6314 slot(8)?,
6315 )
6316 .map_err(kernel_error)?;
6317 graph
6318 .enqueue_resident_seal(inserted.candidate_handle(), slot(9)?)
6319 .map_err(kernel_error)?;
6320 graph
6321 .enqueue_resident_insert_support(
6322 first.candidate_handle(),
6323 statement()?,
6324 support(0)?,
6325 slot(10)?,
6326 )
6327 .map_err(kernel_error)?;
6328 Ok(())
6329 })
6330 .unwrap();
6331 captured.launch(&stream).unwrap();
6332 let after = provider.host_transfer_stats();
6333 let metadata_after = provider.host_launch_metadata_transfer_stats();
6334 assert_eq!(
6335 (
6336 after.htod_calls,
6337 after.htod_bytes,
6338 after.dtoh_calls,
6339 after.dtoh_bytes
6340 ),
6341 (
6342 before.htod_calls,
6343 before.htod_bytes,
6344 before.dtoh_calls,
6345 before.dtoh_bytes
6346 )
6347 );
6348 assert_eq!(
6349 (metadata_after.htod_calls, metadata_after.htod_bytes),
6350 (metadata_before.htod_calls, metadata_before.htod_bytes)
6351 );
6352 assert_eq!(provider.untracked_metadata_dtoh_count(), untracked_before);
6353 stream.synchronize().unwrap();
6354 let observed = provider
6355 .dtoh_small_metadata_untracked(&receipts, 11)
6356 .unwrap();
6357 assert_eq!(observed[2].words[0], STATUS_OK);
6358 assert_eq!(observed[3].words[0], STATUS_SUPPORT_CAPACITY);
6359 assert_eq!(observed[4].words[0], STATUS_SUPPORT_CAPACITY);
6360 assert_eq!(
6361 observed[5].words[0], STATUS_OK,
6362 "the refused candidate must be retired"
6363 );
6364 assert_eq!(observed[6].words[0], STATUS_INACTIVE);
6365 assert_eq!(observed[7].words[0], STATUS_INACTIVE);
6366 assert_eq!(
6367 observed[8].words[0], STATUS_OK,
6368 "a failed fork must not retire another candidate"
6369 );
6370 assert_eq!(observed[9].words[0], STATUS_OK);
6371 assert_eq!(&observed[9].words[13..16], &[1, 1, 1]);
6372 assert_eq!(observed[10].words[0], STATUS_STALE_GENERATION);
6373 assert_eq!(observed[8].words[8], observed[2].words[8] + 1);
6374 assert_eq!(observed[8].words[10], observed[2].words[10] + 1);
6375 assert_eq!(observed[8].words[12], observed[2].words[12] + 1);
6376 }
6377
6378 #[test]
6379 fn transition_reserve_checks_both_lanes_without_mutating_the_arena() {
6380 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
6381 return;
6382 }
6383 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
6384 .with_stream_capacity(1)
6385 .build()
6386 .unwrap();
6387 let provider = Arc::new(provider);
6388 let runtime = Arc::clone(provider.memory().runtime().unwrap());
6389 let stream_id = runtime.stream_pool().acquire().unwrap();
6390 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
6391 let domain = provider
6392 .bind_resident_execution_domain(runtime, stream_id, stream)
6393 .unwrap();
6394 let check = |caps: [u32; 4], changes: &[(usize, u64)], expected: u64| {
6395 let mut graph = provider
6396 .allocate_semantic_hypergraph(
6397 &domain,
6398 SemanticHypergraphCapacities::try_new(caps[0], caps[1], caps[2], caps[3])
6399 .unwrap(),
6400 )
6401 .unwrap();
6402 let mut before = download_test_arena(&provider, &graph);
6403 for &(index, value) in changes {
6404 before[index] = value;
6405 }
6406 provider
6407 .htod_sync_copy_into_tracked(&before, &mut graph.arena)
6408 .unwrap();
6409 let mut command = graph.command_for(OP_PREFLIGHT_TRANSITION);
6412 command.words[8] = 0;
6413 command.words[9] = 1;
6414 let receipt = graph.run(command, ArenaAccess::Read).unwrap();
6415 assert_eq!(
6416 receipt.words[0], expected,
6417 "capacities={caps:?}, changes={changes:?}"
6418 );
6419 assert_eq!(
6420 receipt.words[41], 0,
6421 "preflight must not acquire a candidate"
6422 );
6423 let after = download_test_arena(&provider, &graph);
6424 assert_eq!(
6425 after, before,
6426 "preflight, including refusal, must be read-only"
6427 );
6428 };
6429 check([3, 4, 4, 4], &[], STATUS_OK);
6430 check([3, 4, 4, 4], &[(1, 0)], STATUS_FOREIGN_OWNER);
6431 check(
6432 [3, 4, 4, 4],
6433 &[(CONTROL_WORDS as usize + 1, 2)],
6434 STATUS_STALE_GENERATION,
6435 );
6436 check(
6437 [3, 4, 4, 4],
6438 &[(CONTROL_WORDS as usize, 0)],
6439 STATUS_INACTIVE,
6440 );
6441 check(
6442 [3, 4, 4, 4],
6443 &[((CONTROL_WORDS + ROOT_WORDS) as usize, 3)],
6444 STATUS_ROOT_CAPACITY,
6445 );
6446 check([2, 4, 4, 4], &[], STATUS_ROOT_CAPACITY);
6447 check([3, 3, 4, 4], &[], STATUS_STATEMENT_CAPACITY);
6448 check([3, 4, 3, 4], &[], STATUS_SUPPORT_CAPACITY);
6449 check([3, 4, 4, 3], &[], STATUS_VERSION_CAPACITY);
6450 let candidate = (CONTROL_WORDS + 3 * ROOT_WORDS) as usize;
6451 check([3, 4, 4, 4], &[(candidate, 1)], STATUS_INACTIVE);
6452 check([3, 4, 4, 4], &[(candidate + 1, u64::MAX - 2)], STATUS_OK);
6453 check(
6454 [3, 4, 4, 4],
6455 &[(candidate + 1, u64::MAX - 1)],
6456 STATUS_GENERATION_EXHAUSTED,
6457 );
6458 check(
6459 [3, 4, 4, 4],
6460 &[(candidate + 1, u64::MAX)],
6461 STATUS_GENERATION_EXHAUSTED,
6462 );
6463 let statements = candidate + CANDIDATE_WORDS as usize;
6464 let supports = statements + 4 * STATEMENT_WORDS as usize;
6465 let versions = supports + 4 * SUPPORT_WORDS as usize;
6466 check(
6469 [3, 5, 4, 4],
6470 &[(statements, 3), (statements + 1, u64::MAX)],
6471 STATUS_OK,
6472 );
6473 check(
6474 [3, 5, 4, 4],
6475 &[
6476 (statements, 3),
6477 (statements + STATEMENT_WORDS as usize + 1, u64::MAX - 1),
6478 ],
6479 STATUS_GENERATION_EXHAUSTED,
6480 );
6481 check(
6482 [3, 5, 4, 4],
6483 &[
6484 (statements, 3),
6485 (statements + 3 * STATEMENT_WORDS as usize + 1, u64::MAX - 1),
6486 ],
6487 STATUS_OK,
6488 );
6489 for (start, width, exhausted) in [
6490 (
6491 statements,
6492 STATEMENT_WORDS as usize,
6493 STATUS_STATEMENT_CAPACITY,
6494 ),
6495 (supports, SUPPORT_WORDS as usize, STATUS_SUPPORT_CAPACITY),
6496 (versions, VERSION_WORDS as usize, STATUS_VERSION_CAPACITY),
6497 ] {
6498 check([3, 4, 4, 4], &[(start, 3)], exhausted);
6501 for slot in 0..4 {
6502 let generation = start + slot * width + 1;
6503 let maximum = u64::MAX - if slot < 2 { 2 } else { 1 };
6504 check([3, 4, 4, 4], &[(generation, maximum)], STATUS_OK);
6505 check(
6506 [3, 4, 4, 4],
6507 &[(generation, maximum + 1)],
6508 STATUS_GENERATION_EXHAUSTED,
6509 );
6510 }
6511 }
6512 for extent in 3..6 {
6513 check(
6514 [3, 4, 4, 4],
6515 &[(CONTROL_WORDS as usize + extent, u32::MAX as u64 - 1)],
6516 STATUS_CORRUPT_LINEAGE,
6517 );
6518 }
6519 let statement = (CONTROL_WORDS + 4 * ROOT_WORDS + CANDIDATE_WORDS) as usize;
6523 let support = statement + 5 * STATEMENT_WORDS as usize;
6524 let version = support + 5 * SUPPORT_WORDS as usize;
6525 let heads = version + 5 * VERSION_WORDS as usize;
6526 let mut reachable = vec![
6527 (CONTROL_WORDS as usize + 3, 1),
6528 (CONTROL_WORDS as usize + 4, 1),
6529 (CONTROL_WORDS as usize + 5, 1),
6530 (statement, 3),
6531 (support, 3),
6532 (version, 3),
6533 (heads, 1),
6534 (version + 6, 1),
6535 ];
6536 for generation in [u64::MAX - 1, u64::MAX] {
6537 reachable.extend([
6538 (statement + 1, generation),
6539 (support + 4, generation),
6540 (version + 4, generation),
6541 ]);
6542 check(
6543 [4, 5, 5, 5],
6544 &reachable,
6545 if generation == u64::MAX {
6546 STATUS_GENERATION_EXHAUSTED
6547 } else {
6548 STATUS_OK
6549 },
6550 );
6551 reachable.truncate(8);
6552 }
6553 }
6554
6555 #[test]
6556 fn resident_transition_reserve_precedes_two_lanes_and_survives_refusal() {
6557 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
6558 return;
6559 }
6560 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
6561 .with_stream_capacity(1)
6562 .build()
6563 .unwrap();
6564 let provider = Arc::new(provider);
6565 let runtime = Arc::clone(provider.memory().runtime().unwrap());
6566 let stream_id = runtime.stream_pool().acquire().unwrap();
6567 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
6568 let domain = provider
6569 .bind_resident_execution_domain(runtime, stream_id, Arc::clone(&stream))
6570 .unwrap();
6571 for refuse_first in [false, true] {
6572 let mut graph = provider
6573 .allocate_semantic_hypergraph(
6574 &domain,
6575 SemanticHypergraphCapacities::try_new(3, 4, 4, 4).unwrap(),
6576 )
6577 .unwrap();
6578 assert_eq!(graph.arena_words * 8, 2080);
6579 let candidate = (CONTROL_WORDS + 3 * ROOT_WORDS) as usize;
6580 let statement_start = candidate + CANDIDATE_WORDS as usize;
6581 let support_start = statement_start + 4 * STATEMENT_WORDS as usize;
6582 let version_start = support_start + 4 * SUPPORT_WORDS as usize;
6583 let mut arena = download_test_arena(&provider, &graph);
6584 arena[candidate + 1] = u64::MAX - 2;
6585 for (start, width) in [
6586 (statement_start, STATEMENT_WORDS as usize),
6587 (support_start, SUPPORT_WORDS as usize),
6588 (version_start, VERSION_WORDS as usize),
6589 ] {
6590 for slot in 0..4 {
6591 arena[start + slot * width + 1] = u64::MAX - if slot < 2 { 2 } else { 1 };
6592 }
6593 }
6594 provider
6595 .htod_sync_copy_into_tracked(&arena, &mut graph.arena)
6596 .unwrap();
6597 let bytes = 4 * std::mem::size_of::<SemanticResidentDecodedStatement>()
6598 + 4 * std::mem::size_of::<SemanticResidentDecodedSupport>()
6599 + std::mem::size_of::<SemanticResidentHandleRecord>()
6600 + 11 * std::mem::size_of::<SemanticResidentReceiptRecord>();
6601 let mut reservation = provider.memory().reserve_bytes(bytes as u64).unwrap();
6602 let mut statements = reservation
6603 .alloc::<SemanticResidentDecodedStatement>(4)
6604 .unwrap();
6605 let mut supports = reservation
6606 .alloc::<SemanticResidentDecodedSupport>(4)
6607 .unwrap();
6608 let handles = reservation
6609 .alloc::<SemanticResidentHandleRecord>(1)
6610 .unwrap();
6611 let receipts = reservation
6612 .alloc::<SemanticResidentReceiptRecord>(11)
6613 .unwrap();
6614 assert_eq!(reservation.remaining_bytes(), 0);
6615 provider
6616 .htod_sync_copy_into_tracked(
6617 &[17, 18, 19, 20].map(|value| SemanticResidentDecodedStatement {
6618 identity_words: [value; 8],
6619 record: value - 17,
6620 reconstruction: [0; 10],
6621 }),
6622 &mut statements,
6623 )
6624 .unwrap();
6625 provider
6626 .htod_sync_copy_into_tracked(
6627 &[0, 1, 2, 3].map(|index| SemanticResidentDecodedSupport {
6628 polarity: if refuse_first && index == 1 { 0 } else { 1 },
6629 provenance_words: [index + 1; 8],
6630 source_words: [3; 8],
6631 context_words: [4; 8],
6632 scope_words: [5; 8],
6633 record: index,
6634 }),
6635 &mut supports,
6636 )
6637 .unwrap();
6638 let mut setup = domain.new_strict_recorder();
6639 setup.read_write(&graph.arena);
6640 setup.read_write(&statements);
6641 setup.read_write(&supports);
6642 setup.write(&handles);
6643 setup.write(&receipts);
6644 unsafe { domain.enqueue(setup, |_| Ok::<(), XlogError>(())) }
6645 .unwrap()
6646 .commit()
6647 .unwrap();
6648 stream.synchronize().unwrap();
6649 let captured = CapturedCudaGraph::capture_on_stream(&stream, || {
6650 let slot = |index| {
6651 SemanticResidentReceiptSlot::new(&receipts, index).map_err(kernel_error)
6652 };
6653 let base = graph
6654 .enqueue_resident_empty_root_handle(
6655 SemanticResidentHandleSlot::new(&handles, 0).map_err(kernel_error)?,
6656 slot(0)?,
6657 )
6658 .map_err(kernel_error)?;
6659 graph
6660 .enqueue_resident_preflight_transition(base.handle(), slot(1)?)
6661 .map_err(kernel_error)?;
6662 for lane in 0..2 {
6663 let base = SemanticResidentRootHandle::Receipt(SemanticResidentReceiptView {
6664 allocation: &receipts,
6665 index: 1,
6666 });
6667 let first = 2 + lane * 4;
6668 let fork = graph
6669 .enqueue_resident_fork(base, slot(first)?)
6670 .map_err(kernel_error)?;
6671 let inserted = graph
6672 .enqueue_resident_insert_support(
6673 fork.candidate_handle(),
6674 SemanticResidentStatementBank::new(&statements, lane * 2)
6675 .map_err(kernel_error)?,
6676 SemanticResidentSupportBank::new(&supports, lane * 2)
6677 .map_err(kernel_error)?,
6678 slot(first + 1)?,
6679 )
6680 .map_err(kernel_error)?;
6681 let inserted = graph
6682 .enqueue_resident_insert_support(
6683 inserted.candidate_handle(),
6684 SemanticResidentStatementBank::new(&statements, lane * 2 + 1)
6685 .map_err(kernel_error)?,
6686 SemanticResidentSupportBank::new(&supports, lane * 2 + 1)
6687 .map_err(kernel_error)?,
6688 slot(first + 2)?,
6689 )
6690 .map_err(kernel_error)?;
6691 graph
6692 .enqueue_resident_seal(inserted.candidate_handle(), slot(first + 3)?)
6693 .map_err(kernel_error)?;
6694 }
6695 graph
6697 .enqueue_resident_preflight_transition(base.handle(), slot(10)?)
6698 .map_err(kernel_error)?;
6699 Ok(())
6700 })
6701 .unwrap();
6702 let before = provider.host_transfer_stats();
6703 let metadata_before = provider.host_launch_metadata_transfer_stats();
6704 let untracked_before = provider.untracked_metadata_dtoh_count();
6705 captured.launch(&stream).unwrap();
6706 let after = provider.host_transfer_stats();
6707 let metadata_after = provider.host_launch_metadata_transfer_stats();
6708 assert_eq!(
6709 (
6710 before.htod_calls,
6711 before.htod_bytes,
6712 before.dtoh_calls,
6713 before.dtoh_bytes
6714 ),
6715 (
6716 after.htod_calls,
6717 after.htod_bytes,
6718 after.dtoh_calls,
6719 after.dtoh_bytes
6720 )
6721 );
6722 assert_eq!(
6723 (metadata_before.htod_calls, metadata_before.htod_bytes),
6724 (metadata_after.htod_calls, metadata_after.htod_bytes)
6725 );
6726 assert_eq!(untracked_before, provider.untracked_metadata_dtoh_count());
6727 stream.synchronize().unwrap();
6728 let observed = provider
6729 .dtoh_small_metadata_untracked(&receipts, 11)
6730 .unwrap();
6731 assert_eq!(observed[1].words[0], STATUS_OK);
6732 assert_eq!(&observed[1].words[33..36], &[1, 0, 1]);
6733 assert_eq!(observed[1].words[41], 0);
6734 assert_eq!(observed[2].words[0], STATUS_OK);
6735 assert_eq!(
6736 observed[5].words[0],
6737 if refuse_first {
6738 STATUS_INVALID_COMMAND
6739 } else {
6740 STATUS_OK
6741 }
6742 );
6743 assert_eq!(
6744 observed[6].words[0], STATUS_OK,
6745 "second lane must start after first retirement"
6746 );
6747 assert_eq!(observed[9].words[0], STATUS_OK);
6748 assert_eq!(
6749 &observed[9].words[13..16],
6750 &[2, 2, 2],
6751 "both lanes start from the same empty base"
6752 );
6753 assert_eq!(observed[10].words[0], STATUS_GENERATION_EXHAUSTED);
6754 if refuse_first {
6755 assert_eq!(
6756 observed[7].words[8],
6757 u64::MAX - 1,
6758 "discarded statement slot is reused"
6759 );
6760 assert_eq!(observed[7].words[10], u64::MAX - 1);
6761 assert_eq!(observed[7].words[12], u64::MAX - 1);
6762 } else {
6763 assert_eq!(&observed[5].words[13..16], &[2, 2, 2]);
6764 assert_ne!(observed[5].words[3], observed[9].words[3]);
6765 }
6766 }
6767 }
6768
6769 #[test]
6770 fn exhausted_candidate_generation_is_rejected_before_fork() {
6771 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
6772 eprintln!("Skipping: set XLOG_REQUIRE_CUDA=1 to run this real-CUDA contract");
6773 return;
6774 }
6775 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
6776 .with_stream_capacity(1)
6777 .build()
6778 .unwrap();
6779 let provider = Arc::new(provider);
6780 let runtime = Arc::clone(provider.memory().runtime().unwrap());
6781 let stream_id = runtime.stream_pool().acquire().unwrap();
6782 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
6783 let domain = provider
6784 .bind_resident_execution_domain(runtime, stream_id, stream)
6785 .unwrap();
6786 let mut graph = provider
6787 .allocate_semantic_hypergraph(
6788 &domain,
6789 SemanticHypergraphCapacities::try_new(2, 1, 1, 1).unwrap(),
6790 )
6791 .unwrap();
6792 let mut arena = download_test_arena(&provider, &graph);
6794 let candidate_offset = (CONTROL_WORDS + ROOT_WORDS * 2) as usize;
6795 arena[candidate_offset + 1] = u64::MAX - 1;
6796 provider
6797 .htod_sync_copy_into_tracked(&arena, &mut graph.arena)
6798 .unwrap();
6799 graph.fork.generation = u64::MAX - 1;
6800 let last = graph.fork(graph.empty_root()).unwrap();
6801 graph.discard(last).unwrap();
6802 let arena = download_test_arena(&provider, &graph);
6803 assert_eq!(arena[candidate_offset + 1], u64::MAX);
6804 let error = graph.fork(graph.empty_root()).unwrap_err();
6805 assert!(matches!(
6806 error,
6807 SemanticHypergraphError::GenerationExhausted {
6808 kind: SemanticHandleKind::Fork,
6809 slot: 0,
6810 }
6811 ));
6812 let after = download_test_arena(&provider, &graph);
6813 assert_eq!(
6814 after, arena,
6815 "an unretirable fork must not acquire or modify scratch"
6816 );
6817 }
6818
6819 #[test]
6820 fn mismatched_launch_abi_is_rejected_without_arena_mutation() {
6821 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
6822 eprintln!("Skipping: set XLOG_REQUIRE_CUDA=1 to run this real-CUDA contract");
6823 return;
6824 }
6825 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
6826 .with_stream_capacity(1)
6827 .build()
6828 .unwrap();
6829 let provider = Arc::new(provider);
6830 let runtime = Arc::clone(provider.memory().runtime().unwrap());
6831 let stream_id = runtime.stream_pool().acquire().unwrap();
6832 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
6833 let domain = provider
6834 .bind_resident_execution_domain(runtime, stream_id, Arc::clone(&stream))
6835 .unwrap();
6836 let graph = provider
6837 .allocate_semantic_hypergraph(
6838 &domain,
6839 SemanticHypergraphCapacities::try_new(2, 1, 1, 1).unwrap(),
6840 )
6841 .unwrap();
6842 let before = download_test_arena(&provider, &graph);
6843 let descriptor = DeviceLaunchDescriptor {
6844 expected_owner: graph.owner,
6845 root_capacity: 2,
6846 statement_capacity: 1,
6847 support_capacity: 1,
6848 version_capacity: 1,
6849 arena_words: graph.arena_words,
6850 command_ptr: graph.command.device_ptr_value(),
6851 command_index: 0,
6852 handle_ptr: 0,
6853 handle_index: 0,
6854 source_receipt_ptr: 0,
6855 source_receipt_index: 0,
6856 decoded_input_ptr: 0,
6857 decoded_input_index: 0,
6858 decoded_statement_ptr: 0,
6859 decoded_statement_index: 0,
6860 decoded_support_ptr: 0,
6861 decoded_support_index: 0,
6862 output_ptr: 0,
6863 output_index: 0,
6864 receipt_ptr: graph.receipt.device_ptr_value(),
6865 receipt_index: 0,
6866 admission: HOST_COMMAND_ADMISSION,
6867 abi_generation: HYPERGRAPH_ABI_GENERATION - 1,
6868 };
6869 let mut recorder = domain.new_strict_recorder();
6870 recorder.read_write(&graph.arena);
6871 recorder.read(&graph.command);
6872 recorder.write(&graph.receipt);
6873 let enqueued = unsafe {
6874 domain.enqueue(recorder, |stream| {
6875 graph.execute.clone().launch_in(
6876 stream,
6877 LaunchConfig {
6878 grid_dim: (1, 1, 1),
6879 block_dim: (1, 1, 1),
6880 shared_mem_bytes: 0,
6881 },
6882 (graph.arena.device_ptr_value(), descriptor),
6883 )
6884 })
6885 }
6886 .unwrap();
6887 enqueued.commit().unwrap();
6888 stream.synchronize().unwrap();
6889 let receipt = provider
6890 .dtoh_small_metadata_untracked(&graph.receipt, 1)
6891 .unwrap();
6892 assert_eq!(receipt[0].words[0], STATUS_ARENA_MISMATCH);
6893 let after = download_test_arena(&provider, &graph);
6894 assert_eq!(after, before);
6895 }
6896
6897 #[test]
6898 fn resident_failed_seal_rolls_back_without_transferring_acquisition() {
6899 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
6900 eprintln!("Skipping: set XLOG_REQUIRE_CUDA=1 to run this real-CUDA contract");
6901 return;
6902 }
6903 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
6904 .with_stream_capacity(1)
6905 .build()
6906 .unwrap();
6907 let provider = Arc::new(provider);
6908 let runtime = Arc::clone(provider.memory().runtime().unwrap());
6909 let stream_id = runtime.stream_pool().acquire().unwrap();
6910 let stream = runtime.stream_pool().resolve(stream_id).unwrap();
6911 let domain = provider
6912 .bind_resident_execution_domain(runtime, stream_id, Arc::clone(&stream))
6913 .unwrap();
6914 let mut graph = provider
6915 .allocate_semantic_hypergraph(
6916 &domain,
6917 SemanticHypergraphCapacities::try_new(1, 1, 1, 1).unwrap(),
6918 )
6919 .unwrap();
6920 let bytes = std::mem::size_of::<SemanticResidentDecodedStatement>()
6921 + std::mem::size_of::<SemanticResidentDecodedSupport>()
6922 + std::mem::size_of::<SemanticResidentHandleRecord>()
6923 + 9 * std::mem::size_of::<SemanticResidentReceiptRecord>();
6924 let mut reservation = provider.memory().reserve_bytes(bytes as u64).unwrap();
6925 let mut statements = reservation
6926 .alloc::<SemanticResidentDecodedStatement>(1)
6927 .unwrap();
6928 let mut supports = reservation
6929 .alloc::<SemanticResidentDecodedSupport>(1)
6930 .unwrap();
6931 let handles = reservation
6932 .alloc::<SemanticResidentHandleRecord>(1)
6933 .unwrap();
6934 let receipts = reservation
6935 .alloc::<SemanticResidentReceiptRecord>(9)
6936 .unwrap();
6937 assert_eq!(reservation.remaining_bytes(), 0);
6938 provider
6939 .htod_sync_copy_into_tracked(
6940 &[SemanticResidentDecodedStatement {
6941 identity_words: [17; 8],
6942 record: 0,
6943 reconstruction: [0; 10],
6944 }],
6945 &mut statements,
6946 )
6947 .unwrap();
6948 provider
6949 .htod_sync_copy_into_tracked(
6950 &[SemanticResidentDecodedSupport {
6951 polarity: 1,
6952 provenance_words: [2; 8],
6953 source_words: [3; 8],
6954 context_words: [4; 8],
6955 scope_words: [5; 8],
6956 record: 0,
6957 }],
6958 &mut supports,
6959 )
6960 .unwrap();
6961 let mut setup = domain.new_strict_recorder();
6962 setup.read_write(&statements);
6963 setup.read_write(&supports);
6964 setup.write(&handles);
6965 setup.write(&receipts);
6966 unsafe { domain.enqueue(setup, |_| Ok::<(), XlogError>(())) }
6967 .unwrap()
6968 .commit()
6969 .unwrap();
6970 stream.synchronize().unwrap();
6971 let captured = CapturedCudaGraph::capture_on_stream(&stream, || {
6972 let slot =
6973 |index| SemanticResidentReceiptSlot::new(&receipts, index).map_err(kernel_error);
6974 let statement =
6975 || SemanticResidentStatementBank::new(&statements, 0).map_err(kernel_error);
6976 let base = graph
6977 .enqueue_resident_empty_root_handle(
6978 SemanticResidentHandleSlot::new(&handles, 0).map_err(kernel_error)?,
6979 slot(0)?,
6980 )
6981 .map_err(kernel_error)?;
6982 let acquired = graph
6983 .enqueue_resident_fork(base.handle(), slot(1)?)
6984 .map_err(kernel_error)?;
6985 let inserted = graph
6986 .enqueue_resident_insert_support(
6987 acquired.candidate_handle(),
6988 statement()?,
6989 SemanticResidentSupportBank::new(&supports, 0).map_err(kernel_error)?,
6990 slot(2)?,
6991 )
6992 .map_err(kernel_error)?;
6993 let refused_seal = graph
6994 .enqueue_resident_seal(inserted.candidate_handle(), slot(3)?)
6995 .map_err(kernel_error)?;
6996 let failed_fork = graph
6997 .enqueue_resident_fork(refused_seal.root_handle(), slot(4)?)
6998 .map_err(kernel_error)?;
6999 graph
7000 .enqueue_resident_seal(failed_fork.candidate_handle(), slot(5)?)
7001 .map_err(kernel_error)?;
7002 let next = graph
7003 .enqueue_resident_fork(base.handle(), slot(6)?)
7004 .map_err(kernel_error)?;
7005 graph
7006 .enqueue_resident_truth(
7007 SemanticResidentView::Fork(next.candidate_handle()),
7008 statement()?,
7009 slot(7)?,
7010 )
7011 .map_err(kernel_error)?;
7012 graph
7013 .enqueue_resident_truth(
7014 SemanticResidentView::Fork(acquired.candidate_handle()),
7015 statement()?,
7016 slot(8)?,
7017 )
7018 .map_err(kernel_error)?;
7019 Ok(())
7020 })
7021 .unwrap();
7022 captured.launch(&stream).unwrap();
7023 stream.synchronize().unwrap();
7024 let observed = provider
7025 .dtoh_small_metadata_untracked(&receipts, 9)
7026 .unwrap();
7027 assert_eq!(observed[3].words[0], STATUS_ROOT_CAPACITY);
7028 assert_eq!(observed[4].words[0], STATUS_ROOT_CAPACITY);
7029 assert_eq!(observed[5].words[0], STATUS_ROOT_CAPACITY);
7030 assert_eq!(
7031 observed[6].words[0], STATUS_OK,
7032 "a refused seal must roll back its candidate"
7033 );
7034 assert_eq!(observed[7].words[0], STATUS_OK);
7035 assert_eq!(
7036 observed[7].words[2], 0,
7037 "a refused lane must leave no partial semantic result"
7038 );
7039 assert_eq!(observed[8].words[0], STATUS_STALE_GENERATION);
7040 assert_eq!(&observed[4].words[40..42], &[0, 0]);
7041 }
7042
7043 #[test]
7044 fn resident_admission_is_capture_safe_and_zero_host_io() {
7045 if std::env::var("XLOG_REQUIRE_CUDA").as_deref() != Ok("1") {
7046 eprintln!("Skipping: set XLOG_REQUIRE_CUDA=1 to run this real-CUDA contract");
7047 return;
7048 }
7049
7050 let provider = CudaProviderBuilder::new(0, MemoryBudget::with_limit(64 * 1024 * 1024))
7051 .with_stream_capacity(1)
7052 .build()
7053 .expect("CUDA provider must be available when XLOG_REQUIRE_CUDA=1");
7054 let provider = Arc::new(provider);
7055 let runtime = Arc::clone(
7056 provider
7057 .memory()
7058 .runtime()
7059 .expect("canonical provider must own a runtime"),
7060 );
7061 let stream_id = runtime
7062 .stream_pool()
7063 .acquire()
7064 .expect("semantic hypergraph must acquire its resident stream");
7065 let stream = runtime
7066 .stream_pool()
7067 .resolve(stream_id)
7068 .expect("resident stream id must resolve through its runtime");
7069 let domain = provider
7070 .bind_resident_execution_domain(Arc::clone(&runtime), stream_id, Arc::clone(&stream))
7071 .expect("provider must bind its exact resident execution domain");
7072 let mut graph = provider
7073 .allocate_semantic_hypergraph(
7074 &domain,
7075 SemanticHypergraphCapacities::try_new(2, 1, 1, 1).unwrap(),
7076 )
7077 .expect("semantic storage initialization must execute on CUDA");
7078
7079 let resident_bytes = (std::mem::size_of::<SemanticResidentDecodedInput>()
7080 + std::mem::size_of::<SemanticResidentHandleRecord>()
7081 + std::mem::size_of::<SemanticResidentDecodedStatement>()
7082 + std::mem::size_of::<SemanticResidentDecodedSupport>()
7083 + 12 * std::mem::size_of::<SemanticResidentReceiptRecord>()
7084 + 3 * std::mem::size_of::<SemanticResidentTruthValue>())
7085 as u64;
7086 let mut reservation = provider
7087 .memory()
7088 .reserve_bytes(resident_bytes)
7089 .expect("resident decoded banks and receipts reservation must succeed");
7090 let mut decoded_inputs = reservation
7091 .alloc::<SemanticResidentDecodedInput>(1)
7092 .expect("decoder-owned input allocation must succeed");
7093 let empty_root_handles = reservation
7094 .alloc::<SemanticResidentHandleRecord>(1)
7095 .expect("resident empty-root handle allocation must succeed");
7096 let decoded_statements = reservation
7097 .alloc::<SemanticResidentDecodedStatement>(1)
7098 .expect("resident decoded statement allocation must succeed");
7099 let decoded_supports = reservation
7100 .alloc::<SemanticResidentDecodedSupport>(1)
7101 .expect("resident decoded support allocation must succeed");
7102 let receipts = reservation
7103 .alloc::<SemanticResidentReceiptRecord>(12)
7104 .expect("resident receipt bank allocation must succeed");
7105 let truth_values = reservation
7106 .alloc::<SemanticResidentTruthValue>(3)
7107 .expect("resident truth-value output allocation must succeed");
7108 assert_eq!(reservation.remaining_bytes(), 0);
7109
7110 let decoded_input = SemanticResidentDecodedInput {
7111 statement_identity_words: [
7112 0x1020_3040,
7113 0x5060_7080,
7114 0x90a0_b0c0,
7115 0xd0e0_f001,
7116 0x1234_5678,
7117 0x9abc_def0,
7118 0x0fed_cba9,
7119 0x8765_4321,
7120 ],
7121 polarity: 1,
7122 provenance_words: [1, 2, 3, 4, 5, 6, 7, 8],
7123 source_words: [11, 12, 13, 14, 15, 16, 17, 18],
7124 context_words: [21, 22, 23, 24, 25, 26, 27, 28],
7125 scope_words: [31, 32, 33, 34, 35, 36, 37, 38],
7126 statement_record: 0,
7127 support_record: 0,
7128 reconstruction: [0; 10],
7129 };
7130 provider
7131 .htod_sync_copy_into_tracked(&[decoded_input], &mut decoded_inputs)
7132 .expect("decoder-owned typed input upload must succeed before measurement");
7133
7134 let mut setup_recorder = domain.new_strict_recorder();
7138 setup_recorder.read_write(&decoded_inputs);
7139 setup_recorder.write(&empty_root_handles);
7140 setup_recorder.write(&decoded_statements);
7141 setup_recorder.write(&decoded_supports);
7142 setup_recorder.write(&receipts);
7143 setup_recorder.write(&truth_values);
7144 let setup_enqueued = unsafe { domain.enqueue(setup_recorder, |_| Ok::<(), XlogError>(())) }
7145 .expect("test setup must adopt resident allocations on the selected stream");
7146 setup_enqueued
7147 .commit()
7148 .expect("test setup must publish resident bank dependencies");
7149 stream
7150 .synchronize()
7151 .expect("test setup allocation adoption must complete before capture");
7152
7153 let data_plane_before = provider.host_transfer_stats();
7154 let launch_metadata_before = provider.host_launch_metadata_transfer_stats();
7155 let tracked_dtoh_before = provider.d2h_transfer_count();
7156 let untracked_dtoh_before = provider.untracked_metadata_dtoh_count();
7157 let final_observation_before = provider.final_observation_transfer_stats();
7158 let semantic_launches_before = graph.execution_stats().cuda_kernel_launches();
7159
7160 let captured = CapturedCudaGraph::capture_on_stream(&stream, || {
7161 let input =
7162 SemanticResidentDecodedInputBank::new(&decoded_inputs, 0).map_err(kernel_error)?;
7163 let statement =
7164 SemanticResidentStatementBank::new(&decoded_statements, 0).map_err(kernel_error)?;
7165 let support =
7166 SemanticResidentSupportBank::new(&decoded_supports, 0).map_err(kernel_error)?;
7167 let materialize_slot =
7168 SemanticResidentReceiptSlot::new(&receipts, 0).map_err(kernel_error)?;
7169 graph
7170 .enqueue_resident_materialize_decoded(input, statement, support, materialize_slot)
7171 .map_err(kernel_error)?;
7172
7173 let handle_slot =
7174 SemanticResidentHandleSlot::new(&empty_root_handles, 0).map_err(kernel_error)?;
7175 let handle_receipt_slot =
7176 SemanticResidentReceiptSlot::new(&receipts, 1).map_err(kernel_error)?;
7177 let empty_root = graph
7178 .enqueue_resident_empty_root_handle(handle_slot, handle_receipt_slot)
7179 .map_err(kernel_error)?;
7180
7181 let statement =
7182 SemanticResidentStatementBank::new(&decoded_statements, 0).map_err(kernel_error)?;
7183 let truth_slot =
7184 SemanticResidentReceiptSlot::new(&receipts, 8).map_err(kernel_error)?;
7185 let base_truth = graph
7186 .enqueue_resident_truth(
7187 SemanticResidentView::Root(empty_root.handle()),
7188 statement,
7189 truth_slot,
7190 )
7191 .map_err(kernel_error)?;
7192 let output = SemanticResidentTruthSlot::new(&truth_values, 1).map_err(kernel_error)?;
7193 let consumer = SemanticResidentReceiptSlot::new(&receipts, 9).map_err(kernel_error)?;
7194 graph
7195 .enqueue_resident_truth_consumer(base_truth.device_view(), output, consumer)
7196 .map_err(kernel_error)?;
7197
7198 let fork_slot = SemanticResidentReceiptSlot::new(&receipts, 2).map_err(kernel_error)?;
7199 let fork_receipt = graph
7200 .enqueue_resident_fork(empty_root.handle(), fork_slot)
7201 .map_err(kernel_error)?;
7202
7203 let statement =
7204 SemanticResidentStatementBank::new(&decoded_statements, 0).map_err(kernel_error)?;
7205 let support =
7206 SemanticResidentSupportBank::new(&decoded_supports, 0).map_err(kernel_error)?;
7207 let insert_slot =
7208 SemanticResidentReceiptSlot::new(&receipts, 3).map_err(kernel_error)?;
7209 let insert_receipt = graph
7210 .enqueue_resident_insert_support(
7211 fork_receipt.candidate_handle(),
7212 statement,
7213 support,
7214 insert_slot,
7215 )
7216 .map_err(kernel_error)?;
7217
7218 let seal_slot = SemanticResidentReceiptSlot::new(&receipts, 4).map_err(kernel_error)?;
7219 let sealed = graph
7220 .enqueue_resident_seal(insert_receipt.candidate_handle(), seal_slot)
7221 .map_err(kernel_error)?;
7222
7223 let statement =
7224 SemanticResidentStatementBank::new(&decoded_statements, 0).map_err(kernel_error)?;
7225 let truth_slot =
7226 SemanticResidentReceiptSlot::new(&receipts, 10).map_err(kernel_error)?;
7227 let sealed_truth = graph
7228 .enqueue_resident_truth(
7229 SemanticResidentView::Root(sealed.root_handle()),
7230 statement,
7231 truth_slot,
7232 )
7233 .map_err(kernel_error)?;
7234 let output = SemanticResidentTruthSlot::new(&truth_values, 2).map_err(kernel_error)?;
7235 let consumer = SemanticResidentReceiptSlot::new(&receipts, 11).map_err(kernel_error)?;
7236 graph
7237 .enqueue_resident_truth_consumer(sealed_truth.device_view(), output, consumer)
7238 .map_err(kernel_error)?;
7239
7240 let refork_slot =
7241 SemanticResidentReceiptSlot::new(&receipts, 5).map_err(kernel_error)?;
7242 let refork = graph
7243 .enqueue_resident_fork(sealed.root_handle(), refork_slot)
7244 .map_err(kernel_error)?;
7245
7246 let truth_slot =
7247 SemanticResidentReceiptSlot::new(&receipts, 6).map_err(kernel_error)?;
7248 let statement =
7249 SemanticResidentStatementBank::new(&decoded_statements, 0).map_err(kernel_error)?;
7250 let truth_receipt = graph
7251 .enqueue_resident_truth(
7252 SemanticResidentView::Fork(refork.candidate_handle()),
7253 statement,
7254 truth_slot,
7255 )
7256 .map_err(kernel_error)?;
7257
7258 let output = SemanticResidentTruthSlot::new(&truth_values, 0).map_err(kernel_error)?;
7259 let consumer_slot =
7260 SemanticResidentReceiptSlot::new(&receipts, 7).map_err(kernel_error)?;
7261 graph
7262 .enqueue_resident_truth_consumer(truth_receipt.device_view(), output, consumer_slot)
7263 .map_err(kernel_error)?;
7264 Ok(())
7265 })
7266 .expect("typed resident semantic chain must be CUDA-capture safe");
7267 assert_eq!(
7268 graph.execution_stats().cuda_kernel_launches(),
7269 semantic_launches_before + 12
7270 );
7271 captured
7272 .launch(&stream)
7273 .expect("captured resident semantic chain must launch");
7274
7275 let data_plane_after = provider.host_transfer_stats();
7276 assert_eq!(data_plane_after.dtoh_bytes, data_plane_before.dtoh_bytes);
7277 assert_eq!(data_plane_after.htod_bytes, data_plane_before.htod_bytes);
7278 assert_eq!(data_plane_after.dtoh_calls, data_plane_before.dtoh_calls);
7279 assert_eq!(data_plane_after.htod_calls, data_plane_before.htod_calls);
7280 let launch_metadata_after = provider.host_launch_metadata_transfer_stats();
7281 assert_eq!(
7282 launch_metadata_after.htod_bytes,
7283 launch_metadata_before.htod_bytes
7284 );
7285 assert_eq!(
7286 launch_metadata_after.htod_calls,
7287 launch_metadata_before.htod_calls
7288 );
7289 assert_eq!(provider.d2h_transfer_count(), tracked_dtoh_before);
7290 assert_eq!(
7291 provider.untracked_metadata_dtoh_count(),
7292 untracked_dtoh_before
7293 );
7294 let final_observation_after = provider.final_observation_transfer_stats();
7295 assert_eq!(
7296 final_observation_after.dtoh_bytes,
7297 final_observation_before.dtoh_bytes
7298 );
7299 assert_eq!(
7300 final_observation_after.dtoh_calls,
7301 final_observation_before.dtoh_calls
7302 );
7303 assert_eq!(
7304 final_observation_after.pinned_receipts,
7305 final_observation_before.pinned_receipts
7306 );
7307
7308 stream
7309 .synchronize()
7310 .expect("the test may observe only after the measured resident interval");
7311 let observed_truths = provider
7312 .dtoh_small_metadata_untracked(&truth_values, 3)
7313 .expect("the test may observe only the typed truth output after synchronization");
7314 for (truth_value, expected) in observed_truths.iter().zip([1, 0, 1]) {
7315 assert_eq!(truth_value.status, STATUS_OK);
7316 assert_eq!(truth_value.truth, expected);
7317 assert_eq!(truth_value.owner, graph.owner);
7318 assert_eq!(truth_value.reserved, 0);
7319 }
7320 let statement_identity = SemanticStatementIdentity(identity_bytes_from_dwords(
7321 decoded_input.statement_identity_words,
7322 ));
7323 let mut expected = Sha256::new();
7324 expected.update(b"xlog.semantic.support.v1\0");
7325 expected.update(statement_identity.as_bytes());
7326 expected.update(1u32.to_le_bytes()); expected.update(1u32.to_le_bytes()); for words in [
7329 decoded_input.provenance_words,
7330 decoded_input.source_words,
7331 decoded_input.context_words,
7332 decoded_input.scope_words,
7333 ] {
7334 expected.update(identity_bytes_from_dwords(words));
7335 }
7336 let expected_support_identity = SemanticSupportIdentity(expected.finalize().into());
7337 assert_eq!(
7338 provider.untracked_metadata_dtoh_count(),
7339 untracked_dtoh_before + 1
7340 );
7341 let observed_receipts = provider
7342 .dtoh_small_metadata_untracked(&receipts, 4)
7343 .expect("the test may observe bounded receipts only after synchronization");
7344 let insert_receipt = &observed_receipts[3];
7345 assert_eq!(insert_receipt.words[0], STATUS_OK);
7346 assert_eq!(insert_receipt.words[1], OUTCOME_INSERTED);
7347 assert_eq!(
7348 receipt_identity(insert_receipt, 24),
7349 *expected_support_identity.as_bytes()
7350 );
7351 assert_eq!(
7352 provider.untracked_metadata_dtoh_count(),
7353 untracked_dtoh_before + 2
7354 );
7355
7356 let replay_data_plane_before = provider.host_transfer_stats();
7357 let replay_launch_metadata_before = provider.host_launch_metadata_transfer_stats();
7358 let replay_tracked_dtoh_before = provider.d2h_transfer_count();
7359 let replay_untracked_dtoh_before = provider.untracked_metadata_dtoh_count();
7360 let replay_final_observation_before = provider.final_observation_transfer_stats();
7361 captured
7362 .launch(&stream)
7363 .expect("captured resident semantic chain must be replayable");
7364 let replay_data_plane_after = provider.host_transfer_stats();
7365 assert_eq!(
7366 replay_data_plane_after.dtoh_bytes,
7367 replay_data_plane_before.dtoh_bytes
7368 );
7369 assert_eq!(
7370 replay_data_plane_after.htod_bytes,
7371 replay_data_plane_before.htod_bytes
7372 );
7373 assert_eq!(
7374 replay_data_plane_after.dtoh_calls,
7375 replay_data_plane_before.dtoh_calls
7376 );
7377 assert_eq!(
7378 replay_data_plane_after.htod_calls,
7379 replay_data_plane_before.htod_calls
7380 );
7381 let replay_launch_metadata_after = provider.host_launch_metadata_transfer_stats();
7382 assert_eq!(
7383 replay_launch_metadata_after.htod_bytes,
7384 replay_launch_metadata_before.htod_bytes
7385 );
7386 assert_eq!(
7387 replay_launch_metadata_after.htod_calls,
7388 replay_launch_metadata_before.htod_calls
7389 );
7390 assert_eq!(provider.d2h_transfer_count(), replay_tracked_dtoh_before);
7391 assert_eq!(
7392 provider.untracked_metadata_dtoh_count(),
7393 replay_untracked_dtoh_before
7394 );
7395 let replay_final_observation_after = provider.final_observation_transfer_stats();
7396 assert_eq!(
7397 replay_final_observation_after.dtoh_bytes,
7398 replay_final_observation_before.dtoh_bytes
7399 );
7400 assert_eq!(
7401 replay_final_observation_after.dtoh_calls,
7402 replay_final_observation_before.dtoh_calls
7403 );
7404 assert_eq!(
7405 replay_final_observation_after.pinned_receipts,
7406 replay_final_observation_before.pinned_receipts
7407 );
7408 stream
7409 .synchronize()
7410 .expect("replayed resident semantic chain must complete");
7411 let replayed_truths = provider
7412 .dtoh_small_metadata_untracked(&truth_values, 3)
7413 .expect("the test may observe the replayed typed output after synchronization");
7414 for (truth_value, status) in
7415 replayed_truths
7416 .iter()
7417 .zip([STATUS_INACTIVE, STATUS_OK, STATUS_INACTIVE])
7418 {
7419 assert_eq!(truth_value.status, status);
7420 assert_eq!(truth_value.truth, 0);
7421 assert_eq!(truth_value.owner, graph.owner);
7422 assert_eq!(truth_value.reserved, 0);
7423 }
7424 let replayed_receipts = provider
7425 .dtoh_small_metadata_untracked(&receipts, 12)
7426 .expect("the test may observe bounded replay receipts only after synchronization");
7427 assert_eq!(replayed_receipts[0].words[0], STATUS_OK);
7428 assert_eq!(replayed_receipts[1].words[0], STATUS_OK);
7429 assert_eq!(replayed_receipts[2].words[0], STATUS_INACTIVE);
7430 assert_eq!(
7431 decode_kind(replayed_receipts[2].words[33]),
7432 SemanticHandleKind::Fork
7433 );
7434 for propagated in replayed_receipts[3..8]
7435 .iter()
7436 .chain(&replayed_receipts[10..])
7437 {
7438 assert_eq!(propagated.words, replayed_receipts[2].words);
7439 }
7440 assert_eq!(replayed_receipts[8].words[0], STATUS_OK);
7441 assert_eq!(replayed_receipts[8].words[2], 0);
7442 assert_eq!(replayed_receipts[9].words[0], STATUS_OK);
7443 assert_eq!(
7444 provider.untracked_metadata_dtoh_count(),
7445 untracked_dtoh_before + 4
7446 );
7447
7448 let launches_before_host_rejection = graph.execution_stats().cuda_kernel_launches();
7449 let launch_metadata_before_host_rejection = provider.host_launch_metadata_transfer_stats();
7450 let empty_root = graph.empty_root();
7451 assert!(matches!(
7452 graph.snapshot(SemanticView::Root(empty_root)),
7453 Err(SemanticHypergraphError::DeviceControlled)
7454 ));
7455 assert_eq!(
7456 graph.execution_stats().cuda_kernel_launches(),
7457 launches_before_host_rejection
7458 );
7459 let launch_metadata_after_host_rejection = provider.host_launch_metadata_transfer_stats();
7460 assert_eq!(
7461 launch_metadata_after_host_rejection.htod_bytes,
7462 launch_metadata_before_host_rejection.htod_bytes
7463 );
7464 assert_eq!(
7465 launch_metadata_after_host_rejection.htod_calls,
7466 launch_metadata_before_host_rejection.htod_calls
7467 );
7468 }
7469}