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}