# `Statifier.Session.Inbox`
[🔗](https://github.com/riddler/statifier-ex/blob/v2.0.0/lib/statifier/session/inbox.ex#L1)

The waiting external events, plus Appendix D's `isCancelEvent` check.

ADR-0002 mechanical deviation, ADR-0003: Appendix D's `mainEventLoop` owns
both queues and blocks on `externalQueue.dequeue()`, checking
`isCancelEvent/1` on what it dequeues. `Statifier.MachineState` owns only the
internal queue, so the waiting external events and the cancel check live
here, in the driver. The semantics of processing one external event are
unchanged; only the storage of the waiting ones moves.

Appendix D's `isCancelEvent` is described by spec 6.4 as platform specific
("here we assume it's a special event we receive"). This platform makes the
cancel a distinct *queue entry* rather than a reserved event name, so no
document can raise one by writing that name, and `cancel_event?/1` is a
structural match.

# `entry`

```elixir
@type entry() ::
  {:event, Statifier.Event.t()}
  | {:invoked_event, String.t(), Statifier.Event.t()}
  | :cancel
```

One waiting entry: an external event, an external event one of *this*
session's own invocations delivered, or the cancel marker.

`{:invoked_event, invoke_id, event}` is a separate kind rather than a flag
on `event` because 6.4.3's discard is a property of *where the entry came
from*, not of the event: 6.4.2 requires an autoforwarded copy to preserve
every 5.10.1 field, `invokeid` included, so an event forwarded down to a
child still carries the *sibling* invocation's id in a frame where it
names nothing. Keying the discard on the entry kind is what keeps a
forwarded sibling event deliverable while a cancelled invocation's own
queued events still drop (`Statifier.Session`'s `handle_continue(:drain, _)`).

# `t`

```elixir
@opaque t()
```

# `cancel_event?`

```elixir
@spec cancel_event?(entry :: entry()) :: boolean()
```

Appendix D's `isCancelEvent/1`: true only for the cancel marker itself.

# `enqueue_cancel`

```elixir
@spec enqueue_cancel(inbox :: t()) :: t()
```

Appends the cancel marker to the back of the inbox.

# `enqueue_event`

```elixir
@spec enqueue_event(inbox :: t(), event :: Statifier.Event.t()) :: t()
```

Appends `event` to the back of the inbox, wrapped as an `:event` entry.

# `enqueue_invoked_event`

```elixir
@spec enqueue_invoked_event(
  inbox :: t(),
  invoke_id :: String.t(),
  event :: Statifier.Event.t()
) :: t()
```

Appends `event` as an entry originating from this session's own invocation
`invoke_id` - the child-to-parent direction only
(`<send target="#_parent">` and `done.invoke.<invokeid>`). Everything else,
autoforwarded copies included, goes through `enqueue_event/2`.

# `new`

```elixir
@spec new() :: t()
```

An empty inbox.

# `next`

```elixir
@spec next(inbox :: t()) :: {:ok, entry(), t()} | :empty
```

Dequeues the front entry, mirroring Appendix D's
`externalQueue.dequeue()`. `:empty` when nothing is waiting - there is no
blocking wait here; the caller decides what to do while idle.

# `pending_events`

```elixir
@spec pending_events(inbox :: t()) :: [Statifier.Event.t()]
```

The waiting events, front to back, cancel markers dropped - the
`internal_events/1`-shaped inspection view
(`Statifier.MachineState.internal_events/1`). No code outside this module
touches `:queue` directly.

# `size`

```elixir
@spec size(inbox :: t()) :: non_neg_integer()
```

The number of waiting entries, events and cancel markers both.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
