orbit-stream 0.6.1

Bounded byte streams and paired exchanges over shared memory for process fleets.
Documentation
# Orbit exchange protocol

`orbit-stream::exchange` is a bounded, same-host transport between two process
lifetimes. It defines transport state and ownership, not HTTP, FastCGI, RPC, or
task semantics.

## Physical resources

Every `Exchanges` table has one duplex control table plus either:

- one payload arena shared by both directions; or
- two directional payload arenas with independent geometry and credit.

`ExchangeSpec::new(control, payload)` selects the shared layout.
`ExchangeSpec::directional(control, a_to_b, b_to_a)` selects the directional
layout. A protocol chooses the latter when the directions need different slot
sizes, capacities, or isolation. The choice changes physical SHM geometry but
does not change the endpoint API.

An arena is fleet-wide. Each node allocates only from its own lane. A chunk is
one immutable publication spanning one or more contiguous slots in that lane;
its descriptor carries the arena kind, owner node, first slot, slot count,
allocation generation, and exact byte length. Returning a received chunk gives
the whole extent back and wakes only its owning producer node.

Slot payload size and lane width are physical geometry. Chunk width is runtime
policy: for a selected slot count `n`, full publication capacity is exactly
`n * slot_payload_bytes`. `Sender::chunk_geometry()` exposes the geometry of
that sender's actual direction, so request and response policy remain
independent when their arenas differ. The helper rejects zero and slot counts
outside the local lane; an adapter may choose `n` per invocation without
changing the SHM contract.

## Data path, rendezvous, and waking

Control frames and payload bytes live in the named SHM tables above. Payload
never travels through a socket: a `DATA` frame publishes an arena descriptor,
and the peer reads the referenced slots directly. This SHM data path is the
source of the transport's throughput advantage over a Unix byte stream.

Peers rendezvous by name, not by descriptor passing. The fleet opens each SHM
segment by name, while the exchange ticket identifies the control slot and
lifetime to claim. No inherited file descriptor or `SCM_RIGHTS` exchange is
required. A process that joined independently can open the same resources as a
forked sibling; the name is the only shared rendezvous material.

Readiness remains a hint over that authoritative SHM state. A blocking caller
parks on a shared generation word. An async caller with a current runtime uses
the named Unix-datagram bell: each process binds a socket name derived from the
segment, node and uid, then the runtime waits on that socket directly. Before
parking, the listener sets `ARMED` in its node's `listening` word. A writer
that observes `ARMED` clears it and sends one byte, waking the runtime worker
in one hop; where no runtime is current, a driver thread waits on the shared
word and relays the wake to the task in two hops.

The gate is important under load. A reader that is already draining SHM is not
armed, so writers send no datagram. Wake syscalls track idle-to-busy
transitions rather than payload throughput. The design is therefore a hybrid,
not a claim that sockets disappeared: SHM carries all control and payload
data, and a one-byte datagram is used only as the async wake wire when a
runtime is present.

On Linux the bell uses an abstract socket name. Other Unix targets use a
socket file below `/tmp/orbit-bell-<uid>/` and remove it on drop. The listener
currently stays on the runtime that was current when the first waker was
registered; multi-runtime handoff if that runtime shuts down is not yet
defined.

## Endpoint and direction

`Exchanges::create()` creates side A and an `ExchangeTicket`.
`Exchanges::open_peer(ticket)` opens side B.

Both sides expose the same capabilities:

```text
side A: Sender(A -> B), Receiver(B -> A)
side B: Sender(B -> A), Receiver(A -> B)
```

Sender and receiver are per-exchange capabilities, not permanent process or
worker roles. One worker may simultaneously hold side A of one exchange, side
B of another, and many senders and receivers from unrelated exchanges.

Applications may name A-to-B "request" and B-to-A "response", but those names
are not part of the transport ABI. Another application may assign the opposite
meaning or use both directions symmetrically.

## Direction state machine

Each direction independently follows:

```text
New -> START -> DATA* -> FIN
                    \-> RESET
```

- `START` occurs exactly once and may carry optional metadata.
- `DATA` carries one payload descriptor per application chunk.
- `FIN` is clean completion for that direction.
- `RESET(code)` is terminal failure for that direction.

The two state machines are independent. Either side may send `START` first; one
direction may produce data or finish while the other remains open. This is what
permits early response, uploads concurrent with responses, and fully duplex
protocols without teaching Orbit their sequencing rules.

Control publication is the visibility boundary. A producer reserves and fills
payload slots privately, then `commit()` publishes exactly one complete
descriptor. Dropping an uncommitted reservation publishes nothing and returns
its credit. If control publication fails, the payload reservation is returned
and the whole chunk may be retried.

## Backpressure

Payload credit is acquired before reading or producing the next external
chunk. When the local node lane has no suitable extent, `poll_ready` returns
`Pending` and blocking readiness parks; neither path polls. Readiness tokens may
coalesce, so a wake means "inspect current credit/state", not "one chunk became
available".

An adapter must not read unbounded data from its origin and queue it outside
the arena. The intended order is:

```text
reserve SHM extent -> read/encode into it -> commit descriptor
```

This makes the configured arena credit the application backpressure boundary.
Kernel, TLS, and protocol-library buffers still exist and must be included in
end-to-end buffering claims.

## Experimental pool session binding

With the optional `orbit-pool` integration, a remote resource use is bound to
an exchange as follows:

```text
caller: Pool::acquire
  LocalReuse -> accept locally and use the object directly
  RemoteReuse -> open_exchange_session(lease, exchanges, metadata)

owner: poll_accept_exchange_session
  open ticket -> decode lease -> validate exact resource generation and fence
  -> return Execution + side B + application metadata
```

The lease frame precedes application metadata inside side A's `START` payload.
The owner accepts it before later application bytes are exposed. Dropping the
returned `Execution` releases resource capacity. Payload credit is checked
before the remote reservation and is readiness-driven when full. If creating or
offering the session still fails, the helper cancels the exact pending fence
only when no offer was successfully published. Once delivery is possible or
uncertain, the caller cannot infer completion; the owner ages abandoned
reservations through `Pool::reconcile`.

The pool and stream generations remain deliberately distinct:

- resource generation identifies one physical resource lifetime;
- exchange/stream generation identifies one transient session lifetime;
- payload allocation generation identifies one slot-extent publication.

No replacement process, new master, or new exchange implies that an older
resource or SHM table is quiescent.

## Adapter obligations

A protocol adapter owns:

- the application metadata codec;
- the meaning of A-to-B and B-to-A;
- conversion between protocol frames and payload chunks;
- mapping a validated `ResourceId` to the actual local object;
- cancellation, timeout, and clean/dirty reuse policy for that object;
- the owner's `reconcile` cadence for abandoned reservations.

It does not implement SHM allocation, stream generations, credit wakeups,
leases, or fencing. HTTP and FastCGI adapters may share orchestration helpers,
but neither protocol is encoded into `orbit-stream` or `orbit-pool`.

## Lifecycle rules

`node_dead(node, incarnation)` is valid only after that exact process lifetime
is confirmed dead. `reset_all` is quiescent-owner maintenance. Attaching a new
master or worker is not proof of quiescence. Unlink removes a name, not existing
mappings, and is not an ordinary shutdown operation.