axiolid_contracts/
integration.rs

1//! Versioned, provider-neutral contracts for downstream capability discovery.
2//!
3//! This module describes what a built integration surface can do. Operation
4//! traits remain the capability truth: a facade or FFI layer must only publish
5//! a [`CapabilityDescriptor`] after constructing and exercising the concrete
6//! provider behind it.
7
8use thiserror::Error;
9
10use crate::{BackendId, CapabilityId, Operation};
11
12/// First downstream integration protocol shipped by Axiolid.
13pub const INTEGRATION_API_VERSION: ApiVersion = ApiVersion::new(0, 4, 0);
14/// Minimum supported Rust compiler for Rust integration profiles.
15pub const MINIMUM_RUST_VERSION: &str = "1.88";
16
17/// Semantic version of an integration API or ABI.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct ApiVersion {
20    pub major: u16,
21    pub minor: u16,
22    pub patch: u16,
23}
24
25impl ApiVersion {
26    pub const fn new(major: u16, minor: u16, patch: u16) -> Self {
27        Self {
28            major,
29            minor,
30            patch,
31        }
32    }
33
34    /// Protocol compatibility is major-version stable and backward compatible.
35    pub const fn supports(self, requested: Self) -> bool {
36        self.major == requested.major
37            && (self.minor > requested.minor
38                || (self.minor == requested.minor && self.patch >= requested.patch))
39    }
40}
41
42/// Supported way an application reaches Axiolid.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
44pub enum IntegrationProfile {
45    /// A Rust application selects only the leaf packages it needs.
46    RustLeaf,
47    /// A Rust application uses the feature-gated `axiolid` facade.
48    RustFacade,
49    /// A native application uses the versioned C ABI.
50    NativeC,
51}
52
53/// Portable representation families visible at an integration boundary.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
55pub enum Representation {
56    Scalar,
57    Linear,
58    Profile2d,
59    AnalyticCurve,
60    AnalyticSurface,
61    Topology,
62    ExactBrep,
63    TriangleMesh,
64    MeshHealth,
65    Measurements,
66    RayHit,
67    ModelGraph,
68    SampledField,
69}
70
71/// Fidelity a capability promises for its output.
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
73pub enum Exactness {
74    /// Analytic identity and certified trims are preserved.
75    Exact,
76    /// The caller supplies an explicit tolerance and receives a bounded result.
77    ToleranceBounded,
78}
79
80impl Exactness {
81    const fn satisfies(self, required: Self) -> bool {
82        matches!(
83            (self, required),
84            (Self::Exact, _) | (Self::ToleranceBounded, Self::ToleranceBounded)
85        )
86    }
87}
88
89/// Cross-boundary ownership model.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
91pub enum Ownership {
92    /// Normal Rust values own their allocations.
93    RustValues,
94    /// Native outputs are opaque handles released by matching Axiolid functions.
95    OpaqueOwnedHandles,
96}
97
98/// Thread-safety promise of one integration profile.
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub enum ThreadSafety {
101    /// Public values are `Send + Sync`; operation-local mutable state is not shared.
102    SendSyncValues,
103    /// A native context may move between threads but cannot be used concurrently.
104    ContextSendNotSync,
105}
106
107/// Unit, coordinate, tolerance, ownership, and execution semantics.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub struct BoundaryContract {
110    /// Coordinates are right-handed Cartesian `f64` values.
111    pub right_handed_cartesian_f64: bool,
112    /// Geometry is unitless; every input and tolerance uses one caller-selected unit.
113    pub caller_defined_consistent_units: bool,
114    /// Approximate operations require an explicit tolerance.
115    pub explicit_tolerance: bool,
116    pub ownership: Ownership,
117    pub thread_safety: ThreadSafety,
118}
119
120impl BoundaryContract {
121    pub const fn rust() -> Self {
122        Self {
123            right_handed_cartesian_f64: true,
124            caller_defined_consistent_units: true,
125            explicit_tolerance: true,
126            ownership: Ownership::RustValues,
127            thread_safety: ThreadSafety::SendSyncValues,
128        }
129    }
130
131    pub const fn native() -> Self {
132        Self {
133            ownership: Ownership::OpaqueOwnedHandles,
134            thread_safety: ThreadSafety::ContextSendNotSync,
135            ..Self::rust()
136        }
137    }
138}
139
140/// One capability backed by a concrete provider in this build.
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
142pub struct CapabilityDescriptor {
143    pub id: CapabilityId,
144    pub operation: Operation,
145    pub provider: BackendId,
146    /// Cargo feature that admitted the provider into this build.
147    pub required_feature: &'static str,
148    pub inputs: &'static [Representation],
149    pub output: Representation,
150    pub exactness: Exactness,
151    pub deterministic: bool,
152}
153
154/// Minimum behavior a caller needs before it submits geometry.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
156pub struct CapabilityRequirement {
157    pub id: CapabilityId,
158    pub output: Representation,
159    pub exactness: Exactness,
160    pub deterministic: bool,
161}
162
163/// Typed reason a capability handshake refused a request.
164#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
165pub enum RequirementRefusal {
166    #[error("integration API {requested:?} is incompatible with advertised {advertised:?}")]
167    ApiVersionUnavailable {
168        requested: ApiVersion,
169        advertised: ApiVersion,
170    },
171    #[error("capability `{capability}` is not available")]
172    CapabilityUnavailable { capability: CapabilityId },
173    #[error("capability `{capability}` returns {advertised:?}, not required {required:?}")]
174    RepresentationUnavailable {
175        capability: CapabilityId,
176        required: Representation,
177        advertised: Representation,
178    },
179    #[error("capability `{capability}` advertises {advertised:?}, not required {required:?}")]
180    ExactnessUnavailable {
181        capability: CapabilityId,
182        required: Exactness,
183        advertised: Exactness,
184    },
185    #[error("capability `{capability}` is not deterministic in this build")]
186    DeterminismUnavailable { capability: CapabilityId },
187}
188
189/// Complete handshake returned by one compiled integration surface.
190#[derive(Debug, Clone, PartialEq, Eq)]
191pub struct IntegrationDescriptor {
192    pub api_version: ApiVersion,
193    pub abi_version: Option<ApiVersion>,
194    pub profile: IntegrationProfile,
195    pub minimum_rust_version: Option<&'static str>,
196    pub enabled_features: Vec<&'static str>,
197    pub representations: Vec<Representation>,
198    pub capabilities: Vec<CapabilityDescriptor>,
199    pub boundary: BoundaryContract,
200}
201
202impl IntegrationDescriptor {
203    /// Honest baseline: a profile with no advertised representation or operation.
204    pub fn empty(profile: IntegrationProfile) -> Self {
205        let native = matches!(profile, IntegrationProfile::NativeC);
206        Self {
207            api_version: INTEGRATION_API_VERSION,
208            abi_version: native.then_some(INTEGRATION_API_VERSION),
209            profile,
210            minimum_rust_version: (!native).then_some(MINIMUM_RUST_VERSION),
211            enabled_features: Vec::new(),
212            representations: Vec::new(),
213            capabilities: Vec::new(),
214            boundary: if native {
215                BoundaryContract::native()
216            } else {
217                BoundaryContract::rust()
218            },
219        }
220    }
221
222    pub fn supports_api(&self, requested: ApiVersion) -> bool {
223        self.api_version.supports(requested)
224    }
225
226    pub fn require_api(&self, requested: ApiVersion) -> Result<(), RequirementRefusal> {
227        if self.supports_api(requested) {
228            Ok(())
229        } else {
230            Err(RequirementRefusal::ApiVersionUnavailable {
231                requested,
232                advertised: self.api_version,
233            })
234        }
235    }
236
237    /// Return the concrete provider descriptor or a typed refusal.
238    pub fn require(
239        &self,
240        requirement: CapabilityRequirement,
241    ) -> Result<&CapabilityDescriptor, RequirementRefusal> {
242        let Some(capability) = self
243            .capabilities
244            .iter()
245            .find(|candidate| candidate.id == requirement.id)
246        else {
247            return Err(RequirementRefusal::CapabilityUnavailable {
248                capability: requirement.id,
249            });
250        };
251
252        if capability.output != requirement.output {
253            return Err(RequirementRefusal::RepresentationUnavailable {
254                capability: requirement.id,
255                required: requirement.output,
256                advertised: capability.output,
257            });
258        }
259        if !capability.exactness.satisfies(requirement.exactness) {
260            return Err(RequirementRefusal::ExactnessUnavailable {
261                capability: requirement.id,
262                required: requirement.exactness,
263                advertised: capability.exactness,
264            });
265        }
266        if requirement.deterministic && !capability.deterministic {
267            return Err(RequirementRefusal::DeterminismUnavailable {
268                capability: requirement.id,
269            });
270        }
271        Ok(capability)
272    }
273}