Skip to main content

dope_runtime/topology/
mod.rs

1//! Typed startup composition for one exact generative driver domain.
2use std::{mem, pin};
3
4use dope_core::{
5    driver,
6    driver::{
7        life::domain::{Closing, Error, Rejected},
8        settings,
9        settings::{claim, errors},
10        storage,
11    },
12};
13
14use crate::{
15    executor::{self, create, drive, session},
16    random::{self, HashState},
17};
18
19pub mod app;
20mod domain;
21pub mod error;
22pub mod lending;
23pub mod ops;
24mod prepared;
25pub mod recipe;
26mod worker;
27
28pub use domain::Domain;
29pub use prepared::Prepared;
30pub use worker::Worker;
31
32/// Pinned storage available while constructing a yielding startup root.
33///
34/// This capability cannot submit I/O, allocate ready slots, or create tasks.
35#[repr(transparent)]
36pub struct Boot<'stage, 'scope, 'life: 'scope, 'p: 'life, 'd: 'p, S: storage::Plan + 'd, Q> {
37    session: &'stage session::Scope<'scope, 'life, 'p, 'd, S, Q>,
38}
39
40impl<'stage, 'scope, 'life: 'scope, 'p: 'life, 'd: 'p, S: storage::Plan + 'd, Q>
41    Boot<'stage, 'scope, 'life, 'p, 'd, S, Q>
42{
43    fn new(session: &'stage session::Scope<'scope, 'life, 'p, 'd, S, Q>) -> Self {
44        Self { session }
45    }
46
47    /// Borrows storage while preserving its pinned address.
48    pub fn pinned(&self) -> pin::Pin<&'life S::Output<'p, 'd>> {
49        self.session.pinned()
50    }
51
52    /// Storage branded by the exact prepared driver domain.
53    pub fn storage(&self) -> &'life S::Output<'p, 'd> {
54        self.session.storage()
55    }
56}
57
58/// Exclusive installation authority issued by [`Prepared`].
59///
60/// Its private session exposes branded resources only while [`Worker::install`]
61/// consumes the exact claim-producing value; the wrapper then disappears.
62pub struct Stage<'stage, 'scope, 'life: 'scope, 'p: 'life, 'd: 'p, S: storage::Plan + 'd, Q> {
63    session: &'stage mut session::Scope<'scope, 'life, 'p, 'd, S, Q>,
64}
65
66impl<'stage, 'scope, 'life: 'scope, 'p: 'life, 'd: 'p, S: storage::Plan + 'd, Q>
67    Stage<'stage, 'scope, 'life, 'p, 'd, S, Q>
68{
69    fn new(session: &'stage mut session::Scope<'scope, 'life, 'p, 'd, S, Q>) -> Self {
70        Self { session }
71    }
72
73    /// Storage branded by the exact prepared driver domain.
74    pub fn storage(&self) -> &'life S::Output<'p, 'd> {
75        self.session.storage()
76    }
77
78    /// Issues the domain's keyed hash capability without exposing the raw
79    /// executor session.
80    pub fn hash_state(&mut self, domain: random::Domain) -> HashState<'d> {
81        self.session.hash_state(domain)
82    }
83
84    /// Borrows the exact prepared driver's staging context.
85    pub fn driver(&mut self) -> driver::Context<'_, 'd> {
86        self.session.driver()
87    }
88}
89
90/// Stages one rollback-safe application from exact driver storage.
91pub trait Installer<S: storage::Plan>: Sized {
92    type App<'life, 'p, 'd>: executor::raw::App<'d>
93    where
94        'd: 'p,
95        'p: 'life,
96        'd: 'life,
97        Self: 'd,
98        S: 'd;
99    type Error;
100
101    fn install<'scope, 'life, 'p, 'd, Q>(
102        self,
103        stage: &mut Stage<'_, 'scope, 'life, 'p, 'd, S, Q>,
104    ) -> Result<Self::App<'life, 'p, 'd>, Self::Error>
105    where
106        'd: 'p,
107        'p: 'life,
108        'd: 'life,
109        'life: 'scope,
110        Self: 'd,
111        S: 'd,
112        Q: executor::Startup;
113}
114
115/// Driver entry failure retaining the rejected operation or its closing owner.
116pub enum EnterError<'owner, S, O, T, Q = ()> {
117    Runtime(Rejected<create::Error<'owner>, O>),
118    Storage(Rejected<S, O>),
119    Close(Closing<'owner, Rejected<S, O>, T, Q>),
120}
121
122impl<'owner, S, O, T, Q> EnterError<'owner, S, O, T, Q> {
123    /// Borrows completed application output while this error retains its teardown owner.
124    pub fn output(&self) -> Option<&T> {
125        match self {
126            Self::Runtime(..) | Self::Storage(..) => None,
127            Self::Close(state) => state.result().as_ref().ok(),
128        }
129    }
130
131    /// Borrows every failure while preserving output, operation, and retry ownership.
132    pub fn cause(&self) -> error::Cause<'_, S> {
133        match self {
134            Self::Runtime(error) => error::Cause::Runtime(error.cause.kind()),
135            Self::Storage(error) => error::Cause::Storage(&error.cause),
136            Self::Close(state) => error::Cause::closing(state),
137        }
138    }
139
140    pub fn kind(&self) -> error::Kind<'_, S, &T> {
141        match self {
142            Self::Runtime(error) => error::Kind::Runtime(error.cause.kind()),
143            Self::Storage(error) => error::Kind::Storage(&error.cause),
144            Self::Close(state) => error::Kind::closing(state),
145        }
146    }
147}
148
149#[derive(Debug)]
150pub enum InstallError<E> {
151    Drive(drive::Error),
152    Install(E),
153}
154
155#[derive(Debug)]
156pub enum Failure<'report, I, T> {
157    Run(session::Error<'report, Result<T, drive::Error>>),
158    Install(I),
159}
160
161impl<I, T> Failure<'_, I, T> {
162    /// Borrows every failure without moving or formatting successful output.
163    pub fn cause(&self) -> error::Failure<'_, I> {
164        match self {
165            Self::Run(error) => error::Failure::Run {
166                cause: error.cause(),
167                output: match error.output() {
168                    Some(Err(error)) => Some(error),
169                    Some(Ok(_)) | None => None,
170                },
171            },
172            Self::Install(error) => error::Failure::Install(error),
173        }
174    }
175}
176
177pub enum RunError<'owner, S, I, O, T, Q = ()> {
178    Create(Rejected<create::Error<'owner>, O>),
179    Storage(Rejected<S, O>),
180    Failure(Failure<'owner, I, T>),
181    Close(ClosingRun<'owner, S, I, O, T, Q>),
182}
183
184impl<'owner, S, I, O, T, Q> RunError<'owner, S, I, O, T, Q> {
185    /// Borrows every failure while preserving output, operation, and retry ownership.
186    pub fn cause(&self) -> error::Run<'_, S, I> {
187        match self {
188            Self::Create(error) => error::Run::Runtime(error.cause.kind()),
189            Self::Storage(error) => error::Run::Storage(&error.cause),
190            Self::Failure(error) => error::Run::Failure(error.cause()),
191            Self::Close(state) => error::Run::Close {
192                result: match state.result() {
193                    Ok(Ok(_)) => Ok(Ok(())),
194                    Ok(Err(error)) => Ok(Err(error.cause())),
195                    Err(error) => Err(&error.cause),
196                },
197                failure: state.failure(),
198            },
199        }
200    }
201
202    pub fn kind(&self) -> RunKind<'_, 'owner, S, I, T> {
203        match self {
204            Self::Create(error) => error::Kind::Runtime(error.cause.kind()),
205            Self::Storage(error) => error::Kind::Storage(&error.cause),
206            Self::Failure(error) => error::Kind::Run(error),
207            Self::Close(state) => error::Kind::closing(state),
208        }
209    }
210}
211
212impl<'owner, S, O, T, Q> From<Error<'owner, Rejected<S, O>, T, Q>>
213    for EnterError<'owner, S, O, T, Q>
214{
215    fn from(error: Error<'owner, Rejected<S, O>, T, Q>) -> Self {
216        match error {
217            Error::Storage(source) => Self::Storage(source),
218            Error::Close(state) => Self::Close(state),
219        }
220    }
221}
222
223impl<'owner, S, I, O, T, Q> From<EnterError<'owner, S, O, Result<T, Failure<'owner, I, T>>, Q>>
224    for RunError<'owner, S, I, O, T, Q>
225{
226    fn from(error: EnterError<'owner, S, O, Result<T, Failure<'owner, I, T>>, Q>) -> Self {
227        match error {
228            EnterError::Runtime(source) => Self::Create(source),
229            EnterError::Storage(source) => Self::Storage(source),
230            EnterError::Close(state) => Self::Close(state),
231        }
232    }
233}
234
235const _: () = {
236    assert!(
237        mem::size_of::<Boot<'static, 'static, 'static, 'static, 'static, (), ()>>()
238            == mem::size_of::<usize>()
239    );
240    assert!(
241        mem::align_of::<Boot<'static, 'static, 'static, 'static, 'static, (), ()>>()
242            == mem::align_of::<usize>()
243    );
244    assert!(
245        mem::size_of::<Stage<'static, 'static, 'static, 'static, 'static, (), ()>>()
246            == mem::size_of::<&mut session::Scope<'static, 'static, 'static, 'static, (), ()>>()
247    );
248};
249
250/// Unresolved storage and installer retained as one startup input.
251///
252/// Resolution consumes both values and retains the claimed installer in the
253/// resulting [`Prepared`], so installation uses those exact resource owners.
254#[must_use = "a topology plan must be resolved before it can enter a driver"]
255#[repr(transparent)]
256pub struct Plan<S, I>((S, I));
257
258impl<S, I> Plan<S, I> {
259    pub const fn new(storage: S, install: I) -> Self {
260        Self((storage, install))
261    }
262
263    /// Transfers both resource owners into a larger topology.
264    pub fn into_parts(self) -> (S, I) {
265        self.0
266    }
267
268    pub fn for_profile<P>(self) -> Result<Prepared<S, I>, errors::Claim>
269    where
270        P: settings::Profile,
271        S: storage::Plan + settings::Resource,
272        I: settings::Resource,
273    {
274        let (storage, install) = self.0;
275        let needs = claim::Needs::from_tree(&(&storage, &install))?;
276        let layout = needs.resolve::<P>()?;
277        Ok(Prepared::new(layout, storage, install))
278    }
279
280    pub fn with_config(self, baseline: settings::Config) -> Result<Prepared<S, I>, errors::Claim>
281    where
282        S: storage::Plan + settings::Resource,
283        I: settings::Resource,
284    {
285        let (storage, install) = self.0;
286        let needs = claim::Needs::from_tree(&(&storage, &install))?;
287        let layout = needs.apply_to(baseline)?;
288        Ok(Prepared::new(layout, storage, install))
289    }
290
291    pub fn for_receive<P>(self) -> Result<Prepared<S, I>, errors::Claim>
292    where
293        P: settings::Profile,
294        S: storage::Plan + settings::Resource,
295        I: settings::Resource,
296    {
297        let (storage, install) = self.0;
298        let needs = claim::Needs::from_tree(&(&storage, &install))?;
299        let layout = needs.exact::<P>()?;
300        Ok(Prepared::new(layout, storage, install))
301    }
302
303    pub fn for_install<P>(self) -> Result<Prepared<S, I>, errors::Claim>
304    where
305        P: settings::Profile,
306        S: storage::Claimless,
307        I: settings::Resource,
308    {
309        let (storage, install) = self.0;
310        let needs = claim::Needs::from_tree(&install)?;
311        let layout = needs.resolve::<P>()?;
312        Ok(Prepared::new(layout, storage, install))
313    }
314
315    pub fn with_install(self, baseline: settings::Config) -> Result<Prepared<S, I>, errors::Claim>
316    where
317        S: storage::Claimless,
318        I: settings::Resource,
319    {
320        let (storage, install) = self.0;
321        let needs = claim::Needs::from_tree(&install)?;
322        let layout = needs.apply_to(baseline)?;
323        Ok(Prepared::new(layout, storage, install))
324    }
325}
326
327const _: () = {
328    assert!(mem::size_of::<Plan<usize, u8>>() == mem::size_of::<(usize, u8)>());
329    assert!(mem::align_of::<Plan<usize, u8>>() == mem::align_of::<(usize, u8)>());
330    assert!(mem::needs_drop::<Plan<String, ()>>() == mem::needs_drop::<String>());
331};
332
333type ClosingRun<'owner, S, I, O, T, Q> =
334    Closing<'owner, Rejected<S, O>, Result<T, Failure<'owner, I, T>>, Q>;
335type RunKind<'a, 'owner, S, I, T> =
336    error::Kind<'a, S, &'a Result<T, Failure<'owner, I, T>>, Failure<'owner, I, T>>;