Skip to main content

wesley_core/domain/
optic.rs

1//! Runtime optic artifact models.
2//!
3//! These types are intentionally domain-empty. They describe the GraphQL
4//! operation shape, declared bounds, and law claims that a host/runtime can
5//! admit, obstruct, witness, or replay.
6
7use crate::domain::ir::TypeReference;
8use crate::domain::operation::OperationType;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::fmt;
12
13/// Canonical JSON codec for Wesley-owned runtime optic admission requirements.
14pub const OPTIC_ADMISSION_REQUIREMENTS_ARTIFACT_CODEC: &str =
15    "wesley.requirements.canonical-json.v0";
16
17/// Compiled contract for one runtime-declared GraphQL optic operation.
18#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
19#[serde(rename_all = "camelCase")]
20pub struct OpticArtifact {
21    /// Stable artifact identity derived from the schema and operation identity.
22    pub artifact_id: String,
23    /// Stable content hash for the full compiled artifact.
24    pub artifact_hash: String,
25    /// Stable schema identity derived from the lowered Wesley IR.
26    pub schema_id: String,
27    /// Stable digest of admission requirements and law claim templates.
28    pub requirements_digest: String,
29    /// Wesley-owned canonical byte artifact for admission requirements.
30    pub requirements_artifact: OpticAdmissionRequirementsArtifact,
31    /// The selected GraphQL operation compiled into an inspectable contract.
32    pub operation: OpticOperation,
33    /// Admission requirements Echo or another runtime must enforce.
34    pub requirements: OpticAdmissionRequirements,
35    /// Descriptor an application can present when registering this artifact.
36    pub registration: OpticRegistrationDescriptor,
37}
38
39/// Wesley-produced descriptor for registering a compiled optic artifact.
40///
41/// This is not a runtime handle and it is not an authority grant. It is the
42/// small descriptor an application can send to Echo alongside the full artifact
43/// so Echo can verify the exact artifact hash and requirements digest it is
44/// accepting into its registry.
45#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
46#[serde(rename_all = "camelCase")]
47pub struct OpticRegistrationDescriptor {
48    /// Stable artifact identity this descriptor refers to.
49    pub artifact_id: String,
50    /// Stable content hash for the full compiled artifact.
51    pub artifact_hash: String,
52    /// Stable schema identity for the referenced artifact.
53    pub schema_id: String,
54    /// Stable operation identity for the referenced artifact.
55    pub operation_id: String,
56    /// Stable digest of admission requirements and law claim templates.
57    pub requirements_digest: String,
58}
59
60/// Echo-owned opaque handle for a registered optic artifact.
61///
62/// Wesley defines the wire shape so callers can name it, but Wesley does not
63/// issue this handle. Echo or another runtime returns it after accepting and
64/// storing a specific artifact hash.
65#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
66#[serde(rename_all = "camelCase")]
67pub struct OpticArtifactHandle {
68    /// Runtime-owned discriminator, normally `optic-artifact-handle`.
69    pub kind: String,
70    /// Runtime-local opaque handle identifier.
71    pub id: String,
72}
73
74/// Admission requirements for an optic artifact.
75#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
76#[serde(rename_all = "camelCase")]
77pub struct OpticAdmissionRequirements {
78    /// Identity binding required before a host/runtime admits the handle.
79    pub identity: IdentityRequirement,
80    /// Permission requirements inferred from the declared optic bounds.
81    pub required_permissions: Vec<PermissionRequirement>,
82    /// Resource labels that must remain inaccessible to the operation.
83    pub forbidden_resources: Vec<String>,
84}
85
86/// Wesley-owned canonical admission requirements byte artifact.
87///
88/// Downstream runtimes import these bytes directly. They should verify the
89/// digest and codec, but they should not serialize Wesley structs to create
90/// admission truth.
91#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
92#[serde(rename_all = "camelCase")]
93pub struct OpticAdmissionRequirementsArtifact {
94    /// Stable digest computed from `bytes` exactly.
95    pub digest: String,
96    /// Explicit codec describing how `bytes` were generated.
97    pub codec: String,
98    /// Canonical requirements bytes generated by Wesley.
99    pub bytes: Vec<u8>,
100}
101
102/// Host, user, quorum, or policy authority grant for invoking an artifact.
103///
104/// Wesley core defines this shape so artifacts and runtime receipts can speak a
105/// shared language, but Wesley does not issue these grants. A host, user,
106/// quorum, or policy authority owns issuance, expiry, delegation, and
107/// attestation.
108#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
109#[serde(rename_all = "camelCase")]
110pub struct CapabilityGrant {
111    /// Stable grant identity.
112    pub grant_id: String,
113    /// Principal receiving bounded authority.
114    pub subject: PrincipalRef,
115    /// Artifact hash this grant covers.
116    pub artifact_hash: String,
117    /// Operation identity this grant covers.
118    pub operation_id: String,
119    /// Requirements digest this grant covers.
120    pub requirements_digest: String,
121    /// Optional basis constraint for the invocation.
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub allowed_basis: Option<BasisConstraint>,
124    /// Aperture constraints accepted by the grant.
125    pub allowed_apertures: Vec<ApertureConstraint>,
126    /// Budget constraint for invocations using the grant.
127    pub budget: BudgetConstraint,
128    /// Optional expiration timestamp supplied by the issuing authority.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub expires_at: Option<String>,
131    /// Rights granted under the artifact requirements.
132    pub rights: Vec<String>,
133    /// Principal or service that issued the grant.
134    pub issuer: PrincipalRef,
135    /// Optional signature supplied by the issuer.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub issuer_signature: Option<String>,
138    /// Optional digest of the delegation chain.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub delegation_chain_digest: Option<String>,
141    /// Optional observer class bound by the grant.
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub observer_class: Option<ObserverClass>,
144    /// Whether the grant may be transferred to another subject.
145    pub non_transferable: bool,
146}
147
148/// Invocation-time presentation of a capability grant.
149#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
150#[serde(rename_all = "camelCase")]
151pub struct CapabilityPresentation {
152    /// Grant identity being presented.
153    pub grant_id: String,
154    /// Principal presenting the grant.
155    pub subject: PrincipalRef,
156    /// Echo-owned artifact handle used for this invocation.
157    pub artifact_handle_id: String,
158    /// Operation identity being invoked.
159    pub operation_id: String,
160    /// Digest of the canonical variable bytes for this invocation.
161    pub variables_digest: String,
162    /// Optional digest of the requested basis/aperture.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub basis_request_digest: Option<String>,
165    /// Nonce preventing replay of the presentation.
166    pub nonce: String,
167    /// Presentation timestamp supplied by the caller or host.
168    pub presented_at: String,
169    /// Optional digest of a proof or signature over the presentation.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub proof_digest: Option<String>,
172}
173
174/// Echo-owned authorization for one exact admitted invocation.
175#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
176#[serde(rename_all = "camelCase")]
177pub struct AdmissionTicket {
178    /// Stable ticket identity.
179    pub ticket_id: String,
180    /// Echo-owned artifact handle used for this invocation.
181    pub artifact_handle: OpticArtifactHandle,
182    /// Capability grant identity admitted for this invocation.
183    pub capability_grant_id: String,
184    /// Operation identity admitted for this invocation.
185    pub operation_id: String,
186    /// Digest of invocation inputs admitted by the runtime.
187    pub invocation_digest: String,
188    /// Runtime-issued admission timestamp.
189    pub issued_at: String,
190    /// Optional runtime-issued ticket expiry.
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub expires_at: Option<String>,
193}
194
195/// Constraint over the state basis allowed by a capability grant.
196#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
197#[serde(rename_all = "camelCase")]
198pub struct BasisConstraint {
199    /// Optional exact basis reference the grant permits.
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub basis_ref: Option<String>,
202    /// Optional maximum staleness in milliseconds.
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub max_staleness_ms: Option<u64>,
205}
206
207/// Constraint over a read or rewrite aperture.
208#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
209#[serde(rename_all = "camelCase")]
210pub struct ApertureConstraint {
211    /// Aperture kind, such as `file_range` or `symbol_context`.
212    pub kind: String,
213    /// Optional numeric limit owned by the host/runtime policy.
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub limit: Option<u64>,
216}
217
218/// Budget constraint attached to a capability grant.
219#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
220#[serde(rename_all = "camelCase")]
221pub struct BudgetConstraint {
222    /// Optional maximum operation count.
223    #[serde(skip_serializing_if = "Option::is_none")]
224    pub max_operations: Option<u64>,
225    /// Optional maximum byte budget.
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub max_bytes: Option<u64>,
228    /// Optional maximum runtime in milliseconds.
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub max_millis: Option<u64>,
231}
232
233/// Observer class bound by a capability grant.
234#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
235#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
236pub enum ObserverClass {
237    /// No runtime observation rights.
238    Oc0,
239    /// Minimal reading rights.
240    Oc1,
241    /// Bounded runtime observation rights.
242    Oc2,
243    /// Broad runtime observation rights under explicit policy.
244    Oc3,
245}
246
247/// Permission granted by a capability grant.
248#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
249#[serde(rename_all = "camelCase")]
250pub struct PermissionGrant {
251    /// Granted action.
252    pub action: PermissionAction,
253    /// Resource label the grant applies to.
254    pub resource: String,
255    /// Host/session-owned source of the grant.
256    pub source: String,
257}
258
259/// Artifact resolver input accepted by Wesley-side registries.
260pub type OpticArtifactRef = OpticRegistrationDescriptor;
261
262/// Resolves compiled optic artifacts from registration descriptors.
263pub trait OpticArtifactResolver {
264    /// Resolves a registration descriptor to its full compiled artifact and
265    /// verifies the descriptor still matches artifact identity and requirements.
266    fn resolve_optic_artifact(
267        &self,
268        registration: &OpticRegistrationDescriptor,
269    ) -> Result<OpticArtifact, ResolveError>;
270}
271
272/// In-memory artifact registry for tests and single-process hosts.
273#[derive(Debug, Default, Clone)]
274pub struct InMemoryOpticArtifactRegistry {
275    artifacts: BTreeMap<String, OpticArtifact>,
276}
277
278impl InMemoryOpticArtifactRegistry {
279    /// Creates an empty registry.
280    pub fn new() -> Self {
281        Self::default()
282    }
283
284    /// Stores an artifact and returns its registration descriptor.
285    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    /// Returns the number of stored artifacts.
294    pub fn len(&self) -> usize {
295        self.artifacts.len()
296    }
297
298    /// Returns true when no artifacts are stored.
299    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(&registration.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/// Error raised when an optic artifact reference cannot resolve cleanly.
366#[derive(Debug, Clone, PartialEq, Eq)]
367pub enum ResolveError {
368    /// No artifact exists for the referenced artifact identity.
369    ArtifactNotFound {
370        /// Artifact identity requested by the descriptor.
371        artifact_id: String,
372    },
373    /// The supplied artifact hash does not match the stored artifact.
374    ArtifactHashMismatch {
375        /// Artifact hash expected by the stored artifact.
376        expected: String,
377        /// Artifact hash supplied by the caller.
378        actual: String,
379    },
380    /// The supplied schema id does not match the stored artifact.
381    SchemaIdMismatch {
382        /// Schema id expected by the stored artifact.
383        expected: String,
384        /// Schema id supplied by the caller.
385        actual: String,
386    },
387    /// The supplied operation id does not match the stored artifact.
388    OperationIdMismatch {
389        /// Operation id expected by the stored artifact.
390        expected: String,
391        /// Operation id supplied by the caller.
392        actual: String,
393    },
394    /// The supplied requirements digest does not match the stored artifact.
395    RequirementsDigestMismatch {
396        /// Requirements digest expected by the stored artifact.
397        expected: String,
398        /// Requirements digest supplied by the caller.
399        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/// Identity requirement a host/runtime must satisfy before admission.
432#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
433#[serde(rename_all = "camelCase")]
434pub struct IdentityRequirement {
435    /// Whether the host/runtime must bind a principal before admission.
436    pub required: bool,
437    /// Accepted principal kinds, or empty when host policy owns the vocabulary.
438    pub accepted_principal_kinds: Vec<String>,
439}
440
441/// Opaque principal reference supplied by a host/session layer.
442#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
443#[serde(rename_all = "camelCase")]
444pub struct PrincipalRef {
445    /// Principal namespace, such as `user`, `agent`, `session`, or `service`.
446    pub kind: String,
447    /// Principal identifier inside the namespace.
448    pub id: String,
449}
450
451/// Permission requirement inferred from an optic declaration.
452#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
453#[serde(rename_all = "camelCase")]
454pub struct PermissionRequirement {
455    /// Required action.
456    pub action: PermissionAction,
457    /// Resource label the action applies to.
458    pub resource: String,
459    /// Compiler source of the requirement.
460    pub source: String,
461}
462
463/// Permission action required for an optic resource label.
464#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
465#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
466pub enum PermissionAction {
467    /// Read access is required.
468    Read,
469    /// Write access is required.
470    Write,
471}
472
473/// Inspectable contract for a selected GraphQL operation.
474#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
475#[serde(rename_all = "camelCase")]
476pub struct OpticOperation {
477    /// Stable operation identity derived from the selected operation shape.
478    pub operation_id: String,
479    /// Optional GraphQL operation name.
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub name: Option<String>,
482    /// GraphQL operation kind.
483    pub kind: OperationKind,
484    /// Root schema field selected by the operation.
485    pub root_field: String,
486    /// Canonical bindings supplied to the selected root field.
487    pub root_arguments: Vec<RootArgumentBinding>,
488    /// Canonical argument bindings supplied to selected payload fields.
489    #[serde(default, skip_serializing_if = "Vec::is_empty")]
490    pub selection_arguments: Vec<SelectionArgumentBinding>,
491    /// Codec shape for the operation variables or root field arguments.
492    pub variable_shape: CodecShape,
493    /// Codec shape for the selected response payload.
494    pub payload_shape: CodecShape,
495    /// Directives preserved from the executable operation.
496    pub directives: Vec<DirectiveRecord>,
497    /// Declared resource footprint, when the operation supplies one.
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub declared_footprint: Option<Footprint>,
500    /// Compiler-produced templates for laws relevant to this operation.
501    pub law_claims: Vec<LawClaimTemplate>,
502}
503
504/// Canonical binding for one argument supplied to an optic root field.
505#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
506#[serde(rename_all = "camelCase")]
507pub struct RootArgumentBinding {
508    /// Root argument name.
509    pub name: String,
510    /// Schema-declared argument type.
511    pub type_ref: TypeReference,
512    /// Canonical JSON representation of the supplied GraphQL input value.
513    pub value_canonical_json: String,
514}
515
516/// Canonical binding for one argument supplied to a selected payload field.
517#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
518#[serde(rename_all = "camelCase")]
519pub struct SelectionArgumentBinding {
520    /// Response-path field that received the argument.
521    pub path: String,
522    /// Field argument name.
523    pub name: String,
524    /// Schema-declared argument type.
525    pub type_ref: TypeReference,
526    /// Canonical JSON representation of the supplied GraphQL input value.
527    pub value_canonical_json: String,
528}
529
530/// GraphQL executable operation kind.
531#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
532#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
533pub enum OperationKind {
534    /// GraphQL query operation.
535    Query,
536    /// GraphQL mutation operation.
537    Mutation,
538    /// GraphQL subscription operation.
539    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/// Named codec view for variables or payload data.
563#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
564#[serde(rename_all = "camelCase")]
565pub struct CodecShape {
566    /// Logical shape name.
567    pub type_name: String,
568    /// Fields visible inside the shape.
569    pub fields: Vec<CodecField>,
570}
571
572/// One field inside a codec shape.
573#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
574#[serde(rename_all = "camelCase")]
575pub struct CodecField {
576    /// Field name or selected response path.
577    pub name: String,
578    /// GraphQL type reference for the field.
579    pub type_ref: TypeReference,
580    /// Whether the field is non-null in the GraphQL type system.
581    pub required: bool,
582    /// Whether the field has an outer or nested list wrapper.
583    pub list: bool,
584}
585
586/// Directive preserved from a compiled executable operation.
587#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
588#[serde(rename_all = "camelCase")]
589pub struct DirectiveRecord {
590    /// Schema coordinate or operation coordinate where the directive was found.
591    pub coordinate: String,
592    /// Directive name without the `@` prefix.
593    pub name: String,
594    /// Canonical JSON object containing the directive arguments.
595    pub arguments_canonical_json: String,
596}
597
598/// Declared resource families an operation may read, write, or must not touch.
599#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
600#[serde(rename_all = "camelCase")]
601pub struct Footprint {
602    /// Resource labels the operation declares it may read.
603    pub reads: Vec<String>,
604    /// Resource labels the operation declares it may write.
605    pub writes: Vec<String>,
606    /// Resource labels the operation declares forbidden.
607    pub forbids: Vec<String>,
608}
609
610/// Compiler-produced declaration that a law is relevant to an operation.
611#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
612#[serde(rename_all = "camelCase")]
613pub struct LawClaimTemplate {
614    /// Stable law identifier.
615    pub law_id: String,
616    /// Stable claim identity for this operation and law pairing.
617    pub claim_id: String,
618    /// Operation identity this claim applies to.
619    pub operation_id: String,
620    /// Evidence categories a runtime or verifier should produce.
621    pub required_evidence: Vec<EvidenceKind>,
622}
623
624/// Evidence category requested by a law claim template.
625#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
626#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
627pub enum EvidenceKind {
628    /// Evidence produced by the Wesley compiler.
629    Compiler,
630    /// Evidence produced by codec inspection or fixture vectors.
631    Codec,
632    /// Evidence produced by host/runtime policy.
633    HostPolicy,
634    /// Evidence produced from runtime trace data.
635    RuntimeTrace,
636    /// Evidence produced by a domain verifier outside Wesley core.
637    DomainVerifier,
638}
639
640/// Runtime or verifier-produced verdict for one law claim.
641#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
642#[serde(rename_all = "camelCase")]
643pub struct LawWitness {
644    /// Stable law identifier.
645    pub law_id: String,
646    /// Claim identity this witness evaluates.
647    pub claim_id: String,
648    /// Optional state basis reference evaluated by the checker.
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub basis_ref: Option<String>,
651    /// Identifier for the checker that produced the verdict.
652    pub checker_id: String,
653    /// Optional hash of the checker artifact.
654    #[serde(skip_serializing_if = "Option::is_none")]
655    pub checker_artifact_hash: Option<String>,
656    /// Verdict for the law claim.
657    pub verdict: LawVerdict,
658    /// Digests of evidence artifacts considered by the checker.
659    pub evidence_digests: Vec<String>,
660    /// Optional digest of the runtime trace considered by the checker.
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub runtime_trace_digest: Option<String>,
663    /// Optional reason for an obstructed verdict.
664    #[serde(skip_serializing_if = "Option::is_none")]
665    pub obstruction_reason: Option<String>,
666    /// Replay hints supplied by the runtime or verifier.
667    pub replay_hints: Vec<ReplayHint>,
668}
669
670/// Verdict produced for a law claim.
671#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
672#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
673pub enum LawVerdict {
674    /// The checker found the law satisfied.
675    Satisfied,
676    /// The checker found a concrete obstruction.
677    Obstructed,
678    /// The checker cannot establish satisfaction or obstruction.
679    Unknown,
680}
681
682/// Hint that helps a runtime replay or inspect a witnessed interaction.
683#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
684#[serde(rename_all = "camelCase")]
685pub struct ReplayHint {
686    /// Hint kind, such as `trace`, `basis`, or `artifact`.
687    pub kind: String,
688    /// Hint value, intentionally opaque to Wesley core.
689    pub value: String,
690}