use serde_json::Value;
use std::fmt;
const REGISTRY: &[(&str, &str)] = &[
(
"memory.write.v1",
include_str!("schemas/memory.write.v1.json"),
),
(
"memory.read.v1",
include_str!("schemas/memory.read.v1.json"),
),
(
"memory.quarantine-check.v1",
include_str!("schemas/memory.quarantine-check.v1.json"),
),
("blocked.v1", include_str!("schemas/blocked.v1.json")),
(
"reason.authorization.v1",
include_str!("schemas/reason.authorization.v1.json"),
),
("boundary.v1", include_str!("schemas/boundary.v1.json")),
("agent_card.v1", include_str!("schemas/agent_card.v1.json")),
(
"agent_card_revocation.v1",
include_str!("schemas/agent_card_revocation.v1.json"),
),
(
"grant_revocation.v1",
include_str!("schemas/grant_revocation.v1.json"),
),
("session.v1", include_str!("schemas/session.v1.json")),
("agent_cert.v1", include_str!("schemas/agent_cert.v1.json")),
("profile.v1", include_str!("schemas/profile.v1.json")),
("workflow.v1", include_str!("schemas/workflow.v1.json")),
(
"verification.packet.v1",
include_str!("schemas/verification.packet.v1.json"),
),
(
"verification.recompute.v1",
include_str!("schemas/verification.recompute.v1.json"),
),
("evaluation.v1", include_str!("schemas/evaluation.v1.json")),
("coverage.v1", include_str!("schemas/coverage.v1.json")),
("halt.v1", include_str!("schemas/halt.v1.json")),
("judgement.v1", include_str!("schemas/judgement.v1.json")),
];
pub fn schema_json(suffix: &str) -> Option<&'static str> {
REGISTRY.iter().find(|(k, _)| *k == suffix).map(|(_, s)| *s)
}
pub fn registered_suffixes() -> Vec<&'static str> {
REGISTRY.iter().map(|(k, _)| *k).collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PredicateError {
MissingField { suffix: String, field: String },
TypeMismatch {
suffix: String,
field: String,
expected: String,
},
NotAnObject { suffix: String },
NotInEnum {
suffix: String,
field: String,
allowed: String,
},
SchemaParse { suffix: String, detail: String },
InvalidPayload { suffix: String, detail: String },
}
impl fmt::Display for PredicateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PredicateError::MissingField { suffix, field } => {
write!(f, "{suffix}: missing required field `{field}`")
}
PredicateError::TypeMismatch {
suffix,
field,
expected,
} => write!(
f,
"{suffix}: field `{field}` has the wrong type (expected {expected})"
),
PredicateError::NotAnObject { suffix } => {
write!(f, "{suffix}: payload must be a JSON object")
}
PredicateError::NotInEnum {
suffix,
field,
allowed,
} => write!(
f,
"{suffix}: field `{field}` has a value outside its allowed set ({allowed})"
),
PredicateError::SchemaParse { suffix, detail } => {
write!(f, "{suffix}: registered schema is invalid JSON: {detail}")
}
PredicateError::InvalidPayload { suffix, detail } => {
write!(f, "{suffix}: invalid payload: {detail}")
}
}
}
}
impl std::error::Error for PredicateError {}
pub fn validate(suffix: &str, payload: Option<&Value>) -> Result<(), PredicateError> {
let Some(schema_str) = schema_json(suffix) else {
return Ok(());
};
let schema: Value =
serde_json::from_str(schema_str).map_err(|e| PredicateError::SchemaParse {
suffix: suffix.to_string(),
detail: e.to_string(),
})?;
let empty = Value::Object(serde_json::Map::new());
let value = payload.unwrap_or(&empty);
let map = value
.as_object()
.ok_or_else(|| PredicateError::NotAnObject {
suffix: suffix.to_string(),
})?;
walk_object(suffix, "", &schema, map)?;
if suffix == "workflow.v1" {
use crate::verify::workflow_conformance::WorkflowDeclaration;
let declaration: WorkflowDeclaration =
serde_json::from_value(value.clone()).map_err(|e| PredicateError::InvalidPayload {
suffix: suffix.to_string(),
detail: e.to_string(),
})?;
declaration
.validate()
.map_err(|errors| PredicateError::InvalidPayload {
suffix: suffix.to_string(),
detail: errors
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join("; "),
})?;
}
Ok(())
}
fn walk_object(
suffix: &str,
path: &str,
schema: &Value,
map: &serde_json::Map<String, Value>,
) -> Result<(), PredicateError> {
let at = |field: &str| -> String {
if path.is_empty() {
field.to_string()
} else {
format!("{path}.{field}")
}
};
if let Some(required) = schema.get("required").and_then(Value::as_array) {
for entry in required {
if let Some(name) = entry.as_str() {
if !map.contains_key(name) {
return Err(PredicateError::MissingField {
suffix: suffix.to_string(),
field: at(name),
});
}
}
}
}
if let Some(props) = schema.get("properties").and_then(Value::as_object) {
for (field, subschema) in props {
let Some(actual) = map.get(field) else {
continue; };
walk_value(suffix, &at(field), subschema, actual)?;
}
}
if let Some(extra) = schema.get("additionalProperties") {
if extra.is_object() {
if let Some(declared) = schema.get("properties").and_then(Value::as_object) {
for (field, actual) in map {
if !declared.contains_key(field) {
walk_value(suffix, &at(field), extra, actual)?;
}
}
} else {
for (field, actual) in map {
walk_value(suffix, &at(field), extra, actual)?;
}
}
}
}
Ok(())
}
fn walk_value(
suffix: &str,
path: &str,
subschema: &Value,
actual: &Value,
) -> Result<(), PredicateError> {
if let Some(type_decl) = subschema.get("type") {
if !type_matches(actual, type_decl) {
return Err(PredicateError::TypeMismatch {
suffix: suffix.to_string(),
field: path.to_string(),
expected: type_decl.to_string(),
});
}
}
if let Some(allowed) = subschema.get("enum").and_then(Value::as_array) {
if !allowed.iter().any(|a| a == actual) {
return Err(PredicateError::NotInEnum {
suffix: suffix.to_string(),
field: path.to_string(),
allowed: Value::Array(allowed.clone()).to_string(),
});
}
}
if let Some(constant) = subschema.get("const") {
if actual != constant {
return Err(PredicateError::NotInEnum {
suffix: suffix.to_string(),
field: path.to_string(),
allowed: constant.to_string(),
});
}
}
if let Some(n) = actual.as_f64() {
if let Some(min) = subschema.get("minimum").and_then(Value::as_f64) {
if n < min {
return Err(PredicateError::InvalidPayload {
suffix: suffix.to_string(),
detail: format!("{path} is {n}, below the minimum {min}"),
});
}
}
if let Some(max) = subschema.get("maximum").and_then(Value::as_f64) {
if n > max {
return Err(PredicateError::InvalidPayload {
suffix: suffix.to_string(),
detail: format!("{path} is {n}, above the maximum {max}"),
});
}
}
}
if let (Some(s), Some(pat)) = (
actual.as_str(),
subschema.get("pattern").and_then(Value::as_str),
) {
if !pattern_matches(pat, s) {
return Err(PredicateError::InvalidPayload {
suffix: suffix.to_string(),
detail: format!("{path} does not match {pat}"),
});
}
}
match actual {
Value::Object(inner) => walk_object(suffix, path, subschema, inner)?,
Value::Array(items) => {
if let Some(item_schema) = subschema.get("items") {
for (i, item) in items.iter().enumerate() {
walk_value(suffix, &format!("{path}[{i}]"), item_schema, item)?;
}
}
}
_ => {}
}
Ok(())
}
fn pattern_matches(pat: &str, s: &str) -> bool {
let Some(body) = pat.strip_prefix('^').and_then(|p| p.strip_suffix('$')) else {
return true;
};
let Some(open) = body.find('[') else {
return true;
};
let literal = &body[..open];
let Some(close) = body[open..].find(']') else {
return true;
};
let class = &body[open + 1..open + close];
let rest = &body[open + close + 1..];
let Some(count) = rest
.strip_prefix('{')
.and_then(|r| r.strip_suffix('}'))
.and_then(|n| n.parse::<usize>().ok())
else {
return true;
};
let Some(tail) = s.strip_prefix(literal) else {
return false;
};
if tail.chars().count() != count {
return false;
}
let in_class = |c: char| -> bool {
let cs: Vec<char> = class.chars().collect();
let mut i = 0;
while i < cs.len() {
if i + 2 < cs.len() && cs[i + 1] == '-' {
if c >= cs[i] && c <= cs[i + 2] {
return true;
}
i += 3;
} else {
if c == cs[i] {
return true;
}
i += 1;
}
}
false
};
tail.chars().all(in_class)
}
fn type_matches(value: &Value, type_decl: &Value) -> bool {
match type_decl {
Value::String(t) => json_is(value, t),
Value::Array(types) => types
.iter()
.any(|t| t.as_str().is_some_and(|t| json_is(value, t))),
_ => true,
}
}
fn json_is(value: &Value, ty: &str) -> bool {
match ty {
"string" => value.is_string(),
"integer" => value.is_i64() || value.is_u64(),
"number" => value.is_number(),
"boolean" => value.is_boolean(),
"object" => value.is_object(),
"array" => value.is_array(),
"null" => value.is_null(),
_ => true,
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn registry_lists_the_three_seed_predicates() {
let suffixes = registered_suffixes();
assert!(suffixes.contains(&"memory.write.v1"));
assert!(suffixes.contains(&"memory.read.v1"));
assert!(suffixes.contains(&"boundary.v1"));
assert!(suffixes.contains(&"agent_card.v1"));
assert!(schema_json("memory.write.v1").is_some());
assert!(schema_json("nope.v1").is_none());
}
#[test]
fn embedded_schemas_parse() {
for s in registered_suffixes() {
let raw = schema_json(s).unwrap();
serde_json::from_str::<Value>(raw).expect("embedded schema must be valid JSON");
}
}
#[test]
fn workflow_declaration_runs_full_typed_validation_before_signing() {
let valid: Value = serde_json::from_str(include_str!(
"../../tests/fixtures/workflow-conformance/declaration.json"
))
.expect("golden workflow declaration parses");
assert!(validate("workflow.v1", Some(&valid)).is_ok());
let mut unknown_field = valid.clone();
unknown_field["nodes"][0]["retry_policy"] = json!({ "max": 99 });
assert!(matches!(
validate("workflow.v1", Some(&unknown_field)),
Err(PredicateError::InvalidPayload { .. })
));
let mut missing_allowed_tools = valid.clone();
missing_allowed_tools["nodes"][0]
.as_object_mut()
.expect("workflow node is an object")
.remove("allowed_tools");
let error = validate("workflow.v1", Some(&missing_allowed_tools))
.expect_err("schema-required nested fields must be refused before signing");
assert!(matches!(
error,
PredicateError::InvalidPayload { .. } | PredicateError::MissingField { .. }
));
assert!(error.to_string().contains("allowed_tools"));
let mut unbounded_cycle = valid;
unbounded_cycle["edges"]
.as_array_mut()
.expect("edges is an array")
.push(json!({ "from": "finish", "to": "inspect", "when": "always" }));
let error = validate("workflow.v1", Some(&unbounded_cycle))
.expect_err("an undeclared workflow cycle must be refused before signing");
assert!(matches!(error, PredicateError::InvalidPayload { .. }));
assert!(error.to_string().contains("bounded loop"));
}
#[test]
fn quarantine_check_valid_passes() {
let payload = json!({
"action_id": "aac_1f2e3d4c",
"provider": "system://zmem",
"chain_root": "u3v9xJ2kQm4Zr8pW1sTnA7bCdEfGhIjKlMnOpQrStUv",
"decision_seq": 1042,
"clean": true,
"quarantined_triggers": [],
"checked_at": "2026-07-17T19:00:00Z"
});
assert!(validate("memory.quarantine-check.v1", Some(&payload)).is_ok());
}
#[test]
fn quarantine_check_missing_verdict_fails_closed() {
let payload = json!({
"action_id": "aac_1f2e3d4c",
"chain_root": "u3v9xJ2kQm4Zr8pW1sTnA7bCdEfGhIjKlMnOpQrStUv",
"decision_seq": 1042
}); let err = validate("memory.quarantine-check.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::MissingField {
suffix: "memory.quarantine-check.v1".into(),
field: "clean".into()
}
);
}
#[test]
fn quarantine_check_stringly_typed_verdict_fails_closed() {
let payload = json!({
"action_id": "aac_1f2e3d4c",
"chain_root": "u3v9xJ2kQm4Zr8pW1sTnA7bCdEfGhIjKlMnOpQrStUv",
"decision_seq": 1042,
"clean": "true"
});
let err = validate("memory.quarantine-check.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::TypeMismatch {
suffix: "memory.quarantine-check.v1".into(),
field: "clean".into(),
expected: "\"boolean\"".into()
}
);
}
#[test]
fn quarantine_check_non_integer_seq_fails_closed() {
let payload = json!({
"action_id": "aac_1f2e3d4c",
"chain_root": "u3v9xJ2kQm4Zr8pW1sTnA7bCdEfGhIjKlMnOpQrStUv",
"decision_seq": "1042",
"clean": true
});
let err = validate("memory.quarantine-check.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::TypeMismatch {
suffix: "memory.quarantine-check.v1".into(),
field: "decision_seq".into(),
expected: "\"integer\"".into()
}
);
}
fn reason_authorization_payload() -> Value {
json!({
"schema": "zerker.reason.authorization.v1",
"status": "authorized",
"request_digest": format!("sha256:{}", "1".repeat(64)),
"mission": {
"id": "mission_release_140",
"digest": format!("sha256:{}", "2".repeat(64))
},
"action": {
"id": "action_deploy_140",
"digest": format!("sha256:{}", "3".repeat(64)),
"tool": "deploy_release",
"arguments": {"environment": "production"},
"effects": []
},
"reasoning": {
"schema": "zerker.reason.result.v2",
"status": "proved",
"query": {"predicate": "authorized", "arguments": ["action_deploy_140"]},
"program_digest": format!("sha256:{}", "4".repeat(64)),
"ontology": {},
"authority": {"classes": ["human-authorized"], "default_admit": ["human-authorized"]},
"proof": {"root": "authorized(action_deploy_140)"},
"disproof": null,
"conflict": null,
"missing": [],
"assumptions": [],
"metrics": {"facts": 3, "rules": 1}
},
"issues": []
})
}
#[test]
fn reason_authorization_valid_shape_passes() {
assert!(validate(
"reason.authorization.v1",
Some(&reason_authorization_payload())
)
.is_ok());
}
#[test]
fn reason_authorization_nested_required_is_enforced() {
let mut p = reason_authorization_payload();
p["action"].as_object_mut().unwrap().remove("id");
assert_eq!(
validate("reason.authorization.v1", Some(&p)),
Err(PredicateError::MissingField {
suffix: "reason.authorization.v1".into(),
field: "action.id".into()
})
);
let mut p = reason_authorization_payload();
p["reasoning"]["status"] = json!("vibes");
assert!(matches!(
validate("reason.authorization.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "reasoning.status"
));
}
#[test]
fn reason_authorization_missing_request_digest_fails_closed() {
let mut payload = reason_authorization_payload();
payload.as_object_mut().unwrap().remove("request_digest");
let err = validate("reason.authorization.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::MissingField {
suffix: "reason.authorization.v1".into(),
field: "request_digest".into()
}
);
}
#[test]
fn reason_authorization_out_of_vocabulary_status_fails_closed() {
let mut payload = reason_authorization_payload();
payload["status"] = json!("probably_safe");
let err = validate("reason.authorization.v1", Some(&payload)).unwrap_err();
assert!(
matches!(&err, PredicateError::NotInEnum { field, .. } if field == "status"),
"expected NotInEnum on status, got {err:?}"
);
}
#[test]
fn reason_authorization_wrong_action_shape_fails_closed() {
let payload = json!({
"schema": "zerker.reason.authorization.v1",
"status": "denied",
"request_digest": format!("sha256:{}", "1".repeat(64)),
"mission": {},
"action": "action_deploy_140",
"reasoning": {},
"issues": []
});
let err = validate("reason.authorization.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::TypeMismatch {
suffix: "reason.authorization.v1".into(),
field: "action".into(),
expected: "\"object\"".into()
}
);
}
#[test]
fn blocked_valid_passes() {
let payload = json!({
"reason_class": "quarantine_triggered",
"refused_kind": "approval",
"approver": "human://alice",
"irreversibility": "one_way_consequential",
"description": "quarantine check reports DIRTY",
"quarantine_receipt": "art_deadbeef00112233"
});
assert!(validate("blocked.v1", Some(&payload)).is_ok());
}
#[test]
fn blocked_out_of_vocabulary_reason_fails_closed() {
let payload = json!({
"reason_class": "just_felt_like_it",
"refused_kind": "approval"
});
let err = validate("blocked.v1", Some(&payload)).unwrap_err();
assert!(
matches!(err, PredicateError::NotInEnum { ref field, .. } if field == "reason_class"),
"expected NotInEnum on reason_class, got {err:?}"
);
}
#[test]
fn blocked_missing_reason_fails_closed() {
let payload = json!({ "refused_kind": "approval" });
let err = validate("blocked.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::MissingField {
suffix: "blocked.v1".into(),
field: "reason_class".into()
}
);
}
#[test]
fn unregistered_suffix_is_backward_compatible() {
assert!(validate("custom.kind.v1", None).is_ok());
assert!(validate("custom.kind.v1", Some(&json!({"anything": 1}))).is_ok());
}
#[test]
fn agent_cert_valid_passes() {
let payload = json!({
"agent": "agent://deployer",
"subject_key_id": "key_abc123",
"subject_public_key": "vEQfSDqVCz4rtqbu5iuhpFuYrah6QALUSCGJYdOKeCY",
"issuer": "ship://ship_b49ff5f291a279c7",
"issued_at": "2026-07-06T12:00:00Z",
"valid_until": "2027-07-06T12:00:00Z",
"model": "claude-fable-5",
"description": null
});
assert!(validate("agent_cert.v1", Some(&payload)).is_ok());
}
#[test]
fn agent_cert_missing_subject_key_fails_closed() {
let payload = json!({
"agent": "agent://deployer",
"subject_key_id": "key_abc123",
"issuer": "ship://ship_x",
"issued_at": "2026-07-06T12:00:00Z",
"valid_until": "2027-07-06T12:00:00Z"
}); let err = validate("agent_cert.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::MissingField {
suffix: "agent_cert.v1".into(),
field: "subject_public_key".into()
}
);
}
#[test]
fn session_record_valid_passes() {
let payload = json!({
"session_id": "ssn_abc123",
"actor": "agent://hermes",
"headline": "Fixed keystore hostname-drift bug",
"outcome": "completed",
"started_at": "2026-07-06T14:00:00Z",
"closed_at": "2026-07-06T15:30:00Z",
"duration_ms": 5400000,
"harness": "claude-code",
"attestation_class": "runtime",
"action_count": 212,
"approval_count": 2,
"handoff_count": 0,
"event_count": 340,
"tools_exercised": ["Bash(git:*)", "Edit(*)"],
"receipt_digest": "sha256:deadbeef",
"receipt_merkle_root": "sha256:cafebabe",
"report_url": null
});
assert!(validate("session.v1", Some(&payload)).is_ok());
}
#[test]
fn session_record_out_of_enum_class_fails_closed() {
let payload = json!({
"session_id": "ssn_abc123",
"actor": "agent://hermes",
"outcome": "completed",
"started_at": "2026-07-06T14:00:00Z",
"closed_at": "2026-07-06T15:30:00Z",
"attestation_class": "super-trusted",
"receipt_digest": "sha256:deadbeef"
});
let err = validate("session.v1", Some(&payload)).unwrap_err();
assert!(
matches!(err, PredicateError::NotInEnum { ref field, .. } if field == "attestation_class"),
"expected NotInEnum for attestation_class, got {err:?}"
);
}
#[test]
fn session_record_out_of_enum_outcome_fails_closed() {
let payload = json!({
"session_id": "ssn_abc123",
"actor": "agent://hermes",
"outcome": "totally-shipped",
"started_at": "2026-07-06T14:00:00Z",
"closed_at": "2026-07-06T15:30:00Z",
"attestation_class": "self",
"receipt_digest": "sha256:deadbeef"
});
assert!(matches!(
validate("session.v1", Some(&payload)).unwrap_err(),
PredicateError::NotInEnum { .. }
));
}
#[test]
fn session_record_missing_required_fails_closed() {
let payload = json!({
"session_id": "ssn_abc123",
"actor": "agent://hermes",
"outcome": "completed",
"started_at": "2026-07-06T14:00:00Z",
"closed_at": "2026-07-06T15:30:00Z",
"receipt_digest": "sha256:deadbeef"
}); let err = validate("session.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::MissingField {
suffix: "session.v1".into(),
field: "attestation_class".into()
}
);
}
#[test]
fn session_record_wrong_type_fails_closed() {
let payload = json!({
"session_id": "ssn_abc123",
"actor": "agent://hermes",
"outcome": "completed",
"started_at": "2026-07-06T14:00:00Z",
"closed_at": "2026-07-06T15:30:00Z",
"attestation_class": "runtime",
"receipt_digest": "sha256:deadbeef",
"tools_exercised": "Bash(git:*)"
}); let err = validate("session.v1", Some(&payload)).unwrap_err();
assert!(matches!(err, PredicateError::TypeMismatch { .. }));
}
#[test]
fn memory_write_valid_passes() {
let payload = json!({
"memory_id": "mem_abc",
"content_hash": "sha256:deadbeef",
"memory_type": "episodic",
"scope": "tenant://acme",
"activegraph_run_id": "run_1",
"supersedes": null
});
assert!(validate("memory.write.v1", Some(&payload)).is_ok());
}
#[test]
fn memory_write_missing_required_fails_closed() {
let payload = json!({
"memory_id": "mem_abc",
"memory_type": "episodic",
"scope": "tenant://acme"
}); let err = validate("memory.write.v1", Some(&payload)).unwrap_err();
assert_eq!(
err,
PredicateError::MissingField {
suffix: "memory.write.v1".into(),
field: "content_hash".into()
}
);
}
#[test]
fn memory_write_wrong_type_fails() {
let payload = json!({
"memory_id": "mem_abc",
"content_hash": 12345, "memory_type": "episodic",
"scope": "tenant://acme"
});
let err = validate("memory.write.v1", Some(&payload)).unwrap_err();
assert!(
matches!(err, PredicateError::TypeMismatch { field, .. } if field == "content_hash")
);
}
#[test]
fn memory_write_nullable_supersedes_accepts_string_and_null() {
let base = |sup: Value| {
json!({
"memory_id": "m", "content_hash": "h", "memory_type": "t", "scope": "s",
"supersedes": sup
})
};
assert!(validate("memory.write.v1", Some(&base(json!("mem_old")))).is_ok());
assert!(validate("memory.write.v1", Some(&base(Value::Null))).is_ok());
assert!(validate("memory.write.v1", Some(&base(json!(7)))).is_err());
}
#[test]
fn registered_predicate_requires_a_payload() {
let err = validate("memory.write.v1", None).unwrap_err();
assert!(matches!(err, PredicateError::MissingField { .. }));
}
#[test]
fn memory_read_valid_and_integer_enforced() {
let ok = json!({
"zmem_receipt_id": "act_1",
"trace_sha256": "abcd",
"query_hash": "qh",
"retrieval_mode": "semantic",
"memories_returned": 3
});
assert!(validate("memory.read.v1", Some(&ok)).is_ok());
let bad = json!({
"zmem_receipt_id": "act_1",
"trace_sha256": "abcd",
"query_hash": "qh",
"retrieval_mode": "semantic",
"memories_returned": "three" });
assert!(matches!(
validate("memory.read.v1", Some(&bad)).unwrap_err(),
PredicateError::TypeMismatch { field, .. } if field == "memories_returned"
));
}
#[test]
fn memory_read_missing_required_fails() {
let payload = json!({
"zmem_receipt_id": "act_1",
"trace_sha256": "abcd",
"retrieval_mode": "semantic",
"memories_returned": 3
}); assert!(matches!(
validate("memory.read.v1", Some(&payload)).unwrap_err(),
PredicateError::MissingField { field, .. } if field == "query_hash"
));
}
#[test]
fn boundary_structural_required_fields_enforced() {
let valid = json!({
"schema": "treeship.boundary.v1",
"subject_ref": "art_aabbccdd11223344",
"actor": {"uri": "agent://codex", "keyid": "key_aaaa1111"},
"checker": {"uri": "human://alice", "keyid": "key_bbbb2222"},
"decision": "allow",
"policy": {"digest": "sha256:p"},
"diet_root": "sha256:r",
"diet": [{"type": "memory_bundle", "digest": "sha256:d"}],
"committed_at": {"anchor": "merkle://zmem/checkpoint#4821", "ts": "2026-06-06T00:00:00Z"}
});
assert!(validate("boundary.v1", Some(&valid)).is_ok());
let mut wrong = valid.clone();
wrong.as_object_mut().unwrap()["committed_at"] = json!("not-an-object");
assert!(matches!(
validate("boundary.v1", Some(&wrong)).unwrap_err(),
PredicateError::TypeMismatch { field, .. } if field == "committed_at"
));
let mut missing = valid.clone();
missing.as_object_mut().unwrap().remove("decision");
assert!(matches!(
validate("boundary.v1", Some(&missing)).unwrap_err(),
PredicateError::MissingField { field, .. } if field == "decision"
));
}
#[test]
fn agent_card_valid_passes() {
let card = json!({
"schema": "agent_card.v1",
"agent": "agent://deployer",
"keyid": "key_9f8e7d6c",
"owner": "human://alice",
"version": "1.2.0",
"capabilities": {
"tools": ["file.read", "file.write", "db.*"],
"models": ["claude-sonnet-4"],
"can_delegate": true
},
"evidence_anchor": { "receipt_count": 1247, "merkle_root": "mroot_a0be" },
"supersedes": null
});
assert!(validate("agent_card.v1", Some(&card)).is_ok());
}
#[test]
fn agent_card_missing_keyid_fails_closed() {
let card = json!({
"schema": "agent_card.v1",
"agent": "agent://deployer",
"version": "1.0.0",
"capabilities": { "tools": ["file.read"] }
});
assert!(matches!(
validate("agent_card.v1", Some(&card)).unwrap_err(),
PredicateError::MissingField { field, .. } if field == "keyid"
));
}
#[test]
fn agent_card_capabilities_must_be_an_object() {
let card = json!({
"schema": "agent_card.v1",
"agent": "agent://deployer",
"keyid": "key_1",
"version": "1.0.0",
"capabilities": ["file.read"] });
assert!(matches!(
validate("agent_card.v1", Some(&card)).unwrap_err(),
PredicateError::TypeMismatch { field, .. } if field == "capabilities"
));
}
#[test]
fn agent_card_revocation_valid_passes() {
let rev = json!({
"schema": "agent_card_revocation.v1",
"card": "art_deadbeefdeadbeef",
"keyid": "key_1",
"reason": "key-rotation",
"revoked_at": "2026-06-23T00:00:00Z"
});
assert!(validate("agent_card_revocation.v1", Some(&rev)).is_ok());
}
#[test]
fn agent_card_revocation_requires_card_id() {
let rev = json!({
"schema": "agent_card_revocation.v1",
"revoked_at": "2026-06-23T00:00:00Z"
});
assert!(matches!(
validate("agent_card_revocation.v1", Some(&rev)).unwrap_err(),
PredicateError::MissingField { field, .. } if field == "card"
));
}
fn packet_payload() -> serde_json::Value {
json!({
"schema": "verification.packet.v1",
"packet_id": "pkt_000042",
"stream_id": "tap-7/req-9f2a",
"sequence": 42,
"prev_packet_id": "pkt_000041",
"model_digest": "sha256:aa11",
"input_digest": "sha256:bb22",
"output_digest": "sha256:cc33",
"reproducibility": "bit_exact",
"produced_at": "2026-09-17T15:00:00Z"
})
}
#[test]
fn verification_packet_valid_passes() {
assert!(validate("verification.packet.v1", Some(&packet_payload())).is_ok());
}
#[test]
fn verification_packet_missing_output_digest_fails_closed() {
let mut p = packet_payload();
p.as_object_mut().unwrap().remove("output_digest");
assert_eq!(
validate("verification.packet.v1", Some(&p)),
Err(PredicateError::MissingField {
suffix: "verification.packet.v1".into(),
field: "output_digest".into(),
})
);
}
#[test]
fn verification_packet_stringly_typed_sequence_fails_closed() {
let mut p = packet_payload();
p["sequence"] = json!("42");
assert!(matches!(
validate("verification.packet.v1", Some(&p)),
Err(PredicateError::TypeMismatch { field, .. }) if field == "sequence"
));
}
#[test]
fn verification_packet_out_of_vocabulary_reproducibility_fails_closed() {
let mut p = packet_payload();
p["reproducibility"] = json!("probably");
assert!(matches!(
validate("verification.packet.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "reproducibility"
));
}
#[test]
fn verification_packet_wrong_schema_const_fails_closed() {
let mut p = packet_payload();
p["schema"] = json!("verification.recompute.v1");
assert!(matches!(
validate("verification.packet.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "schema"
));
}
fn recompute_payload() -> serde_json::Value {
json!({
"schema": "verification.recompute.v1",
"packet": "art_0123456789abcdef0123456789abcdef",
"packet_id": "pkt_000042",
"method": "difr",
"verdict": "match",
"distance": 0.0012,
"threshold": 0.01,
"recomputed_at": "2026-09-17T15:05:00Z"
})
}
#[test]
fn verification_recompute_valid_passes() {
assert!(validate("verification.recompute.v1", Some(&recompute_payload())).is_ok());
}
#[test]
fn verification_recompute_out_of_vocabulary_verdict_fails_closed() {
let mut p = recompute_payload();
p["verdict"] = json!("mostly");
assert!(matches!(
validate("verification.recompute.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "verdict"
));
}
#[test]
fn verification_recompute_missing_packet_fails_closed() {
let mut p = recompute_payload();
p.as_object_mut().unwrap().remove("packet");
assert_eq!(
validate("verification.recompute.v1", Some(&p)),
Err(PredicateError::MissingField {
suffix: "verification.recompute.v1".into(),
field: "packet".into(),
})
);
}
fn evaluation_payload() -> serde_json::Value {
json!({
"schema": "evaluation.v1",
"subject_kind": "model",
"subject_digest": "sha256:aa11",
"suite_id": "sandbox-escape-v3",
"suite_digest": "sha256:bb22",
"result_digest": "sha256:cc33",
"verdict": "pass",
"score": 0.02,
"threshold": 0.05,
"capability": "sandbox-escape",
"evaluated_at": "2026-09-17T18:00:00Z"
})
}
#[test]
fn evaluation_valid_passes() {
assert!(validate("evaluation.v1", Some(&evaluation_payload())).is_ok());
}
#[test]
fn evaluation_out_of_vocabulary_verdict_fails_closed() {
let mut p = evaluation_payload();
p["verdict"] = json!("mostly");
assert!(matches!(
validate("evaluation.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "verdict"
));
}
#[test]
fn evaluation_out_of_vocabulary_subject_kind_fails_closed() {
let mut p = evaluation_payload();
p["subject_kind"] = json!("vibes");
assert!(matches!(
validate("evaluation.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "subject_kind"
));
}
#[test]
fn evaluation_missing_suite_digest_fails_closed() {
let mut p = evaluation_payload();
p.as_object_mut().unwrap().remove("suite_digest");
assert_eq!(
validate("evaluation.v1", Some(&p)),
Err(PredicateError::MissingField {
suffix: "evaluation.v1".into(),
field: "suite_digest".into(),
})
);
}
#[test]
fn evaluation_stringly_typed_score_fails_closed() {
let mut p = evaluation_payload();
p["score"] = json!("0.02");
assert!(matches!(
validate("evaluation.v1", Some(&p)),
Err(PredicateError::TypeMismatch { field, .. }) if field == "score"
));
}
fn coverage_payload() -> serde_json::Value {
json!({
"schema": "coverage.v1",
"session_id": "ssn_0011223344556677",
"actor": "agent://claude-code",
"declared_level": "high",
"harnesses": [{
"harness_id": "claude-code",
"status": "instrumented",
"coverage": "high",
"connection_modes": ["native-hook", "mcp", "git-reconcile"],
"known_gaps": ["Built-in tools the user invokes outside hooks rely on git-reconcile."]
}],
"observed": {
"events": 14,
"event_types": {"session.started": 1, "agent.called_tool": 12, "session.closed": 1},
"hosts": ["host_aa"],
"agent_instances": 1,
"event_log_skipped": 0
},
"gaps": ["Built-in tools the user invokes outside hooks rely on git-reconcile."],
"closed_at": "2026-09-18T20:00:00Z"
})
}
#[test]
fn coverage_valid_passes() {
assert!(validate("coverage.v1", Some(&coverage_payload())).is_ok());
}
#[test]
fn coverage_requires_observed() {
let mut p = coverage_payload();
p.as_object_mut().unwrap().remove("observed");
assert_eq!(
validate("coverage.v1", Some(&p)),
Err(PredicateError::MissingField {
suffix: "coverage.v1".into(),
field: "observed".into(),
})
);
}
#[test]
fn coverage_rejects_unknown_level() {
let mut p = coverage_payload();
p["declared_level"] = json!("total");
assert!(validate("coverage.v1", Some(&p)).is_err());
}
#[test]
fn coverage_none_level_with_no_harnesses_is_valid() {
let mut p = coverage_payload();
p["declared_level"] = json!("none");
p["harnesses"] = json!([]);
assert!(validate("coverage.v1", Some(&p)).is_ok());
}
#[test]
fn halt_valid_passes() {
let p = json!({"schema":"halt.v1","action":"halt","actor":"agent://claude-code","reason":"off-task network calls","issued_at":"2026-09-18T10:00:00Z"});
assert!(validate("halt.v1", Some(&p)).is_ok());
let l = json!({"schema":"halt.v1","action":"lift","actor":"agent://claude-code","halt":"art_0123","issued_at":"2026-09-18T11:00:00Z"});
assert!(validate("halt.v1", Some(&l)).is_ok());
}
#[test]
fn halt_out_of_vocabulary_action_fails_closed() {
let p = json!({"schema":"halt.v1","action":"pause","actor":"agent://x","issued_at":"2026-09-18T10:00:00Z"});
assert!(matches!(
validate("halt.v1", Some(&p)),
Err(PredicateError::NotInEnum { field, .. }) if field == "action"
));
}
#[test]
fn halt_missing_actor_fails_closed() {
let p = json!({"schema":"halt.v1","action":"halt","issued_at":"2026-09-18T10:00:00Z"});
assert_eq!(
validate("halt.v1", Some(&p)),
Err(PredicateError::MissingField {
suffix: "halt.v1".into(),
field: "actor".into()
})
);
}
fn judgement_payload() -> serde_json::Value {
json!({
"schema": "judgement.v1",
"judge": {"model": "jev-1.13.0", "provider": "typesafe", "kind": "decision-model", "replayable": false},
"state_digest": format!("sha256:{}", "ab".repeat(32)),
"questions_digest": format!("sha256:{}", "cd".repeat(32)),
"question": {"key": "destructive", "type": "noul",
"instructions": "Does this command delete or overwrite files outside the workspace?"},
"answer": {"noul": 0.93},
"threshold": {"value": 0.85, "applies_to": "noul", "set_by": "card:agent://claude-code"},
"outcome": "refused",
"effect": "deny",
"latency_ms": 140,
"judged_at": "2026-09-22T20:00:00Z"
})
}
#[test]
fn judgement_valid_passes() {
assert!(validate("judgement.v1", Some(&judgement_payload())).is_ok());
}
#[test]
fn judgement_rejects_unknown_outcome() {
let mut p = judgement_payload();
p["outcome"] = json!("shrugged");
assert!(validate("judgement.v1", Some(&p)).is_err());
}
#[test]
fn judgement_rejects_unknown_question_type() {
let mut p = judgement_payload();
p["question"]["type"] = json!("essay");
assert!(validate("judgement.v1", Some(&p)).is_err());
}
#[test]
fn judgement_requires_judge_model() {
let mut p = judgement_payload();
p["judge"] = json!({"provider": "typesafe"});
assert!(validate("judgement.v1", Some(&p)).is_err());
}
#[test]
fn judgement_rejects_probability_out_of_range() {
let mut p = judgement_payload();
p["answer"]["noul"] = json!(1.4);
assert!(validate("judgement.v1", Some(&p)).is_err());
}
}