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}