Skip to main content

agent_frame/
packet.rs

1//! Packets: the chain that makes "never derive from a derivation" a shape.
2//!
3//! # Reusing the Merkle node rather than re-minting one
4//!
5//! An earlier revision hand-rolled `Packet { prior: Option<PacketId>, units }`
6//! and hashed it. That is a hand-rolled Merkle node — a payload plus a parent
7//! link, with identity over both — in a workspace whose first-principle rule
8//! says every persisted structure derives its identity through
9//! `content-addressable`, and whose own decision record cited that rule while
10//! breaking it.
11//!
12//! A packet is now [`MerkleNode<PacketBody>`]: the predecessor is a **parent
13//! link**, which is what a parent link is for, and the id is minted by the
14//! crate that owns id-minting. Genesis is the empty parent set — the same fact
15//! the old `prior: None` encoded, expressed in the vocabulary the rest of the
16//! line already speaks.
17//!
18//! # Borrowing a DAG node does not make v0 a DAG
19//!
20//! [`MerkleNode`] carries a parent *set*, because a Merkle DAG node does. v0
21//! mints a **chain**: the referent is `formal/ContextOps/Basic.lean`, whose
22//! `Chain` has exactly two constructors — `genesis (units)` and
23//! `sealed (prior : Chain) (units)`. There is no multi-parent constructor to
24//! correspond to.
25//!
26//! So [`Packet`] does **not** implement [`serde::Deserialize`], for the same
27//! reason [`crate::Unit`] does not. Foreign bytes decode to [`RawPacket`] — an
28//! ordinary untrusted DAG node — and cross into a `Packet` only through the
29//! fallible [`TryFrom`] below, which admits zero parents or exactly one.
30//!
31//! The state that closes is a real one, not a hypothetical: a decoded
32//! two-parent node satisfied `is_genesis() == false` *and* `prior() == None`
33//! simultaneously. Neither constructor can mint that, and `newt frame parents`
34//! rendered it as `outcome: "genesis"` — "no parents: this frame is an origin,
35//! and the chain ends here" — of a packet with two. A forensic surface that
36//! reports the absence of a link it silently dropped is worse than one that
37//! refuses to answer.
38
39use std::collections::BTreeSet;
40
41use content_addressable::{ContentAddressable, ContentError, ContentId, MerkleNode};
42use serde::{Deserialize, Serialize};
43
44/// The content address of a [`crate::Unit`].
45///
46/// A **newtype**, not an alias. As aliases, `UnitId` and `PacketId` were the
47/// same type, so a packet id fitted a unit slot silently and the compiler had
48/// nothing to say about it. Provenance that can be assembled out of the wrong
49/// kind of address is not provenance.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
51#[serde(transparent)]
52pub struct UnitId(ContentId);
53
54/// The content address of a [`Packet`]. A newtype for the same reason as
55/// [`UnitId`].
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
57#[serde(transparent)]
58pub struct PacketId(ContentId);
59
60macro_rules! id_newtype {
61    ($t:ident, $what:literal) => {
62        impl $t {
63            #[doc = concat!("Borrow the underlying content id of this ", $what, ".")]
64            #[must_use]
65            pub const fn as_content_id(&self) -> &ContentId {
66                &self.0
67            }
68
69            #[doc = concat!("Consume this ", $what, " id, yielding the content id.")]
70            #[must_use]
71            pub const fn into_content_id(self) -> ContentId {
72                self.0
73            }
74        }
75
76        impl From<ContentId> for $t {
77            fn from(id: ContentId) -> Self {
78                Self(id)
79            }
80        }
81
82        impl std::fmt::Display for $t {
83            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84                self.0.fmt(f)
85            }
86        }
87
88        impl std::str::FromStr for $t {
89            type Err = <ContentId as std::str::FromStr>::Err;
90            fn from_str(s: &str) -> Result<Self, Self::Err> {
91                s.parse::<ContentId>().map(Self)
92            }
93        }
94    };
95}
96
97id_newtype!(UnitId, "unit");
98id_newtype!(PacketId, "packet");
99
100/// What a packet carries: the units it seals, in order.
101///
102/// The predecessor is **not** here — it is the node's parent link. Putting it
103/// in the payload as well would be two encodings of one fact, which is how a
104/// chain gets silently re-rooted.
105#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(deny_unknown_fields)]
107pub struct PacketBody {
108    /// The units this packet seals, in order.
109    pub units: Vec<UnitId>,
110}
111
112/// One sealed packet: an ordered list of units, rooted in a predecessor.
113///
114/// Identity is over the whole node — payload and parent links together — so a
115/// packet cannot be re-parented without changing its id.
116#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
117#[serde(transparent)]
118pub struct Packet(MerkleNode<PacketBody>);
119
120/// The transparent decode target: **an untrusted DAG node**.
121///
122/// Deliberately the [`MerkleNode`] itself rather than a fresh DTO. A `RawUnit`
123/// had to be minted because a `Unit` is not structurally a plain record of its
124/// own fields; a raw packet *is* exactly a Merkle node with a `PacketBody`
125/// payload, and standing a second type up beside one that already has the right
126/// shape and the right wire form is the sprawl this crate is written against.
127///
128/// It asserts nothing: the parent set may hold any number of links, which is
129/// what gives `TryFrom<RawPacket> for Packet` something to refuse.
130pub type RawPacket = MerkleNode<PacketBody>;
131
132/// Why a decoded packet was refused admission.
133///
134/// Named apart from [`crate::AdmitError`] rather than folded into it: a reader
135/// of a log needs to know whether the thing that failed the v0 contract was a
136/// unit or a packet, and a shared enum answers that only by which variant fired.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
138pub enum PacketAdmitError {
139    /// The node has more than one parent. v0 mints a chain, not a DAG.
140    #[error(
141        "decoded packet has {parents} parents: v0 mints a CHAIN, so a packet has zero \
142         parents (genesis) or exactly one. A node with several has no single `prior`, \
143         so it is neither genesis nor following — a third state no constructor can mint \
144         and no forensic reader can name."
145    )]
146    NotAChain {
147        /// How many parent links the decoded node carried.
148        parents: usize,
149    },
150}
151
152impl Packet {
153    /// A packet with no predecessor.
154    #[must_use]
155    pub fn genesis(units: Vec<UnitId>) -> Self {
156        Self(MerkleNode::genesis(PacketBody { units }))
157    }
158
159    /// A packet linking `prior` as its predecessor.
160    #[must_use]
161    pub fn following(prior: PacketId, units: Vec<UnitId>) -> Self {
162        Self(MerkleNode::new(
163            PacketBody { units },
164            [prior.into_content_id()],
165        ))
166    }
167
168    /// The units this packet seals, in order.
169    #[must_use]
170    pub fn units(&self) -> &[UnitId] {
171        &self.0.payload().units
172    }
173
174    /// The predecessor links.
175    #[must_use]
176    pub fn parents(&self) -> &BTreeSet<ContentId> {
177        self.0.parents()
178    }
179
180    /// The single predecessor, when there is exactly one.
181    ///
182    /// v0 mints chains, so a packet has zero parents (genesis) or one.
183    #[must_use]
184    pub fn prior(&self) -> Option<PacketId> {
185        let mut it = self.0.parents().iter();
186        match (it.next(), it.next()) {
187            (Some(one), None) => Some(PacketId::from(*one)),
188            _ => None,
189        }
190    }
191
192    /// Whether this packet starts a chain.
193    #[must_use]
194    pub fn is_genesis(&self) -> bool {
195        self.0.parents().is_empty()
196    }
197
198    /// This packet's address.
199    ///
200    /// # Errors
201    ///
202    /// Propagates an encoding failure from the canonical form.
203    pub fn id(&self) -> Result<PacketId, ContentError> {
204        self.0.id().map(PacketId::from)
205    }
206}
207
208impl TryFrom<RawPacket> for Packet {
209    type Error = PacketAdmitError;
210
211    /// **The only way in from foreign bytes.**
212    ///
213    /// One rule, because the chain has one shape: at most one parent. Nothing
214    /// else about a decoded packet is a claim this layer can check — whether the
215    /// units it names exist, or admit, needs a store, and inventing one here
216    /// would be v0 growing a storage service to answer a structural question.
217    ///
218    /// # Errors
219    ///
220    /// [`PacketAdmitError::NotAChain`] when the node carries two or more parents.
221    fn try_from(raw: RawPacket) -> Result<Self, Self::Error> {
222        let parents = raw.parents().len();
223        if parents > 1 {
224            return Err(PacketAdmitError::NotAChain { parents });
225        }
226        Ok(Self(raw))
227    }
228}
229
230impl ContentAddressable for Packet {
231    fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
232        self.0.canonical_form()
233    }
234}