Skip to main content

telltale_machine/
owned.rs

1//! Preferred owned-session helpers for host integration.
2//!
3//! These wrappers are the preferred public path for embedders that want to
4//! respect the host-runtime ownership contract. Lower-level session accessors
5//! remain available for tests and internal runtime wiring, but production host
6//! mutation should flow through an owned capability.
7
8use crate::engine::{ProtocolMachine, ProtocolMachineError};
9use crate::loader::CodeImage;
10use crate::session::{
11    CancellationWitness, OwnershipCapability, OwnershipError, OwnershipReceipt, OwnershipScope,
12    ReadinessWitness, SessionHostMutation, SessionId,
13};
14
15/// Capability-bearing handle returned by the preferred owned open path.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct OwnedSession {
18    session_id: SessionId,
19    capability: OwnershipCapability,
20}
21
22impl OwnedSession {
23    pub(crate) fn new(session_id: SessionId, capability: OwnershipCapability) -> Self {
24        Self {
25            session_id,
26            capability,
27        }
28    }
29
30    /// Session identifier for this owned handle.
31    #[must_use]
32    pub fn session_id(&self) -> SessionId {
33        self.session_id
34    }
35
36    /// Live ownership capability carried by this handle.
37    #[must_use]
38    pub fn capability(&self) -> &OwnershipCapability {
39        &self.capability
40    }
41
42    /// Apply one session-local host mutation through the ownership gate.
43    ///
44    /// # Errors
45    ///
46    /// Returns an `OwnershipError` if the capability is stale or lacks scope.
47    pub fn apply_host_mutation(
48        &self,
49        machine: &mut ProtocolMachine,
50        mutation: SessionHostMutation,
51    ) -> Result<(), OwnershipError> {
52        machine
53            .sessions_mut()
54            .apply_owned_session_mutation(&self.capability, mutation)
55    }
56
57    /// Issue a single-use readiness witness for a protocol-critical check.
58    ///
59    /// # Errors
60    ///
61    /// Returns an `OwnershipError` if the capability is stale or lacks session scope.
62    pub fn issue_readiness_witness(
63        &self,
64        machine: &mut ProtocolMachine,
65        predicate_ref: impl Into<String>,
66    ) -> Result<ReadinessWitness, OwnershipError> {
67        machine
68            .sessions_mut()
69            .issue_readiness_witness(&self.capability, predicate_ref)
70    }
71
72    /// Consume a previously issued readiness witness exactly once.
73    ///
74    /// # Errors
75    ///
76    /// Returns an `OwnershipError` if the witness is stale, forged, mismatched, or reused.
77    pub fn consume_readiness_witness(
78        &self,
79        machine: &mut ProtocolMachine,
80        witness: &ReadinessWitness,
81    ) -> Result<(), OwnershipError> {
82        machine
83            .sessions_mut()
84            .consume_readiness_witness(&self.capability, witness)
85    }
86
87    /// Begin an explicit ownership transfer from this handle.
88    ///
89    /// # Errors
90    ///
91    /// Returns an `OwnershipError` if the capability is stale.
92    pub fn begin_transfer(
93        &self,
94        machine: &mut ProtocolMachine,
95        new_owner_id: impl Into<String>,
96        new_scope: OwnershipScope,
97    ) -> Result<OwnershipReceipt, OwnershipError> {
98        machine
99            .sessions_mut()
100            .begin_ownership_transfer(&self.capability, new_owner_id, new_scope)
101    }
102
103    /// Commit an explicit ownership transfer and return the refreshed handle.
104    ///
105    /// # Errors
106    ///
107    /// Returns an `OwnershipError` if the receipt is stale or mismatched.
108    pub fn commit_transfer(
109        &self,
110        machine: &mut ProtocolMachine,
111        receipt: &OwnershipReceipt,
112    ) -> Result<Self, OwnershipError> {
113        let capability = machine.sessions_mut().commit_ownership_transfer(receipt)?;
114        Ok(Self::new(receipt.session_id, capability))
115    }
116
117    /// Attenuate the handle scope and return the refreshed capability.
118    ///
119    /// # Errors
120    ///
121    /// Returns an `OwnershipError` if the capability is stale or transfer-pending.
122    pub fn attenuate_scope(
123        &self,
124        machine: &mut ProtocolMachine,
125        new_scope: OwnershipScope,
126    ) -> Result<Self, OwnershipError> {
127        let capability = machine
128            .sessions_mut()
129            .attenuate_ownership_scope(&self.capability, new_scope)?;
130        Ok(Self::new(self.session_id, capability))
131    }
132
133    /// Release the live ownership claim for this handle.
134    ///
135    /// # Errors
136    ///
137    /// Returns an `OwnershipError` if the capability is stale.
138    pub fn release(&self, machine: &mut ProtocolMachine) -> Result<(), OwnershipError> {
139        machine.sessions_mut().release_ownership(&self.capability)
140    }
141
142    /// Fault the session because the current owner died.
143    ///
144    /// # Errors
145    ///
146    /// Returns an `OwnershipError` if the live owner no longer matches this handle.
147    pub fn mark_owner_died(
148        &self,
149        machine: &mut ProtocolMachine,
150    ) -> Result<CancellationWitness, OwnershipError> {
151        machine.mark_owner_died(self.session_id, &self.capability.owner_id)
152    }
153
154    /// Cancel the session because this transfer was abandoned.
155    ///
156    /// # Errors
157    ///
158    /// Returns an `OwnershipError` if the receipt no longer matches the live staged transfer.
159    pub fn cancel_abandoned_transfer(
160        &self,
161        machine: &mut ProtocolMachine,
162        receipt: &OwnershipReceipt,
163    ) -> Result<CancellationWitness, OwnershipError> {
164        machine.cancel_abandoned_transfer(receipt)
165    }
166}
167
168impl ProtocolMachine {
169    /// Preferred choreography open path that immediately claims session ownership.
170    ///
171    /// Third-party host integrations use this owned helper so subsequent
172    /// session-local mutation flows through an explicit ownership capability.
173    ///
174    /// # Errors
175    ///
176    /// Returns a `ProtocolMachineError` if the choreography cannot be loaded or the initial
177    /// ownership claim fails.
178    pub fn load_choreography_owned(
179        &mut self,
180        image: &CodeImage,
181        owner_id: impl Into<String>,
182    ) -> Result<OwnedSession, ProtocolMachineError> {
183        let sid = self.load_choreography(image)?;
184        let capability = self
185            .sessions_mut()
186            .claim_ownership(sid, owner_id, OwnershipScope::Session)
187            .map_err(|err| ProtocolMachineError::OwnershipContract(format!("{err:?}")))?;
188        Ok(OwnedSession::new(sid, capability))
189    }
190}