Skip to main content

telltale_vm/
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::loader::CodeImage;
9use crate::session::{
10    CancellationWitness, OwnershipCapability, OwnershipError, OwnershipReceipt, OwnershipScope,
11    ReadinessWitness, SessionHostMutation, SessionId,
12};
13use crate::vm::{VMError, VM};
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        vm: &mut VM,
50        mutation: SessionHostMutation,
51    ) -> Result<(), OwnershipError> {
52        vm.sessions_mut()
53            .apply_owned_session_mutation(&self.capability, mutation)
54    }
55
56    /// Issue a single-use readiness witness for a protocol-critical check.
57    ///
58    /// # Errors
59    ///
60    /// Returns an `OwnershipError` if the capability is stale or lacks session scope.
61    pub fn issue_readiness_witness(
62        &self,
63        vm: &mut VM,
64        predicate_ref: impl Into<String>,
65    ) -> Result<ReadinessWitness, OwnershipError> {
66        vm.sessions_mut()
67            .issue_readiness_witness(&self.capability, predicate_ref)
68    }
69
70    /// Consume a previously issued readiness witness exactly once.
71    ///
72    /// # Errors
73    ///
74    /// Returns an `OwnershipError` if the witness is stale, forged, mismatched, or reused.
75    pub fn consume_readiness_witness(
76        &self,
77        vm: &mut VM,
78        witness: &ReadinessWitness,
79    ) -> Result<(), OwnershipError> {
80        vm.sessions_mut()
81            .consume_readiness_witness(&self.capability, witness)
82    }
83
84    /// Begin an explicit ownership transfer from this handle.
85    ///
86    /// # Errors
87    ///
88    /// Returns an `OwnershipError` if the capability is stale.
89    pub fn begin_transfer(
90        &self,
91        vm: &mut VM,
92        new_owner_id: impl Into<String>,
93        new_scope: OwnershipScope,
94    ) -> Result<OwnershipReceipt, OwnershipError> {
95        vm.sessions_mut()
96            .begin_ownership_transfer(&self.capability, new_owner_id, new_scope)
97    }
98
99    /// Commit an explicit ownership transfer and return the refreshed handle.
100    ///
101    /// # Errors
102    ///
103    /// Returns an `OwnershipError` if the receipt is stale or mismatched.
104    pub fn commit_transfer(
105        &self,
106        vm: &mut VM,
107        receipt: &OwnershipReceipt,
108    ) -> Result<Self, OwnershipError> {
109        let capability = vm.sessions_mut().commit_ownership_transfer(receipt)?;
110        Ok(Self::new(receipt.session_id, capability))
111    }
112
113    /// Attenuate the handle scope and return the refreshed capability.
114    ///
115    /// # Errors
116    ///
117    /// Returns an `OwnershipError` if the capability is stale or transfer-pending.
118    pub fn attenuate_scope(
119        &self,
120        vm: &mut VM,
121        new_scope: OwnershipScope,
122    ) -> Result<Self, OwnershipError> {
123        let capability = vm
124            .sessions_mut()
125            .attenuate_ownership_scope(&self.capability, new_scope)?;
126        Ok(Self::new(self.session_id, capability))
127    }
128
129    /// Release the live ownership claim for this handle.
130    ///
131    /// # Errors
132    ///
133    /// Returns an `OwnershipError` if the capability is stale.
134    pub fn release(&self, vm: &mut VM) -> Result<(), OwnershipError> {
135        vm.sessions_mut().release_ownership(&self.capability)
136    }
137
138    /// Fault the session because the current owner died.
139    ///
140    /// # Errors
141    ///
142    /// Returns an `OwnershipError` if the live owner no longer matches this handle.
143    pub fn mark_owner_died(&self, vm: &mut VM) -> Result<CancellationWitness, OwnershipError> {
144        vm.mark_owner_died(self.session_id, &self.capability.owner_id)
145    }
146
147    /// Cancel the session because this transfer was abandoned.
148    ///
149    /// # Errors
150    ///
151    /// Returns an `OwnershipError` if the receipt no longer matches the live staged transfer.
152    pub fn cancel_abandoned_transfer(
153        &self,
154        vm: &mut VM,
155        receipt: &OwnershipReceipt,
156    ) -> Result<CancellationWitness, OwnershipError> {
157        vm.cancel_abandoned_transfer(receipt)
158    }
159}
160
161impl VM {
162    /// Preferred choreography open path that immediately claims session ownership.
163    ///
164    /// Third-party host integrations use this owned helper so subsequent
165    /// session-local mutation flows through an explicit ownership capability.
166    ///
167    /// # Errors
168    ///
169    /// Returns a `VMError` if the choreography cannot be loaded or the initial
170    /// ownership claim fails.
171    pub fn load_choreography_owned(
172        &mut self,
173        image: &CodeImage,
174        owner_id: impl Into<String>,
175    ) -> Result<OwnedSession, VMError> {
176        let sid = self.load_choreography(image)?;
177        let capability = self
178            .sessions_mut()
179            .claim_ownership(sid, owner_id, OwnershipScope::Session)
180            .map_err(|err| VMError::OwnershipContract(format!("{err:?}")))?;
181        Ok(OwnedSession::new(sid, capability))
182    }
183}