Skip to main content

agent_frame/
derivation.rs

1//! Derivations: the addressed fact a unit's identity is computed over.
2//!
3//! # The defect this type exists to fix
4//!
5//! The first revision of this kernel hashed a `Unit` holding
6//! `{ op, depth, root_kind, life, addressed: bool }`. None of those fields
7//! names the material. Two elisions over completely different sources, with the
8//! same root kind, serialize to identical bytes and therefore mint **the same
9//! content id** — the distinguishing information never enters the hash. And
10//! `addressed: true` is a *declaration* that a unit is addressed, not an
11//! address: nothing can be fetched from it, so `checkOf .elide = .rederive` had
12//! nothing to re-derive.
13//!
14//! A [`Derivation`] carries the address instead of asserting one. The source is
15//! named, the byte range within it is named, and the material that range covers
16//! is named — so a consumer who holds the source bytes can recompute the claim
17//! and compare. That is what makes the id self-validating rather than
18//! self-describing.
19//!
20//! # Two id profiles, deliberately
21//!
22//! `source` and `elided` are [`RawContentId`] — they name **opaque byte
23//! strings**. `root` is a [`ContentId`] — it names a **canonical structured
24//! value** ([`crate::RootEvent`]). `content-addressable` mints these as
25//! different types with no `PartialEq` and no `From` between them precisely so
26//! this distinction cannot be lost by accident, and the kernel honours it.
27
28use content_addressable::{ContentAddressable, ContentError, ContentId, RawContentId};
29use serde::{Deserialize, Serialize};
30
31use crate::op::Op;
32
33/// A half-open byte range `[start, end)` within a source.
34///
35/// Half-open so that adjacent spans tile without overlap and an empty span is
36/// exactly `start == end` — which admission rejects, because a range covering
37/// no bytes addresses nothing.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
39#[serde(deny_unknown_fields)]
40pub struct Span {
41    /// First byte of the range, inclusive.
42    pub start: u64,
43    /// One past the last byte of the range, exclusive.
44    pub end: u64,
45}
46
47impl Span {
48    /// A span over `[start, end)`.
49    #[must_use]
50    pub const fn new(start: u64, end: u64) -> Self {
51        Self { start, end }
52    }
53
54    /// Number of bytes this span covers, saturating on an inverted range.
55    #[must_use]
56    pub const fn len(&self) -> u64 {
57        self.end.saturating_sub(self.start)
58    }
59
60    /// Whether this span covers no bytes — including the inverted case.
61    #[must_use]
62    pub const fn is_empty(&self) -> bool {
63        self.end <= self.start
64    }
65
66    /// Borrow the bytes this span covers, or `None` if it runs past the end.
67    #[must_use]
68    pub fn slice<'a>(&self, source: &'a [u8]) -> Option<&'a [u8]> {
69        let start = usize::try_from(self.start).ok()?;
70        let end = usize::try_from(self.end).ok()?;
71        if end < start || end > source.len() {
72            return None;
73        }
74        source.get(start..end)
75    }
76}
77
78/// What was done, to which material, on whose authority.
79///
80/// **This is the hashed body of a unit.** Change the source, the span, the
81/// operation or the root and you get a different [`ContentId`] — which is the
82/// property the whole kernel rests on and is asserted directly by
83/// `tests/addressing.rs`.
84#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
85#[serde(deny_unknown_fields)]
86pub struct Derivation {
87    /// What was done to the material.
88    pub op: Op,
89    /// The source byte string this derivation is over.
90    pub source: RawContentId,
91    /// Which byte range of `source`.
92    pub span: Span,
93    /// The material that range covers.
94    ///
95    /// Functionally redundant given `source` and `span` — and that is the
96    /// point. It is the **claim being checked**: re-derivation recomputes the
97    /// hash of the addressed bytes and compares it to this field. A check with
98    /// nothing recorded to compare against is not a check, and the earlier
99    /// revision shipped the word `rederive` without one.
100    pub elided: RawContentId,
101    /// The [`crate::RootEvent`] that caused this derivation.
102    pub root: ContentId,
103}
104
105impl Derivation {
106    /// Address a derivation over `source` at `span`, rooted in `root`.
107    ///
108    /// Returns `None` when the span does not lie within `source` — an address
109    /// that does not resolve is not an address, so it cannot be built.
110    #[must_use]
111    pub fn over(op: Op, source: &[u8], span: Span, root: ContentId) -> Option<Self> {
112        let addressed = span.slice(source)?;
113        Some(Self {
114            op,
115            source: RawContentId::from_content(source),
116            span,
117            elided: RawContentId::from_content(addressed),
118            root,
119        })
120    }
121
122    /// This derivation's address — and therefore its unit's.
123    ///
124    /// # Errors
125    ///
126    /// Propagates an encoding failure from the canonical form.
127    pub fn id(&self) -> Result<ContentId, ContentError> {
128        self.content_id()
129    }
130}
131
132impl ContentAddressable for Derivation {
133    fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
134        content_addressable::canonical::to_canonical_dagcbor(self)
135    }
136}