use std::sync::Arc;
use agentplane::case::CaseStore;
use agentplane::core::{
CorrelationKey, Digest, Outcome, Skill, SkillDescriptor, SkillError, Tainted,
};
use agentplane::journal::{JournalStore, Record, RecordKind, payload};
use agentplane::keyring::KeyRing;
use agentplane::runtime::{Runtime, StepCtx};
use agentplane::store::RedbStore;
use agentplane::testkit::MemoryKeyRing;
use serde_json::{Value, json};
#[derive(Debug)]
struct Intake;
#[async_trait::async_trait]
impl Skill for Intake {
fn descriptor(&self) -> SkillDescriptor {
SkillDescriptor::new("intake").provides("claim.intake")
}
async fn invoke(
&self,
cx: &mut StepCtx<'_>,
input: Tainted<Value>,
) -> Result<Outcome, SkillError> {
let (_, version) = cx.case_state().await?;
cx.put_case_state(version, input.peek().clone()).await?;
Ok(Outcome::done(Tainted::trusted(json!({ "recorded": true }))))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let raw = Arc::new(RedbStore::open_in_memory()?);
let keys = Arc::new(MemoryKeyRing::default());
let tenant = agentplane::core::TenantId::default();
let ring: Arc<dyn KeyRing> = keys.clone();
let rt = Runtime::builder_on(Arc::clone(&raw))
.keyring(ring)
.skill(Intake)
.build();
let out = rt
.run_correlated(
"claim.intake",
Tainted::trusted(
json!({ "claimant": "Ada Lovelace", "iban": "GB29 NWBK 6016 1331 9268 19" }),
),
"claim",
&[CorrelationKey::new("claim", "CLM-42")],
)
.await?;
println!("1. run → {:?}", out.status);
let case = raw
.correlate(&[CorrelationKey::new("claim", "CLM-42")])
.await?
.expect("the case exists");
let stored = raw.case(case).await?.expect("stored");
println!(
"2. in the store → {} (sealed: {})",
stored.state,
payload::is_sealed(&stored.state)
);
let records = raw.read(out.run_id, 1).await?;
let journalled = records
.iter()
.find_map(|r| match r.kind() {
RecordKind::RunAdmitted { input, .. } => Some(input.clone()),
_ => None,
})
.expect("the run was admitted");
println!(
" journal input → {journalled} (sealed: {})",
payload::is_sealed(&journalled)
);
println!(
" the name is in the stored bytes: {}",
String::from_utf8_lossy(records[0].raw()).contains("Lovelace")
);
keys.destroy(
&agentplane::keyring::scope(&tenant, &case.to_string()),
cx_now(),
"subject exercised the right to erasure",
)
.await?;
println!("\n3. erased → the wrapping key is destroyed");
let after = raw.case(case).await?.expect("still listed");
println!(
" case state → unreadable: {}",
payload::is_sealed(&after.state)
);
Record::verify_chain(&records, Digest::ZERO)?;
println!(
"\n4. chain verifies → yes, with no key at all — the erasure cost the \
data, not the proof"
);
Ok(())
}
#[allow(clippy::disallowed_methods)]
fn cx_now() -> agentplane::core::Timestamp {
agentplane::core::Timestamp::now_utc()
}