use std::collections::{BTreeMap, BTreeSet};
use content_addressable::{ContentAddressable, ContentError, ContentId, MerkleNode, RawContentId};
use serde::{Deserialize, Serialize};
use crate::{verify_unit, Op, Unit, UnitId, Verified, VerifyError};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventOrigin {
Operator,
Model,
Tool,
Historical,
Harness,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReplyVerdict {
Answer,
Narration,
Question,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum EventKind {
Observation,
Intervention,
Verdict {
verdict: ReplyVerdict,
},
Retrieval,
Elision {
unit: UnitId,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EventBody {
pub root: ContentId,
pub origin: EventOrigin,
pub kind: EventKind,
pub payload: RawContentId,
pub seq: u64,
pub sources: BTreeSet<ContentId>,
pub depth: u32,
}
pub type RawEvent = MerkleNode<EventBody>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct Event(RawEvent);
#[derive(Debug, thiserror::Error)]
pub enum EventError {
#[error("event reference is absent or unauthorized: {0}")]
MissingReference(Box<ContentId>),
#[error("event reference {expected} resolves to {actual}")]
ReferenceMismatch {
expected: Box<ContentId>,
actual: Box<ContentId>,
},
#[error("invalid event: {0}")]
Invalid(&'static str),
#[error("event declares depth {declared}, but its derivation implies {implied}")]
DepthMismatch {
declared: u32,
implied: u32,
},
#[error("event generation depth {0} exceeds the bound of one")]
DepthExceeded(u32),
#[error(transparent)]
Content(#[from] Box<ContentError>),
#[error(transparent)]
Elision(#[from] VerifyError),
}
impl From<ContentError> for EventError {
fn from(error: ContentError) -> Self {
Self::Content(Box::new(error))
}
}
impl Event {
pub fn new(
body: EventBody,
parents: impl IntoIterator<Item = ContentId>,
resolve: impl Fn(&ContentId) -> Option<Event>,
) -> Result<Self, EventError> {
Self::admit(MerkleNode::new(body, parents), resolve)
}
pub fn admit(
raw: RawEvent,
resolve: impl Fn(&ContentId) -> Option<Event>,
) -> Result<Self, EventError> {
let body = raw.payload();
if !body.sources.is_subset(raw.parents()) {
return Err(EventError::Invalid(
"derivation sources must also be causal parents",
));
}
let mut parents = BTreeMap::new();
for id in raw.parents() {
let parent = resolve(id).ok_or_else(|| EventError::MissingReference(Box::new(*id)))?;
let actual = parent.id()?;
if actual != *id {
return Err(EventError::ReferenceMismatch {
expected: Box::new(*id),
actual: Box::new(actual),
});
}
parents.insert(*id, parent);
}
let source_depth = body
.sources
.iter()
.map(|id| parents[id].depth())
.max()
.unwrap_or(0);
let implied = match &body.kind {
EventKind::Observation => {
if body.origin == EventOrigin::Harness || !body.sources.is_empty() {
return Err(EventError::Invalid(
"observations must be external and have no derivation sources",
));
}
0
}
EventKind::Intervention => {
require_harness(body)?;
Op::Generate.depth_after(source_depth)
}
EventKind::Verdict { .. } => {
require_harness(body)?;
let source = single_source(body, &parents)?;
if parents.len() != 1
|| source.body().origin != EventOrigin::Model
|| source.body().kind != EventKind::Observation
{
return Err(EventError::Invalid(
"a verdict must name exactly one model reply observation as its parent",
));
}
Op::Generate.depth_after(source_depth)
}
EventKind::Retrieval | EventKind::Elision { .. } => {
let source = single_source(body, &parents)?;
if body.origin != source.body().origin {
return Err(EventError::Invalid(
"retrieval and elision must preserve source origin",
));
}
Op::Elide.depth_after(source_depth)
}
};
if implied > 1 {
return Err(EventError::DepthExceeded(implied));
}
if body.depth != implied {
return Err(EventError::DepthMismatch {
declared: body.depth,
implied,
});
}
Ok(Self(raw))
}
#[must_use]
pub fn body(&self) -> &EventBody {
self.0.payload()
}
#[must_use]
pub fn parents(&self) -> &BTreeSet<ContentId> {
self.0.parents()
}
#[must_use]
pub fn depth(&self) -> u32 {
self.body().depth
}
pub fn id(&self) -> Result<ContentId, ContentError> {
self.0.id()
}
#[must_use]
pub fn to_raw(&self) -> RawEvent {
self.0.clone()
}
pub fn verify_elision(
&self,
unit: &Unit,
source: &Event,
bytes: &[u8],
) -> Result<Verified, EventError> {
let EventKind::Elision { unit: expected } = &self.body().kind else {
return Err(EventError::Invalid("event is not an elision"));
};
if *expected != unit.id()?
|| unit.root() != self.body().root
|| !self.body().sources.contains(&source.id()?)
|| source.body().payload != unit.derivation().source
{
return Err(EventError::Invalid(
"elision unit, source, or root does not match the event",
));
}
Ok(verify_unit(unit, bytes)?)
}
}
fn require_harness(body: &EventBody) -> Result<(), EventError> {
if body.origin != EventOrigin::Harness {
return Err(EventError::Invalid(
"generated interventions and verdicts must have harness origin",
));
}
Ok(())
}
fn single_source<'a>(
body: &EventBody,
parents: &'a BTreeMap<ContentId, Event>,
) -> Result<&'a Event, EventError> {
if body.sources.len() != 1 {
return Err(EventError::Invalid(
"operation requires exactly one derivation source",
));
}
Ok(&parents[body.sources.first().expect("source cardinality checked")])
}
impl ContentAddressable for Event {
fn canonical_form(&self) -> Result<Vec<u8>, ContentError> {
self.0.canonical_form()
}
}