Skip to main content

telltale_machine/
transfer_semantics.rs

1//! Shared transfer/delegation semantics used by cooperative and threaded ProtocolMachines.
2
3use std::collections::BTreeSet;
4
5use crate::coroutine::{Coroutine, Fault, Value};
6use crate::faults::{
7    transfer_fault_endpoint_not_owned, transfer_fault_expect_endpoint_register,
8    transfer_fault_expect_nat_target, transfer_fault_target_id_out_of_range,
9};
10use crate::instr::Endpoint;
11use crate::session::{OwnershipScope, SessionId};
12
13/// Decoded transfer request from one instruction instance.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct TransferRequest {
16    /// Endpoint to be transferred.
17    pub endpoint: Endpoint,
18    /// Destination coroutine id.
19    pub target_id: usize,
20}
21
22/// Typed receipt for one delegation/transfer handoff.
23#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
24pub struct DelegationReceipt {
25    /// Monotonic receipt id within one runtime.
26    pub receipt_id: u64,
27    /// Session carrying the delegated endpoint/fragment.
28    pub session: SessionId,
29    /// Endpoint being delegated.
30    pub endpoint: Endpoint,
31    /// Source coroutine id.
32    pub from_coro: usize,
33    /// Target coroutine id.
34    pub to_coro: usize,
35    /// Scope explicitly transferred by this delegation.
36    pub scope: OwnershipScope,
37}
38
39/// Auditable outcome for one delegation attempt.
40#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
41pub struct DelegationAuditRecord {
42    /// Scheduler tick at which the delegation was recorded.
43    pub tick: u64,
44    /// Typed transfer receipt.
45    pub receipt: DelegationReceipt,
46    /// Final transfer status.
47    pub status: DelegationStatus,
48    /// Optional failure detail when delegation did not commit.
49    pub reason: Option<String>,
50}
51
52/// Final state of one delegation attempt.
53#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
54pub enum DelegationStatus {
55    /// Delegation committed successfully.
56    Committed,
57    /// Delegation failed and was rolled back.
58    RolledBack,
59}
60
61/// Decode transfer endpoint/target operands and enforce source ownership.
62///
63/// # Errors
64///
65/// Returns a `Fault` if registers are invalid, type mismatches occur, or
66/// the source coroutine does not own the endpoint.
67pub fn decode_transfer_request(
68    coro: &Coroutine,
69    role: &str,
70    endpoint_reg: u16,
71    target_reg: u16,
72) -> Result<TransferRequest, Fault> {
73    let endpoint_val = coro
74        .regs
75        .get(usize::from(endpoint_reg))
76        .ok_or(Fault::OutOfRegisters)?
77        .clone();
78    let endpoint = match endpoint_val {
79        Value::Endpoint(endpoint) => endpoint,
80        _ => return Err(transfer_fault_expect_endpoint_register(role)),
81    };
82
83    let target_val = coro
84        .regs
85        .get(usize::from(target_reg))
86        .ok_or(Fault::OutOfRegisters)?
87        .clone();
88    let target_id = match target_val {
89        Value::Nat(v) => {
90            usize::try_from(v).map_err(|_| transfer_fault_target_id_out_of_range(role))?
91        }
92        _ => return Err(transfer_fault_expect_nat_target(role)),
93    };
94
95    if !coro.owned_endpoints.contains(&endpoint) {
96        return Err(transfer_fault_endpoint_not_owned());
97    }
98
99    Ok(TransferRequest {
100        endpoint,
101        target_id,
102    })
103}
104
105/// Build the explicit authority scope transferred for one endpoint handoff.
106#[must_use]
107pub fn delegation_scope_for_endpoint(endpoint: &Endpoint) -> OwnershipScope {
108    OwnershipScope::Fragments(BTreeSet::from([endpoint.role.clone()]))
109}
110
111/// Build a typed receipt for one endpoint handoff.
112#[must_use]
113pub fn delegation_receipt(
114    receipt_id: u64,
115    endpoint: Endpoint,
116    from_coro: usize,
117    to_coro: usize,
118) -> DelegationReceipt {
119    DelegationReceipt {
120        receipt_id,
121        session: endpoint.sid,
122        scope: delegation_scope_for_endpoint(&endpoint),
123        endpoint,
124        from_coro,
125        to_coro,
126    }
127}
128
129/// Validate the local coherence requirements for one delegation handoff.
130///
131/// This is the runtime analogue of requiring delegation to stay inside the
132/// intended session/fragment boundary rather than mutating ownership ad hoc.
133///
134/// # Errors
135///
136/// Returns a `Fault` if source/target coroutine session binding disagrees with
137/// the delegated endpoint session.
138pub fn validate_delegation_coherence(
139    source: &Coroutine,
140    target: &Coroutine,
141    endpoint: &Endpoint,
142    role: &str,
143) -> Result<(), Fault> {
144    if source.session_id != endpoint.sid {
145        return Err(Fault::Transfer {
146            message: format!(
147                "{role}: delegated endpoint {}:{} is not owned by source session {}",
148                endpoint.sid, endpoint.role, source.session_id
149            ),
150        });
151    }
152    if target.session_id != endpoint.sid {
153        return Err(Fault::Transfer {
154            message: format!(
155                "{role}: target coroutine session {} mismatches delegated endpoint session {}",
156                target.session_id, endpoint.sid
157            ),
158        });
159    }
160    Ok(())
161}
162
163/// Move endpoint ownership plus all endpoint-scoped progress/knowledge bundles.
164///
165/// If `target` is `None`, ownership is removed then restored on `source`.
166///
167/// # Errors
168///
169/// Returns a `Fault` if the source coroutine does not own the endpoint.
170pub fn move_endpoint_bundle(
171    endpoint: &Endpoint,
172    source: &mut Coroutine,
173    target: Option<&mut Coroutine>,
174) -> Result<(), Fault> {
175    if !source.owned_endpoints.contains(endpoint) {
176        return Err(transfer_fault_endpoint_not_owned());
177    }
178
179    let mut moved_tokens = Vec::new();
180    source.progress_tokens.retain(|token| {
181        if token.endpoint == *endpoint {
182            moved_tokens.push(token.clone());
183            false
184        } else {
185            true
186        }
187    });
188    let mut moved_knowledge = Vec::new();
189    source.knowledge_set.retain(|fact| {
190        if fact.endpoint == *endpoint {
191            moved_knowledge.push(fact.clone());
192            false
193        } else {
194            true
195        }
196    });
197    source.owned_endpoints.retain(|e| e != endpoint);
198
199    if let Some(target) = target {
200        target.owned_endpoints.push(endpoint.clone());
201        target.progress_tokens.extend(moved_tokens);
202        target.knowledge_set.extend(moved_knowledge);
203    } else {
204        source.owned_endpoints.push(endpoint.clone());
205        source.progress_tokens.extend(moved_tokens);
206        source.knowledge_set.extend(moved_knowledge);
207    }
208
209    Ok(())
210}
211
212/// Snapshot endpoint owners by coroutine id.
213#[must_use]
214pub fn endpoint_owner_ids(coroutines: &[Coroutine], endpoint: &Endpoint) -> Vec<usize> {
215    coroutines
216        .iter()
217        .filter_map(|coro| coro.owned_endpoints.contains(endpoint).then_some(coro.id))
218        .collect()
219}