Skip to main content

behavior/effects/
actions.rs

1//! The explicit result of one actor behavior transition.
2
3use super::sending::{
4    ClassifySettlement, InterpretItem, InterpretSends, Interpretation, InterpreterFault,
5    ItemSettlement, SendEffects, SendInput, SendSettlements, SettledItem, SettlementStatus,
6    SourceAdmission, SourceCustody,
7};
8use crate::actor::{
9    Address, BirthMode, Births, ChildCreationProduct, ChildHead, ChildNamespaceExhausted,
10    CreateChild, CreationRejection, Creations, DispatchBirth, NoBirths, RoutedCreation,
11};
12use crate::next::{Never, Step, Stopped};
13
14pub type Become<Ph = Never> = Step<Ph, Stopped>;
15
16/// Complete owned settlement of one [`Actions`] value.
17///
18/// Creations retain their declared vector order, sends retain their named
19/// product shape, and `become_` is the exact verdict already committed by the
20/// pure behavior fold. The enclosing [`Interpretation`] records whether the
21/// interpreter completed or corrupted while retaining this entire shape.
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct ActionSettlement<Creations, Sends, Ph> {
24    pub creations: Creations,
25    pub sends: Sends,
26    pub become_: Become<Ph>,
27}
28
29/// Final settlement of the creation leg of one [`Actions`] value.
30#[doc(hidden)]
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub enum CreationSettlement<Requests, Settlements> {
33    /// The whole batch was routed and every child has an exact settlement.
34    Settled(Settlements),
35    /// Route preparation rejected the complete unchanged batch.
36    Rejected {
37        creations: Requests,
38        reason: ChildNamespaceExhausted,
39    },
40    /// Route preparation corrupted before transferring any request.
41    Corrupt {
42        creations: Requests,
43        fault: InterpreterFault,
44    },
45}
46
47/// Static complete-settlement product selected by one concrete action type.
48///
49/// This projection performs no interpretation and names no runtime. It lets a
50/// lifecycle host retain the exact result type of an action without rebuilding
51/// its creation or send products.
52pub trait ActionSettlements {
53    type Settlements;
54}
55
56impl<A, Ph, Sends, Birth> ActionSettlements for Actions<A, Ph, Sends, Birth>
57where
58    A: Address,
59    Sends: SendSettlements,
60    Birth: CreationSettlements<A>,
61{
62    type Settlements = ActionSettlement<Birth::Settlements, Sends::Settlements, Ph>;
63}
64
65impl<Requests, Settlements> ClassifySettlement for CreationSettlement<Requests, Settlements>
66where
67    Settlements: ClassifySettlement,
68{
69    fn settlement_status(&self) -> SettlementStatus {
70        match self {
71            Self::Settled(settlements) => settlements.settlement_status(),
72            Self::Rejected { .. } => SettlementStatus::Rejected,
73            Self::Corrupt { .. } => SettlementStatus::Corrupt,
74        }
75    }
76}
77
78impl<Item> ClassifySettlement for Creations<Item>
79where
80    Item: ClassifySettlement,
81{
82    fn settlement_status(&self) -> SettlementStatus {
83        let mut status = SettlementStatus::Accepted;
84        for item in self.iter() {
85            status = status.combine(item.settlement_status());
86        }
87        status
88    }
89}
90
91/// Static creation-settlement product selected by one birth mode.
92#[doc(hidden)]
93pub trait CreationSettlements<A: Address>: BirthMode {
94    type Settlements: ClassifySettlement;
95}
96
97impl<A: Address> CreationSettlements<A> for NoBirths {
98    type Settlements = Creations<Never>;
99}
100
101impl<A, C> CreationSettlements<A> for Births<C>
102where
103    A: Address,
104    C: ChildCreationProduct<A, ChildHead>,
105{
106    type Settlements = CreationSettlement<
107        Creations<CreateChild<A, C>>,
108        Creations<
109            SettledItem<
110                RoutedCreation<A, C>,
111                ItemSettlement<
112                    RoutedCreation<A, C>,
113                    <C as ChildCreationProduct<A, ChildHead>>::Result,
114                    CreationRejection,
115                    Never,
116                >,
117            >,
118        >,
119    >;
120}
121
122/// One complete creation batch returned to its live creator.
123///
124/// The value retains either every routed child settlement or the entire
125/// unchanged batch rejected during route preparation. It is interpreter-facing
126/// custody, not an application protocol or another creation operation.
127#[doc(hidden)]
128#[must_use = "a returned creation batch must be admitted or retained"]
129pub struct CreationsSettled<A, C>
130where
131    A: Address,
132    C: ChildCreationProduct<A, ChildHead>,
133{
134    settlement: <Births<C> as CreationSettlements<A>>::Settlements,
135}
136
137impl<A, C> CreationsSettled<A, C>
138where
139    A: Address,
140    C: ChildCreationProduct<A, ChildHead>,
141{
142    #[must_use]
143    pub const fn new(settlement: <Births<C> as CreationSettlements<A>>::Settlements) -> Self {
144        Self { settlement }
145    }
146
147    #[must_use]
148    pub fn into_settlement(self) -> <Births<C> as CreationSettlements<A>>::Settlements {
149        self.settlement
150    }
151}
152
153/// Generic admission of one complete creation result to its live creator.
154///
155/// `NoBirths` needs no host capability. `Births<C>` uses the same source
156/// admission law as returning send results. Closed admission returns the
157/// complete batch unchanged for parent or root custody.
158#[doc(hidden)]
159pub trait CreationCustody<A, Host, RootEvent>: CreationSettlements<A>
160where
161    A: Address,
162{
163    fn offer_creation(
164        settlement: Self::Settlements,
165        host: &mut Host,
166    ) -> impl core::future::Future<Output = SourceCustody<Self::Settlements>> + Send;
167}
168
169impl<A, Host, RootEvent> CreationCustody<A, Host, RootEvent> for NoBirths
170where
171    A: Address,
172{
173    fn offer_creation(
174        settlement: Self::Settlements,
175        _: &mut Host,
176    ) -> impl core::future::Future<Output = SourceCustody<Self::Settlements>> + Send {
177        core::future::ready(SourceCustody::Open(settlement))
178    }
179}
180
181impl<A, C, Host, RootEvent> CreationCustody<A, Host, RootEvent> for Births<C>
182where
183    A: Address,
184    A::Nonce: Send,
185    C: ChildCreationProduct<A, ChildHead> + Send,
186    <C as ChildCreationProduct<A, ChildHead>>::Result: Send,
187    Host: SourceAdmission<RootEvent, Births<C>, CreationsSettled<A, C>>,
188    RootEvent: crate::EventIngress<Births<C>, CreationsSettled<A, C>>,
189{
190    fn offer_creation(
191        settlement: Self::Settlements,
192        host: &mut Host,
193    ) -> impl core::future::Future<Output = SourceCustody<Self::Settlements>> + Send {
194        async move {
195            match host.admit_source(CreationsSettled::new(settlement)).await {
196                Ok(()) => SourceCustody::Open(CreationSettlement::Settled(Creations::empty())),
197                Err(returned) => SourceCustody::Closed(returned.into_settlement()),
198            }
199        }
200    }
201}
202
203/// Generic interpretation of the one creation leg selected by a birth mode.
204///
205/// `NoBirths` needs no runtime capability. `Births<C>` performs one real batch
206/// route attempt followed by independent child establishment in declared order.
207#[doc(hidden)]
208pub trait InterpretCreations<A, Interpreter, RootEvent, Path>: CreationSettlements<A>
209where
210    A: Address,
211{
212    fn interpret_creations(
213        creations: Creations<CreateChild<A, Self::Child>>,
214        interpreter: &mut Interpreter,
215    ) -> impl core::future::Future<Output = Interpretation<Self::Settlements>> + Send;
216}
217
218impl<A, Interpreter, RootEvent, Path> InterpretCreations<A, Interpreter, RootEvent, Path>
219    for NoBirths
220where
221    A: Address,
222{
223    fn interpret_creations(
224        _: Creations<CreateChild<A, Never>>,
225        _: &mut Interpreter,
226    ) -> impl core::future::Future<Output = Interpretation<Self::Settlements>> + Send {
227        core::future::ready(Interpretation::Complete(Creations::empty()))
228    }
229}
230
231impl<A, C, Interpreter, RootEvent, Path> InterpretCreations<A, Interpreter, RootEvent, Path>
232    for Births<C>
233where
234    A: Address,
235    A::Nonce: Send,
236    C: ChildCreationProduct<A, ChildHead> + DispatchBirth<A, Interpreter> + Send,
237    <C as ChildCreationProduct<A, ChildHead>>::Result: Send,
238    Interpreter: InterpretItem<Creations<CreateChild<A, C>>, RootEvent, Path> + Send,
239{
240    fn interpret_creations(
241        creations: Creations<CreateChild<A, C>>,
242        interpreter: &mut Interpreter,
243    ) -> impl core::future::Future<Output = Interpretation<Self::Settlements>> + Send {
244        async move {
245            let routed = match interpreter.interpret_item(creations).await {
246                ItemSettlement::Accepted(routed) => routed,
247                ItemSettlement::Rejected { item, reason } => {
248                    return Interpretation::Complete(CreationSettlement::Rejected {
249                        creations: item,
250                        reason,
251                    });
252                }
253                ItemSettlement::Blocked { prerequisite, .. } => match prerequisite {},
254                ItemSettlement::Corrupt { item, fault } => {
255                    return Interpretation::Corrupt(CreationSettlement::Corrupt {
256                        creations: item,
257                        fault,
258                    });
259                }
260            };
261
262            let mut remaining = routed.into_iter();
263            let mut settled = Vec::with_capacity(remaining.len());
264            while let Some(creation) = remaining.next() {
265                let (creation, route) = creation.into_parts();
266                let (id, child, kind) = creation.into_parts();
267                let settlement = child.dispatch_birth(id, route, kind, interpreter).await;
268                match settlement {
269                    corrupt @ ItemSettlement::Corrupt { .. } => {
270                        settled.push(SettledItem::Attempted(corrupt));
271                        settled.extend(remaining.map(SettledItem::Unattempted));
272                        return Interpretation::Corrupt(CreationSettlement::Settled(
273                            Creations::from_items(settled),
274                        ));
275                    }
276                    creation => settled.push(SettledItem::Attempted(creation)),
277                }
278            }
279            Interpretation::Complete(CreationSettlement::Settled(Creations::from_items(settled)))
280        }
281    }
282}
283
284impl<Creations, Sends, Ph> ClassifySettlement for ActionSettlement<Creations, Sends, Ph>
285where
286    Creations: ClassifySettlement,
287    Sends: ClassifySettlement,
288{
289    fn settlement_status(&self) -> SettlementStatus {
290        self.creations
291            .settlement_status()
292            .combine(self.sends.settlement_status())
293    }
294}
295
296/// Capability to append one communication at a statically selected lane while
297/// preserving every other actor-transition effect.
298pub trait AppendSend<Input, Path>: Sized {
299    /// Append `input` exactly once and preserve creations and the next verdict.
300    #[must_use]
301    fn append_send(self, input: Input) -> Self;
302}
303
304/// Bombay's typed realization of the actor transition effects: communications,
305/// fresh actor creation, and next behavior or termination.
306///
307/// [`Actions::interpret`] resolves every fresh creation in `creates` before the
308/// named `sends` product. A committed resolution installs and binds the child;
309/// a semantic rejection binds nothing but remains an accepted interpretation
310/// receipt. This ordering lets a same-action [`crate::ChildDelivery`] or typed
311/// creation-observation request use the authoritative result rather than
312/// Behavior's intent. Each child-operation interpreter returns `Blocked` only
313/// when its exact binding prerequisite rejected, while independent later items
314/// are still attempted. Interpreter corruption retains the factual prefix and
315/// every exact remaining value. No later creation may inherit a failed binding.
316/// Creation order is vector order, and named send products declare their own
317/// stable order. Constructing a value remains pure.
318#[must_use = "actor transition effects must be interpreted, inspected, or retained"]
319pub struct Actions<A: Address, Ph, Sends, Birth: BirthMode> {
320    pub sends: Sends,
321    pub creates: Creations<CreateChild<A, Birth::Child>>,
322    pub become_: Become<Ph>,
323}
324
325impl<A, Ph, Sends, Birth> core::fmt::Debug for Actions<A, Ph, Sends, Birth>
326where
327    A: Address + core::fmt::Debug,
328    A::Nonce: core::fmt::Debug,
329    Ph: core::fmt::Debug,
330    Sends: core::fmt::Debug,
331    Birth: BirthMode,
332    Birth::Child: core::fmt::Debug,
333{
334    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
335        formatter
336            .debug_struct("Actions")
337            .field("sends", &self.sends)
338            .field("creates", &self.creates)
339            .field("become", &self.become_)
340            .finish()
341    }
342}
343
344impl<A, Ph, Sends, Birth> PartialEq for Actions<A, Ph, Sends, Birth>
345where
346    A: Address + PartialEq,
347    A::Nonce: PartialEq,
348    Ph: PartialEq,
349    Sends: PartialEq,
350    Birth: BirthMode,
351    Birth::Child: PartialEq,
352{
353    fn eq(&self, other: &Self) -> bool {
354        self.sends == other.sends && self.creates == other.creates && self.become_ == other.become_
355    }
356}
357
358impl<A, Ph, Sends, Birth> Eq for Actions<A, Ph, Sends, Birth>
359where
360    A: Address + Eq,
361    A::Nonce: Eq,
362    Ph: Eq,
363    Sends: Eq,
364    Birth: BirthMode,
365    Birth::Child: Eq,
366{
367}
368
369impl<A: Address, Ph, Sends, Birth: BirthMode> Actions<A, Ph, Sends, Birth> {
370    /// Transform only the send effects, preserving creation order and the
371    /// next-behavior verdict exactly.
372    #[must_use]
373    pub fn map_sends<Mapped>(
374        self,
375        map: impl FnOnce(Sends) -> Mapped,
376    ) -> Actions<A, Ph, Mapped, Birth> {
377        Actions {
378            sends: map(self.sends),
379            creates: self.creates,
380            become_: self.become_,
381        }
382    }
383
384    /// Transform only the next-behavior verdict, preserving sends and
385    /// creation order exactly.
386    #[must_use]
387    pub fn map_become<NextPh>(
388        self,
389        map: impl FnOnce(Become<Ph>) -> Become<NextPh>,
390    ) -> Actions<A, NextPh, Sends, Birth> {
391        Actions {
392            sends: self.sends,
393            creates: self.creates,
394            become_: map(self.become_),
395        }
396    }
397
398    /// Append one communication to its statically selected send lane.
399    ///
400    /// This is a pure transformation of the send leg. It preserves creation
401    /// order and the exact continue, phase-change, or termination verdict.
402    /// `Path` is compile-time lane evidence; no runtime lookup is performed.
403    #[must_use]
404    pub fn with_send<Input, Path>(mut self, input: Input) -> Self
405    where
406        Sends: SendInput<Input, Path>,
407    {
408        <Sends as SendInput<Input, Path>>::emit(&mut self.sends, input);
409        self
410    }
411
412    /// Interpret this complete action value using one statically typed runtime.
413    ///
414    /// Creation items are attempted in vector order before the named send
415    /// product. Lawful creation rejection or blocking is retained and does not
416    /// suppress sends; concrete child-operation interpreters decide whether an
417    /// exact binding prerequisite was satisfied. Interpreter corruption stops
418    /// traversal and retains every later creation and the complete send product
419    /// as unattempted. The next-behavior verdict is never reconstructed.
420    pub async fn interpret<Interpreter, RootEvent, Path>(
421        self,
422        interpreter: &mut Interpreter,
423    ) -> Interpretation<
424        ActionSettlement<Birth::Settlements, <Sends as SendSettlements>::Settlements, Ph>,
425    >
426    where
427        Ph: Send,
428        Sends: InterpretSends<Interpreter, RootEvent, Path>,
429        Birth: InterpretCreations<A, Interpreter, RootEvent, Path>,
430        Interpreter: Send,
431    {
432        let Actions {
433            sends,
434            creates,
435            become_,
436        } = self;
437        let creations = match Birth::interpret_creations(creates, interpreter).await {
438            Interpretation::Complete(creations) => creations,
439            Interpretation::Corrupt(creations) => {
440                return Interpretation::Corrupt(ActionSettlement {
441                    creations,
442                    sends: Sends::unattempted(sends),
443                    become_,
444                });
445            }
446        };
447
448        match Sends::interpret(sends, interpreter).await {
449            Interpretation::Complete(sends) => Interpretation::Complete(ActionSettlement {
450                creations,
451                sends,
452                become_,
453            }),
454            Interpretation::Corrupt(sends) => Interpretation::Corrupt(ActionSettlement {
455                creations,
456                sends,
457                become_,
458            }),
459        }
460    }
461}
462
463impl<A, Ph, Sends, Birth, Input, Path> AppendSend<Input, Path> for Actions<A, Ph, Sends, Birth>
464where
465    A: Address,
466    Birth: BirthMode,
467    Sends: SendInput<Input, Path>,
468{
469    fn append_send(self, input: Input) -> Self {
470        self.with_send::<Input, Path>(input)
471    }
472}
473
474impl<A: Address, Ph, Sends: SendEffects, Birth: BirthMode> Actions<A, Ph, Sends, Birth> {
475    #[must_use]
476    pub const fn new(
477        sends: Sends,
478        creates: Creations<CreateChild<A, Birth::Child>>,
479        become_: Become<Ph>,
480    ) -> Self {
481        Self {
482            sends,
483            creates,
484            become_,
485        }
486    }
487
488    #[must_use]
489    pub fn just(become_: Become<Ph>) -> Self {
490        Self {
491            sends: Sends::empty(),
492            creates: Creations::empty(),
493            become_,
494        }
495    }
496
497    #[must_use]
498    pub fn cont() -> Self {
499        Self::just(Step::Continue)
500    }
501    #[must_use]
502    pub fn stop() -> Self {
503        Self::just(Step::Stop(Stopped))
504    }
505    #[must_use]
506    pub fn goto(phase: Ph) -> Self {
507        Self::just(Step::Goto(phase))
508    }
509
510    /// Continue after emitting the complete declared send product.
511    #[must_use]
512    pub fn send(sends: Sends) -> Self {
513        Self::new(sends, Creations::empty(), Step::Continue)
514    }
515
516    /// Continue after staging the complete declared creation product.
517    #[must_use]
518    pub fn create(creates: Creations<CreateChild<A, Birth::Child>>) -> Self {
519        Self::new(Sends::empty(), creates, Step::Continue)
520    }
521}
522
523impl<A: Address, Ph, Sends, Birth: BirthMode>
524    From<(Sends, Creations<CreateChild<A, Birth::Child>>, Become<Ph>)>
525    for Actions<A, Ph, Sends, Birth>
526{
527    fn from(
528        (sends, creates, become_): (Sends, Creations<CreateChild<A, Birth::Child>>, Become<Ph>),
529    ) -> Self {
530        Self {
531            sends,
532            creates,
533            become_,
534        }
535    }
536}
537
538pub type Acted<A, Ph, Sends, Birth, E> = Result<Actions<A, Ph, Sends, Birth>, E>;
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543    use crate::{Births, CreationSequence, MailAddr, NoBirths, Own};
544
545    fn two_ids() -> (crate::CreationId, crate::CreationId) {
546        let mut sequence = CreationSequence::new();
547        let first = sequence.issue().expect("the first creation ID exists");
548        let second = sequence.issue().expect("the second creation ID exists");
549        (first, second)
550    }
551
552    #[test]
553    fn equality_and_debug_cover_every_named_effect_leg() {
554        type Plain = Actions<MailAddr, u8, Vec<u8>, NoBirths>;
555        type Creating = Actions<MailAddr, Never, Vec<u8>, Births<u8>>;
556
557        let value = Plain::new(vec![1], Creations::empty(), Step::Goto(3));
558        assert_eq!(
559            value,
560            Plain::new(vec![1], Creations::empty(), Step::Goto(3))
561        );
562        assert_ne!(
563            value,
564            Plain::new(vec![2], Creations::empty(), Step::Goto(3))
565        );
566        assert_ne!(
567            value,
568            Plain::new(vec![1], Creations::empty(), Step::Goto(4))
569        );
570        assert_ne!(
571            value,
572            Plain::new(vec![1], Creations::empty(), Step::Continue)
573        );
574        assert_eq!(
575            format!("{value:?}"),
576            "Actions { sends: [1], creates: Creations { items: [] }, become: Goto(3) }"
577        );
578
579        let (first, second) = two_ids();
580        let created = Creating::new(
581            Vec::new(),
582            Creations::one(CreateChild::birth(first, 9)),
583            Step::Continue,
584        );
585        let other_creation = Creating::new(
586            Vec::new(),
587            Creations::one(CreateChild::birth(second, 9)),
588            Step::Continue,
589        );
590        assert_ne!(created, other_creation);
591    }
592
593    #[test]
594    fn mapping_sends_preserves_creation_order_and_verdict() {
595        let (first, second) = two_ids();
596        let actions: Actions<MailAddr, u8, Vec<u8>, Births<()>> = Actions::new(
597            vec![1, 2],
598            Creations::one(CreateChild::birth(first, ())).and(CreateChild::replacement(
599                second,
600                first,
601                (),
602            )),
603            Step::Goto(7),
604        );
605
606        let mapped = actions.map_sends(|sends| sends.len());
607        assert_eq!(mapped.sends, 2);
608        assert_eq!(
609            mapped
610                .creates
611                .iter()
612                .map(CreateChild::id)
613                .collect::<Vec<_>>(),
614            [first, second]
615        );
616        assert!(matches!(mapped.become_, Step::Goto(7)));
617    }
618
619    #[test]
620    fn mapping_become_preserves_sends_and_creation_order() {
621        let (first, _) = two_ids();
622        let actions: Actions<MailAddr, u8, Vec<u8>, Births<()>> = Actions::new(
623            vec![1, 2],
624            Creations::one(CreateChild::birth(first, ())),
625            Step::Goto(7),
626        );
627
628        let mapped: Actions<MailAddr, Never, Vec<u8>, Births<()>> =
629            actions.map_become(|_| Step::Stop(Stopped));
630        assert_eq!(mapped.sends, [1, 2]);
631        assert_eq!(
632            mapped.creates.iter().next().map(CreateChild::id),
633            Some(first)
634        );
635        assert!(matches!(mapped.become_, Step::Stop(Stopped)));
636    }
637
638    #[test]
639    fn fluent_send_changes_only_the_selected_effect_leg() {
640        let (first, second) = two_ids();
641        let actions: Actions<MailAddr, u8, Vec<u8>, Births<()>> = Actions::new(
642            vec![1],
643            Creations::one(CreateChild::birth(first, ())).and(CreateChild::replacement(
644                second,
645                first,
646                (),
647            )),
648            Step::Goto(7),
649        )
650        .with_send::<_, Own>(2)
651        .with_send::<_, Own>(3);
652
653        assert_eq!(actions.sends, [1, 2, 3]);
654        assert_eq!(
655            actions
656                .creates
657                .iter()
658                .map(CreateChild::id)
659                .collect::<Vec<_>>(),
660            [first, second]
661        );
662        assert!(matches!(actions.become_, Step::Goto(7)));
663
664        let stopped: Actions<MailAddr, Never, Vec<u8>, NoBirths> =
665            Actions::stop().with_send::<_, Own>(5);
666        assert_eq!(stopped.sends, [5]);
667        assert!(matches!(stopped.become_, Step::Stop(Stopped)));
668    }
669}