use std::sync::Arc;
use crate::blob::{BlobError, BlobStore};
use crate::core::{Digest, Timestamp};
use super::conformance::Report;
pub async fn check(store: &Arc<dyn BlobStore>, label: &str, report: &mut Report) {
let at = Timestamp::from_unix_timestamp(1_760_000_000).expect("a valid test instant");
the_store_computes_the_address(store, label, report).await;
a_read_verifies_what_it_returns(store, label, report).await;
an_absent_blob_is_not_an_erased_one(store, label, at, report).await;
an_erasure_stands(store, label, at, report).await;
}
pub async fn check_backing(store: &Arc<dyn BlobStore>, label: &str, report: &mut Report) {
let at = Timestamp::from_unix_timestamp(1_760_000_000).expect("a valid test instant");
the_envelope_pair_round_trips(store, label, report).await;
a_sealed_write_cannot_undo_an_erasure(store, label, at, report).await;
}
fn bytes(label: &str, what: &str) -> Vec<u8> {
format!("agentplane/conformance/{label}/{what}").into_bytes()
}
async fn the_store_computes_the_address(store: &Arc<dyn BlobStore>, label: &str, r: &mut Report) {
r.checked += 1;
let payload = bytes(label, "addressed");
let digest = match store.put(&payload).await {
Ok(d) => d,
Err(e) => {
r.record("put stores bytes", format!("put failed: {e}"));
return;
}
};
if digest != Digest::of(&payload) {
r.record(
"put returns the content digest",
"the address a caller links erasure traversal to is not the one the bytes \
were stored at, so nothing that follows the link can reach them",
);
return;
}
r.checked += 1;
match store.put(&payload).await {
Ok(again) if again == digest => {}
Ok(_) => r.record(
"the same bytes are the same write",
"a second write of identical bytes answered a different address",
),
Err(e) => r.record(
"the same bytes are the same write",
format!("re-writing identical bytes failed: {e}"),
),
}
r.checked += 1;
match store.has(digest).await {
Ok(true) => {}
Ok(false) => r.record("has sees a stored blob", "has answered false after a put"),
Err(e) => r.record("has sees a stored blob", format!("has failed: {e}")),
}
}
async fn a_read_verifies_what_it_returns(store: &Arc<dyn BlobStore>, label: &str, r: &mut Report) {
let payload = bytes(label, "verified");
let Ok(digest) = store.put(&payload).await else {
r.record("put stores bytes", "put failed before the read checks");
return;
};
r.checked += 1;
match store.get(digest).await {
Ok(back) if back == payload => {}
Ok(_) => r.record("get returns what was stored", "the bytes came back changed"),
Err(e) => r.record("get returns what was stored", format!("get failed: {e}")),
}
}
async fn the_envelope_pair_round_trips(store: &Arc<dyn BlobStore>, label: &str, r: &mut Report) {
let envelope = bytes(label, "envelope");
let address = Digest::of(&bytes(label, "plaintext"));
r.checked += 1;
if let Err(e) = store.put_at(address, &envelope).await {
r.record("put_at stores at a foreign address", format!("{e}"));
return;
}
r.checked += 1;
match store.get_raw(address).await {
Ok(back) if back == envelope => {}
Ok(_) => r.record("get_raw does not verify", "the envelope came back changed"),
Err(e) => r.record(
"get_raw does not verify",
format!("a raw read of an envelope failed, so sealing cannot work here: {e}"),
),
}
r.checked += 1;
match store.get(address).await {
Err(BlobError::Corrupt { .. }) => {}
other => r.record(
"get verifies before returning",
format!(
"bytes that do not hash to their address were not reported corrupt: {}",
describe(&other)
),
),
}
}
async fn a_sealed_write_cannot_undo_an_erasure(
store: &Arc<dyn BlobStore>,
label: &str,
at: Timestamp,
r: &mut Report,
) {
let payload = bytes(label, "sealed-erased");
let Ok(digest) = store.put(&payload).await else {
r.record("put stores bytes", "put failed before the erasure check");
return;
};
if let Err(e) = store.expire(digest, at, "art-17 request").await {
r.record("expire drops the bytes", format!("expire failed: {e}"));
return;
}
r.checked += 1;
match store.put_at(digest, &payload).await {
Err(BlobError::Expired { .. }) => {}
Ok(()) => r.record(
"a sealed write cannot undo an erasure",
"put_at over an erased address was accepted, so the data is back while the \
tombstone still says when it went",
),
Err(e) => r.record(
"a sealed write cannot undo an erasure",
format!("the write was refused, but not as an erasure: {e}"),
),
}
}
async fn an_absent_blob_is_not_an_erased_one(
store: &Arc<dyn BlobStore>,
label: &str,
at: Timestamp,
r: &mut Report,
) {
let never = Digest::of(&bytes(label, "never-written"));
r.checked += 1;
match store.get(never).await {
Err(BlobError::NotFound(_)) => {}
other => r.record(
"an unwritten address is not found",
format!(
"an address nothing was ever written to answered {}",
describe(&other)
),
),
}
r.checked += 1;
match store.has(never).await {
Ok(false) => {}
Ok(true) => r.record("has is false for an unwritten address", "has answered true"),
Err(e) => r.record("has is false for an unwritten address", format!("{e}")),
}
r.checked += 1;
let dangling = Digest::of(&bytes(label, "linked-never-stored"));
match store
.expire(dangling, at, "a linked digest with no bytes")
.await
{
Ok(()) => match store.get(dangling).await {
Err(BlobError::Expired { .. }) => {}
other => r.record(
"erasing an unwritten address leaves a tombstone",
format!("it answered {} afterwards", describe(&other)),
),
},
Err(e) => r.record(
"erasing an unwritten address is allowed",
format!("expire refused a linked digest with no bytes: {e}"),
),
}
}
async fn an_erasure_stands(store: &Arc<dyn BlobStore>, label: &str, at: Timestamp, r: &mut Report) {
let payload = bytes(label, "erased");
let Ok(digest) = store.put(&payload).await else {
r.record("put stores bytes", "put failed before the erasure checks");
return;
};
if let Err(e) = store.expire(digest, at, "art-17 request").await {
r.record("expire drops the bytes", format!("expire failed: {e}"));
return;
}
r.checked += 1;
match store.get(digest).await {
Err(BlobError::Expired {
at: when, reason, ..
}) => {
if when != at.unix_timestamp() {
r.record(
"a tombstone keeps the instant it was written with",
format!("expired at {when}, not {}", at.unix_timestamp()),
);
}
if reason != "art-17 request" {
r.record(
"a tombstone keeps the reason it was written with",
format!("the reason came back as {reason:?}"),
);
}
}
other => r.record(
"an erased blob reads as expired",
format!(
"erasure and loss are different facts and only one is an incident; \
this answered {}",
describe(&other)
),
),
}
r.checked += 1;
match store.has(digest).await {
Ok(false) => {}
Ok(true) => r.record("has is false after an erasure", "the bytes are still there"),
Err(e) => r.record("has is false after an erasure", format!("{e}")),
}
r.checked += 1;
let later = Timestamp::from_unix_timestamp(1_790_000_000).expect("a later instant");
match store.expire(digest, later, "a retry").await {
Ok(()) => match store.get(digest).await {
Err(BlobError::Expired { at: when, .. }) if when == at.unix_timestamp() => {}
Err(BlobError::Expired { at: when, .. }) => r.record(
"the first tombstone stands",
format!("a repeated erasure moved the date to {when}"),
),
other => r.record(
"the first tombstone stands",
format!("a repeated erasure answered {}", describe(&other)),
),
},
Err(e) => r.record("expiring twice is the same expiry", format!("{e}")),
}
r.checked += 1;
match store.put(&payload).await {
Err(BlobError::Expired { .. }) => {}
Ok(_) => r.record(
"a write cannot undo an erasure",
"re-writing the erased bytes was accepted, so the data is back while the \
tombstone still says when it went — an erasure reported as discharged and \
then silently reversed",
),
Err(e) => r.record(
"a write cannot undo an erasure",
format!("the write was refused, but not as an erasure: {e}"),
),
}
r.checked += 1;
match store.get(digest).await {
Err(BlobError::Expired { .. }) => {}
other => r.record(
"the erasure survives the attempt to undo it",
format!("after the refused writes it answered {}", describe(&other)),
),
}
}
fn describe(outcome: &Result<Vec<u8>, BlobError>) -> String {
match outcome {
Ok(bytes) => format!("{} byte(s) of content", bytes.len()),
Err(e) => e.to_string(),
}
}