Skip to main content

sim_relation_site/
contract.rs

1use std::{fmt, time::Duration};
2
3use sim_kernel::{CapabilityName, Datum, Symbol};
4use sim_relation_core::{Cell, Row, RowType};
5use sim_relation_migrate::CheckedProgram;
6use sim_relation_plan::{CheckedMutation, CheckedQuery};
7
8/// Provider-neutral storage authority passed to a relation driver.
9///
10/// Paths are deliberately absent: the capsule resolves the opaque reference.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum StorageLocator {
13    /// A private, connection-scoped database.
14    Memory,
15    /// A preopened storage reference with explicit write authority.
16    Preopened {
17        /// Stable preopened reference resolved only by the capsule.
18        reference: Symbol,
19        /// Authority granted for this connection.
20        access: StorageAccess,
21    },
22}
23
24/// Authority carried by a preopened storage reference.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum StorageAccess {
27    /// Reads are admitted and mutations fail closed.
28    ReadOnly,
29    /// Reads and admitted mutations are allowed.
30    ReadWrite,
31}
32
33impl StorageLocator {
34    /// Decodes the closed public locator grammar.
35    pub fn from_datum(value: &Datum) -> Result<Self, SiteError> {
36        let Datum::Node { tag, fields } = value else {
37            return Err(SiteError::Locator);
38        };
39        if tag == &Symbol::qualified("relation", "memory") && fields.is_empty() {
40            return Ok(Self::Memory);
41        }
42        if tag != &Symbol::qualified("relation", "preopened") {
43            return Err(SiteError::Locator);
44        }
45        let field = |name: &str| {
46            fields
47                .iter()
48                .find(|(key, _)| key == &Symbol::new(name))
49                .map(|(_, value)| value)
50        };
51        if fields.len() != 2 {
52            return Err(SiteError::Locator);
53        }
54        let Some(Datum::Symbol(reference)) = field("ref") else {
55            return Err(SiteError::Locator);
56        };
57        let access = match field("access") {
58            Some(Datum::Symbol(value)) if value == &Symbol::new("read-only") => {
59                StorageAccess::ReadOnly
60            }
61            Some(Datum::Symbol(value)) if value == &Symbol::new("read-write") => {
62                StorageAccess::ReadWrite
63            }
64            _ => return Err(SiteError::Locator),
65        };
66        Ok(Self::Preopened {
67            reference: reference.clone(),
68            access,
69        })
70    }
71}
72
73/// Declared provider identity checked before a driver can be installed.
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct DriverManifest {
76    /// Exact kernel site export.
77    pub site: Symbol,
78    /// Exact provider behavior identity.
79    pub provider: Symbol,
80}
81impl DriverManifest {
82    /// Admits only the canonical SQLite realization and rejects aliases.
83    pub fn sqlite(site: Symbol, provider: Symbol) -> Result<Self, SiteError> {
84        if site != Symbol::qualified("relation/site", "sqlite")
85            || provider != Symbol::qualified("relation/provider", "sqlite")
86        {
87            return Err(SiteError::Registration);
88        }
89        Ok(Self { site, provider })
90    }
91}
92
93/// A loaded site name paired with a provider-opaque locator.
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub struct RelationPlacement {
96    pub(crate) site: Symbol,
97    pub(crate) locator: Datum,
98}
99impl RelationPlacement {
100    /// Names a loaded relation site and preserves the locator as ordinary data.
101    pub fn new(site: Symbol, locator: Datum) -> Self {
102        Self { site, locator }
103    }
104    /// Loaded site symbol.
105    pub fn site(&self) -> &Symbol {
106        &self.site
107    }
108    /// Opaque locator, interpreted only by the selected driver.
109    pub fn locator(&self) -> &Datum {
110        &self.locator
111    }
112}
113
114/// Mandatory execution maxima and optional time/buffering bounds.
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
116pub struct Limits {
117    /// Maximum emitted rows.
118    pub rows: u64,
119    /// Maximum emitted cells.
120    pub cells: u64,
121    /// Maximum logical datum bytes.
122    pub bytes: u64,
123    /// Maximum provider work units.
124    pub work: u64,
125    /// Optional elapsed deadline.
126    pub deadline: Option<Duration>,
127    /// Optional maximum provider buffering.
128    pub buffer_bytes: Option<u64>,
129}
130impl Limits {
131    /// Constructs limits, rejecting every zero bound.
132    pub fn new(rows: u64, cells: u64, bytes: u64, work: u64) -> Result<Self, SiteError> {
133        if [rows, cells, bytes, work].contains(&0) {
134            return Err(SiteError::InvalidLimits);
135        }
136        Ok(Self {
137            rows,
138            cells,
139            bytes,
140            work,
141            deadline: None,
142            buffer_bytes: None,
143        })
144    }
145}
146
147/// Ordered, typed parameter values.
148#[derive(Clone, Debug, PartialEq, Eq)]
149pub struct Bindings(Row);
150impl Bindings {
151    /// Checks arity, domains, and nullability against a plan's parameter type.
152    pub fn new(
153        expected: &RowType,
154        cells: impl IntoIterator<Item = Cell>,
155    ) -> Result<Self, SiteError> {
156        Row::new(expected.clone(), cells)
157            .map(Self)
158            .map_err(|e| SiteError::Bindings(e.to_string()))
159    }
160    /// Checked row supplied to providers.
161    pub fn row(&self) -> &Row {
162        &self.0
163    }
164}
165
166/// Push target for query and returning rows.
167pub trait RowSink {
168    /// Accepts one already type-checked row.
169    fn push(&mut self, row: Row) -> Result<(), SiteError>;
170}
171
172/// In-memory row collector for callers that need a complete bounded result.
173///
174/// The relation site enforces row, cell, byte, and work limits before rows
175/// reach this sink, so collecting here does not create a second limit policy.
176#[derive(Clone, Debug, Default, PartialEq, Eq)]
177pub struct VecRowSink {
178    rows: Vec<Row>,
179}
180
181impl VecRowSink {
182    /// Borrows the rows collected so far in provider order.
183    pub fn rows(&self) -> &[Row] {
184        &self.rows
185    }
186
187    /// Returns the collected rows in provider order.
188    pub fn into_rows(self) -> Vec<Row> {
189        self.rows
190    }
191}
192
193impl RowSink for VecRowSink {
194    fn push(&mut self, row: Row) -> Result<(), SiteError> {
195        self.rows.push(row);
196        Ok(())
197    }
198}
199
200/// Provider-reported bounded work and redaction-safe facts.
201#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
202pub struct ProviderStats {
203    /// Provider work units consumed.
204    pub work: u64,
205    /// Rows affected by a mutation or migration.
206    pub affected: u64,
207}
208
209/// Stable identity of an execution bound, suitable for a redacted limit receipt.
210#[derive(Clone, Copy, Debug, PartialEq, Eq)]
211pub enum LimitKind {
212    /// Emitted row count.
213    Rows,
214    /// Emitted cell count.
215    Cells,
216    /// Logical output bytes.
217    Bytes,
218    /// Provider work units.
219    Work,
220    /// Delivered-clock deadline.
221    Deadline,
222}
223
224/// Receipt deliberately excludes locator, bindings, row values, and provider errors.
225#[derive(Clone, Debug, PartialEq, Eq)]
226pub struct Receipt {
227    /// Stable checked plan/program identity when applicable.
228    pub operation_id: String,
229    /// Public operation kind.
230    pub operation: Operation,
231    /// Rows pushed to the sink.
232    pub rows: u64,
233    /// Cells pushed to the sink.
234    pub cells: u64,
235    /// Logical bytes pushed to the sink.
236    pub bytes: u64,
237    /// Provider work units.
238    pub work: u64,
239    /// Rows affected.
240    pub affected: u64,
241}
242
243/// Capability/effect kind selected at the operation boundary.
244#[derive(Clone, Copy, Debug, PartialEq, Eq)]
245pub enum Operation {
246    /// Read checked rows.
247    Read,
248    /// Apply a checked data mutation.
249    Write,
250    /// Apply an admitted schema operation.
251    Schema,
252    /// Apply an admitted migration.
253    Migrate,
254    /// Enter a closure-managed transaction.
255    Transaction,
256    /// Attach a provider-specific locator.
257    Attach,
258}
259impl Operation {
260    fn label(self) -> &'static str {
261        match self {
262            Self::Read => "read",
263            Self::Write => "write",
264            Self::Schema => "schema",
265            Self::Migrate => "migrate",
266            Self::Transaction => "transaction",
267            Self::Attach => "attach",
268        }
269    }
270    pub(crate) fn capability(self) -> CapabilityName {
271        CapabilityName::new(format!("relation.{}", self.label()))
272    }
273    pub(crate) fn effect(self) -> Symbol {
274        Symbol::qualified("effect/relation", self.label())
275    }
276}
277
278/// Site failure. Sensitive provider detail is never included in receipts.
279#[derive(Clone, Debug, PartialEq, Eq)]
280pub enum SiteError {
281    /// At least one mandatory limit was zero.
282    InvalidLimits,
283    /// Bindings violated the checked parameter contract.
284    Bindings(String),
285    /// Provider emitted a row with the wrong type.
286    RowType,
287    /// A mandatory or optional bound was exceeded.
288    Limit(LimitKind),
289    /// Provider rejected its opaque locator.
290    Locator,
291    /// A provider was undeclared or its manifest identity was malformed.
292    Registration,
293    /// A stable provider failure category.
294    Constraint,
295    /// The provider could not acquire its bounded lock.
296    Locked,
297    /// The requested operation exceeds storage authority.
298    ReadOnly,
299    /// Execution was interrupted by cancellation or deadline.
300    Interrupted,
301    /// Durable provider state is corrupt.
302    Corruption,
303    /// A provider value could not satisfy the admitted domain.
304    Conversion,
305    /// Live physical objects disagree with their attestation.
306    Drift,
307    /// Provider operation failed; detail is intentionally redacted.
308    Provider,
309    /// Kernel capability/effect machinery rejected the operation.
310    Kernel(String),
311}
312impl fmt::Display for SiteError {
313    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314        write!(f, "{self:?}")
315    }
316}
317impl std::error::Error for SiteError {}
318
319/// A provider session. It can receive only sealed checked operations.
320pub trait Session {
321    /// Execute a checked query and push typed rows.
322    fn query(
323        &mut self,
324        plan: &CheckedQuery,
325        bindings: &Bindings,
326        limits: &Limits,
327        sink: &mut dyn RowSink,
328    ) -> Result<ProviderStats, SiteError>;
329    /// Execute a checked mutation and push typed returning rows.
330    fn mutate(
331        &mut self,
332        plan: &CheckedMutation,
333        bindings: &Bindings,
334        limits: &Limits,
335        sink: &mut dyn RowSink,
336    ) -> Result<ProviderStats, SiteError>;
337    /// Apply an admitted migration program.
338    fn migrate(
339        &mut self,
340        program: &CheckedProgram,
341        limits: &Limits,
342    ) -> Result<ProviderStats, SiteError>;
343    /// Perform a provider schema operation using an admitted migration program.
344    fn schema(
345        &mut self,
346        program: &CheckedProgram,
347        limits: &Limits,
348    ) -> Result<ProviderStats, SiteError>;
349    /// Run a closure-managed transaction.
350    fn transaction(
351        &mut self,
352        body: &mut dyn FnMut(&mut dyn Transaction) -> Result<(), SiteError>,
353    ) -> Result<(), SiteError>;
354    /// Attach another provider-specific locator.
355    fn attach(&mut self, locator: &Datum, limits: &Limits) -> Result<ProviderStats, SiteError>;
356}
357
358/// Transaction-scoped checked operations. Raw commit/rollback methods are absent.
359pub trait Transaction: Session {
360    /// Run a closure-managed savepoint; failure must roll back before returning.
361    fn savepoint(
362        &mut self,
363        body: &mut dyn FnMut(&mut dyn Transaction) -> Result<(), SiteError>,
364    ) -> Result<(), SiteError>;
365}
366
367/// Provider factory. Locator validation occurs only inside `connect`.
368pub trait Driver: Send + Sync {
369    /// Validate the opaque locator and open a session.
370    fn connect(&self, locator: &Datum, limits: &Limits) -> Result<Box<dyn Session>, SiteError>;
371}