use crate::domain::ir::TypeReference;
use crate::domain::operation::OperationType;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
pub const OPTIC_ADMISSION_REQUIREMENTS_ARTIFACT_CODEC: &str =
"wesley.requirements.canonical-json.v0";
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct OpticArtifact {
pub artifact_id: String,
pub artifact_hash: String,
pub schema_id: String,
pub requirements_digest: String,
pub requirements_artifact: OpticAdmissionRequirementsArtifact,
pub operation: OpticOperation,
pub requirements: OpticAdmissionRequirements,
pub registration: OpticRegistrationDescriptor,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OpticRegistrationDescriptor {
pub artifact_id: String,
pub artifact_hash: String,
pub schema_id: String,
pub operation_id: String,
pub requirements_digest: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OpticArtifactHandle {
pub kind: String,
pub id: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OpticAdmissionRequirements {
pub identity: IdentityRequirement,
pub required_permissions: Vec<PermissionRequirement>,
pub forbidden_resources: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct OpticAdmissionRequirementsArtifact {
pub digest: String,
pub codec: String,
pub bytes: Vec<u8>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CapabilityGrant {
pub grant_id: String,
pub subject: PrincipalRef,
pub artifact_hash: String,
pub operation_id: String,
pub requirements_digest: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub allowed_basis: Option<BasisConstraint>,
pub allowed_apertures: Vec<ApertureConstraint>,
pub budget: BudgetConstraint,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
pub rights: Vec<String>,
pub issuer: PrincipalRef,
#[serde(skip_serializing_if = "Option::is_none")]
pub issuer_signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delegation_chain_digest: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub observer_class: Option<ObserverClass>,
pub non_transferable: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct CapabilityPresentation {
pub grant_id: String,
pub subject: PrincipalRef,
pub artifact_handle_id: String,
pub operation_id: String,
pub variables_digest: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub basis_request_digest: Option<String>,
pub nonce: String,
pub presented_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub proof_digest: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AdmissionTicket {
pub ticket_id: String,
pub artifact_handle: OpticArtifactHandle,
pub capability_grant_id: String,
pub operation_id: String,
pub invocation_digest: String,
pub issued_at: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BasisConstraint {
#[serde(skip_serializing_if = "Option::is_none")]
pub basis_ref: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_staleness_ms: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ApertureConstraint {
pub kind: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct BudgetConstraint {
#[serde(skip_serializing_if = "Option::is_none")]
pub max_operations: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_bytes: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_millis: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum ObserverClass {
Oc0,
Oc1,
Oc2,
Oc3,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PermissionGrant {
pub action: PermissionAction,
pub resource: String,
pub source: String,
}
pub type OpticArtifactRef = OpticRegistrationDescriptor;
pub trait OpticArtifactResolver {
fn resolve_optic_artifact(
&self,
registration: &OpticRegistrationDescriptor,
) -> Result<OpticArtifact, ResolveError>;
}
#[derive(Debug, Default, Clone)]
pub struct InMemoryOpticArtifactRegistry {
artifacts: BTreeMap<String, OpticArtifact>,
}
impl InMemoryOpticArtifactRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, mut artifact: OpticArtifact) -> OpticRegistrationDescriptor {
let registration = registration_descriptor_for_artifact(&artifact);
artifact.registration = registration.clone();
self.artifacts
.insert(artifact.artifact_id.clone(), artifact);
registration
}
pub fn len(&self) -> usize {
self.artifacts.len()
}
pub fn is_empty(&self) -> bool {
self.artifacts.is_empty()
}
}
fn registration_descriptor_for_artifact(artifact: &OpticArtifact) -> OpticRegistrationDescriptor {
OpticRegistrationDescriptor {
artifact_id: artifact.artifact_id.clone(),
artifact_hash: artifact.artifact_hash.clone(),
schema_id: artifact.schema_id.clone(),
operation_id: artifact.operation.operation_id.clone(),
requirements_digest: artifact.requirements_digest.clone(),
}
}
impl OpticArtifactResolver for InMemoryOpticArtifactRegistry {
fn resolve_optic_artifact(
&self,
registration: &OpticRegistrationDescriptor,
) -> Result<OpticArtifact, ResolveError> {
let artifact = self
.artifacts
.get(®istration.artifact_id)
.ok_or_else(|| ResolveError::ArtifactNotFound {
artifact_id: registration.artifact_id.clone(),
})?;
verify_registration_matches_artifact(registration, artifact)?;
Ok(artifact.clone())
}
}
fn verify_registration_matches_artifact(
registration: &OpticRegistrationDescriptor,
artifact: &OpticArtifact,
) -> Result<(), ResolveError> {
if registration.artifact_hash != artifact.artifact_hash {
return Err(ResolveError::ArtifactHashMismatch {
expected: artifact.artifact_hash.clone(),
actual: registration.artifact_hash.clone(),
});
}
if registration.schema_id != artifact.schema_id {
return Err(ResolveError::SchemaIdMismatch {
expected: artifact.schema_id.clone(),
actual: registration.schema_id.clone(),
});
}
if registration.operation_id != artifact.operation.operation_id {
return Err(ResolveError::OperationIdMismatch {
expected: artifact.operation.operation_id.clone(),
actual: registration.operation_id.clone(),
});
}
if registration.requirements_digest != artifact.requirements_digest {
return Err(ResolveError::RequirementsDigestMismatch {
expected: artifact.requirements_digest.clone(),
actual: registration.requirements_digest.clone(),
});
}
Ok(())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveError {
ArtifactNotFound {
artifact_id: String,
},
ArtifactHashMismatch {
expected: String,
actual: String,
},
SchemaIdMismatch {
expected: String,
actual: String,
},
OperationIdMismatch {
expected: String,
actual: String,
},
RequirementsDigestMismatch {
expected: String,
actual: String,
},
}
impl fmt::Display for ResolveError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ResolveError::ArtifactNotFound { artifact_id } => {
write!(formatter, "optic artifact '{artifact_id}' was not found")
}
ResolveError::ArtifactHashMismatch { expected, actual } => write!(
formatter,
"optic artifact hash mismatch: expected '{expected}', got '{actual}'"
),
ResolveError::SchemaIdMismatch { expected, actual } => write!(
formatter,
"optic schema id mismatch: expected '{expected}', got '{actual}'"
),
ResolveError::OperationIdMismatch { expected, actual } => write!(
formatter,
"optic operation id mismatch: expected '{expected}', got '{actual}'"
),
ResolveError::RequirementsDigestMismatch { expected, actual } => write!(
formatter,
"optic requirements digest mismatch: expected '{expected}', got '{actual}'"
),
}
}
}
impl std::error::Error for ResolveError {}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct IdentityRequirement {
pub required: bool,
pub accepted_principal_kinds: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PrincipalRef {
pub kind: String,
pub id: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PermissionRequirement {
pub action: PermissionAction,
pub resource: String,
pub source: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PermissionAction {
Read,
Write,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct OpticOperation {
pub operation_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
pub kind: OperationKind,
pub root_field: String,
pub root_arguments: Vec<RootArgumentBinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub selection_arguments: Vec<SelectionArgumentBinding>,
pub variable_shape: CodecShape,
pub payload_shape: CodecShape,
pub directives: Vec<DirectiveRecord>,
#[serde(skip_serializing_if = "Option::is_none")]
pub declared_footprint: Option<Footprint>,
pub law_claims: Vec<LawClaimTemplate>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RootArgumentBinding {
pub name: String,
pub type_ref: TypeReference,
pub value_canonical_json: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct SelectionArgumentBinding {
pub path: String,
pub name: String,
pub type_ref: TypeReference,
pub value_canonical_json: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OperationKind {
Query,
Mutation,
Subscription,
}
impl From<OperationType> for OperationKind {
fn from(value: OperationType) -> Self {
match value {
OperationType::Query => OperationKind::Query,
OperationType::Mutation => OperationKind::Mutation,
OperationType::Subscription => OperationKind::Subscription,
}
}
}
impl From<OperationKind> for OperationType {
fn from(value: OperationKind) -> Self {
match value {
OperationKind::Query => OperationType::Query,
OperationKind::Mutation => OperationType::Mutation,
OperationKind::Subscription => OperationType::Subscription,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CodecShape {
pub type_name: String,
pub fields: Vec<CodecField>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CodecField {
pub name: String,
pub type_ref: TypeReference,
pub required: bool,
pub list: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct DirectiveRecord {
pub coordinate: String,
pub name: String,
pub arguments_canonical_json: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Footprint {
pub reads: Vec<String>,
pub writes: Vec<String>,
pub forbids: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LawClaimTemplate {
pub law_id: String,
pub claim_id: String,
pub operation_id: String,
pub required_evidence: Vec<EvidenceKind>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum EvidenceKind {
Compiler,
Codec,
HostPolicy,
RuntimeTrace,
DomainVerifier,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct LawWitness {
pub law_id: String,
pub claim_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub basis_ref: Option<String>,
pub checker_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub checker_artifact_hash: Option<String>,
pub verdict: LawVerdict,
pub evidence_digests: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub runtime_trace_digest: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub obstruction_reason: Option<String>,
pub replay_hints: Vec<ReplayHint>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum LawVerdict {
Satisfied,
Obstructed,
Unknown,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ReplayHint {
pub kind: String,
pub value: String,
}