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_REQUIREMENTS_ARTIFACT_CODEC: &str = "wesley.requirements.canonical-json.v0";
15
16#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
18#[serde(rename_all = "camelCase")]
19pub struct OpticArtifact {
20 pub artifact_id: String,
22 pub artifact_hash: String,
24 pub schema_id: String,
26 pub requirements_digest: String,
28 pub requirements_artifact: OpticRequirementsArtifact,
30 pub operation: OpticOperation,
32 pub requirements: OpticRequirements,
34 pub registration: OpticRegistrationDescriptor,
36}
37
38#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
44#[serde(rename_all = "camelCase")]
45pub struct OpticRegistrationDescriptor {
46 pub artifact_id: String,
48 pub artifact_hash: String,
50 pub schema_id: String,
52 pub operation_id: String,
54 pub requirements_digest: String,
56}
57
58#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
60#[serde(rename_all = "camelCase")]
61pub struct OpticRequirements {
62 pub identity: IdentityRequirement,
64 pub required_permissions: Vec<PermissionRequirement>,
66 pub forbidden_resources: Vec<String>,
68}
69
70#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
76#[serde(rename_all = "camelCase")]
77pub struct OpticRequirementsArtifact {
78 pub digest: String,
80 pub codec: String,
82 pub bytes: Vec<u8>,
84}
85
86pub type OpticArtifactRef = OpticRegistrationDescriptor;
88
89pub trait OpticArtifactResolver {
91 fn resolve_optic_artifact(
94 &self,
95 registration: &OpticRegistrationDescriptor,
96 ) -> Result<OpticArtifact, ResolveError>;
97}
98
99#[derive(Debug, Default, Clone)]
101pub struct InMemoryOpticArtifactRegistry {
102 artifacts: BTreeMap<String, OpticArtifact>,
103}
104
105impl InMemoryOpticArtifactRegistry {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn insert(&mut self, mut artifact: OpticArtifact) -> OpticRegistrationDescriptor {
113 let registration = registration_descriptor_for_artifact(&artifact);
114 artifact.registration = registration.clone();
115 self.artifacts
116 .insert(artifact.artifact_id.clone(), artifact);
117 registration
118 }
119
120 pub fn len(&self) -> usize {
122 self.artifacts.len()
123 }
124
125 pub fn is_empty(&self) -> bool {
127 self.artifacts.is_empty()
128 }
129}
130
131fn registration_descriptor_for_artifact(artifact: &OpticArtifact) -> OpticRegistrationDescriptor {
132 OpticRegistrationDescriptor {
133 artifact_id: artifact.artifact_id.clone(),
134 artifact_hash: artifact.artifact_hash.clone(),
135 schema_id: artifact.schema_id.clone(),
136 operation_id: artifact.operation.operation_id.clone(),
137 requirements_digest: artifact.requirements_digest.clone(),
138 }
139}
140
141impl OpticArtifactResolver for InMemoryOpticArtifactRegistry {
142 fn resolve_optic_artifact(
143 &self,
144 registration: &OpticRegistrationDescriptor,
145 ) -> Result<OpticArtifact, ResolveError> {
146 let artifact = self
147 .artifacts
148 .get(®istration.artifact_id)
149 .ok_or_else(|| ResolveError::ArtifactNotFound {
150 artifact_id: registration.artifact_id.clone(),
151 })?;
152 verify_registration_matches_artifact(registration, artifact)?;
153 Ok(artifact.clone())
154 }
155}
156
157fn verify_registration_matches_artifact(
158 registration: &OpticRegistrationDescriptor,
159 artifact: &OpticArtifact,
160) -> Result<(), ResolveError> {
161 if registration.artifact_hash != artifact.artifact_hash {
162 return Err(ResolveError::ArtifactHashMismatch {
163 expected: artifact.artifact_hash.clone(),
164 actual: registration.artifact_hash.clone(),
165 });
166 }
167
168 if registration.schema_id != artifact.schema_id {
169 return Err(ResolveError::SchemaIdMismatch {
170 expected: artifact.schema_id.clone(),
171 actual: registration.schema_id.clone(),
172 });
173 }
174
175 if registration.operation_id != artifact.operation.operation_id {
176 return Err(ResolveError::OperationIdMismatch {
177 expected: artifact.operation.operation_id.clone(),
178 actual: registration.operation_id.clone(),
179 });
180 }
181
182 if registration.requirements_digest != artifact.requirements_digest {
183 return Err(ResolveError::RequirementsDigestMismatch {
184 expected: artifact.requirements_digest.clone(),
185 actual: registration.requirements_digest.clone(),
186 });
187 }
188
189 Ok(())
190}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum ResolveError {
195 ArtifactNotFound {
197 artifact_id: String,
199 },
200 ArtifactHashMismatch {
202 expected: String,
204 actual: String,
206 },
207 SchemaIdMismatch {
209 expected: String,
211 actual: String,
213 },
214 OperationIdMismatch {
216 expected: String,
218 actual: String,
220 },
221 RequirementsDigestMismatch {
223 expected: String,
225 actual: String,
227 },
228}
229
230impl fmt::Display for ResolveError {
231 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
232 match self {
233 ResolveError::ArtifactNotFound { artifact_id } => {
234 write!(formatter, "optic artifact '{artifact_id}' was not found")
235 }
236 ResolveError::ArtifactHashMismatch { expected, actual } => write!(
237 formatter,
238 "optic artifact hash mismatch: expected '{expected}', got '{actual}'"
239 ),
240 ResolveError::SchemaIdMismatch { expected, actual } => write!(
241 formatter,
242 "optic schema id mismatch: expected '{expected}', got '{actual}'"
243 ),
244 ResolveError::OperationIdMismatch { expected, actual } => write!(
245 formatter,
246 "optic operation id mismatch: expected '{expected}', got '{actual}'"
247 ),
248 ResolveError::RequirementsDigestMismatch { expected, actual } => write!(
249 formatter,
250 "optic requirements digest mismatch: expected '{expected}', got '{actual}'"
251 ),
252 }
253 }
254}
255
256impl std::error::Error for ResolveError {}
257
258#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
260#[serde(rename_all = "camelCase")]
261pub struct IdentityRequirement {
262 pub required: bool,
264 pub accepted_principal_kinds: Vec<String>,
266}
267
268#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
270#[serde(rename_all = "camelCase")]
271pub struct PermissionRequirement {
272 pub action: PermissionAction,
274 pub resource: String,
276 pub source: String,
278}
279
280#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
282#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
283pub enum PermissionAction {
284 Read,
286 Write,
288}
289
290#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
292#[serde(rename_all = "camelCase")]
293pub struct OpticOperation {
294 pub operation_id: String,
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub name: Option<String>,
299 pub kind: OperationKind,
301 pub root_field: String,
303 pub root_arguments: Vec<RootArgumentBinding>,
305 #[serde(default, skip_serializing_if = "Vec::is_empty")]
307 pub selection_arguments: Vec<SelectionArgumentBinding>,
308 pub variable_shape: CodecShape,
310 pub payload_shape: CodecShape,
312 pub directives: Vec<DirectiveRecord>,
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub declared_footprint: Option<Footprint>,
317 pub law_claims: Vec<LawClaimTemplate>,
319}
320
321#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
323#[serde(rename_all = "camelCase")]
324pub struct RootArgumentBinding {
325 pub name: String,
327 pub type_ref: TypeReference,
329 pub value_canonical_json: String,
331}
332
333#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
335#[serde(rename_all = "camelCase")]
336pub struct SelectionArgumentBinding {
337 pub path: String,
339 pub name: String,
341 pub type_ref: TypeReference,
343 pub value_canonical_json: String,
345}
346
347#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
349#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
350pub enum OperationKind {
351 Query,
353 Mutation,
355 Subscription,
357}
358
359impl From<OperationType> for OperationKind {
360 fn from(value: OperationType) -> Self {
361 match value {
362 OperationType::Query => OperationKind::Query,
363 OperationType::Mutation => OperationKind::Mutation,
364 OperationType::Subscription => OperationKind::Subscription,
365 }
366 }
367}
368
369impl From<OperationKind> for OperationType {
370 fn from(value: OperationKind) -> Self {
371 match value {
372 OperationKind::Query => OperationType::Query,
373 OperationKind::Mutation => OperationType::Mutation,
374 OperationKind::Subscription => OperationType::Subscription,
375 }
376 }
377}
378
379#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
381#[serde(rename_all = "camelCase")]
382pub struct CodecShape {
383 pub type_name: String,
385 pub fields: Vec<CodecField>,
387}
388
389#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
391#[serde(rename_all = "camelCase")]
392pub struct CodecField {
393 pub name: String,
395 pub type_ref: TypeReference,
397 pub required: bool,
399 pub list: bool,
401}
402
403#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
405#[serde(rename_all = "camelCase")]
406pub struct DirectiveRecord {
407 pub coordinate: String,
409 pub name: String,
411 pub arguments_canonical_json: String,
413}
414
415#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
417#[serde(rename_all = "camelCase")]
418pub struct Footprint {
419 pub reads: Vec<String>,
421 pub writes: Vec<String>,
423 pub forbids: Vec<String>,
425}
426
427#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
429#[serde(rename_all = "camelCase")]
430pub struct LawClaimTemplate {
431 pub law_id: String,
433 pub claim_id: String,
435 pub operation_id: String,
437 pub required_evidence: Vec<EvidenceKind>,
439}
440
441#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
443#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
444pub enum EvidenceKind {
445 Compiler,
447 Codec,
449 HostPolicy,
451 RuntimeTrace,
453 DomainVerifier,
455}
456
457#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
459#[serde(rename_all = "camelCase")]
460pub struct LawWitness {
461 pub law_id: String,
463 pub claim_id: String,
465 #[serde(skip_serializing_if = "Option::is_none")]
467 pub basis_ref: Option<String>,
468 pub checker_id: String,
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub checker_artifact_hash: Option<String>,
473 pub verdict: LawVerdict,
475 pub evidence_digests: Vec<String>,
477 #[serde(skip_serializing_if = "Option::is_none")]
479 pub runtime_trace_digest: Option<String>,
480 #[serde(skip_serializing_if = "Option::is_none")]
482 pub obstruction_reason: Option<String>,
483 pub replay_hints: Vec<ReplayHint>,
485}
486
487#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
489#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
490pub enum LawVerdict {
491 Satisfied,
493 Obstructed,
495 Unknown,
497}
498
499#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
501#[serde(rename_all = "camelCase")]
502pub struct ReplayHint {
503 pub kind: String,
505 pub value: String,
507}