use lifeloop::{
AcceptablePlacement, AdapterRole, CapabilityDegradation, FailureClass, FrameClass,
FrameContext, IntegrationMode, LifecycleEventKind, LifecycleReceipt, NegotiationOutcome,
PayloadEnvelope, PayloadReceipt, PlacementClass, PlacementOutcome, ReceiptStatus,
RequirementLevel, RetryClass, SCHEMA_VERSION, SupportState, Warning,
};
use serde_json::Value;
use std::collections::BTreeMap;
const SPEC_SCHEMA_VERSION_LABEL: &str = "lifeloop.v0.1";
const SPEC_LIFECYCLE_EVENTS: &[&str] = &[
"session.starting",
"session.started",
"frame.opening",
"frame.opened",
"context.pressure_observed",
"context.compacted",
"frame.ending",
"frame.ended",
"session.ending",
"session.ended",
"supervisor.tick",
"capability.degraded",
"receipt.emitted",
"receipt.gap_detected",
];
const SPEC_INTEGRATION_MODES: &[&str] = &[
"manual_skill",
"launcher_wrapper",
"native_hook",
"reference_adapter",
"telemetry_only",
];
const SPEC_SUPPORT_STATES: &[&str] = &["native", "synthesized", "manual", "partial", "unavailable"];
const SPEC_MANIFEST_PLACEMENT_CLASSES: &[&str] = &[
"pre_session",
"pre_frame_leading",
"pre_frame_trailing",
"tool_result",
"manual_operator",
];
const SPEC_ADAPTER_ROLES: &[&str] = &["primary_worker", "worker", "supervisor", "observer"];
const SPEC_RECEIPT_STATUSES: &[&str] = &["observed", "delivered", "skipped", "degraded", "failed"];
const SPEC_FAILURE_CLASSES: &[&str] = &[
"adapter_unavailable",
"capability_unsupported",
"capability_degraded",
"placement_unavailable",
"payload_too_large",
"payload_rejected",
"identity_unavailable",
"transport_error",
"timeout",
"operator_required",
"state_conflict",
"invalid_request",
"internal_error",
];
const SPEC_RETRY_CLASSES: &[&str] = &[
"safe_retry",
"retry_after_reread",
"retry_after_reconfigure",
"retry_after_operator",
"do_not_retry",
];
const SPEC_PLACEMENT_CLASSES: &[&str] = &[
"developer_equivalent_frame",
"pre_prompt_frame",
"side_channel_context",
"receipt_only",
];
const SPEC_PLACEMENT_OUTCOMES: &[&str] = &["delivered", "skipped", "degraded", "failed"];
const SPEC_REQUIREMENT_LEVELS: &[&str] = &["required", "preferred", "optional"];
const SPEC_NEGOTIATION_OUTCOMES: &[&str] =
&["satisfied", "degraded", "unsupported", "requires_operator"];
const SPEC_LIFECYCLE_RECEIPT_FIELDS: &[(&str, &str)] = &[
("schema_version", "required"),
("receipt_id", "required"),
("idempotency_key", "required_nullable"),
("client_id", "required"),
("adapter_id", "required"),
("invocation_id", "required"),
("event", "required"),
("event_id", "required"),
("sequence", "required_nullable"),
("parent_receipt_id", "required_nullable"),
("integration_mode", "required"),
("status", "required"),
("at_epoch_s", "required"),
("harness_session_id", "optional"),
("harness_run_id", "optional"),
("harness_task_id", "optional"),
("payload_receipts", "optional"),
("telemetry_summary", "optional"),
("capability_degradations", "optional"),
("failure_class", "required_nullable"),
("retry_class", "required_nullable"),
("warnings", "optional"),
];
const SPEC_PAYLOAD_ENVELOPE_FIELDS: &[(&str, &str)] = &[
("schema_version", "required"),
("payload_id", "required"),
("client_id", "required"),
("payload_kind", "required"),
("format", "required"),
("content_encoding", "required"),
("body", "optional"),
("body_ref", "optional"),
("byte_size", "required"),
("content_digest", "optional"),
("acceptable_placements", "required"),
("idempotency_key", "optional"),
("expires_at_epoch_s", "optional"),
("redaction", "optional"),
("metadata", "optional"),
];
const SPEC_FRAME_CONTEXT_FIELDS: &[(&str, &str)] = &[
("frame_id", "required"),
("parent_frame_id", "optional"),
("frame_class", "required"),
];
const SPEC_ADAPTER_MANIFEST_FIELDS: &[(&str, &str)] = &[
("contract_version", "required"),
("adapter_id", "required"),
("adapter_version", "required"),
("display_name", "required"),
("role", "required"),
("integration_modes", "required"),
("lifecycle_events", "required"),
("placement", "required"),
("context_pressure", "required"),
("receipts", "required"),
("session_identity", "optional"),
("session_rename", "optional"),
("approval_surface", "optional"),
("failure_modes", "optional"),
("telemetry_sources", "optional"),
("known_degradations", "optional"),
];
const SPEC_FAILURE_TO_RETRY: &[(&str, &str)] = &[
("adapter_unavailable", "retry_after_reconfigure"),
("capability_unsupported", "do_not_retry"),
("capability_degraded", "retry_after_reread"),
("placement_unavailable", "retry_after_reconfigure"),
("payload_too_large", "do_not_retry"),
("payload_rejected", "retry_after_reconfigure"),
("identity_unavailable", "retry_after_reconfigure"),
("transport_error", "safe_retry"),
("timeout", "safe_retry"),
("operator_required", "retry_after_operator"),
("state_conflict", "retry_after_reread"),
("invalid_request", "do_not_retry"),
("internal_error", "retry_after_reread"),
];
fn wire_names<T: serde::Serialize>(values: &[T]) -> Vec<String> {
values
.iter()
.map(|v| {
serde_json::to_value(v)
.expect("serialize")
.as_str()
.expect("string wire name")
.to_string()
})
.collect()
}
fn json_keys(value: &Value) -> Vec<String> {
match value {
Value::Object(map) => map.keys().cloned().collect(),
_ => panic!("expected JSON object, got {value:?}"),
}
}
fn assert_keys_match_spec(actual: &Value, spec: &[(&str, &str)], type_name: &str) {
let keys: Vec<String> = json_keys(actual);
let expected: Vec<String> = spec.iter().map(|(name, _)| name.to_string()).collect();
let missing: Vec<&String> = expected.iter().filter(|n| !keys.contains(n)).collect();
let extra: Vec<&String> = keys
.iter()
.filter(|n| !spec.iter().any(|(s, _)| *s == n.as_str()))
.collect();
assert!(
missing.is_empty() && extra.is_empty(),
"{type_name} fields drift from spec:\n missing: {missing:?}\n extra: {extra:?}\n spec: {expected:?}\n actual: {keys:?}"
);
}
#[test]
fn schema_version_label_matches_spec_narrative() {
assert_eq!(
SCHEMA_VERSION, SPEC_SCHEMA_VERSION_LABEL,
"SCHEMA_VERSION drift: code says {SCHEMA_VERSION:?}, spec narrative says {SPEC_SCHEMA_VERSION_LABEL:?}"
);
}
#[test]
fn lifecycle_event_kinds_match_spec() {
assert_eq!(
wire_names(LifecycleEventKind::ALL),
SPEC_LIFECYCLE_EVENTS,
"LifecycleEventKind drift from spec lifecycle-event-vocabulary table"
);
}
#[test]
fn integration_modes_match_spec() {
let mut actual = wire_names(IntegrationMode::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_INTEGRATION_MODES
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"IntegrationMode drift from spec integration-modes list"
);
}
#[test]
fn support_states_match_spec() {
let mut actual = wire_names(SupportState::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_SUPPORT_STATES.iter().map(|s| s.to_string()).collect();
expected.sort();
assert_eq!(
actual, expected,
"SupportState drift from spec support-values table"
);
}
#[test]
fn adapter_roles_match_spec() {
let mut actual = wire_names(AdapterRole::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_ADAPTER_ROLES.iter().map(|s| s.to_string()).collect();
expected.sort();
assert_eq!(actual, expected, "AdapterRole drift from spec roles list");
}
#[test]
fn receipt_statuses_match_spec() {
let mut actual = wire_names(ReceiptStatus::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_RECEIPT_STATUSES
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"ReceiptStatus drift from spec receipt-statuses list"
);
}
#[test]
fn failure_classes_match_spec() {
let mut actual = wire_names(FailureClass::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_FAILURE_CLASSES.iter().map(|s| s.to_string()).collect();
expected.sort();
assert_eq!(
actual, expected,
"FailureClass drift from spec failure-classes table"
);
}
#[test]
fn retry_classes_match_spec() {
let mut actual = wire_names(RetryClass::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_RETRY_CLASSES.iter().map(|s| s.to_string()).collect();
expected.sort();
assert_eq!(
actual, expected,
"RetryClass drift from spec retry-classes table"
);
}
#[test]
fn placement_classes_match_spec() {
let mut actual = wire_names(PlacementClass::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_PLACEMENT_CLASSES
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"PlacementClass drift from spec placement-classes table"
);
}
#[test]
fn placement_outcomes_match_spec() {
let mut actual = wire_names(PlacementOutcome::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_PLACEMENT_OUTCOMES
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"PlacementOutcome drift from spec payload-receipt status values"
);
}
#[test]
fn requirement_levels_match_spec() {
let mut actual = wire_names(RequirementLevel::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_REQUIREMENT_LEVELS
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"RequirementLevel drift from spec requirement-levels table"
);
}
#[test]
fn negotiation_outcomes_match_spec() {
let mut actual = wire_names(NegotiationOutcome::ALL);
actual.sort();
let mut expected: Vec<String> = SPEC_NEGOTIATION_OUTCOMES
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"NegotiationOutcome drift from spec negotiation-outcomes table"
);
}
#[test]
fn frame_classes_present() {
let mut actual = wire_names(FrameClass::ALL);
actual.sort();
assert_eq!(actual, vec!["subcall".to_string(), "top_level".to_string()]);
}
#[test]
fn failure_to_retry_default_table_matches_spec() {
let actual_pairs: Vec<(String, String)> = FailureClass::ALL
.iter()
.map(|fc| {
let fc_name = serde_json::to_value(fc)
.unwrap()
.as_str()
.unwrap()
.to_string();
let rc_name = serde_json::to_value(fc.default_retry())
.unwrap()
.as_str()
.unwrap()
.to_string();
(fc_name, rc_name)
})
.collect();
let expected_pairs: Vec<(String, String)> = SPEC_FAILURE_TO_RETRY
.iter()
.map(|(fc, rc)| (fc.to_string(), rc.to_string()))
.collect();
let actual_map: BTreeMap<&str, &str> = actual_pairs
.iter()
.map(|(f, r)| (f.as_str(), r.as_str()))
.collect();
let expected_map: BTreeMap<&str, &str> = expected_pairs
.iter()
.map(|(f, r)| (f.as_str(), r.as_str()))
.collect();
assert_eq!(
actual_map, expected_map,
"FailureClass::default_retry() drift from spec failure→retry table"
);
}
fn fully_populated_frame_context() -> FrameContext {
FrameContext::subcall("frm-2", "frm-1")
}
fn fully_populated_payload() -> PayloadEnvelope {
PayloadEnvelope {
schema_version: SCHEMA_VERSION.to_string(),
payload_id: "pay-1".into(),
client_id: "ccd".into(),
payload_kind: "instruction_frame".into(),
format: "client-defined".into(),
content_encoding: "utf8".into(),
body: Some("hello".into()),
body_ref: None,
byte_size: 5,
content_digest: Some("sha256:abc".into()),
acceptable_placements: vec![AcceptablePlacement {
placement: PlacementClass::PrePromptFrame,
requirement: RequirementLevel::Required,
}],
idempotency_key: Some("idem-1".into()),
expires_at_epoch_s: Some(1_778_200_000),
redaction: Some("none".into()),
metadata: serde_json::Map::from_iter([("k".into(), serde_json::Value::String("v".into()))]),
}
}
fn fully_populated_receipt() -> LifecycleReceipt {
LifecycleReceipt {
schema_version: SCHEMA_VERSION.to_string(),
receipt_id: "lfr-1".into(),
idempotency_key: Some("idem-1".into()),
client_id: "ccd".into(),
adapter_id: "codex".into(),
invocation_id: "inv-1".into(),
event: LifecycleEventKind::FrameOpening,
event_id: "evt-1".into(),
sequence: Some(42),
parent_receipt_id: Some("lfr-0".into()),
integration_mode: IntegrationMode::NativeHook,
status: ReceiptStatus::Delivered,
at_epoch_s: 1_778_100_000,
harness_session_id: Some("session-123".into()),
harness_run_id: Some("run-456".into()),
harness_task_id: Some("task-789".into()),
payload_receipts: vec![PayloadReceipt {
payload_id: "pay-1".into(),
placement: PlacementClass::PrePromptFrame,
status: PlacementOutcome::Delivered,
byte_size: 5,
}],
telemetry_summary: serde_json::Map::from_iter([(
"context_pressure".into(),
serde_json::Value::String("moderate".into()),
)]),
capability_degradations: vec![CapabilityDegradation {
capability: "context_pressure".into(),
previous_support: SupportState::Native,
current_support: SupportState::Unavailable,
evidence: Some("missing".into()),
retry_class: Some(RetryClass::RetryAfterReconfigure),
}],
failure_class: None,
retry_class: None,
warnings: vec![Warning {
code: "demo".into(),
message: "demo".into(),
capability: None,
}],
}
}
#[test]
fn frame_context_fields_match_spec() {
let value = serde_json::to_value(fully_populated_frame_context()).unwrap();
assert_keys_match_spec(&value, SPEC_FRAME_CONTEXT_FIELDS, "FrameContext");
}
#[test]
fn payload_envelope_fields_match_spec() {
let with_body = serde_json::to_value(fully_populated_payload()).unwrap();
let mut alt = fully_populated_payload();
alt.body = None;
alt.body_ref = Some("blob-1".into());
let with_body_ref = serde_json::to_value(&alt).unwrap();
let mut union = serde_json::Map::new();
for v in [&with_body, &with_body_ref] {
for (k, val) in v.as_object().expect("object") {
union.insert(k.clone(), val.clone());
}
}
assert_keys_match_spec(
&Value::Object(union),
SPEC_PAYLOAD_ENVELOPE_FIELDS,
"PayloadEnvelope",
);
}
#[test]
fn lifecycle_receipt_fields_match_spec() {
let value = serde_json::to_value(fully_populated_receipt()).unwrap();
assert_keys_match_spec(&value, SPEC_LIFECYCLE_RECEIPT_FIELDS, "LifecycleReceipt");
}
#[test]
fn lifecycle_receipt_required_nullable_const_matches_spec() {
let from_spec: std::collections::BTreeSet<&str> = SPEC_LIFECYCLE_RECEIPT_FIELDS
.iter()
.filter(|(_, presence)| *presence == "required_nullable")
.map(|(field, _)| *field)
.collect();
let from_code: std::collections::BTreeSet<&str> = LifecycleReceipt::REQUIRED_NULLABLE_FIELDS
.iter()
.copied()
.collect();
assert_eq!(
from_code, from_spec,
"LifecycleReceipt::REQUIRED_NULLABLE_FIELDS disagrees with the spec \
taxonomy in SPEC_LIFECYCLE_RECEIPT_FIELDS. If the spec added or \
removed a required-nullable field, update the const in src/lib.rs \
in the same commit."
);
}
#[test]
fn lifecycle_receipt_required_nullable_fields_present_when_none() {
let r = LifecycleReceipt {
schema_version: SCHEMA_VERSION.to_string(),
receipt_id: "lfr-min".into(),
idempotency_key: None,
client_id: "ccd".into(),
adapter_id: "codex".into(),
invocation_id: "inv-min".into(),
event: LifecycleEventKind::SessionStarting,
event_id: "evt-min".into(),
sequence: None,
parent_receipt_id: None,
integration_mode: IntegrationMode::NativeHook,
status: ReceiptStatus::Observed,
at_epoch_s: 1_778_100_000,
harness_session_id: None,
harness_run_id: None,
harness_task_id: None,
payload_receipts: Vec::new(),
telemetry_summary: serde_json::Map::new(),
capability_degradations: Vec::new(),
failure_class: None,
retry_class: None,
warnings: Vec::new(),
};
let value = serde_json::to_value(&r).unwrap();
let keys: std::collections::BTreeSet<String> =
value.as_object().unwrap().keys().cloned().collect();
for (field, presence) in SPEC_LIFECYCLE_RECEIPT_FIELDS {
if *presence == "required" || *presence == "required_nullable" {
assert!(
keys.contains(*field),
"required field `{field}` ({presence}) missing from minimal LifecycleReceipt JSON: {keys:?}"
);
}
}
}
#[test]
fn adapter_manifest_fields_match_spec() {
let manifest = lifeloop::AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: "codex".into(),
adapter_version: "0.1.0".into(),
display_name: "Codex".into(),
role: AdapterRole::PrimaryWorker,
integration_modes: vec![IntegrationMode::NativeHook],
lifecycle_events: BTreeMap::from([(
LifecycleEventKind::SessionStarting,
lifeloop::ManifestLifecycleEventSupport {
support: SupportState::Native,
modes: vec![IntegrationMode::NativeHook],
},
)]),
placement: BTreeMap::from([(
lifeloop::ManifestPlacementClass::PreSession,
lifeloop::ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: Some(8192),
},
)]),
context_pressure: lifeloop::ManifestContextPressure {
support: SupportState::Synthesized,
evidence: None,
},
receipts: lifeloop::ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: SupportState::Unavailable,
},
session_identity: Some(lifeloop::ManifestSessionIdentity {
harness_session_id: SupportState::Native,
harness_run_id: SupportState::Synthesized,
harness_task_id: SupportState::Unavailable,
}),
session_rename: Some(lifeloop::ManifestSessionRename {
support: SupportState::Manual,
}),
approval_surface: Some(lifeloop::ManifestApprovalSurface {
support: SupportState::Manual,
}),
failure_modes: vec![FailureClass::TransportError],
telemetry_sources: vec![lifeloop::ManifestTelemetrySource {
source: "context-pressure-log".into(),
support: SupportState::Synthesized,
}],
known_degradations: vec![lifeloop::ManifestKnownDegradation {
capability: "supervisor.tick".into(),
previous_support: SupportState::Native,
current_support: SupportState::Unavailable,
evidence: Some("not exposed on this build".into()),
}],
};
let value = serde_json::to_value(&manifest).unwrap();
assert_keys_match_spec(&value, SPEC_ADAPTER_MANIFEST_FIELDS, "AdapterManifest");
}
#[test]
fn manifest_placement_classes_match_spec() {
let mut actual: Vec<String> = lifeloop::ManifestPlacementClass::ALL
.iter()
.map(|v| {
serde_json::to_value(v)
.expect("serialize")
.as_str()
.expect("string wire name")
.to_string()
})
.collect();
actual.sort();
let mut expected: Vec<String> = SPEC_MANIFEST_PLACEMENT_CLASSES
.iter()
.map(|s| s.to_string())
.collect();
expected.sort();
assert_eq!(
actual, expected,
"ManifestPlacementClass drift from spec manifest-placement-classes table"
);
}