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