1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4use std::collections::{BTreeMap, BTreeSet};
5use std::fmt;
6
7use super::{
8 AttributeId, CapabilityCatalog, ContractVersion, DimensionConstraint, ElementType,
9 OperationContract, OperationDescriptor, OperationId, OracleSpec, SemanticValue, VNextError,
10 MAX_REFERENCE_ORACLE_DEPTH,
11};
12
13pub const MAX_ORACLE_TENSOR_RANK: usize = 16;
15pub const MAX_ORACLE_TENSOR_ELEMENTS: usize = 16 * 1024 * 1024;
17pub const MAX_ORACLE_TENSOR_BYTES: usize = 64 * 1024 * 1024;
19pub const MAX_ORACLE_TENSORS: usize = 64;
21pub const MAX_ORACLE_CALL_BYTES: usize = 64 * 1024 * 1024;
23pub const MAX_ORACLE_ATTRIBUTES: usize = 256;
25pub const MAX_ORACLE_ATTRIBUTE_BYTES: usize = 1024 * 1024;
27pub const MAX_ORACLE_WIRE_BYTES: usize = 16 * 1024 * 1024;
29
30fn invalid_oracle(reason: impl Into<String>) -> VNextError {
31 VNextError::InvalidExecutionPlan {
32 reason: reason.into(),
33 }
34}
35
36pub fn validate_oracle_wire_byte_length(byte_length: usize) -> Result<(), VNextError> {
38 if byte_length > MAX_ORACLE_WIRE_BYTES {
39 return Err(invalid_oracle(format!(
40 "oracle wire bytes exceed {MAX_ORACLE_WIRE_BYTES}"
41 )));
42 }
43 Ok(())
44}
45
46fn decode_untrusted_oracle_wire<T: DeserializeOwned>(
47 bytes: &[u8],
48 context: &'static str,
49) -> Result<T, VNextError> {
50 validate_oracle_wire_byte_length(bytes.len())?;
51 serde_json::from_slice(bytes).map_err(|error| VNextError::Serialization {
52 context,
53 message: error.to_string(),
54 })
55}
56
57fn is_canonical_sha256(value: &str) -> bool {
58 value.len() == 64
59 && value
60 .bytes()
61 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
62}
63
64fn validate_oracle_identity(value: &str) -> Result<(), VNextError> {
65 if value.is_empty() || value.len() > 160 {
66 return Err(VNextError::InvalidIdentity {
67 kind: "operation oracle",
68 value: value.to_owned(),
69 reason: "identity must contain between 1 and 160 bytes",
70 });
71 }
72 if !value.bytes().all(|byte| {
73 byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':' | b'/')
74 }) {
75 return Err(VNextError::InvalidIdentity {
76 kind: "operation oracle",
77 value: value.to_owned(),
78 reason: "identity contains a non-portable character",
79 });
80 }
81 Ok(())
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
86#[serde(try_from = "String", into = "String")]
87pub struct OperationOracleId(String);
88
89impl OperationOracleId {
90 pub fn new(value: impl Into<String>) -> Result<Self, VNextError> {
91 let value = value.into();
92 validate_oracle_identity(&value)?;
93 Ok(Self(value))
94 }
95
96 pub fn as_str(&self) -> &str {
97 &self.0
98 }
99}
100
101impl TryFrom<String> for OperationOracleId {
102 type Error = VNextError;
103
104 fn try_from(value: String) -> Result<Self, Self::Error> {
105 Self::new(value)
106 }
107}
108
109impl From<OperationOracleId> for String {
110 fn from(value: OperationOracleId) -> Self {
111 value.0
112 }
113}
114
115impl fmt::Display for OperationOracleId {
116 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
117 formatter.write_str(&self.0)
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
123pub struct OperationOracleDescriptor {
124 oracle_id: OperationOracleId,
125 version: ContractVersion,
126 implementation_fingerprint: String,
127 operation_id: OperationId,
128 operation_fingerprint: String,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct UnvalidatedOperationOracleDescriptor {
135 pub oracle_id: OperationOracleId,
136 pub version: ContractVersion,
137 pub implementation_fingerprint: String,
138 pub operation_id: OperationId,
139 pub operation_fingerprint: String,
140}
141
142#[derive(Deserialize)]
143#[serde(deny_unknown_fields)]
144struct OperationOracleDescriptorWire {
145 oracle_id: OperationOracleId,
146 version: ContractVersion,
147 implementation_fingerprint: String,
148 operation_id: OperationId,
149 operation_fingerprint: String,
150}
151
152impl UnvalidatedOperationOracleDescriptor {
153 pub fn revalidate(self) -> Result<OperationOracleDescriptor, VNextError> {
154 OperationOracleDescriptor::new(
155 self.oracle_id,
156 self.version,
157 self.implementation_fingerprint,
158 self.operation_id,
159 self.operation_fingerprint,
160 )
161 }
162}
163
164impl OperationOracleDescriptor {
165 pub fn new(
166 oracle_id: OperationOracleId,
167 version: ContractVersion,
168 implementation_fingerprint: impl Into<String>,
169 operation_id: OperationId,
170 operation_fingerprint: impl Into<String>,
171 ) -> Result<Self, VNextError> {
172 let implementation_fingerprint = implementation_fingerprint.into();
173 let operation_fingerprint = operation_fingerprint.into();
174 if version.major == 0 {
175 return Err(invalid_oracle(format!(
176 "oracle `{oracle_id}` has a zero contract major version"
177 )));
178 }
179 if !is_canonical_sha256(&implementation_fingerprint) {
180 return Err(invalid_oracle(format!(
181 "oracle `{oracle_id}` implementation fingerprint is not canonical SHA-256"
182 )));
183 }
184 if !is_canonical_sha256(&operation_fingerprint) {
185 return Err(invalid_oracle(format!(
186 "oracle `{oracle_id}` operation fingerprint is not canonical SHA-256"
187 )));
188 }
189 Ok(Self {
190 oracle_id,
191 version,
192 implementation_fingerprint,
193 operation_id,
194 operation_fingerprint,
195 })
196 }
197
198 pub fn oracle_id(&self) -> &OperationOracleId {
199 &self.oracle_id
200 }
201
202 pub const fn version(&self) -> ContractVersion {
203 self.version
204 }
205
206 pub fn implementation_fingerprint(&self) -> &str {
207 &self.implementation_fingerprint
208 }
209
210 pub fn operation_id(&self) -> &OperationId {
211 &self.operation_id
212 }
213
214 pub fn operation_fingerprint(&self) -> &str {
215 &self.operation_fingerprint
216 }
217
218 pub fn fingerprint(&self) -> Result<String, VNextError> {
219 let bytes = serde_json::to_vec(self).map_err(|error| VNextError::Serialization {
220 context: "serialize operation oracle descriptor",
221 message: error.to_string(),
222 })?;
223 Ok(format!("{:x}", Sha256::digest(bytes)))
224 }
225
226 pub fn decode_untrusted(
227 bytes: &[u8],
228 ) -> Result<UnvalidatedOperationOracleDescriptor, VNextError> {
229 let wire: OperationOracleDescriptorWire =
230 decode_untrusted_oracle_wire(bytes, "decode untrusted operation oracle descriptor")?;
231 Ok(UnvalidatedOperationOracleDescriptor {
232 oracle_id: wire.oracle_id,
233 version: wire.version,
234 implementation_fingerprint: wire.implementation_fingerprint,
235 operation_id: wire.operation_id,
236 operation_fingerprint: wire.operation_fingerprint,
237 })
238 }
239}
240
241#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
245pub struct OracleTensor {
246 dimensions: Vec<u64>,
247 element_type: ElementType,
248 bytes: Vec<u8>,
249}
250
251#[derive(Debug, Clone, PartialEq, Eq)]
254pub struct UnvalidatedOracleTensor {
255 pub dimensions: Vec<u64>,
256 pub element_type: ElementType,
257 pub bytes: Vec<u8>,
258}
259
260#[derive(Deserialize)]
261#[serde(deny_unknown_fields)]
262struct OracleTensorWire {
263 dimensions: Vec<u64>,
264 element_type: ElementType,
265 bytes: Vec<u8>,
266}
267
268impl UnvalidatedOracleTensor {
269 pub fn revalidate(self) -> Result<OracleTensor, VNextError> {
270 OracleTensor::new(self.dimensions, self.element_type, self.bytes)
271 }
272}
273
274impl OracleTensor {
275 pub fn new(
276 dimensions: Vec<u64>,
277 element_type: ElementType,
278 bytes: Vec<u8>,
279 ) -> Result<Self, VNextError> {
280 if dimensions.len() > MAX_ORACLE_TENSOR_RANK {
281 return Err(invalid_oracle(format!(
282 "oracle tensor rank exceeds {MAX_ORACLE_TENSOR_RANK}"
283 )));
284 }
285 if dimensions.iter().any(|extent| *extent == 0) {
286 return Err(invalid_oracle("oracle tensor has a zero extent"));
287 }
288 let elements = dimensions.iter().try_fold(1usize, |elements, extent| {
289 let extent = usize::try_from(*extent)
290 .map_err(|_| invalid_oracle("oracle tensor extent does not fit usize"))?;
291 elements
292 .checked_mul(extent)
293 .ok_or_else(|| invalid_oracle("oracle tensor element count overflows usize"))
294 })?;
295 if elements > MAX_ORACLE_TENSOR_ELEMENTS {
296 return Err(invalid_oracle(format!(
297 "oracle tensor elements exceed {MAX_ORACLE_TENSOR_ELEMENTS}"
298 )));
299 }
300 let element_bytes = usize::try_from(element_type.size_bytes())
301 .map_err(|_| invalid_oracle("oracle tensor element width does not fit usize"))?;
302 let expected_bytes = elements
303 .checked_mul(element_bytes)
304 .ok_or_else(|| invalid_oracle("oracle tensor byte count overflows usize"))?;
305 if expected_bytes > MAX_ORACLE_TENSOR_BYTES || bytes.len() != expected_bytes {
306 return Err(invalid_oracle(format!(
307 "oracle tensor requires exactly {expected_bytes} bytes within the {MAX_ORACLE_TENSOR_BYTES} byte limit"
308 )));
309 }
310 validate_scalar_encodings(element_type, &bytes)?;
311 Ok(Self {
312 dimensions,
313 element_type,
314 bytes,
315 })
316 }
317
318 pub fn dimensions(&self) -> &[u64] {
319 &self.dimensions
320 }
321
322 pub const fn element_type(&self) -> ElementType {
323 self.element_type
324 }
325
326 pub fn bytes(&self) -> &[u8] {
327 &self.bytes
328 }
329
330 pub fn element_count(&self) -> usize {
331 self.bytes.len() / self.element_type.size_bytes() as usize
332 }
333
334 pub fn decode_untrusted(bytes: &[u8]) -> Result<UnvalidatedOracleTensor, VNextError> {
335 let wire: OracleTensorWire =
336 decode_untrusted_oracle_wire(bytes, "decode untrusted oracle tensor")?;
337 Ok(UnvalidatedOracleTensor {
338 dimensions: wire.dimensions,
339 element_type: wire.element_type,
340 bytes: wire.bytes,
341 })
342 }
343
344 fn numeric_value(&self, index: usize) -> Result<f64, VNextError> {
345 if index >= self.element_count() {
346 return Err(invalid_oracle(
347 "oracle tensor element index is out of bounds",
348 ));
349 }
350 let width = self.element_type.size_bytes() as usize;
351 let offset = index * width;
352 let bytes = &self.bytes[offset..offset + width];
353 let value = match self.element_type {
354 ElementType::Bool => {
355 return Err(invalid_oracle(
356 "tolerance comparison is not defined for boolean tensors",
357 ));
358 }
359 ElementType::U8 => f64::from(bytes[0]),
360 ElementType::I8 => f64::from(i8::from_le_bytes([bytes[0]])),
361 ElementType::U32 => f64::from(u32::from_le_bytes(
362 bytes.try_into().expect("validated width"),
363 )),
364 ElementType::I32 => f64::from(i32::from_le_bytes(
365 bytes.try_into().expect("validated width"),
366 )),
367 ElementType::F16 => f16_to_f64(u16::from_le_bytes(
368 bytes.try_into().expect("validated width"),
369 )),
370 ElementType::Bf16 => f64::from(f32::from_bits(
371 u32::from(u16::from_le_bytes(
372 bytes.try_into().expect("validated width"),
373 )) << 16,
374 )),
375 ElementType::F32 => f64::from(f32::from_le_bytes(
376 bytes.try_into().expect("validated width"),
377 )),
378 };
379 Ok(value)
380 }
381}
382
383fn validate_scalar_encodings(element_type: ElementType, bytes: &[u8]) -> Result<(), VNextError> {
384 match element_type {
385 ElementType::Bool if bytes.iter().any(|value| !matches!(value, 0 | 1)) => Err(
386 invalid_oracle("canonical boolean oracle tensors contain only 0 or 1"),
387 ),
388 ElementType::F16
389 if bytes.chunks_exact(2).any(|bytes| {
390 u16::from_le_bytes(bytes.try_into().expect("two-byte chunk")) & 0x7c00 == 0x7c00
391 }) =>
392 {
393 Err(invalid_oracle(
394 "oracle tensors reject non-finite f16 values",
395 ))
396 }
397 ElementType::Bf16
398 if bytes.chunks_exact(2).any(|bytes| {
399 u16::from_le_bytes(bytes.try_into().expect("two-byte chunk")) & 0x7f80 == 0x7f80
400 }) =>
401 {
402 Err(invalid_oracle(
403 "oracle tensors reject non-finite bf16 values",
404 ))
405 }
406 ElementType::F32
407 if bytes.chunks_exact(4).any(|bytes| {
408 !f32::from_le_bytes(bytes.try_into().expect("four-byte chunk")).is_finite()
409 }) =>
410 {
411 Err(invalid_oracle(
412 "oracle tensors reject non-finite f32 values",
413 ))
414 }
415 _ => Ok(()),
416 }
417}
418
419fn f16_to_f64(bits: u16) -> f64 {
420 let sign = if bits & 0x8000 == 0 { 1.0 } else { -1.0 };
421 let exponent = i32::from((bits >> 10) & 0x1f);
422 let fraction = f64::from(bits & 0x03ff) / 1024.0;
423 if exponent == 0 {
424 sign * fraction * 2.0_f64.powi(-14)
425 } else {
426 sign * (1.0 + fraction) * 2.0_f64.powi(exponent - 15)
427 }
428}
429
430#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
432pub struct OperationOracleRequest {
433 operation_id: OperationId,
434 operation_fingerprint: String,
435 inputs: Vec<OracleTensor>,
436 attributes: BTreeMap<AttributeId, SemanticValue>,
437}
438
439#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct UnvalidatedOperationOracleRequest {
441 pub operation_id: OperationId,
442 pub operation_fingerprint: String,
443 pub inputs: Vec<UnvalidatedOracleTensor>,
444 pub attributes: BTreeMap<AttributeId, SemanticValue>,
445}
446
447#[derive(Deserialize)]
448#[serde(deny_unknown_fields)]
449struct OperationOracleRequestWire {
450 operation_id: OperationId,
451 operation_fingerprint: String,
452 inputs: Vec<OracleTensorWire>,
453 attributes: BTreeMap<AttributeId, SemanticValue>,
454}
455
456impl UnvalidatedOperationOracleRequest {
457 pub fn revalidate(self) -> Result<OperationOracleRequest, VNextError> {
458 OperationOracleRequest::new(
459 self.operation_id,
460 self.operation_fingerprint,
461 self.inputs
462 .into_iter()
463 .map(UnvalidatedOracleTensor::revalidate)
464 .collect::<Result<Vec<_>, _>>()?,
465 self.attributes,
466 )
467 }
468}
469
470impl OperationOracleRequest {
471 pub fn new(
472 operation_id: OperationId,
473 operation_fingerprint: impl Into<String>,
474 inputs: Vec<OracleTensor>,
475 attributes: BTreeMap<AttributeId, SemanticValue>,
476 ) -> Result<Self, VNextError> {
477 let operation_fingerprint = operation_fingerprint.into();
478 if !is_canonical_sha256(&operation_fingerprint) {
479 return Err(invalid_oracle(
480 "oracle request operation fingerprint is not canonical SHA-256",
481 ));
482 }
483 validate_tensor_collection("oracle request inputs", &inputs, false)?;
484 validate_oracle_attributes(&attributes)?;
485 Ok(Self {
486 operation_id,
487 operation_fingerprint,
488 inputs,
489 attributes,
490 })
491 }
492
493 pub fn operation_id(&self) -> &OperationId {
494 &self.operation_id
495 }
496
497 pub fn operation_fingerprint(&self) -> &str {
498 &self.operation_fingerprint
499 }
500
501 pub fn inputs(&self) -> &[OracleTensor] {
502 &self.inputs
503 }
504
505 pub fn attributes(&self) -> &BTreeMap<AttributeId, SemanticValue> {
506 &self.attributes
507 }
508
509 pub fn decode_untrusted(bytes: &[u8]) -> Result<UnvalidatedOperationOracleRequest, VNextError> {
510 let wire: OperationOracleRequestWire =
511 decode_untrusted_oracle_wire(bytes, "decode untrusted operation oracle request")?;
512 Ok(UnvalidatedOperationOracleRequest {
513 operation_id: wire.operation_id,
514 operation_fingerprint: wire.operation_fingerprint,
515 inputs: wire
516 .inputs
517 .into_iter()
518 .map(|tensor| UnvalidatedOracleTensor {
519 dimensions: tensor.dimensions,
520 element_type: tensor.element_type,
521 bytes: tensor.bytes,
522 })
523 .collect(),
524 attributes: wire.attributes,
525 })
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
531pub struct OperationOracleResult {
532 outputs: Vec<OracleTensor>,
533}
534
535#[derive(Debug, Clone, PartialEq, Eq)]
536pub struct UnvalidatedOperationOracleResult {
537 pub outputs: Vec<UnvalidatedOracleTensor>,
538}
539
540#[derive(Deserialize)]
541#[serde(deny_unknown_fields)]
542struct OperationOracleResultWire {
543 outputs: Vec<OracleTensorWire>,
544}
545
546impl UnvalidatedOperationOracleResult {
547 pub fn revalidate(self) -> Result<OperationOracleResult, VNextError> {
548 OperationOracleResult::new(
549 self.outputs
550 .into_iter()
551 .map(UnvalidatedOracleTensor::revalidate)
552 .collect::<Result<Vec<_>, _>>()?,
553 )
554 }
555}
556
557impl OperationOracleResult {
558 pub fn new(outputs: Vec<OracleTensor>) -> Result<Self, VNextError> {
559 validate_tensor_collection("oracle result outputs", &outputs, true)?;
560 Ok(Self { outputs })
561 }
562
563 pub fn outputs(&self) -> &[OracleTensor] {
564 &self.outputs
565 }
566
567 pub fn decode_untrusted(bytes: &[u8]) -> Result<UnvalidatedOperationOracleResult, VNextError> {
568 let wire: OperationOracleResultWire =
569 decode_untrusted_oracle_wire(bytes, "decode untrusted operation oracle result")?;
570 Ok(UnvalidatedOperationOracleResult {
571 outputs: wire
572 .outputs
573 .into_iter()
574 .map(|tensor| UnvalidatedOracleTensor {
575 dimensions: tensor.dimensions,
576 element_type: tensor.element_type,
577 bytes: tensor.bytes,
578 })
579 .collect(),
580 })
581 }
582}
583
584fn validate_tensor_collection(
585 context: &str,
586 tensors: &[OracleTensor],
587 require_nonempty: bool,
588) -> Result<(), VNextError> {
589 if tensors.len() > MAX_ORACLE_TENSORS || (require_nonempty && tensors.is_empty()) {
590 return Err(invalid_oracle(format!(
591 "{context} must contain {} to {MAX_ORACLE_TENSORS} tensors",
592 usize::from(require_nonempty)
593 )));
594 }
595 let total_bytes = tensors.iter().try_fold(0usize, |total, tensor| {
596 total
597 .checked_add(tensor.bytes.len())
598 .ok_or_else(|| invalid_oracle(format!("{context} byte count overflows usize")))
599 })?;
600 if total_bytes > MAX_ORACLE_CALL_BYTES {
601 return Err(invalid_oracle(format!(
602 "{context} exceeds {MAX_ORACLE_CALL_BYTES} cumulative bytes"
603 )));
604 }
605 Ok(())
606}
607
608fn validate_oracle_attributes(
609 attributes: &BTreeMap<AttributeId, SemanticValue>,
610) -> Result<(), VNextError> {
611 if attributes.len() > MAX_ORACLE_ATTRIBUTES {
612 return Err(invalid_oracle(format!(
613 "oracle request exceeds {MAX_ORACLE_ATTRIBUTES} attributes"
614 )));
615 }
616 for value in attributes.values() {
617 value.validate("oracle request attributes")?;
618 }
619 let bytes = serde_json::to_vec(attributes).map_err(|error| VNextError::Serialization {
620 context: "serialize operation oracle attributes",
621 message: error.to_string(),
622 })?;
623 if bytes.len() > MAX_ORACLE_ATTRIBUTE_BYTES {
624 return Err(invalid_oracle(format!(
625 "oracle request attributes exceed {MAX_ORACLE_ATTRIBUTE_BYTES} canonical bytes"
626 )));
627 }
628 Ok(())
629}
630
631pub trait OperationOracle: Send + Sync {
634 fn descriptor(&self) -> &OperationOracleDescriptor;
635
636 fn invoke(&self, request: &OperationOracleRequest)
637 -> Result<OperationOracleResult, VNextError>;
638}
639
640pub struct OperationOracleRegistration {
643 expected_descriptor: OperationOracleDescriptor,
644 oracle: Box<dyn OperationOracle>,
645}
646
647impl OperationOracleRegistration {
648 pub fn new(
649 expected_descriptor: OperationOracleDescriptor,
650 oracle: Box<dyn OperationOracle>,
651 ) -> Result<Self, VNextError> {
652 if oracle.descriptor() != &expected_descriptor {
653 return Err(invalid_oracle(
654 "operation oracle implementation differs from its trusted registration descriptor",
655 ));
656 }
657 Ok(Self {
658 expected_descriptor,
659 oracle,
660 })
661 }
662
663 pub fn descriptor(&self) -> &OperationOracleDescriptor {
664 &self.expected_descriptor
665 }
666}
667
668struct RegisteredOracle {
669 descriptor: OperationOracleDescriptor,
670 oracle: Box<dyn OperationOracle>,
671}
672
673pub struct OperationOracleRegistry {
676 catalog_fingerprint: String,
677 operations: BTreeMap<OperationId, OperationDescriptor>,
678 contracts: BTreeMap<OperationId, Box<dyn OperationContract>>,
679 terminal_operations: BTreeMap<OperationId, OperationId>,
680 oracles: BTreeMap<OperationId, RegisteredOracle>,
681}
682
683impl OperationOracleRegistry {
684 pub fn new(
685 catalog: &CapabilityCatalog,
686 contracts: Vec<Box<dyn OperationContract>>,
687 registrations: Vec<OperationOracleRegistration>,
688 ) -> Result<Self, VNextError> {
689 let operations = catalog.operations().clone();
690 let mut contract_map = BTreeMap::new();
691 for contract in contracts {
692 let descriptor = contract.descriptor();
693 descriptor.validate()?;
694 let catalog_descriptor = operations.get(&descriptor.id).ok_or_else(|| {
695 invalid_oracle(format!(
696 "oracle registry contract `{}` is absent from the capability catalog",
697 descriptor.id
698 ))
699 })?;
700 if descriptor != catalog_descriptor
701 || descriptor.fingerprint()? != catalog_descriptor.fingerprint()?
702 {
703 return Err(invalid_oracle(format!(
704 "oracle registry contract `{}` differs from the capability catalog",
705 descriptor.id
706 )));
707 }
708 contract.validate_signature(&catalog_descriptor.inputs, &catalog_descriptor.outputs)?;
709 let operation_id = descriptor.id.clone();
710 if contract_map
711 .insert(operation_id.clone(), contract)
712 .is_some()
713 {
714 return Err(invalid_oracle(format!(
715 "oracle registry has duplicate contract `{operation_id}`"
716 )));
717 }
718 }
719 if contract_map.keys().collect::<BTreeSet<_>>()
720 != operations.keys().collect::<BTreeSet<_>>()
721 {
722 return Err(invalid_oracle(
723 "oracle registry requires exactly one contract for every catalog operation",
724 ));
725 }
726
727 let terminal_operations = resolve_terminal_operations(&operations)?;
728 let terminal_ids = terminal_operations
729 .values()
730 .cloned()
731 .collect::<BTreeSet<_>>();
732 let mut oracle_ids = BTreeSet::new();
733 let mut oracle_map = BTreeMap::new();
734 for registration in registrations {
735 let OperationOracleRegistration {
736 expected_descriptor,
737 oracle,
738 } = registration;
739 if oracle.descriptor() != &expected_descriptor {
740 return Err(invalid_oracle(
741 "operation oracle descriptor changed after trusted registration",
742 ));
743 }
744 let operation = operations
745 .get(expected_descriptor.operation_id())
746 .ok_or_else(|| {
747 invalid_oracle(format!(
748 "oracle `{}` targets an operation absent from the capability catalog",
749 expected_descriptor.oracle_id()
750 ))
751 })?;
752 if matches!(operation.oracle, OracleSpec::ReferenceOperation { .. }) {
753 return Err(invalid_oracle(format!(
754 "reference operation `{}` cannot register a direct oracle",
755 operation.id
756 )));
757 }
758 if expected_descriptor.operation_fingerprint() != operation.fingerprint()? {
759 return Err(invalid_oracle(format!(
760 "oracle `{}` operation fingerprint differs from `{}`",
761 expected_descriptor.oracle_id(),
762 operation.id
763 )));
764 }
765 if !oracle_ids.insert(expected_descriptor.oracle_id().clone()) {
766 return Err(invalid_oracle(format!(
767 "oracle registry has duplicate identity `{}`",
768 expected_descriptor.oracle_id()
769 )));
770 }
771 let operation_id = operation.id.clone();
772 if oracle_map
773 .insert(
774 operation_id.clone(),
775 RegisteredOracle {
776 descriptor: expected_descriptor,
777 oracle,
778 },
779 )
780 .is_some()
781 {
782 return Err(invalid_oracle(format!(
783 "terminal operation `{operation_id}` has multiple oracles"
784 )));
785 }
786 }
787 if oracle_map.keys().cloned().collect::<BTreeSet<_>>() != terminal_ids {
788 return Err(invalid_oracle(
789 "every terminal non-reference operation must have exactly one oracle",
790 ));
791 }
792
793 Ok(Self {
794 catalog_fingerprint: catalog.fingerprint()?,
795 operations,
796 contracts: contract_map,
797 terminal_operations,
798 oracles: oracle_map,
799 })
800 }
801
802 pub fn catalog_fingerprint(&self) -> &str {
803 &self.catalog_fingerprint
804 }
805
806 pub fn contract(
807 &self,
808 operation_id: &OperationId,
809 ) -> Result<&dyn OperationContract, VNextError> {
810 self.contracts
811 .get(operation_id)
812 .map(Box::as_ref)
813 .ok_or_else(|| invalid_oracle(format!("operation `{operation_id}` is not registered")))
814 }
815
816 pub fn bind<'registry>(
817 &'registry self,
818 operation_id: &OperationId,
819 ) -> Result<BoundOperationOracle<'registry>, VNextError> {
820 let requested_operation = self.operations.get(operation_id).ok_or_else(|| {
821 invalid_oracle(format!("operation `{operation_id}` is not registered"))
822 })?;
823 let terminal_id = self.terminal_operations.get(operation_id).ok_or_else(|| {
824 invalid_oracle(format!(
825 "operation `{operation_id}` has no validated terminal oracle"
826 ))
827 })?;
828 let terminal_operation = self
829 .operations
830 .get(terminal_id)
831 .ok_or_else(|| invalid_oracle("validated terminal operation disappeared"))?;
832 let registered = self
833 .oracles
834 .get(terminal_id)
835 .ok_or_else(|| invalid_oracle("validated terminal oracle disappeared"))?;
836 if registered.oracle.descriptor() != ®istered.descriptor {
837 return Err(invalid_oracle(
838 "registered oracle descriptor changed before binding",
839 ));
840 }
841 Ok(BoundOperationOracle {
842 requested_operation,
843 terminal_operation,
844 registered,
845 })
846 }
847}
848
849fn resolve_terminal_operations(
850 operations: &BTreeMap<OperationId, OperationDescriptor>,
851) -> Result<BTreeMap<OperationId, OperationId>, VNextError> {
852 let mut resolved = BTreeMap::new();
853 for (root_id, root) in operations {
854 let mut current = root;
855 let mut visited = BTreeSet::new();
856 for _ in 0..MAX_REFERENCE_ORACLE_DEPTH {
857 if !visited.insert(current.id.clone()) {
858 return Err(invalid_oracle(format!(
859 "reference oracle chain from `{root_id}` contains a cycle"
860 )));
861 }
862 let OracleSpec::ReferenceOperation {
863 operation_id,
864 version,
865 } = ¤t.oracle
866 else {
867 resolved.insert(root_id.clone(), current.id.clone());
868 break;
869 };
870 let reference = operations.get(operation_id).ok_or_else(|| {
871 invalid_oracle(format!(
872 "reference oracle `{operation_id}` for `{}` is missing",
873 current.id
874 ))
875 })?;
876 if !reference.version.satisfies(*version)
877 || current.inputs != reference.inputs
878 || current.outputs != reference.outputs
879 || current.attributes != reference.attributes
880 {
881 return Err(invalid_oracle(format!(
882 "reference oracle `{operation_id}` is incompatible with `{}`",
883 current.id
884 )));
885 }
886 current = reference;
887 }
888 if !resolved.contains_key(root_id) {
889 return Err(invalid_oracle(format!(
890 "reference oracle chain from `{root_id}` exceeds depth {MAX_REFERENCE_ORACLE_DEPTH}"
891 )));
892 }
893 }
894 Ok(resolved)
895}
896
897pub struct BoundOperationOracle<'registry> {
899 requested_operation: &'registry OperationDescriptor,
900 terminal_operation: &'registry OperationDescriptor,
901 registered: &'registry RegisteredOracle,
902}
903
904impl BoundOperationOracle<'_> {
905 pub fn requested_operation_id(&self) -> &OperationId {
906 &self.requested_operation.id
907 }
908
909 pub fn terminal_operation_id(&self) -> &OperationId {
910 &self.terminal_operation.id
911 }
912
913 pub fn descriptor(&self) -> &OperationOracleDescriptor {
914 &self.registered.descriptor
915 }
916
917 pub fn comparison_policy(&self) -> &OracleSpec {
918 &self.terminal_operation.oracle
919 }
920
921 pub fn invoke(
922 &self,
923 inputs: Vec<OracleTensor>,
924 attributes: BTreeMap<AttributeId, SemanticValue>,
925 ) -> Result<OperationOracleResult, VNextError> {
926 self.invoke_internal(inputs, attributes)
927 .map(|(result, _)| result)
928 }
929
930 pub fn invoke_and_compare(
931 &self,
932 inputs: Vec<OracleTensor>,
933 attributes: BTreeMap<AttributeId, SemanticValue>,
934 actual: &OperationOracleResult,
935 ) -> Result<bool, VNextError> {
936 let (reference, mut symbols) = self.invoke_internal(inputs, attributes)?;
937 validate_tensors_against_contracts(
938 "actual oracle comparison outputs",
939 actual.outputs(),
940 &self.requested_operation.outputs,
941 &mut symbols,
942 )?;
943 compare_oracle_results(self.comparison_policy(), actual, &reference)
944 }
945
946 fn invoke_internal(
947 &self,
948 inputs: Vec<OracleTensor>,
949 attributes: BTreeMap<AttributeId, SemanticValue>,
950 ) -> Result<(OperationOracleResult, BTreeMap<String, u64>), VNextError> {
951 if self.registered.oracle.descriptor() != &self.registered.descriptor {
952 return Err(invalid_oracle(
953 "registered oracle descriptor changed before invocation",
954 ));
955 }
956 let mut symbols = BTreeMap::new();
957 validate_tensors_against_contracts(
958 "oracle request inputs",
959 &inputs,
960 &self.requested_operation.inputs,
961 &mut symbols,
962 )?;
963 self.requested_operation.validate_attributes(&attributes)?;
964 let request = OperationOracleRequest::new(
965 self.terminal_operation.id.clone(),
966 self.terminal_operation.fingerprint()?,
967 inputs,
968 attributes,
969 )?;
970 let result = self.registered.oracle.invoke(&request)?;
971 if self.registered.oracle.descriptor() != &self.registered.descriptor {
972 return Err(invalid_oracle(
973 "registered oracle descriptor changed during invocation",
974 ));
975 }
976 validate_tensors_against_contracts(
977 "oracle result outputs",
978 result.outputs(),
979 &self.terminal_operation.outputs,
980 &mut symbols,
981 )?;
982 Ok((result, symbols))
983 }
984}
985
986fn validate_tensors_against_contracts(
987 context: &str,
988 tensors: &[OracleTensor],
989 contracts: &[super::TensorContract],
990 symbols: &mut BTreeMap<String, u64>,
991) -> Result<(), VNextError> {
992 if tensors.len() != contracts.len() {
993 return Err(invalid_oracle(format!(
994 "{context} count {} differs from contract count {}",
995 tensors.len(),
996 contracts.len()
997 )));
998 }
999 for (index, (tensor, contract)) in tensors.iter().zip(contracts).enumerate() {
1000 if tensor.dimensions.len() != contract.dimensions().len()
1001 || !contract.element_types().contains(&tensor.element_type)
1002 {
1003 return Err(invalid_oracle(format!(
1004 "{context}[{index}] rank or dtype differs from the operation contract"
1005 )));
1006 }
1007 for (axis, (extent, constraint)) in tensor
1008 .dimensions
1009 .iter()
1010 .zip(contract.dimensions())
1011 .enumerate()
1012 {
1013 let accepted = match constraint {
1014 DimensionConstraint::Exact(expected) => extent == expected,
1015 DimensionConstraint::Range { minimum, maximum } => {
1016 minimum <= extent && extent <= maximum
1017 }
1018 DimensionConstraint::Symbol(symbol) => match symbols.get(symbol) {
1019 Some(expected) => expected == extent,
1020 None => {
1021 symbols.insert(symbol.clone(), *extent);
1022 true
1023 }
1024 },
1025 };
1026 if !accepted {
1027 return Err(invalid_oracle(format!(
1028 "{context}[{index}] axis {axis} violates the operation contract"
1029 )));
1030 }
1031 }
1032 }
1033 Ok(())
1034}
1035
1036pub fn compare_oracle_results(
1041 policy: &OracleSpec,
1042 actual: &OperationOracleResult,
1043 reference: &OperationOracleResult,
1044) -> Result<bool, VNextError> {
1045 if actual.outputs.len() != reference.outputs.len() {
1046 return Err(invalid_oracle(
1047 "oracle comparison result counts are different",
1048 ));
1049 }
1050 for (index, (actual, reference)) in actual.outputs.iter().zip(&reference.outputs).enumerate() {
1051 if actual.dimensions != reference.dimensions
1052 || actual.element_type != reference.element_type
1053 {
1054 return Err(invalid_oracle(format!(
1055 "oracle comparison tensor {index} shape or dtype is different"
1056 )));
1057 }
1058 }
1059
1060 match policy {
1061 OracleSpec::Exact => Ok(actual
1062 .outputs
1063 .iter()
1064 .zip(&reference.outputs)
1065 .all(|(actual, reference)| actual.bytes == reference.bytes)),
1066 OracleSpec::AbsoluteTolerance { tolerance } => {
1067 compare_with_tolerance(actual, reference, rational_to_f64(*tolerance)?, false)
1068 }
1069 OracleSpec::RelativeTolerance { tolerance } => {
1070 compare_with_tolerance(actual, reference, rational_to_f64(*tolerance)?, true)
1071 }
1072 OracleSpec::ReferenceOperation { .. } => Err(invalid_oracle(
1073 "reference operation policy must be resolved to a terminal oracle before comparison",
1074 )),
1075 }
1076}
1077
1078fn rational_to_f64(value: super::CanonicalRational) -> Result<f64, VNextError> {
1079 if value.numerator() < 0 {
1080 return Err(invalid_oracle("oracle tolerance must not be negative"));
1081 }
1082 let tolerance = value.numerator() as f64 / value.denominator() as f64;
1083 if !tolerance.is_finite() {
1084 return Err(invalid_oracle("oracle tolerance must be finite"));
1085 }
1086 Ok(tolerance)
1087}
1088
1089fn compare_with_tolerance(
1090 actual: &OperationOracleResult,
1091 reference: &OperationOracleResult,
1092 tolerance: f64,
1093 relative: bool,
1094) -> Result<bool, VNextError> {
1095 for (actual, reference) in actual.outputs.iter().zip(&reference.outputs) {
1096 for index in 0..actual.element_count() {
1097 let actual = actual.numeric_value(index)?;
1098 let reference = reference.numeric_value(index)?;
1099 let difference = (actual - reference).abs();
1100 let allowed = if relative {
1101 tolerance * reference.abs()
1102 } else {
1103 tolerance
1104 };
1105 if difference > allowed {
1106 return Ok(false);
1107 }
1108 }
1109 }
1110 Ok(true)
1111}