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