1use crate::domain::ir::TypeReference;
8use crate::domain::operation::OperationType;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::fmt;
12
13#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
15#[serde(rename_all = "camelCase")]
16pub struct OpticArtifact {
17 pub artifact_id: String,
19 pub artifact_hash: String,
21 pub schema_id: String,
23 pub requirements_digest: String,
25 pub operation: OpticOperation,
27 pub requirements: OpticAdmissionRequirements,
29 pub registration: OpticRegistrationDescriptor,
31}
32
33#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
40#[serde(rename_all = "camelCase")]
41pub struct OpticRegistrationDescriptor {
42 pub artifact_id: String,
44 pub artifact_hash: String,
46 pub schema_id: String,
48 pub operation_id: String,
50 pub requirements_digest: String,
52}
53
54#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
60#[serde(rename_all = "camelCase")]
61pub struct OpticArtifactHandle {
62 pub kind: String,
64 pub id: String,
66}
67
68#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
70#[serde(rename_all = "camelCase")]
71pub struct OpticAdmissionRequirements {
72 pub identity: IdentityRequirement,
74 pub required_permissions: Vec<PermissionRequirement>,
76 pub forbidden_resources: Vec<String>,
78}
79
80#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
87#[serde(rename_all = "camelCase")]
88pub struct CapabilityGrant {
89 pub grant_id: String,
91 pub subject: PrincipalRef,
93 pub artifact_hash: String,
95 pub operation_id: String,
97 pub requirements_digest: String,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub allowed_basis: Option<BasisConstraint>,
102 pub allowed_apertures: Vec<ApertureConstraint>,
104 pub budget: BudgetConstraint,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub expires_at: Option<String>,
109 pub rights: Vec<String>,
111 pub issuer: PrincipalRef,
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub issuer_signature: Option<String>,
116 #[serde(skip_serializing_if = "Option::is_none")]
118 pub delegation_chain_digest: Option<String>,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub observer_class: Option<ObserverClass>,
122 pub non_transferable: bool,
124}
125
126#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
128#[serde(rename_all = "camelCase")]
129pub struct CapabilityPresentation {
130 pub grant_id: String,
132 pub subject: PrincipalRef,
134 pub artifact_handle_id: String,
136 pub operation_id: String,
138 pub variables_digest: String,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub basis_request_digest: Option<String>,
143 pub nonce: String,
145 pub presented_at: String,
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub proof_digest: Option<String>,
150}
151
152#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
154#[serde(rename_all = "camelCase")]
155pub struct AdmissionTicket {
156 pub ticket_id: String,
158 pub artifact_handle: OpticArtifactHandle,
160 pub capability_grant_id: String,
162 pub operation_id: String,
164 pub invocation_digest: String,
166 pub issued_at: String,
168 #[serde(skip_serializing_if = "Option::is_none")]
170 pub expires_at: Option<String>,
171}
172
173#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
175#[serde(rename_all = "camelCase")]
176pub struct BasisConstraint {
177 #[serde(skip_serializing_if = "Option::is_none")]
179 pub basis_ref: Option<String>,
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub max_staleness_ms: Option<u64>,
183}
184
185#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
187#[serde(rename_all = "camelCase")]
188pub struct ApertureConstraint {
189 pub kind: String,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub limit: Option<u64>,
194}
195
196#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
198#[serde(rename_all = "camelCase")]
199pub struct BudgetConstraint {
200 #[serde(skip_serializing_if = "Option::is_none")]
202 pub max_operations: Option<u64>,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub max_bytes: Option<u64>,
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub max_millis: Option<u64>,
209}
210
211#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
213#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
214pub enum ObserverClass {
215 Oc0,
217 Oc1,
219 Oc2,
221 Oc3,
223}
224
225#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
227#[serde(rename_all = "camelCase")]
228pub struct PermissionGrant {
229 pub action: PermissionAction,
231 pub resource: String,
233 pub source: String,
235}
236
237pub type OpticArtifactRef = OpticRegistrationDescriptor;
239
240pub trait OpticArtifactResolver {
242 fn resolve_optic_artifact(
245 &self,
246 registration: &OpticRegistrationDescriptor,
247 ) -> Result<OpticArtifact, ResolveError>;
248}
249
250#[derive(Debug, Default, Clone)]
252pub struct InMemoryOpticArtifactRegistry {
253 artifacts: BTreeMap<String, OpticArtifact>,
254}
255
256impl InMemoryOpticArtifactRegistry {
257 pub fn new() -> Self {
259 Self::default()
260 }
261
262 pub fn insert(&mut self, mut artifact: OpticArtifact) -> OpticRegistrationDescriptor {
264 let registration = registration_descriptor_for_artifact(&artifact);
265 artifact.registration = registration.clone();
266 self.artifacts
267 .insert(artifact.artifact_id.clone(), artifact);
268 registration
269 }
270
271 pub fn len(&self) -> usize {
273 self.artifacts.len()
274 }
275
276 pub fn is_empty(&self) -> bool {
278 self.artifacts.is_empty()
279 }
280}
281
282fn registration_descriptor_for_artifact(artifact: &OpticArtifact) -> OpticRegistrationDescriptor {
283 OpticRegistrationDescriptor {
284 artifact_id: artifact.artifact_id.clone(),
285 artifact_hash: artifact.artifact_hash.clone(),
286 schema_id: artifact.schema_id.clone(),
287 operation_id: artifact.operation.operation_id.clone(),
288 requirements_digest: artifact.requirements_digest.clone(),
289 }
290}
291
292impl OpticArtifactResolver for InMemoryOpticArtifactRegistry {
293 fn resolve_optic_artifact(
294 &self,
295 registration: &OpticRegistrationDescriptor,
296 ) -> Result<OpticArtifact, ResolveError> {
297 let artifact = self
298 .artifacts
299 .get(®istration.artifact_id)
300 .ok_or_else(|| ResolveError::ArtifactNotFound {
301 artifact_id: registration.artifact_id.clone(),
302 })?;
303 verify_registration_matches_artifact(registration, artifact)?;
304 Ok(artifact.clone())
305 }
306}
307
308fn verify_registration_matches_artifact(
309 registration: &OpticRegistrationDescriptor,
310 artifact: &OpticArtifact,
311) -> Result<(), ResolveError> {
312 if registration.artifact_hash != artifact.artifact_hash {
313 return Err(ResolveError::ArtifactHashMismatch {
314 expected: artifact.artifact_hash.clone(),
315 actual: registration.artifact_hash.clone(),
316 });
317 }
318
319 if registration.schema_id != artifact.schema_id {
320 return Err(ResolveError::SchemaIdMismatch {
321 expected: artifact.schema_id.clone(),
322 actual: registration.schema_id.clone(),
323 });
324 }
325
326 if registration.operation_id != artifact.operation.operation_id {
327 return Err(ResolveError::OperationIdMismatch {
328 expected: artifact.operation.operation_id.clone(),
329 actual: registration.operation_id.clone(),
330 });
331 }
332
333 if registration.requirements_digest != artifact.requirements_digest {
334 return Err(ResolveError::RequirementsDigestMismatch {
335 expected: artifact.requirements_digest.clone(),
336 actual: registration.requirements_digest.clone(),
337 });
338 }
339
340 Ok(())
341}
342
343#[derive(Debug, Clone, PartialEq, Eq)]
345pub enum ResolveError {
346 ArtifactNotFound {
348 artifact_id: String,
350 },
351 ArtifactHashMismatch {
353 expected: String,
355 actual: String,
357 },
358 SchemaIdMismatch {
360 expected: String,
362 actual: String,
364 },
365 OperationIdMismatch {
367 expected: String,
369 actual: String,
371 },
372 RequirementsDigestMismatch {
374 expected: String,
376 actual: String,
378 },
379}
380
381impl fmt::Display for ResolveError {
382 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
383 match self {
384 ResolveError::ArtifactNotFound { artifact_id } => {
385 write!(formatter, "optic artifact '{artifact_id}' was not found")
386 }
387 ResolveError::ArtifactHashMismatch { expected, actual } => write!(
388 formatter,
389 "optic artifact hash mismatch: expected '{expected}', got '{actual}'"
390 ),
391 ResolveError::SchemaIdMismatch { expected, actual } => write!(
392 formatter,
393 "optic schema id mismatch: expected '{expected}', got '{actual}'"
394 ),
395 ResolveError::OperationIdMismatch { expected, actual } => write!(
396 formatter,
397 "optic operation id mismatch: expected '{expected}', got '{actual}'"
398 ),
399 ResolveError::RequirementsDigestMismatch { expected, actual } => write!(
400 formatter,
401 "optic requirements digest mismatch: expected '{expected}', got '{actual}'"
402 ),
403 }
404 }
405}
406
407impl std::error::Error for ResolveError {}
408
409#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
411#[serde(rename_all = "camelCase")]
412pub struct IdentityRequirement {
413 pub required: bool,
415 pub accepted_principal_kinds: Vec<String>,
417}
418
419#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
421#[serde(rename_all = "camelCase")]
422pub struct PrincipalRef {
423 pub kind: String,
425 pub id: String,
427}
428
429#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
431#[serde(rename_all = "camelCase")]
432pub struct PermissionRequirement {
433 pub action: PermissionAction,
435 pub resource: String,
437 pub source: String,
439}
440
441#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
443#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
444pub enum PermissionAction {
445 Read,
447 Write,
449}
450
451#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
453#[serde(rename_all = "camelCase")]
454pub struct OpticOperation {
455 pub operation_id: String,
457 #[serde(skip_serializing_if = "Option::is_none")]
459 pub name: Option<String>,
460 pub kind: OperationKind,
462 pub root_field: String,
464 pub root_arguments: Vec<RootArgumentBinding>,
466 #[serde(default, skip_serializing_if = "Vec::is_empty")]
468 pub selection_arguments: Vec<SelectionArgumentBinding>,
469 pub variable_shape: CodecShape,
471 pub payload_shape: CodecShape,
473 pub directives: Vec<DirectiveRecord>,
475 #[serde(skip_serializing_if = "Option::is_none")]
477 pub declared_footprint: Option<Footprint>,
478 pub law_claims: Vec<LawClaimTemplate>,
480}
481
482#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
484#[serde(rename_all = "camelCase")]
485pub struct RootArgumentBinding {
486 pub name: String,
488 pub type_ref: TypeReference,
490 pub value_canonical_json: String,
492}
493
494#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
496#[serde(rename_all = "camelCase")]
497pub struct SelectionArgumentBinding {
498 pub path: String,
500 pub name: String,
502 pub type_ref: TypeReference,
504 pub value_canonical_json: String,
506}
507
508#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
510#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
511pub enum OperationKind {
512 Query,
514 Mutation,
516 Subscription,
518}
519
520impl From<OperationType> for OperationKind {
521 fn from(value: OperationType) -> Self {
522 match value {
523 OperationType::Query => OperationKind::Query,
524 OperationType::Mutation => OperationKind::Mutation,
525 OperationType::Subscription => OperationKind::Subscription,
526 }
527 }
528}
529
530impl From<OperationKind> for OperationType {
531 fn from(value: OperationKind) -> Self {
532 match value {
533 OperationKind::Query => OperationType::Query,
534 OperationKind::Mutation => OperationType::Mutation,
535 OperationKind::Subscription => OperationType::Subscription,
536 }
537 }
538}
539
540#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
542#[serde(rename_all = "camelCase")]
543pub struct CodecShape {
544 pub type_name: String,
546 pub fields: Vec<CodecField>,
548}
549
550#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
552#[serde(rename_all = "camelCase")]
553pub struct CodecField {
554 pub name: String,
556 pub type_ref: TypeReference,
558 pub required: bool,
560 pub list: bool,
562}
563
564#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
566#[serde(rename_all = "camelCase")]
567pub struct DirectiveRecord {
568 pub coordinate: String,
570 pub name: String,
572 pub arguments_canonical_json: String,
574}
575
576#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
578#[serde(rename_all = "camelCase")]
579pub struct Footprint {
580 pub reads: Vec<String>,
582 pub writes: Vec<String>,
584 pub forbids: Vec<String>,
586}
587
588#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
590#[serde(rename_all = "camelCase")]
591pub struct LawClaimTemplate {
592 pub law_id: String,
594 pub claim_id: String,
596 pub operation_id: String,
598 pub required_evidence: Vec<EvidenceKind>,
600}
601
602#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
604#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
605pub enum EvidenceKind {
606 Compiler,
608 Codec,
610 HostPolicy,
612 RuntimeTrace,
614 DomainVerifier,
616}
617
618#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
620#[serde(rename_all = "camelCase")]
621pub struct LawWitness {
622 pub law_id: String,
624 pub claim_id: String,
626 #[serde(skip_serializing_if = "Option::is_none")]
628 pub basis_ref: Option<String>,
629 pub checker_id: String,
631 #[serde(skip_serializing_if = "Option::is_none")]
633 pub checker_artifact_hash: Option<String>,
634 pub verdict: LawVerdict,
636 pub evidence_digests: Vec<String>,
638 #[serde(skip_serializing_if = "Option::is_none")]
640 pub runtime_trace_digest: Option<String>,
641 #[serde(skip_serializing_if = "Option::is_none")]
643 pub obstruction_reason: Option<String>,
644 pub replay_hints: Vec<ReplayHint>,
646}
647
648#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
650#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
651pub enum LawVerdict {
652 Satisfied,
654 Obstructed,
656 Unknown,
658}
659
660#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
662#[serde(rename_all = "camelCase")]
663pub struct ReplayHint {
664 pub kind: String,
666 pub value: String,
668}