1use crate::domain::ir::TypeReference;
8use crate::domain::operation::OperationType;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::fmt;
12
13pub const OPERATION_REQUIREMENTS_ARTIFACT_CODEC: &str = "wesley.requirements.canonical-json.v0";
15
16#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
18#[serde(rename_all = "camelCase")]
19pub struct OperationArtifact {
20 pub artifact_id: String,
22 pub artifact_hash: String,
24 pub schema_id: String,
26 pub requirements_digest: String,
28 pub requirements_artifact: OperationRequirementsArtifact,
30 pub operation: CompiledOperation,
32 pub requirements: OperationRequirements,
34 pub registration: OperationRegistrationDescriptor,
36}
37
38#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
44#[serde(rename_all = "camelCase")]
45pub struct OperationRegistrationDescriptor {
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 OperationRequirements {
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 OperationRequirementsArtifact {
78 pub digest: String,
80 pub codec: String,
82 pub bytes: Vec<u8>,
84}
85
86pub type OperationArtifactRef = OperationRegistrationDescriptor;
88
89pub trait OperationArtifactResolver {
91 fn resolve_operation_artifact(
94 &self,
95 registration: &OperationRegistrationDescriptor,
96 ) -> Result<OperationArtifact, ResolveError>;
97}
98
99#[derive(Debug, Default, Clone)]
101pub struct InMemoryOperationArtifactRegistry {
102 artifacts: BTreeMap<String, OperationArtifact>,
103}
104
105impl InMemoryOperationArtifactRegistry {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn insert(&mut self, mut artifact: OperationArtifact) -> OperationRegistrationDescriptor {
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(
132 artifact: &OperationArtifact,
133) -> OperationRegistrationDescriptor {
134 OperationRegistrationDescriptor {
135 artifact_id: artifact.artifact_id.clone(),
136 artifact_hash: artifact.artifact_hash.clone(),
137 schema_id: artifact.schema_id.clone(),
138 operation_id: artifact.operation.operation_id.clone(),
139 requirements_digest: artifact.requirements_digest.clone(),
140 }
141}
142
143impl OperationArtifactResolver for InMemoryOperationArtifactRegistry {
144 fn resolve_operation_artifact(
145 &self,
146 registration: &OperationRegistrationDescriptor,
147 ) -> Result<OperationArtifact, ResolveError> {
148 let artifact = self
149 .artifacts
150 .get(®istration.artifact_id)
151 .ok_or_else(|| ResolveError::ArtifactNotFound {
152 artifact_id: registration.artifact_id.clone(),
153 })?;
154 verify_registration_matches_artifact(registration, artifact)?;
155 Ok(artifact.clone())
156 }
157}
158
159fn verify_registration_matches_artifact(
160 registration: &OperationRegistrationDescriptor,
161 artifact: &OperationArtifact,
162) -> Result<(), ResolveError> {
163 if registration.artifact_hash != artifact.artifact_hash {
164 return Err(ResolveError::ArtifactHashMismatch {
165 expected: artifact.artifact_hash.clone(),
166 actual: registration.artifact_hash.clone(),
167 });
168 }
169
170 if registration.schema_id != artifact.schema_id {
171 return Err(ResolveError::SchemaIdMismatch {
172 expected: artifact.schema_id.clone(),
173 actual: registration.schema_id.clone(),
174 });
175 }
176
177 if registration.operation_id != artifact.operation.operation_id {
178 return Err(ResolveError::OperationIdMismatch {
179 expected: artifact.operation.operation_id.clone(),
180 actual: registration.operation_id.clone(),
181 });
182 }
183
184 if registration.requirements_digest != artifact.requirements_digest {
185 return Err(ResolveError::RequirementsDigestMismatch {
186 expected: artifact.requirements_digest.clone(),
187 actual: registration.requirements_digest.clone(),
188 });
189 }
190
191 Ok(())
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum ResolveError {
197 ArtifactNotFound {
199 artifact_id: String,
201 },
202 ArtifactHashMismatch {
204 expected: String,
206 actual: String,
208 },
209 SchemaIdMismatch {
211 expected: String,
213 actual: String,
215 },
216 OperationIdMismatch {
218 expected: String,
220 actual: String,
222 },
223 RequirementsDigestMismatch {
225 expected: String,
227 actual: String,
229 },
230}
231
232impl fmt::Display for ResolveError {
233 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
234 match self {
235 ResolveError::ArtifactNotFound { artifact_id } => {
236 write!(
237 formatter,
238 "operation artifact '{artifact_id}' was not found"
239 )
240 }
241 ResolveError::ArtifactHashMismatch { expected, actual } => write!(
242 formatter,
243 "operation artifact hash mismatch: expected '{expected}', got '{actual}'"
244 ),
245 ResolveError::SchemaIdMismatch { expected, actual } => write!(
246 formatter,
247 "operation artifact schema id mismatch: expected '{expected}', got '{actual}'"
248 ),
249 ResolveError::OperationIdMismatch { expected, actual } => write!(
250 formatter,
251 "operation artifact id mismatch: expected '{expected}', got '{actual}'"
252 ),
253 ResolveError::RequirementsDigestMismatch { expected, actual } => write!(
254 formatter,
255 "operation requirements digest mismatch: expected '{expected}', got '{actual}'"
256 ),
257 }
258 }
259}
260
261impl std::error::Error for ResolveError {}
262
263#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
265#[serde(rename_all = "camelCase")]
266pub struct IdentityRequirement {
267 pub required: bool,
269 pub accepted_principal_kinds: Vec<String>,
271}
272
273#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
275#[serde(rename_all = "camelCase")]
276pub struct PermissionRequirement {
277 pub action: PermissionAction,
279 pub resource: String,
281 pub source: String,
283}
284
285#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
287#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
288pub enum PermissionAction {
289 Read,
291 Write,
293}
294
295#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
297#[serde(rename_all = "camelCase")]
298pub struct CompiledOperation {
299 pub operation_id: String,
301 #[serde(skip_serializing_if = "Option::is_none")]
303 pub name: Option<String>,
304 pub kind: OperationKind,
306 pub root_field: String,
308 pub root_arguments: Vec<RootArgumentBinding>,
310 #[serde(default, skip_serializing_if = "Vec::is_empty")]
312 pub selection_arguments: Vec<SelectionArgumentBinding>,
313 pub variable_shape: CodecShape,
315 pub payload_shape: CodecShape,
317 pub directives: Vec<DirectiveRecord>,
319 #[serde(skip_serializing_if = "Option::is_none")]
321 pub declared_footprint: Option<Footprint>,
322 pub law_claims: Vec<LawClaimTemplate>,
324}
325
326#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
328#[serde(rename_all = "camelCase")]
329pub struct RootArgumentBinding {
330 pub name: String,
332 pub type_ref: TypeReference,
334 pub value_canonical_json: String,
336}
337
338#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
340#[serde(rename_all = "camelCase")]
341pub struct SelectionArgumentBinding {
342 pub path: String,
344 pub name: String,
346 pub type_ref: TypeReference,
348 pub value_canonical_json: String,
350}
351
352#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
354#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
355pub enum OperationKind {
356 Query,
358 Mutation,
360 Subscription,
362}
363
364impl From<OperationType> for OperationKind {
365 fn from(value: OperationType) -> Self {
366 match value {
367 OperationType::Query => OperationKind::Query,
368 OperationType::Mutation => OperationKind::Mutation,
369 OperationType::Subscription => OperationKind::Subscription,
370 }
371 }
372}
373
374impl From<OperationKind> for OperationType {
375 fn from(value: OperationKind) -> Self {
376 match value {
377 OperationKind::Query => OperationType::Query,
378 OperationKind::Mutation => OperationType::Mutation,
379 OperationKind::Subscription => OperationType::Subscription,
380 }
381 }
382}
383
384#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
386#[serde(rename_all = "camelCase")]
387pub struct CodecShape {
388 pub type_name: String,
390 pub fields: Vec<CodecField>,
392}
393
394#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
396#[serde(rename_all = "camelCase")]
397pub struct CodecField {
398 pub name: String,
400 pub type_ref: TypeReference,
402 pub required: bool,
404 pub list: bool,
406}
407
408#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
410#[serde(rename_all = "camelCase")]
411pub struct DirectiveRecord {
412 pub coordinate: String,
414 pub name: String,
416 pub arguments_canonical_json: String,
418}
419
420#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
422#[serde(rename_all = "camelCase")]
423pub struct Footprint {
424 pub reads: Vec<String>,
426 pub writes: Vec<String>,
428 pub forbids: Vec<String>,
430}
431
432#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
434#[serde(rename_all = "camelCase")]
435pub struct LawClaimTemplate {
436 pub law_id: String,
438 pub claim_id: String,
440 pub operation_id: String,
442 pub required_evidence: Vec<EvidenceKind>,
444}
445
446#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
448#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
449pub enum EvidenceKind {
450 Compiler,
452 Codec,
454 HostPolicy,
456 RuntimeTrace,
458 DomainVerifier,
460}
461
462#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
464#[serde(rename_all = "camelCase")]
465pub struct LawWitness {
466 pub law_id: String,
468 pub claim_id: String,
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub basis_ref: Option<String>,
473 pub checker_id: String,
475 #[serde(skip_serializing_if = "Option::is_none")]
477 pub checker_artifact_hash: Option<String>,
478 pub verdict: LawVerdict,
480 pub evidence_digests: Vec<String>,
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub runtime_trace_digest: Option<String>,
485 #[serde(skip_serializing_if = "Option::is_none")]
487 pub obstruction_reason: Option<String>,
488 pub replay_hints: Vec<ReplayHint>,
490}
491
492#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
494#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
495pub enum LawVerdict {
496 Satisfied,
498 Obstructed,
500 Unknown,
502}
503
504#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
506#[serde(rename_all = "camelCase")]
507pub struct ReplayHint {
508 pub kind: String,
510 pub value: String,
512}