use serde::{Deserialize, Serialize};
use super::records::{
MemoryAuthor, MemoryId, MemoryKind, MemoryRecord, MemoryScope, NewMemoryRecord, RecordStatus,
TrustTier,
};
use super::staged::StagedOp;
use crate::identity_first::agent_memory::AgentMemoryError;
pub const RELEASE_SUCCESSOR_SUFFIX: &str = "-released";
pub const MAX_REVIEW_RATIONALE_BYTES: usize = 400;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QuarantineDecision {
Release,
Tombstone,
}
impl QuarantineDecision {
pub fn as_str(&self) -> &'static str {
match self {
Self::Release => "release",
Self::Tombstone => "tombstone",
}
}
pub fn parse(value: &str) -> Option<Self> {
match value {
"release" => Some(Self::Release),
"tombstone" => Some(Self::Tombstone),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum QuarantineReviewer {
Operator { principal: Option<String> },
Steward { run_id: String },
}
impl QuarantineReviewer {
pub fn author(&self) -> MemoryAuthor {
match self {
Self::Operator { .. } => MemoryAuthor::Operator,
Self::Steward { run_id } => MemoryAuthor::Steward {
run_id: run_id.clone(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QuarantineReviewRequest {
pub scope: MemoryScope,
pub memory_id: MemoryId,
pub decision: QuarantineDecision,
pub expected_content_hash: String,
pub reviewer: QuarantineReviewer,
pub rationale: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReviewedRecordReceipt {
pub memory_id: MemoryId,
pub scope: MemoryScope,
pub kind: MemoryKind,
pub status: RecordStatus,
pub trust: TrustTier,
pub ever_quarantined: bool,
pub content_hash: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub supersedes: Option<MemoryId>,
pub derived_from: Vec<MemoryId>,
pub created_at_ms: u64,
pub updated_at_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ReviewAudit {
pub verdict: QuarantineDecision,
pub reviewer: QuarantineReviewer,
pub origin: MemoryId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub successor: Option<MemoryId>,
pub expected_content_hash: String,
pub origin_quarantine_reason: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub expired_promotions: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub invalidated_promotions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rationale: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ReviewDecision {
pub audit_token: String,
pub decided_at_ms: u64,
pub review: ReviewAudit,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuarantineReviewOutcome {
Released {
origin: ReviewedRecordReceipt,
successor: ReviewedRecordReceipt,
superseded_prior: Option<MemoryId>,
decision: ReviewDecision,
},
AlreadyReleased {
origin: ReviewedRecordReceipt,
successor: ReviewedRecordReceipt,
decision: ReviewDecision,
},
Tombstoned {
origin: ReviewedRecordReceipt,
decision: ReviewDecision,
},
AlreadyTombstoned {
origin: ReviewedRecordReceipt,
decision: ReviewDecision,
},
}
impl QuarantineReviewOutcome {
pub fn outcome_str(&self) -> &'static str {
match self {
Self::Released { .. } => "released",
Self::AlreadyReleased { .. } => "already_released",
Self::Tombstoned { .. } => "tombstoned",
Self::AlreadyTombstoned { .. } => "already_tombstoned",
}
}
pub fn applied(&self) -> bool {
matches!(self, Self::Released { .. } | Self::Tombstoned { .. })
}
pub fn origin(&self) -> &ReviewedRecordReceipt {
match self {
Self::Released { origin, .. }
| Self::AlreadyReleased { origin, .. }
| Self::Tombstoned { origin, .. }
| Self::AlreadyTombstoned { origin, .. } => origin,
}
}
pub fn successor(&self) -> Option<&ReviewedRecordReceipt> {
match self {
Self::Released { successor, .. } | Self::AlreadyReleased { successor, .. } => {
Some(successor)
}
Self::Tombstoned { .. } | Self::AlreadyTombstoned { .. } => None,
}
}
pub fn decision(&self) -> &ReviewDecision {
match self {
Self::Released { decision, .. }
| Self::AlreadyReleased { decision, .. }
| Self::Tombstoned { decision, .. }
| Self::AlreadyTombstoned { decision, .. } => decision,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QuarantineReviewRefusal {
NotFound,
ContentMismatch,
NotQuarantined {
status: &'static str,
released_as: Option<MemoryId>,
},
GatePending {
pending_id: String,
expires_at_ms: u64,
},
SuccessorConflict { successor_id: MemoryId },
SecretDetected { class: &'static str },
StaleUpdate {
prior: MemoryId,
prior_status: &'static str,
},
}
impl QuarantineReviewRefusal {
pub fn reason_str(&self) -> &'static str {
match self {
Self::NotFound => "not_found",
Self::ContentMismatch => "content_mismatch",
Self::NotQuarantined { .. } => "not_quarantined",
Self::GatePending { .. } => "gate_pending",
Self::SuccessorConflict { .. } => "successor_conflict",
Self::SecretDetected { .. } => "secret_detected",
Self::StaleUpdate { .. } => "stale_update",
}
}
}
impl std::fmt::Display for QuarantineReviewRefusal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NotFound => write!(f, "no such memory record in this scope"),
Self::ContentMismatch => write!(
f,
"the record's content is not the content that was reviewed"
),
Self::NotQuarantined {
status,
released_as: Some(successor),
} => write!(
f,
"record is {status}, not quarantined: it was released as '{successor}'"
),
Self::NotQuarantined {
status,
released_as: None,
} => write!(f, "record is {status}, not quarantined"),
Self::GatePending {
pending_id,
expires_at_ms,
} => write!(
f,
"a gated promotion of this record awaits a gating decision ('{pending_id}'); \
it expires at {expires_at_ms} ms"
),
Self::SuccessorConflict { successor_id } => write!(
f,
"another record already holds the release successor id '{successor_id}'"
),
Self::SecretDetected { class } => write!(
f,
"release refused: content matches the '{class}' secret pattern class \
(§10.4); tombstone is the only exit"
),
Self::StaleUpdate {
prior,
prior_status,
} => write!(
f,
"release refused: this quarantined update's prior '{prior}' is \
{prior_status}, not active"
),
}
}
}
#[derive(Debug)]
pub enum QuarantineReviewError {
Refused(QuarantineReviewRefusal),
Store(AgentMemoryError),
}
impl std::fmt::Display for QuarantineReviewError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Refused(refusal) => write!(f, "quarantine review refused: {refusal}"),
Self::Store(err) => write!(f, "{err}"),
}
}
}
impl std::error::Error for QuarantineReviewError {}
impl From<AgentMemoryError> for QuarantineReviewError {
fn from(err: AgentMemoryError) -> Self {
Self::Store(err)
}
}
pub fn release_successor_id(origin: &str) -> MemoryId {
format!("{origin}{RELEASE_SUCCESSOR_SUFFIX}")
}
pub fn release_copy(record: &MemoryRecord) -> NewMemoryRecord {
NewMemoryRecord {
kind: record.kind,
title: record.title.clone(),
description: record.description.clone(),
body: record.body.clone(),
tags: record.tags.clone(),
evidence: Vec::new(),
verification: record.provenance.verification.clone(),
}
}
pub fn quarantine_release_ops(
origin: &MemoryRecord,
create_rationale: String,
tombstone_rationale: String,
) -> Vec<StagedOp> {
let successor = Some(release_successor_id(&origin.id));
let record = release_copy(origin);
let derived_from = vec![origin.id.clone()];
let first = match &origin.supersedes {
Some(prior) => StagedOp::Supersede {
id: successor,
prior: prior.clone(),
record,
trust: TrustTier::AgentObserved,
derived_from,
rationale: Some(create_rationale),
},
None => StagedOp::Create {
id: successor,
scope: origin.scope.clone(),
record,
trust: TrustTier::AgentObserved,
derived_from,
rationale: Some(create_rationale),
created_at_ms: None,
updated_at_ms: None,
},
};
vec![
first,
StagedOp::Tombstone {
id: origin.id.clone(),
rationale: Some(tombstone_rationale),
},
]
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
use crate::memory::records::{MemoryProvenance, UsageStats};
fn quarantined(supersedes: Option<&str>) -> MemoryRecord {
MemoryRecord {
id: "mem-origin".to_string(),
scope: MemoryScope::Identity {
realm: "default".to_string(),
identity: "lead:main".to_string(),
},
kind: MemoryKind::Preference,
title: "Reading preference".to_string(),
description: "When recommending books".to_string(),
body: "Prefers slow literary fantasy.".to_string(),
tags: vec!["epistemic:operator_said".to_string()],
provenance: MemoryProvenance {
evidence: Vec::new(),
author: MemoryAuthor::Agent {
identity: "lead:main".to_string(),
},
profile: None,
verification: None,
},
trust: TrustTier::AgentObserved,
status: RecordStatus::Quarantined {
reason: "session tainted".to_string(),
},
supersedes: supersedes.map(str::to_string),
derived_from: Vec::new(),
working_set_rank: None,
created_at_ms: 1,
updated_at_ms: 1,
usage: UsageStats::default(),
ever_quarantined: true,
}
}
#[test]
fn reviewers_author_their_batches() {
assert_eq!(
QuarantineReviewer::Operator { principal: None }.author(),
MemoryAuthor::Operator
);
assert_eq!(
QuarantineReviewer::Steward {
run_id: "dream-1".to_string()
}
.author(),
MemoryAuthor::Steward {
run_id: "dream-1".to_string()
}
);
}
#[test]
fn decision_round_trips_its_wire_names() {
for decision in [QuarantineDecision::Release, QuarantineDecision::Tombstone] {
assert_eq!(QuarantineDecision::parse(decision.as_str()), Some(decision));
}
assert_eq!(QuarantineDecision::parse("hold"), None);
}
#[test]
fn release_of_a_fresh_write_creates_the_named_successor_then_tombstones() {
let origin = quarantined(None);
let ops = quarantine_release_ops(&origin, "create".into(), "tombstone".into());
assert_eq!(ops.len(), 2);
let StagedOp::Create {
id,
scope,
record,
trust,
derived_from,
..
} = &ops[0]
else {
panic!("first op must create the successor: {ops:?}");
};
assert_eq!(id.as_deref(), Some("mem-origin-released"));
assert_eq!(scope, &origin.scope);
assert_eq!(*trust, TrustTier::AgentObserved);
assert_eq!(derived_from, &vec!["mem-origin".to_string()]);
assert_eq!(record.body, origin.body);
assert_eq!(record.tags, origin.tags);
assert!(record.evidence.is_empty());
assert!(matches!(&ops[1], StagedOp::Tombstone { id, .. } if id == "mem-origin"));
}
#[test]
fn release_of_a_quarantined_update_supersedes_its_prior() {
let origin = quarantined(Some("mem-prior"));
let ops = quarantine_release_ops(&origin, "create".into(), "tombstone".into());
let StagedOp::Supersede {
id,
prior,
trust,
derived_from,
..
} = &ops[0]
else {
panic!("first op must supersede the prior: {ops:?}");
};
assert_eq!(id.as_deref(), Some("mem-origin-released"));
assert_eq!(prior, "mem-prior");
assert_eq!(*trust, TrustTier::AgentObserved);
assert_eq!(derived_from, &vec!["mem-origin".to_string()]);
assert!(matches!(&ops[1], StagedOp::Tombstone { id, .. } if id == "mem-origin"));
}
}