use std::collections::BTreeSet;
use std::fs;
use std::path::{Path, PathBuf};
use lifeloop::router::{
LifeloopFailureMapper, TransportError, classes_for_negotiation_outcome,
failure_class_for_transport, retry_class_for,
};
use lifeloop::telemetry::PressureObservation;
use lifeloop::{
AdapterManifest, CallbackRequest, CallbackResponse, FailureClass, FrameContext,
LifecycleEventKind, LifecycleReceipt, NegotiationOutcome, PayloadEnvelope, PlacementClass,
RetryClass,
};
use serde::Deserialize;
use serde_json::Value;
#[derive(Debug, Deserialize)]
struct Fixture {
kind: String,
expect: Expect,
#[serde(default)]
expect_error: Option<String>,
#[serde(default)]
description: Option<String>,
data: Value,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
enum Expect {
Valid,
Invalid,
}
fn conformance_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("conformance")
}
fn collect_fixtures(root: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
let mut stack = vec![root.to_path_buf()];
while let Some(dir) = stack.pop() {
let entries = match fs::read_dir(&dir) {
Ok(entries) => entries,
Err(_) => continue,
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
stack.push(path);
} else if path.extension().and_then(|s| s.to_str()) == Some("json") {
out.push(path);
}
}
}
out.sort();
out
}
fn load(path: &Path) -> Fixture {
let bytes = fs::read(path).unwrap_or_else(|e| panic!("read {path:?}: {e}"));
serde_json::from_slice::<Fixture>(&bytes)
.unwrap_or_else(|e| panic!("parse fixture envelope {path:?}: {e}"))
}
fn run_one(path: &Path, fixture: &Fixture) -> &'static str {
let kind = fixture.kind.as_str();
let result: Result<(), String> = match kind {
"callback_request" => parse_and_validate::<CallbackRequest>(&fixture.data),
"callback_response" => parse_and_validate::<CallbackResponse>(&fixture.data),
"lifecycle_receipt" => parse_and_validate::<LifecycleReceipt>(&fixture.data),
"payload_envelope" => parse_and_validate::<PayloadEnvelope>(&fixture.data),
"frame_context" => parse_and_validate::<FrameContext>(&fixture.data),
"adapter_manifest" => parse_and_validate::<AdapterManifest>(&fixture.data),
"pressure_observation" => parse_only::<PressureObservation>(&fixture.data),
"client_class" => run_client_class(&fixture.data),
"capability_negotiation" => run_capability_negotiation(&fixture.data),
"failure_mapping" => run_failure_mapping(&fixture.data),
other => panic!("{path:?}: unknown fixture kind `{other}`"),
};
match (result, fixture.expect) {
(Ok(()), Expect::Valid) => "valid_ok",
(Err(msg), Expect::Invalid) => {
if let Some(needle) = &fixture.expect_error {
assert!(
msg.contains(needle),
"{path:?}: expected error containing `{needle}`, got `{msg}`"
);
}
"invalid_ok"
}
(Ok(()), Expect::Invalid) => {
panic!("{path:?}: fixture expected to be invalid but parsed and validated cleanly")
}
(Err(msg), Expect::Valid) => {
panic!("{path:?}: fixture expected to be valid but failed: {msg}")
}
}
}
trait ContractObject: serde::de::DeserializeOwned {
fn run_validate(&self) -> Result<(), String>;
}
impl ContractObject for CallbackRequest {
fn run_validate(&self) -> Result<(), String> {
self.validate().map_err(|e| e.to_string())
}
}
impl ContractObject for CallbackResponse {
fn run_validate(&self) -> Result<(), String> {
self.validate().map_err(|e| e.to_string())
}
}
impl ContractObject for LifecycleReceipt {
fn run_validate(&self) -> Result<(), String> {
self.validate().map_err(|e| e.to_string())
}
}
impl ContractObject for PayloadEnvelope {
fn run_validate(&self) -> Result<(), String> {
self.validate().map_err(|e| e.to_string())
}
}
impl ContractObject for FrameContext {
fn run_validate(&self) -> Result<(), String> {
self.validate().map_err(|e| e.to_string())
}
}
impl ContractObject for AdapterManifest {
fn run_validate(&self) -> Result<(), String> {
self.validate().map_err(|e| e.to_string())
}
}
fn parse_and_validate<T: ContractObject>(data: &Value) -> Result<(), String> {
let typed: T = serde_json::from_value(data.clone()).map_err(|e| format!("deserialize: {e}"))?;
typed.run_validate()
}
fn parse_only<T: serde::de::DeserializeOwned + serde::Serialize>(
data: &Value,
) -> Result<(), String> {
let typed: T = serde_json::from_value(data.clone()).map_err(|e| format!("deserialize: {e}"))?;
let _back = serde_json::to_value(&typed).map_err(|e| format!("reserialize: {e}"))?;
Ok(())
}
#[derive(Debug, Deserialize)]
struct ClientClassFixture {
client_id: String,
description: String,
payload_kinds: Vec<String>,
example_payload: Value,
}
fn run_client_class(data: &Value) -> Result<(), String> {
let cls: ClientClassFixture =
serde_json::from_value(data.clone()).map_err(|e| format!("deserialize: {e}"))?;
if cls.client_id.is_empty() {
return Err("client_id must not be empty".into());
}
if cls.description.is_empty() {
return Err("description must not be empty".into());
}
if cls.payload_kinds.is_empty() {
return Err("payload_kinds must list at least one kind".into());
}
let payload: PayloadEnvelope = serde_json::from_value(cls.example_payload.clone())
.map_err(|e| format!("example_payload deserialize: {e}"))?;
payload.validate().map_err(|e| e.to_string())?;
if payload.client_id != cls.client_id {
return Err(format!(
"example_payload.client_id `{}` must match fixture client_id `{}`",
payload.client_id, cls.client_id
));
}
if !cls.payload_kinds.contains(&payload.payload_kind) {
return Err(format!(
"example_payload.payload_kind `{}` not in declared payload_kinds {:?}",
payload.payload_kind, cls.payload_kinds
));
}
Ok(())
}
#[derive(Debug, Deserialize)]
struct CapabilityNegotiationFixture {
outcome: NegotiationOutcome,
#[serde(default)]
explicit_failure_class: Option<FailureClass>,
expected: Option<FailureRetryPair>,
}
#[derive(Debug, Deserialize)]
struct FailureRetryPair {
failure_class: FailureClass,
retry_class: RetryClass,
}
fn run_capability_negotiation(data: &Value) -> Result<(), String> {
let fx: CapabilityNegotiationFixture =
serde_json::from_value(data.clone()).map_err(|e| format!("deserialize: {e}"))?;
let actual = classes_for_negotiation_outcome(fx.outcome, fx.explicit_failure_class);
match (&actual, &fx.expected) {
(None, None) => Ok(()),
(Some((af, ar)), Some(exp)) => {
if *af == exp.failure_class && *ar == exp.retry_class {
Ok(())
} else {
Err(format!(
"negotiation projection mismatch: got ({af:?}, {ar:?}), expected ({:?}, {:?})",
exp.failure_class, exp.retry_class
))
}
}
(None, Some(exp)) => Err(format!(
"expected ({:?}, {:?}) but classes_for_negotiation_outcome returned None",
exp.failure_class, exp.retry_class
)),
(Some(pair), None) => Err(format!(
"expected None but classes_for_negotiation_outcome returned {pair:?}"
)),
}
}
#[derive(Debug, Deserialize)]
struct FailureMappingFixture {
failure_class: FailureClass,
expected_retry_class: RetryClass,
#[serde(default)]
transport_example: Option<TransportExample>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case", tag = "kind", content = "detail")]
enum TransportExample {
Io(String),
Timeout,
Internal(String),
}
fn run_failure_mapping(data: &Value) -> Result<(), String> {
let fx: FailureMappingFixture =
serde_json::from_value(data.clone()).map_err(|e| format!("deserialize: {e}"))?;
let actual_retry = retry_class_for(fx.failure_class);
if actual_retry != fx.expected_retry_class {
return Err(format!(
"default retry for {:?}: got {actual_retry:?}, expected {:?}",
fx.failure_class, fx.expected_retry_class
));
}
if fx.failure_class.default_retry() != fx.expected_retry_class {
return Err(format!(
"FailureClass::{:?}::default_retry diverges from expected_retry_class",
fx.failure_class
));
}
if let Some(example) = fx.transport_example {
let te = match example {
TransportExample::Io(d) => TransportError::Io(d),
TransportExample::Timeout => TransportError::Timeout,
TransportExample::Internal(d) => TransportError::Internal(d),
};
let mapper = LifeloopFailureMapper::new();
let (fc, rc) = mapper.map_transport_error(&te);
let direct_fc = failure_class_for_transport(&te);
if direct_fc != fc {
return Err(format!("free-fn vs mapper drift: {direct_fc:?} vs {fc:?}"));
}
if fc != fx.failure_class {
return Err(format!(
"transport_example maps to {fc:?} but fixture declares {:?}",
fx.failure_class
));
}
if rc != fx.expected_retry_class {
return Err(format!(
"transport_example retry {rc:?} != expected {:?}",
fx.expected_retry_class
));
}
}
Ok(())
}
#[test]
fn conformance_suite_walks_every_fixture() {
let root = conformance_root();
let files = collect_fixtures(&root);
assert!(
!files.is_empty(),
"no conformance fixtures found under {root:?}"
);
let mut counts = std::collections::BTreeMap::<String, usize>::new();
for path in &files {
let fixture = load(path);
let outcome = run_one(path, &fixture);
let bucket = format!("{}.{}", fixture.kind, outcome);
*counts.entry(bucket).or_default() += 1;
}
eprintln!("conformance fixture counts: {counts:#?}");
eprintln!("conformance fixture total: {}", files.len());
}
fn all_event_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("events"))
.iter()
.map(|p| load(p))
.collect()
}
fn all_placement_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("placements"))
.iter()
.map(|p| load(p))
.collect()
}
fn all_receipt_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("receipts"))
.iter()
.map(|p| load(p))
.collect()
}
fn all_failure_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("failures"))
.iter()
.map(|p| load(p))
.collect()
}
fn all_manifest_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("manifests"))
.iter()
.map(|p| load(p))
.collect()
}
fn all_client_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("clients"))
.iter()
.map(|p| load(p))
.collect()
}
fn all_renewal_fixtures() -> Vec<Fixture> {
collect_fixtures(&conformance_root().join("renewal"))
.iter()
.map(|p| load(p))
.collect()
}
#[test]
fn every_lifecycle_event_kind_has_an_event_fixture() {
let mut covered = BTreeSet::new();
for fx in all_event_fixtures() {
assert_eq!(
fx.kind, "callback_request",
"events/ fixtures must be callback_request kind"
);
let event = fx
.data
.get("event")
.and_then(|v| v.as_str())
.unwrap_or_else(|| panic!("event fixture missing data.event: {:?}", fx.description));
covered.insert(event.to_string());
}
let expected: BTreeSet<String> = LifecycleEventKind::ALL
.iter()
.map(|kind| {
serde_json::to_value(kind)
.unwrap()
.as_str()
.unwrap()
.to_string()
})
.collect();
let missing: Vec<_> = expected.difference(&covered).collect();
assert!(
missing.is_empty(),
"events/ fixtures missing coverage for lifecycle event kinds: {missing:?}"
);
}
#[test]
fn every_placement_class_has_a_placement_fixture() {
let mut covered = BTreeSet::new();
for fx in all_placement_fixtures() {
assert_eq!(
fx.kind, "payload_envelope",
"placements/ fixtures must be payload_envelope kind"
);
let placement = fx
.data
.get("acceptable_placements")
.and_then(|v| v.as_array())
.and_then(|arr| arr.first())
.and_then(|p| p.get("placement"))
.and_then(|v| v.as_str())
.unwrap_or_else(|| {
panic!(
"placement fixture missing acceptable_placements[0].placement: {:?}",
fx.description
)
});
covered.insert(placement.to_string());
}
let expected: BTreeSet<String> = PlacementClass::ALL
.iter()
.map(|c| {
serde_json::to_value(c)
.unwrap()
.as_str()
.unwrap()
.to_string()
})
.collect();
let missing: Vec<_> = expected.difference(&covered).collect();
assert!(
missing.is_empty(),
"placements/ fixtures missing coverage for placement classes: {missing:?}"
);
}
#[test]
fn receipt_fixtures_cover_every_receipt_status_and_special_case() {
let mut required: BTreeSet<&'static str> = [
"delivered",
"degraded",
"skipped",
"blocked",
"failed",
"idempotency",
"conflict",
"receipt_gap",
]
.into_iter()
.collect();
for fx in all_receipt_fixtures() {
if let Some(desc) = &fx.description {
for tag in &required.clone() {
if desc.contains(tag) {
required.remove(tag);
}
}
}
}
assert!(
required.is_empty(),
"receipts/ corpus missing fixtures for: {required:?}"
);
}
#[test]
fn failure_mapping_fixtures_cover_every_failure_class() {
let mut covered = BTreeSet::new();
for fx in all_failure_fixtures() {
if fx.kind != "failure_mapping" {
continue;
}
let class = fx
.data
.get("failure_class")
.and_then(|v| v.as_str())
.unwrap_or_else(|| panic!("failure_mapping fixture missing failure_class"));
covered.insert(class.to_string());
}
let expected: BTreeSet<String> = FailureClass::ALL
.iter()
.map(|c| {
serde_json::to_value(c)
.unwrap()
.as_str()
.unwrap()
.to_string()
})
.collect();
let missing: Vec<_> = expected.difference(&covered).collect();
assert!(
missing.is_empty(),
"failures/ corpus missing failure_mapping fixtures for: {missing:?}"
);
}
#[test]
fn renewal_fixtures_cover_reset_prepare_and_continuation_delivery() {
let mut payload_kinds = BTreeSet::new();
for fx in all_renewal_fixtures() {
assert_eq!(
fx.kind, "lifecycle_receipt",
"renewal/ fixtures must be lifecycle_receipt kind"
);
let receipts = fx
.data
.get("payload_receipts")
.and_then(|v| v.as_array())
.unwrap_or_else(|| panic!("renewal fixture missing payload_receipts"));
for receipt in receipts {
if let Some(kind) = receipt.get("payload_kind").and_then(|v| v.as_str()) {
payload_kinds.insert(kind.to_string());
}
}
}
for required in [
"lifeloop.renewal.reset_prepare",
"lifeloop.renewal.continuation",
] {
assert!(
payload_kinds.contains(required),
"renewal/ corpus missing payload kind `{required}`; saw {payload_kinds:?}"
);
}
}
#[test]
fn renewal_failure_mapping_fixtures_cover_issue_3_cases() {
let mut descriptions = BTreeSet::new();
for fx in all_failure_fixtures() {
if let Some(desc) = fx.description {
descriptions.insert(desc);
}
}
for required in [
"renewal reset unsupported",
"renewal continuation delivery lost",
"renewal capability went stale",
"renewal path is manual-only",
] {
assert!(
descriptions.iter().any(|desc| desc.contains(required)),
"failures/ corpus missing renewal case `{required}`"
);
}
}
#[test]
fn manifest_fixtures_cover_codex_and_claude_before_v1_freeze() {
let mut ids = BTreeSet::new();
for fx in all_manifest_fixtures() {
if fx.kind != "adapter_manifest" {
continue;
}
let id = fx
.data
.get("adapter_id")
.and_then(|v| v.as_str())
.unwrap_or_else(|| panic!("manifest fixture missing adapter_id"));
ids.insert(id.to_string());
}
assert!(
ids.contains("codex"),
"manifests/ corpus missing codex (required for v1 freeze gate)"
);
assert!(
ids.contains("claude"),
"manifests/ corpus missing claude (required for v1 freeze gate)"
);
}
#[test]
fn at_least_two_distinct_client_class_shapes_exist() {
let mut clients = BTreeSet::new();
for fx in all_client_fixtures() {
assert_eq!(
fx.kind, "client_class",
"clients/ fixtures must be client_class"
);
let id = fx
.data
.get("client_id")
.and_then(|v| v.as_str())
.unwrap_or_else(|| panic!("client_class fixture missing client_id"));
clients.insert(id.to_string());
}
assert!(
clients.len() >= 2,
"v1 freeze gate requires at least two client-class shapes; found: {clients:?}"
);
}
#[test]
fn failure_class_default_retry_table_is_complete_via_mapper() {
for fc in FailureClass::ALL {
let direct = fc.default_retry();
let via_helper = retry_class_for(*fc);
assert_eq!(
direct, via_helper,
"FailureClass::{fc:?}: default_retry diverges from retry_class_for"
);
}
}