1use crate::domain::ir::TypeReference;
8use crate::domain::operation::OperationType;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::fmt;
12
13pub const OPTIC_ADMISSION_REQUIREMENTS_ARTIFACT_CODEC: &str =
15 "wesley.requirements.canonical-json.v0";
16
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
19#[serde(rename_all = "camelCase")]
20pub struct OpticArtifact {
21 pub artifact_id: String,
23 pub artifact_hash: String,
25 pub schema_id: String,
27 pub requirements_digest: String,
29 pub requirements_artifact: OpticAdmissionRequirementsArtifact,
31 pub operation: OpticOperation,
33 pub requirements: OpticAdmissionRequirements,
35 pub registration: OpticRegistrationDescriptor,
37}
38
39#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
46#[serde(rename_all = "camelCase")]
47pub struct OpticRegistrationDescriptor {
48 pub artifact_id: String,
50 pub artifact_hash: String,
52 pub schema_id: String,
54 pub operation_id: String,
56 pub requirements_digest: String,
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
66#[serde(rename_all = "camelCase")]
67pub struct OpticArtifactHandle {
68 pub kind: String,
70 pub id: String,
72}
73
74#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
76#[serde(rename_all = "camelCase")]
77pub struct OpticAdmissionRequirements {
78 pub identity: IdentityRequirement,
80 pub required_permissions: Vec<PermissionRequirement>,
82 pub forbidden_resources: Vec<String>,
84}
85
86#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
92#[serde(rename_all = "camelCase")]
93pub struct OpticAdmissionRequirementsArtifact {
94 pub digest: String,
96 pub codec: String,
98 pub bytes: Vec<u8>,
100}
101
102#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
109#[serde(rename_all = "camelCase")]
110pub struct CapabilityGrant {
111 pub grant_id: String,
113 pub subject: PrincipalRef,
115 pub artifact_hash: String,
117 pub operation_id: String,
119 pub requirements_digest: String,
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub allowed_basis: Option<BasisConstraint>,
124 pub allowed_apertures: Vec<ApertureConstraint>,
126 pub budget: BudgetConstraint,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub expires_at: Option<String>,
131 pub rights: Vec<String>,
133 pub issuer: PrincipalRef,
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub issuer_signature: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub delegation_chain_digest: Option<String>,
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub observer_class: Option<ObserverClass>,
144 pub non_transferable: bool,
146}
147
148#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
150#[serde(rename_all = "camelCase")]
151pub struct CapabilityPresentation {
152 pub grant_id: String,
154 pub subject: PrincipalRef,
156 pub artifact_handle_id: String,
158 pub operation_id: String,
160 pub variables_digest: String,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub basis_request_digest: Option<String>,
165 pub nonce: String,
167 pub presented_at: String,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub proof_digest: Option<String>,
172}
173
174#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
176#[serde(rename_all = "camelCase")]
177pub struct AdmissionTicket {
178 pub ticket_id: String,
180 pub artifact_handle: OpticArtifactHandle,
182 pub capability_grant_id: String,
184 pub operation_id: String,
186 pub invocation_digest: String,
188 pub issued_at: String,
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub expires_at: Option<String>,
193}
194
195#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
197#[serde(rename_all = "camelCase")]
198pub struct BasisConstraint {
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub basis_ref: Option<String>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub max_staleness_ms: Option<u64>,
205}
206
207#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
209#[serde(rename_all = "camelCase")]
210pub struct ApertureConstraint {
211 pub kind: String,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub limit: Option<u64>,
216}
217
218#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
220#[serde(rename_all = "camelCase")]
221pub struct BudgetConstraint {
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub max_operations: Option<u64>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub max_bytes: Option<u64>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub max_millis: Option<u64>,
231}
232
233#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
235#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
236pub enum ObserverClass {
237 Oc0,
239 Oc1,
241 Oc2,
243 Oc3,
245}
246
247#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
249#[serde(rename_all = "camelCase")]
250pub struct PermissionGrant {
251 pub action: PermissionAction,
253 pub resource: String,
255 pub source: String,
257}
258
259pub type OpticArtifactRef = OpticRegistrationDescriptor;
261
262pub trait OpticArtifactResolver {
264 fn resolve_optic_artifact(
267 &self,
268 registration: &OpticRegistrationDescriptor,
269 ) -> Result<OpticArtifact, ResolveError>;
270}
271
272#[derive(Debug, Default, Clone)]
274pub struct InMemoryOpticArtifactRegistry {
275 artifacts: BTreeMap<String, OpticArtifact>,
276}
277
278impl InMemoryOpticArtifactRegistry {
279 pub fn new() -> Self {
281 Self::default()
282 }
283
284 pub fn insert(&mut self, mut artifact: OpticArtifact) -> OpticRegistrationDescriptor {
286 let registration = registration_descriptor_for_artifact(&artifact);
287 artifact.registration = registration.clone();
288 self.artifacts
289 .insert(artifact.artifact_id.clone(), artifact);
290 registration
291 }
292
293 pub fn len(&self) -> usize {
295 self.artifacts.len()
296 }
297
298 pub fn is_empty(&self) -> bool {
300 self.artifacts.is_empty()
301 }
302}
303
304fn registration_descriptor_for_artifact(artifact: &OpticArtifact) -> OpticRegistrationDescriptor {
305 OpticRegistrationDescriptor {
306 artifact_id: artifact.artifact_id.clone(),
307 artifact_hash: artifact.artifact_hash.clone(),
308 schema_id: artifact.schema_id.clone(),
309 operation_id: artifact.operation.operation_id.clone(),
310 requirements_digest: artifact.requirements_digest.clone(),
311 }
312}
313
314impl OpticArtifactResolver for InMemoryOpticArtifactRegistry {
315 fn resolve_optic_artifact(
316 &self,
317 registration: &OpticRegistrationDescriptor,
318 ) -> Result<OpticArtifact, ResolveError> {
319 let artifact = self
320 .artifacts
321 .get(®istration.artifact_id)
322 .ok_or_else(|| ResolveError::ArtifactNotFound {
323 artifact_id: registration.artifact_id.clone(),
324 })?;
325 verify_registration_matches_artifact(registration, artifact)?;
326 Ok(artifact.clone())
327 }
328}
329
330fn verify_registration_matches_artifact(
331 registration: &OpticRegistrationDescriptor,
332 artifact: &OpticArtifact,
333) -> Result<(), ResolveError> {
334 if registration.artifact_hash != artifact.artifact_hash {
335 return Err(ResolveError::ArtifactHashMismatch {
336 expected: artifact.artifact_hash.clone(),
337 actual: registration.artifact_hash.clone(),
338 });
339 }
340
341 if registration.schema_id != artifact.schema_id {
342 return Err(ResolveError::SchemaIdMismatch {
343 expected: artifact.schema_id.clone(),
344 actual: registration.schema_id.clone(),
345 });
346 }
347
348 if registration.operation_id != artifact.operation.operation_id {
349 return Err(ResolveError::OperationIdMismatch {
350 expected: artifact.operation.operation_id.clone(),
351 actual: registration.operation_id.clone(),
352 });
353 }
354
355 if registration.requirements_digest != artifact.requirements_digest {
356 return Err(ResolveError::RequirementsDigestMismatch {
357 expected: artifact.requirements_digest.clone(),
358 actual: registration.requirements_digest.clone(),
359 });
360 }
361
362 Ok(())
363}
364
365#[derive(Debug, Clone, PartialEq, Eq)]
367pub enum ResolveError {
368 ArtifactNotFound {
370 artifact_id: String,
372 },
373 ArtifactHashMismatch {
375 expected: String,
377 actual: String,
379 },
380 SchemaIdMismatch {
382 expected: String,
384 actual: String,
386 },
387 OperationIdMismatch {
389 expected: String,
391 actual: String,
393 },
394 RequirementsDigestMismatch {
396 expected: String,
398 actual: String,
400 },
401}
402
403impl fmt::Display for ResolveError {
404 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
405 match self {
406 ResolveError::ArtifactNotFound { artifact_id } => {
407 write!(formatter, "optic artifact '{artifact_id}' was not found")
408 }
409 ResolveError::ArtifactHashMismatch { expected, actual } => write!(
410 formatter,
411 "optic artifact hash mismatch: expected '{expected}', got '{actual}'"
412 ),
413 ResolveError::SchemaIdMismatch { expected, actual } => write!(
414 formatter,
415 "optic schema id mismatch: expected '{expected}', got '{actual}'"
416 ),
417 ResolveError::OperationIdMismatch { expected, actual } => write!(
418 formatter,
419 "optic operation id mismatch: expected '{expected}', got '{actual}'"
420 ),
421 ResolveError::RequirementsDigestMismatch { expected, actual } => write!(
422 formatter,
423 "optic requirements digest mismatch: expected '{expected}', got '{actual}'"
424 ),
425 }
426 }
427}
428
429impl std::error::Error for ResolveError {}
430
431#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
433#[serde(rename_all = "camelCase")]
434pub struct IdentityRequirement {
435 pub required: bool,
437 pub accepted_principal_kinds: Vec<String>,
439}
440
441#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
443#[serde(rename_all = "camelCase")]
444pub struct PrincipalRef {
445 pub kind: String,
447 pub id: String,
449}
450
451#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
453#[serde(rename_all = "camelCase")]
454pub struct PermissionRequirement {
455 pub action: PermissionAction,
457 pub resource: String,
459 pub source: String,
461}
462
463#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
465#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
466pub enum PermissionAction {
467 Read,
469 Write,
471}
472
473#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
475#[serde(rename_all = "camelCase")]
476pub struct OpticOperation {
477 pub operation_id: String,
479 #[serde(skip_serializing_if = "Option::is_none")]
481 pub name: Option<String>,
482 pub kind: OperationKind,
484 pub root_field: String,
486 pub root_arguments: Vec<RootArgumentBinding>,
488 #[serde(default, skip_serializing_if = "Vec::is_empty")]
490 pub selection_arguments: Vec<SelectionArgumentBinding>,
491 pub variable_shape: CodecShape,
493 pub payload_shape: CodecShape,
495 pub directives: Vec<DirectiveRecord>,
497 #[serde(skip_serializing_if = "Option::is_none")]
499 pub declared_footprint: Option<Footprint>,
500 pub law_claims: Vec<LawClaimTemplate>,
502}
503
504#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
506#[serde(rename_all = "camelCase")]
507pub struct RootArgumentBinding {
508 pub name: String,
510 pub type_ref: TypeReference,
512 pub value_canonical_json: String,
514}
515
516#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
518#[serde(rename_all = "camelCase")]
519pub struct SelectionArgumentBinding {
520 pub path: String,
522 pub name: String,
524 pub type_ref: TypeReference,
526 pub value_canonical_json: String,
528}
529
530#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
532#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
533pub enum OperationKind {
534 Query,
536 Mutation,
538 Subscription,
540}
541
542impl From<OperationType> for OperationKind {
543 fn from(value: OperationType) -> Self {
544 match value {
545 OperationType::Query => OperationKind::Query,
546 OperationType::Mutation => OperationKind::Mutation,
547 OperationType::Subscription => OperationKind::Subscription,
548 }
549 }
550}
551
552impl From<OperationKind> for OperationType {
553 fn from(value: OperationKind) -> Self {
554 match value {
555 OperationKind::Query => OperationType::Query,
556 OperationKind::Mutation => OperationType::Mutation,
557 OperationKind::Subscription => OperationType::Subscription,
558 }
559 }
560}
561
562#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
564#[serde(rename_all = "camelCase")]
565pub struct CodecShape {
566 pub type_name: String,
568 pub fields: Vec<CodecField>,
570}
571
572#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
574#[serde(rename_all = "camelCase")]
575pub struct CodecField {
576 pub name: String,
578 pub type_ref: TypeReference,
580 pub required: bool,
582 pub list: bool,
584}
585
586#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
588#[serde(rename_all = "camelCase")]
589pub struct DirectiveRecord {
590 pub coordinate: String,
592 pub name: String,
594 pub arguments_canonical_json: String,
596}
597
598#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
600#[serde(rename_all = "camelCase")]
601pub struct Footprint {
602 pub reads: Vec<String>,
604 pub writes: Vec<String>,
606 pub forbids: Vec<String>,
608}
609
610#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
612#[serde(rename_all = "camelCase")]
613pub struct LawClaimTemplate {
614 pub law_id: String,
616 pub claim_id: String,
618 pub operation_id: String,
620 pub required_evidence: Vec<EvidenceKind>,
622}
623
624#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
626#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
627pub enum EvidenceKind {
628 Compiler,
630 Codec,
632 HostPolicy,
634 RuntimeTrace,
636 DomainVerifier,
638}
639
640#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
642#[serde(rename_all = "camelCase")]
643pub struct LawWitness {
644 pub law_id: String,
646 pub claim_id: String,
648 #[serde(skip_serializing_if = "Option::is_none")]
650 pub basis_ref: Option<String>,
651 pub checker_id: String,
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub checker_artifact_hash: Option<String>,
656 pub verdict: LawVerdict,
658 pub evidence_digests: Vec<String>,
660 #[serde(skip_serializing_if = "Option::is_none")]
662 pub runtime_trace_digest: Option<String>,
663 #[serde(skip_serializing_if = "Option::is_none")]
665 pub obstruction_reason: Option<String>,
666 pub replay_hints: Vec<ReplayHint>,
668}
669
670#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
672#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
673pub enum LawVerdict {
674 Satisfied,
676 Obstructed,
678 Unknown,
680}
681
682#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
684#[serde(rename_all = "camelCase")]
685pub struct ReplayHint {
686 pub kind: String,
688 pub value: String,
690}