Skip to main content

rings_node/extension/ext/
protocol.rs

1//! The pure core of an extension: the [`Protocol`] trait authors implement, its typed
2//! step algebra ([`Transition`]), and the decode boundary ([`Wire`] → `Event`).
3//!
4//! An extension owns **its own** effect algebra (`Protocol::Effect`) — the core defines
5//! no global `Effect` enum. Effects are interpreted by the extension's own
6//! [`Interpret`](super::Interpret) shell, which is handed a namespace-scoped capability
7//! ([`Scope`](super::Scope)) — `send`/`inject` confined to its own namespace. This is what
8//! keeps the effect set from becoming a global command bus: a new extension brings its own
9//! effects and its own interpreter without ever touching the core.
10
11use bytes::Bytes;
12use rings_core::dht::Did;
13
14/// The raw boundary input handed to [`Protocol::decode`]: an inbound message's authenticated
15/// sender, this node's own did, and the opaque payload bytes. `decode` turns this into the
16/// protocol's typed `Event` (or rejects it).
17///
18/// `from == me` marks a **self re-injected** message (a local command or an effect's result
19/// fed back into the router); any other `from` is a network message from an authenticated
20/// peer (a peer cannot forge `from`).
21pub struct Wire<'a> {
22    /// Authenticated sender of the message.
23    pub from: Did,
24    /// This node's own did (so `decode` can tell self-injection from a peer).
25    pub me: Did,
26    /// Opaque payload bytes; the protocol's own codec decides how to read them.
27    pub payload: &'a [u8],
28}
29
30/// The explicit result of a failed [`Protocol::decode`]: the input is malformed or not for
31/// this protocol. The router drops it (a defined no-op) instead of the pure step silently
32/// returning an unchanged state — so "valid no-op" and "undecodable input" are
33/// distinguishable.
34#[derive(Clone, Debug, PartialEq, Eq)]
35pub struct Reject(pub String);
36
37/// Read-only state carrier passed *into* a step: the protocol's current state `S` plus
38/// read-only node facts. The state is borrowed; a step returns the next state in its
39/// [`Transition`] rather than mutating in place.
40pub struct Ctx<'a, S> {
41    /// This node's DID.
42    pub did: Did,
43    /// Current protocol state (read-only here).
44    pub state: &'a S,
45}
46
47/// A locally re-injected message: the output of an effect fed back into the router as a
48/// fresh inbound, re-decoded by the target namespace's protocol. `Inbound ≅ (Namespace,
49/// from, payload)` — the same shape the router takes from the wire, so re-injection and
50/// inbound delivery share one path.
51///
52/// The fields are `pub(crate)`: only the router constructs an `Inbound` (from an interpreter's
53/// scoped re-inject, with the namespace and `from` it controls), so an extension shell cannot
54/// fabricate one with an arbitrary namespace or a forged remote `from`.
55#[derive(Clone, Debug, PartialEq, Eq)]
56pub(crate) struct Inbound {
57    /// Target protocol namespace.
58    pub(crate) namespace: String,
59    /// Sender to attribute the re-injected message to (`this node` for self-events).
60    pub(crate) from: Did,
61    /// Payload bytes (re-decoded by the target protocol).
62    pub(crate) payload: Bytes,
63}
64
65/// The output of a step: the next state and the protocol's own effects to run.
66/// `Transition (S, E) ≅ (S, [E])` — the Writer-over-State pair, now parameterized by the
67/// protocol's private effect type `E`. `PartialEq`/`Eq` are derived (when `S`/`E` are) so
68/// tests can compare whole transitions.
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct Transition<S, E> {
71    /// Next state.
72    pub state: S,
73    /// Effects to run, in order.
74    pub effects: Vec<E>,
75}
76
77impl<S, E> Transition<S, E> {
78    /// A pure transition with no effects: `pure s = (s, ε)`.
79    pub fn pure(state: S) -> Self {
80        Self {
81            state,
82            effects: Vec::new(),
83        }
84    }
85
86    /// A transition with effects.
87    pub fn with(state: S, effects: Vec<E>) -> Self {
88        Self { state, effects }
89    }
90}
91
92/// A protocol: a `namespace`, an initial state, a **decode boundary**, and a state
93/// transition that is pure **by contract**.
94///
95/// ```text
96///   init   :              → S
97///   decode : Wire        ⇀ Event            (partial: may Reject)
98///   step   : (Ctx S, Event) → Transition (S, Effect)
99/// ```
100///
101/// `decode` is the single place raw bytes become a typed `Event`; a malformed/foreign
102/// message is an explicit [`Reject`], not a silent no-op in `step`. `step` is then total
103/// over well-typed events and pure: no IO, no clocks, no globals. All side effects are
104/// described as values of the protocol's **own** `Effect` type and performed by the
105/// extension's [`Interpret`](super::Interpret) shell.
106pub trait Protocol {
107    /// Protocol-private state, owned by the runtime and threaded through `step`.
108    type State;
109    /// The protocol's typed input, produced by [`decode`](Protocol::decode).
110    type Event: super::MaybeSend;
111    /// The protocol's **own** effect algebra (the core defines no global effect enum).
112    type Effect;
113
114    /// The namespace this protocol is registered and routed under.
115    fn namespace(&self) -> &str;
116
117    /// Optional online-node capability labels advertised when this protocol is registered.
118    fn capabilities(&self) -> &'static [&'static str] {
119        &[]
120    }
121
122    /// Initial state. `init : 1 → S`.
123    fn init(&self) -> Self::State;
124
125    /// Decode the boundary into a typed event, or [`Reject`] it. `decode : Wire ⇀ Event`.
126    fn decode(&self, wire: Wire<'_>) -> Result<Self::Event, Reject>;
127
128    /// Pure transition. `step : (Ctx S, Event) → Transition (S, Effect)`.
129    fn step(
130        &self,
131        ctx: Ctx<'_, Self::State>,
132        event: Self::Event,
133    ) -> Transition<Self::State, Self::Effect>;
134}