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}