use super::era::*;
use super::parser::parse_mcp_transcript_detailed;
use super::types::McpInputFormat;
use serde_json::Value;
use std::path::PathBuf;
fn crate_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn corpus_dir() -> PathBuf {
crate_root().join("tests/fixtures/mcp-era-parity-v0")
}
fn read_json(path: PathBuf) -> Value {
let bytes = std::fs::read(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
serde_json::from_slice(&bytes).unwrap_or_else(|e| panic!("parse {}: {e}", path.display()))
}
fn manifest() -> Value {
read_json(corpus_dir().join("MANIFEST.json"))
}
fn pin() -> Value {
read_json(corpus_dir().join("PIN.json"))
}
fn vectors() -> Vec<Value> {
manifest()["vectors"].as_array().expect("vectors").clone()
}
fn vector(id: &str) -> Value {
vectors()
.into_iter()
.find(|v| v["id"] == id)
.unwrap_or_else(|| panic!("no vector {id}"))
}
fn transcript_text(id: &str) -> String {
let file = vector(id)["file"].as_str().expect("file").to_string();
std::fs::read_to_string(corpus_dir().join(file)).expect("read vector")
}
fn transcript(id: &str) -> Value {
serde_json::from_str(&transcript_text(id)).expect("vector parses")
}
fn contexts(id: &str) -> Vec<McpEraContext> {
parse_mcp_transcript_detailed(&transcript_text(id), McpInputFormat::StreamableHttp)
.unwrap_or_else(|e| panic!("{id} must parse: {e:?}"))
.into_iter()
.filter(|p| p.context.result_observation.is_some())
.map(|p| p.context)
.collect()
}
fn response_contexts_by_id(id: &str) -> Vec<(CorrelationId, McpEraContext)> {
parse_mcp_transcript_detailed(&transcript_text(id), McpInputFormat::StreamableHttp)
.unwrap_or_else(|e| panic!("{id} must parse: {e:?}"))
.into_iter()
.filter(|p| p.context.result_observation.is_some())
.map(|p| {
let key = p
.context
.correlation
.clone()
.unwrap_or_else(|| panic!("{id}: a response with no correlation key"));
(key, p.context)
})
.collect()
}
fn manifest_correlation_id(vector_id: &str, raw: &Value) -> CorrelationId {
match raw {
Value::String(s) => CorrelationId::Str(s.clone()),
Value::Number(n) => correlation_key(&Value::Number(n.clone()))
.unwrap_or_else(|| panic!("{vector_id}: {n} is not an id this build keys on")),
other => panic!("{vector_id}: jsonrpc_id must be a JSON string or number, got {other}"),
}
}
fn context_for_call(vector_id: &str, key: &CorrelationId) -> McpEraContext {
let mut found: Vec<McpEraContext> = response_contexts_by_id(vector_id)
.into_iter()
.filter(|(k, _)| k == key)
.map(|(_, ctx)| ctx)
.collect();
assert_eq!(
found.len(),
1,
"{vector_id}: expected exactly one response on {key:?}"
);
found.remove(0)
}
fn sole_context(id: &str) -> McpEraContext {
let mut all = contexts(id);
assert_eq!(all.len(), 1, "{id} was expected to carry one response");
all.remove(0)
}
fn pinned_schema(era: &str) -> Value {
let p = pin();
let entry = p["schemas"]
.as_array()
.expect("schemas")
.iter()
.find(|s| s["era"] == era)
.unwrap_or_else(|| panic!("no pinned schema for era {era}"))
.clone();
let file = entry["vendored_as"].as_str().expect("vendored_as");
let bytes = std::fs::read(corpus_dir().join(file)).expect("read vendored schema");
let actual = {
use sha2::{Digest, Sha256};
hex::encode(Sha256::digest(&bytes))
};
assert_eq!(
actual,
entry["sha256"].as_str().expect("sha256"),
"vendored {file} does not match PIN.json; the corpus means something other than it says"
);
serde_json::from_slice(&bytes).expect("schema parses")
}
fn definitions_root(schema: &Value) -> &'static str {
match (schema.get("$defs"), schema.get("definitions")) {
(Some(_), None) => "$defs",
(None, Some(_)) => "definitions",
(Some(_), Some(_)) => {
panic!("pinned artifact carries both $defs and definitions; the pin must carry exactly one")
}
(None, None) => panic!("pinned artifact has neither $defs nor definitions"),
}
}
fn schema_accepts(era: &str, def: &str, doc: &Value) -> bool {
let schema = pinned_schema(era);
let root = definitions_root(&schema);
let defs = schema[root].clone();
assert!(
defs.get(def).is_some(),
"{era}: no definition {def} under {root}; the manifest names a definition the artifact \
does not have"
);
let mut subschema = serde_json::Map::new();
subschema.insert("$schema".to_string(), schema["$schema"].clone());
subschema.insert(root.to_string(), defs);
subschema.insert("$ref".to_string(), Value::String(format!("#/{root}/{def}")));
jsonschema::validator_for(&Value::Object(subschema))
.unwrap_or_else(|e| panic!("{era}/{def}: compile schema: {e}"))
.is_valid(doc)
}
fn request_messages(id: &str) -> Vec<Value> {
transcript(id)["entries"]
.as_array()
.expect("entries")
.iter()
.filter_map(|e| e.get("request").cloned())
.collect()
}
fn result_payloads(id: &str) -> Vec<Value> {
transcript(id)["entries"]
.as_array()
.expect("entries")
.iter()
.filter_map(|e| e.get("response").and_then(|r| r.get("result")).cloned())
.collect()
}
#[test]
fn every_vector_file_exists_and_parses() {
for v in vectors() {
let doc = read_json(corpus_dir().join(v["file"].as_str().expect("file")));
assert!(
doc.get("transport").is_some(),
"{} not a transcript",
v["id"]
);
}
}
fn every_response_bears_a_result(entries: &[Value]) -> Result<(), String> {
for (i, e) in entries.iter().enumerate() {
let Some(response) = e.get("response") else {
continue;
};
match classify_message(response) {
Ok(MessageKind::Response) => {}
Ok(_) => {
return Err(format!(
"entry {i} sits in a response slot but the parser classifies it as another \
kind of message, so it never reaches a result conclusion"
))
}
Err(_) => {
return Err(format!(
"entry {i} sits in a response slot and is not a classifiable message"
))
}
}
if response.get("result").is_none() {
return Err(format!(
"entry {i} is a response with no result; this corpus reads responses through \
result_observation, so such a response is invisible to coverage"
));
}
}
Ok(())
}
#[test]
fn a_response_without_a_result_is_refused() {
let ok = serde_json::json!([
{"response": {"jsonrpc": "2.0", "id": 1, "result": {"content": []}}},
{"request": {"jsonrpc": "2.0", "id": 2, "method": "tools/call"}},
]);
assert!(every_response_bears_a_result(ok.as_array().expect("array")).is_ok());
let one_error = serde_json::json!([
{"response": {"jsonrpc": "2.0", "id": 1, "error": {"code": -32000, "message": "no"}}},
]);
let two_errors = serde_json::json!([
{"response": {"jsonrpc": "2.0", "id": 1, "error": {"code": -32000, "message": "no"}}},
{"response": {"jsonrpc": "2.0", "id": 1, "error": {"code": -32000, "message": "no"}}},
]);
for (why, entries) in [
("a single error response", one_error),
(
"two error responses on one id, the duplicate coverage cannot see",
two_errors,
),
] {
let got = every_response_bears_a_result(entries.as_array().expect("array"))
.expect_err(&format!("{why} must be refused"));
assert!(
got.contains("response with no result"),
"{why}: got {got:?}"
);
}
let hybrid = serde_json::json!([
{"response": {"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"result": {"content": []}}},
]);
let got = every_response_bears_a_result(hybrid.as_array().expect("array"))
.expect_err("a method+result hybrid in a response slot must be refused");
assert!(
got.contains("classifies it as another kind of message"),
"the hybrid must be refused on classification, not on its body: got {got:?}"
);
assert!(
!got.contains("tools/call"),
"the fault must not echo the method the input chose: {got:?}"
);
}
#[test]
fn no_vector_carries_a_response_without_a_result() {
for v in vectors() {
let id = v["id"].as_str().expect("id");
let doc = transcript(id);
let entries = doc["entries"].as_array().expect("entries");
if let Err(fault) = every_response_bears_a_result(entries) {
panic!("{id}: {fault}");
}
}
}
fn exactly_one<T>(what: &str, mut found: Vec<T>) -> Result<T, String> {
if found.len() != 1 {
return Err(format!(
"expected exactly one {what}, found {}",
found.len()
));
}
Ok(found.remove(0))
}
#[test]
fn exactly_one_refuses_none_and_many() {
assert!(exactly_one("thing", vec![1]).is_ok());
for (why, v) in [
("none", vec![]),
("two", vec![1, 2]),
("three", vec![1, 2, 3]),
] {
let got = exactly_one("thing", v).expect_err(&format!("{why} must be refused"));
assert!(got.contains("expected exactly one thing"), "{why}: {got:?}");
}
}
fn structural_faults(ids: &[String], paths: &[String], on_disk: &[String]) -> Result<(), String> {
let mut seen_ids = std::collections::HashSet::new();
for i in ids {
if !seen_ids.insert(i) {
return Err(format!("duplicate vector id {i}"));
}
}
let mut seen_paths = std::collections::HashSet::new();
for p in paths {
if !seen_paths.insert(p) {
return Err(format!("duplicate vector path {p}"));
}
}
let declared: std::collections::HashSet<&String> = paths.iter().collect();
let present: std::collections::HashSet<&String> = on_disk.iter().collect();
let missing: Vec<&&String> = declared.difference(&present).collect();
if !missing.is_empty() {
return Err(format!(
"manifest names files that are not there: {missing:?}"
));
}
let unlisted: Vec<&&String> = present.difference(&declared).collect();
if !unlisted.is_empty() {
return Err(format!("vector files no manifest row names: {unlisted:?}"));
}
Ok(())
}
#[test]
fn structural_faults_reject_duplicates_and_drift() {
let v = |xs: &[&str]| xs.iter().map(|x| x.to_string()).collect::<Vec<_>>();
assert!(structural_faults(&v(&["a", "b"]), &v(&["x", "y"]), &v(&["y", "x"])).is_ok());
for (why, ids, paths, disk, fault) in [
(
"two rows sharing an id, everything else exact",
v(&["a", "a"]),
v(&["x", "y"]),
v(&["x", "y"]),
"duplicate vector id a",
),
(
"two rows naming one file, ids distinct",
v(&["a", "b"]),
v(&["x", "x"]),
v(&["x"]),
"duplicate vector path x",
),
(
"a row naming a file that is not there",
v(&["a", "b"]),
v(&["x", "y"]),
v(&["x"]),
"files that are not there",
),
(
"a file no row names",
v(&["a"]),
v(&["x"]),
v(&["x", "z"]),
"no manifest row names",
),
] {
let got =
structural_faults(&ids, &paths, &disk).expect_err(&format!("{why} must be refused"));
assert!(
got.contains(fault),
"{why}: expected {fault:?}, got {got:?}"
);
}
}
#[test]
fn the_corpus_and_the_directory_describe_one_set() {
let ids: Vec<String> = vectors()
.iter()
.map(|v| v["id"].as_str().expect("id").to_string())
.collect();
let paths: Vec<String> = vectors()
.iter()
.map(|v| v["file"].as_str().expect("file").to_string())
.collect();
let mut on_disk: Vec<String> = std::fs::read_dir(corpus_dir().join("vectors"))
.expect("vectors dir")
.map(|e| {
format!(
"vectors/{}",
e.expect("entry").file_name().to_string_lossy()
)
})
.collect();
on_disk.sort();
if let Err(fault) = structural_faults(&ids, &paths, &on_disk) {
panic!("corpus structure: {fault}");
}
}
#[test]
fn no_two_vectors_are_the_same_bytes_and_the_capability_axis_varies() {
use sha2::{Digest, Sha256};
let mut seen: Vec<(String, String)> = Vec::new();
for v in vectors() {
let id = v["id"].as_str().expect("id").to_string();
let file = v["file"].as_str().expect("file");
let digest = hex::encode(Sha256::digest(
std::fs::read(corpus_dir().join(file)).expect("read vector"),
));
if let Some((other, _)) = seen.iter().find(|(_, d)| *d == digest) {
panic!(
"{id} and {other} are byte-identical; two rows cannot exercise two things on one \
input"
);
}
seen.push((id, digest));
}
let declared: Vec<&str> = vectors()
.iter()
.filter_map(|v| {
v["observation"]
.get("capabilities")
.and_then(|c| c.as_str())
})
.map(|s| match s {
"core_only" => "core_only",
"absent" => "absent",
"extension_not_understood" => "extension_not_understood",
other => panic!("unrecognised capability observation {other:?}"),
})
.collect();
for state in ["core_only", "absent", "extension_not_understood"] {
assert!(
declared.contains(&state),
"no vector declares capabilities {state}; the axis must vary, not just be labelled"
);
}
}
#[test]
fn the_corpus_declares_its_lower_maturity() {
let m = manifest();
assert_eq!(m["maturity"], "exploratory");
assert!(m["maturity_note"]
.as_str()
.expect("maturity_note")
.contains("privileged-mcp-action-v0"));
}
#[test]
fn every_vector_references_the_frozen_profile_row() {
let row_id = pin()["profile_baseline"]["row"]
.as_str()
.expect("row")
.to_string();
let frozen =
read_json(crate_root().join("../../conformance/privileged-mcp-action-v0/MANIFEST.json"));
let row = frozen["vectors"]
.as_array()
.expect("frozen vectors")
.iter()
.find(|r| r["id"] == row_id.as_str())
.unwrap_or_else(|| panic!("frozen row {row_id} is gone"))
.clone();
let claims = &row["expected"]["claims"];
assert_eq!(claims["policy_decision_recorded"]["status"], "confirmed");
for cell in [
"caller_visible_denial",
"upstream_delivery",
"external_side_effect",
] {
assert_eq!(
claims[cell]["status"], "incomplete",
"{cell} must stay incomplete: a wire transcript carries no vantage that could upgrade it"
);
}
for v in vectors() {
assert_eq!(
v["profile_baseline"],
row_id.as_str(),
"{} must reference the frozen row rather than restate a matrix",
v["id"]
);
}
}
#[test]
fn the_result_definition_accepts_complete_alongside_input_requests() {
let contradiction = result_payloads("complete-with-input-requests").remove(0);
assert_eq!(contradiction["resultType"], "complete");
assert!(contradiction.get("inputRequests").is_some());
assert!(
schema_accepts("2026-07-28", "CallToolResult", &contradiction),
"the CallToolResult definition was expected to accept this object"
);
}
fn expect_valid(id: &str, slot: &str, spec: &Value) -> bool {
match spec["expect"].as_str() {
Some("valid") => true,
Some("invalid") => false,
other => panic!(
"{id}/{slot}: expect must be \"valid\" or \"invalid\", got {other:?}; an unrecognised \
value must never fall through to a verdict"
),
}
}
#[test]
fn each_vectors_schema_expectations_hold() {
for v in vectors() {
let id = v["id"].as_str().expect("id");
let s = &v["schema"];
let era = s["era"].as_str().expect("era");
for (slot, docs) in [
("request_message", request_messages(id)),
("result_payload", result_payloads(id)),
] {
let spec = &s[slot];
if era == "ambiguous" {
assert_eq!(spec["expect"], "not_applicable", "{id}/{slot}");
assert!(spec["definition"].is_null(), "{id}/{slot}");
continue;
}
let want_valid = expect_valid(id, slot, spec);
let def = spec["definition"]
.as_str()
.unwrap_or_else(|| panic!("{id}/{slot}: a resolved era must name a definition"));
assert!(!docs.is_empty(), "{id}/{slot}: nothing to validate");
for (hop, doc) in docs.iter().enumerate() {
assert_eq!(
schema_accepts(era, def, doc),
want_valid,
"{id}/{slot} hop {hop}: {def} under {era}"
);
}
}
}
}
#[test]
fn the_conflicting_vector_flips_only_on_the_result() {
let request = exactly_one("request", request_messages("conflicting-era-signals"))
.unwrap_or_else(|e| panic!("conflicting-era-signals: {e}"));
let result = exactly_one("result", result_payloads("conflicting-era-signals"))
.unwrap_or_else(|e| panic!("conflicting-era-signals: {e}"));
for (era, want) in [("2025-06-18", true), ("2026-07-28", true)] {
assert_eq!(
schema_accepts(era, "CallToolRequest", &request),
want,
"request under {era}"
);
}
for (era, want) in [("2025-06-18", true), ("2026-07-28", false)] {
assert_eq!(
schema_accepts(era, "CallToolResult", &result),
want,
"result under {era}"
);
}
}
#[test]
fn an_undetermined_era_states_no_schema_verdict() {
for id in ["unknown-era", "conflicting-era-signals"] {
let s = vector(id)["schema"].clone();
assert_eq!(s["era"], "ambiguous", "{id}");
for slot in ["request_message", "result_payload"] {
assert_eq!(s[slot]["expect"], "not_applicable", "{id}/{slot}");
assert!(s[slot]["definition"].is_null(), "{id}/{slot}");
}
}
}
enum Expected {
Exact(ResultConclusion),
}
fn unknown_reason(id: &str, observation: &str) -> UnknownReason {
let rest = observation
.strip_prefix("unknown:")
.unwrap_or_else(|| panic!("{id}: {observation:?} is not an unknown-era observation"));
match rest {
"no_signal" => UnknownReason::NoSignal,
"malformed_signal" => UnknownReason::MalformedSignal,
_ => match rest.strip_prefix("unsupported_version:") {
Some(v) => UnknownReason::UnsupportedVersion(v.to_string()),
None => panic!("{id}: unrecognised unknown reason {rest:?}"),
},
}
}
fn conflicting_versions(id: &str, observation: &str) -> (String, String) {
let rest = observation
.strip_prefix("conflicting:")
.unwrap_or_else(|| panic!("{id}: {observation:?} is not a conflicting-era observation"));
let (header, body) = rest
.split_once('/')
.unwrap_or_else(|| panic!("{id}: {rest:?} must name both versions"));
(header.to_string(), body.to_string())
}
fn expected_conclusion(v: &Value) -> Expected {
let id = v["id"].as_str().expect("id");
let era_observation = v["observation"]["era"].as_str().expect("observation.era");
let label = v["conclusion"]
.as_str()
.unwrap_or_else(|| panic!("{id}: conclusion must be a string"));
decode_conclusion(id, label, era_observation)
}
fn decode_conclusion(id: &str, label: &str, era_observation: &str) -> Expected {
match label {
"terminal" => Expected::Exact(ResultConclusion::Terminal),
"non_terminal" => Expected::Exact(ResultConclusion::NonTerminal),
"invalid:missing_result_type" => {
Expected::Exact(ResultConclusion::Invalid(InvalidReason::MissingResultType))
}
"incomplete:unrecognized_result_type" => Expected::Exact(ResultConclusion::Incomplete(
IncompleteReason::UnrecognizedResultType,
)),
"incomplete:era_unknown" => Expected::Exact(ResultConclusion::Incomplete(
IncompleteReason::EraUnknown(unknown_reason(id, era_observation)),
)),
"invalid:era_conflicting" => {
let (header, body) = conflicting_versions(id, era_observation);
Expected::Exact(ResultConclusion::Invalid(InvalidReason::EraConflicting {
header,
body,
}))
}
"incomplete:contradictory_result" => Expected::Exact(ResultConclusion::Incomplete(
IncompleteReason::ContradictoryResult,
)),
"invalid:uncontinuable_input_required" => Expected::Exact(ResultConclusion::Invalid(
InvalidReason::UncontinuableInputRequired,
)),
"incomplete:recognition_undeterminable" => Expected::Exact(ResultConclusion::Incomplete(
IncompleteReason::RecognitionUndeterminable,
)),
other => panic!(
"{id}: unrecognised conclusion {other:?}; the manifest and the enum have drifted apart"
),
}
}
fn expected_era(id: &str, observation: &str) -> EraResolution {
if let Some(v) = observation.strip_prefix("known:") {
return EraResolution::Known(v.to_string());
}
if observation.starts_with("unknown:") {
return EraResolution::Unknown(unknown_reason(id, observation));
}
if observation.starts_with("conflicting:") {
let (header, body) = conflicting_versions(id, observation);
return EraResolution::Conflicting { header, body };
}
panic!("{id}: unrecognised era observation {observation:?}")
}
fn expected_result(id: &str, observation: &str) -> ResultObservation {
match observation {
"missing" => ResultObservation::Missing,
"complete" => ResultObservation::Complete,
"input_required" => ResultObservation::InputRequired,
"unrecognized" => ResultObservation::Unrecognized,
"malformed" => ResultObservation::Malformed,
"complete_with_continuation" => ResultObservation::CompleteWithContinuation,
"input_required_without_continuation" => {
ResultObservation::InputRequiredWithoutContinuation
}
other => panic!("{id}: unrecognised result observation {other:?}"),
}
}
fn actual_conclusion(ctx: &McpEraContext) -> ResultConclusion {
conclude(
&ctx.era,
&ctx.result_observation.clone().expect("a result"),
ctx.capability_observation.as_ref(),
)
}
fn per_call_coverage(
declared: &[CorrelationId],
responses: &[CorrelationId],
) -> Result<(), String> {
let mut named = std::collections::HashSet::new();
for d in declared {
if !named.insert(d.clone()) {
return Err(format!("per_call names id {d:?} more than once"));
}
}
let mut answered = std::collections::HashSet::new();
for r in responses {
if !answered.insert(r.clone()) {
return Err(format!("the transcript answers id {r:?} more than once"));
}
}
let uncovered: Vec<&CorrelationId> = answered.difference(&named).collect();
if !uncovered.is_empty() {
return Err(format!("responses with no per_call row: {uncovered:?}"));
}
let unanswered: Vec<&CorrelationId> = named.difference(&answered).collect();
if !unanswered.is_empty() {
return Err(format!("per_call rows with no response: {unanswered:?}"));
}
Ok(())
}
#[test]
fn per_call_coverage_rejects_duplicate_missing_and_extra_rows() {
let s = |v: &[&str]| {
v.iter()
.map(|x| CorrelationId::Num(x.to_string()))
.collect::<Vec<_>>()
};
assert!(per_call_coverage(&s(&["1", "2"]), &s(&["2", "1"])).is_ok());
assert!(per_call_coverage(&s(&[]), &s(&[])).is_ok());
for (why, declared, responses, fault) in [
(
"a row naming an id twice, with coverage otherwise exact",
s(&["1", "1", "2"]),
s(&["1", "2"]),
"names id Num(\"1\") more than once",
),
(
"a response no row accounts for",
s(&["1"]),
s(&["1", "2"]),
"responses with no per_call row",
),
(
"a row naming a response the transcript does not carry, nothing else wrong",
s(&["1", "2", "3"]),
s(&["1", "2"]),
"per_call rows with no response",
),
(
"a transcript answering one id twice",
s(&["1", "2"]),
s(&["1", "1", "2"]),
"answers id Num(\"1\") more than once",
),
] {
let got =
per_call_coverage(&declared, &responses).expect_err(&format!("{why} must be refused"));
assert!(
got.contains(fault),
"{why}: expected the fault {fault:?}, got {got:?}"
);
}
}
#[test]
fn every_vector_observes_and_concludes_exactly_what_the_manifest_says() {
for v in vectors() {
let id = v["id"].as_str().expect("id");
let obs = &v["observation"];
let want_era = expected_era(id, obs["era"].as_str().expect("observation.era"));
let want_result = obs["result"].as_str().expect("observation.result");
let per_call = v.get("per_call").and_then(|p| p.as_array()).cloned();
assert!(
per_call.is_some() != v.get("conclusion").is_some(),
"{id}: a vector carries exactly one of conclusion or per_call"
);
let labelled: Vec<(String, McpEraContext, String)> = match &per_call {
Some(rows) => {
let declared: Vec<CorrelationId> = rows
.iter()
.map(|r| manifest_correlation_id(id, &r["jsonrpc_id"]))
.collect();
let answered: Vec<CorrelationId> = response_contexts_by_id(id)
.into_iter()
.map(|(key, _)| key)
.collect();
if let Err(fault) = per_call_coverage(&declared, &answered) {
panic!("{id}: {fault}");
}
rows.iter()
.map(|r| {
let key = manifest_correlation_id(id, &r["jsonrpc_id"]);
let label = r["conclusion"].as_str().expect("conclusion").to_string();
let ctx = context_for_call(id, &key);
(format!("call {key:?}"), ctx, label)
})
.collect()
}
None => {
let label = v["conclusion"].as_str().expect("conclusion").to_string();
contexts(id)
.into_iter()
.enumerate()
.map(|(hop, ctx)| (format!("hop {hop}"), ctx, label.clone()))
.collect()
}
};
assert!(!labelled.is_empty(), "{id}: no response to conclude on");
for (where_, ctx, label) in labelled {
assert_eq!(ctx.era, want_era, "{id} {where_}: era observation");
let observed = ctx.result_observation.clone().expect("a result");
assert_eq!(
observed,
expected_result(id, want_result),
"{id} {where_}: result observation"
);
let Expected::Exact(want) =
decode_conclusion(id, &label, obs["era"].as_str().expect("observation.era"));
assert_eq!(actual_conclusion(&ctx), want, "{id} {where_}: conclusion");
}
}
}
#[test]
fn the_capability_observation_is_a_state_of_its_own() {
let core_only = sole_context("capabilities-core-only-noncore-token");
let absent = sole_context("capabilities-absent-noncore-token");
let extension = sole_context("capabilities-unknown-extension");
for (id, other, want) in [
(
"capabilities-absent-noncore-token",
&absent,
CapabilityObservation::Absent,
),
(
"capabilities-unknown-extension",
&extension,
CapabilityObservation::ExtensionNotUnderstood,
),
] {
assert_eq!(core_only.envelope, other.envelope, "{id}: envelope");
assert_eq!(core_only.era, other.era, "{id}: era");
assert_eq!(
core_only.request_metadata, other.request_metadata,
"{id}: all three carry the same protocolVersion, so this must not be the field that \
distinguishes them"
);
assert_eq!(
core_only.result_observation, other.result_observation,
"{id}: the result token is the same in all three; the difference is upstream of it"
);
assert_eq!(other.capability_observation, Some(want), "{id}");
}
assert_eq!(
core_only.capability_observation,
Some(CapabilityObservation::CoreOnly),
"a present and empty set is a complete statement, not silence"
);
}
#[test]
fn an_orphan_response_under_2026_cannot_close_the_recognition_question() {
let text = r#"{"transport":"streamable-http",
"transport_context":{"headers":{"MCP-Protocol-Version":"2026-07-28"}},
"entries":[{"timestamp_ms":1000,"response":{"jsonrpc":"2.0","id":9,
"result":{"resultType":"io.vendor/unknown","content":[]}}}]}"#;
let mut all: Vec<McpEraContext> =
parse_mcp_transcript_detailed(text, McpInputFormat::StreamableHttp)
.expect("parses")
.into_iter()
.filter(|p| p.context.result_observation.is_some())
.map(|p| p.context)
.collect();
assert_eq!(all.len(), 1, "one orphan response");
let ctx = all.remove(0);
assert_eq!(ctx.era, EraResolution::Known("2026-07-28".to_string()));
assert_eq!(
ctx.result_observation,
Some(ResultObservation::Unrecognized)
);
assert_eq!(
ctx.capability_observation, None,
"no request was correlated, so nothing was observed; this is not `Some(Absent)`"
);
assert_eq!(
actual_conclusion(&ctx),
ResultConclusion::Incomplete(IncompleteReason::RecognitionUndeterminable),
"an unseen capability set cannot establish that nothing advertised covers the token"
);
}
#[test]
fn a_legacy_era_keeps_the_closed_answer_without_a_capability_set() {
assert_eq!(
conclude(
&EraResolution::Known("2025-06-18".to_string()),
&ResultObservation::Unrecognized,
None,
),
ResultConclusion::Incomplete(IncompleteReason::UnrecognizedResultType)
);
}
#[test]
fn a_legacy_call_is_not_judged_against_the_capability_contract() {
for (why, caps_member) in [
("ordinary legacy _meta, no capability member at all", ""),
(
"a future capability key that is malformed by 2026 rules",
r#","io.modelcontextprotocol/clientCapabilities":"not-an-object""#,
),
] {
let text = format!(
r#"{{"transport":"streamable-http",
"transport_context":{{"headers":{{"MCP-Protocol-Version":"2025-06-18"}}}},
"entries":[
{{"timestamp_ms":1000,"request":{{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{{"name":"Calc","arguments":{{}},"_meta":{{
"io.modelcontextprotocol/protocolVersion":"2025-06-18"{caps_member}}}}}}}}},
{{"timestamp_ms":1001,"response":{{"jsonrpc":"2.0","id":1,
"result":{{"resultType":"io.vendor/unknown","content":[]}}}}}}]}}"#
);
let mut all: Vec<McpEraContext> =
parse_mcp_transcript_detailed(&text, McpInputFormat::StreamableHttp)
.unwrap_or_else(|e| panic!("{why}: must parse: {e:?}"))
.into_iter()
.filter(|p| p.context.result_observation.is_some())
.map(|p| p.context)
.collect();
assert_eq!(all.len(), 1, "{why}");
let ctx = all.remove(0);
assert_eq!(
ctx.era,
EraResolution::Known("2025-06-18".to_string()),
"{why}"
);
assert_eq!(
ctx.result_observation,
Some(ResultObservation::Unrecognized),
"{why}"
);
assert_eq!(
actual_conclusion(&ctx),
ResultConclusion::Incomplete(IncompleteReason::UnrecognizedResultType),
"{why}: a revision that does not define the capability contract is not judged by it"
);
}
}
#[test]
fn a_modern_request_must_state_its_capability_set() {
fn assess(text: &str) -> Vec<RequestAssessment> {
parse_mcp_transcript_detailed(text, McpInputFormat::StreamableHttp)
.unwrap_or_else(|e| panic!("must parse: {e:?}"))
.into_iter()
.filter(|p| p.context.request_metadata.is_some())
.map(|p| {
conclude_request(
&p.context.era,
p.context.request_metadata.as_ref().expect("metadata"),
p.context.capability_observation.as_ref(),
)
})
.collect()
}
for (why, meta, want) in [
(
"a stated core-only set is a complete statement",
r#""io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}"#,
RequestAssessment::Valid,
),
(
"the version alone leaves a required member unstated",
r#""io.modelcontextprotocol/protocolVersion":"2026-07-28""#,
RequestAssessment::Invalid(InvalidReason::MissingCapabilities),
),
(
"a set that arrived and could not be read is a fault, not an absence",
r#""io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":7"#,
RequestAssessment::Invalid(InvalidReason::MalformedCapabilities),
),
] {
let text = format!(
r#"{{"transport":"streamable-http",
"transport_context":{{"headers":{{"MCP-Protocol-Version":"2026-07-28"}}}},
"entries":[{{"timestamp_ms":1000,"request":{{"jsonrpc":"2.0","id":1,
"method":"tools/call","params":{{"name":"Calc","arguments":{{}},
"_meta":{{{meta}}}}}}}}}]}}"#
);
assert_eq!(assess(&text), vec![want], "{why}");
}
}
#[test]
fn a_legacy_request_is_not_held_to_the_capability_contract() {
for (why, capability) in [
(
"no capability member at all",
Some(CapabilityObservation::Absent),
),
("nothing observed", None),
(
"a future key that is malformed by 2026 rules",
Some(CapabilityObservation::Malformed),
),
] {
assert_eq!(
conclude_request(
&EraResolution::Known("2025-06-18".to_string()),
&RequestMetadata::Present("2025-06-18".to_string()),
capability.as_ref(),
),
RequestAssessment::Valid,
"{why}"
);
}
}
fn request_advertising(caps: Value) -> Value {
serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": "Calc", "arguments": {}, "_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": caps,
}},
})
}
#[test]
fn beyond_core_is_wider_than_the_extensions_map() {
for (why, caps, want) in [
(
"an empty set advertises nothing",
serde_json::json!({}),
CapabilityObservation::CoreOnly,
),
(
"core members this build has rules for",
serde_json::json!({"roots": {}, "sampling": {}, "elicitation": {}}),
CapabilityObservation::CoreOnly,
),
(
"both open maps present and empty is a complete statement that none is offered",
serde_json::json!({"experimental": {}, "extensions": {}}),
CapabilityObservation::CoreOnly,
),
(
"a non-empty experimental map is a capability with no rule here",
serde_json::json!({"experimental": {"io.vendor/try": {}}}),
CapabilityObservation::ExtensionNotUnderstood,
),
(
"a non-empty extensions map, the same read one member over",
serde_json::json!({"extensions": {"io.vendor/partial-results": {}}}),
CapabilityObservation::ExtensionNotUnderstood,
),
(
"an unrecognised top-level member is legal and unevaluable, never core",
serde_json::json!({"roots": {}, "io.vendor/whatever": {}}),
CapabilityObservation::ExtensionNotUnderstood,
),
(
"a known member that is not an object is a broken statement, not silence",
serde_json::json!({"roots": "yes"}),
CapabilityObservation::Malformed,
),
(
"an open map that is not a map is unreadable for the same reason",
serde_json::json!({"extensions": []}),
CapabilityObservation::Malformed,
),
] {
assert_eq!(
observe_client_capabilities(&request_advertising(caps)),
Some(want),
"{why}"
);
}
}
#[test]
fn no_capability_observation_is_not_the_same_as_an_absent_member() {
for (why, raw) in [
(
"no params at all",
serde_json::json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call"}),
),
(
"params present, no _meta to look in",
serde_json::json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "Calc", "arguments": {}}}),
),
] {
assert_eq!(
observe_client_capabilities(&raw),
None,
"{why}: nothing was observed, which is not a statement that nothing was advertised"
);
}
let readable = serde_json::json!({
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "Calc", "arguments": {}, "_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
}},
});
assert_eq!(
observe_client_capabilities(&readable),
Some(CapabilityObservation::Absent),
"the container was readable and the member was not in it"
);
}
#[test]
fn an_unreadable_capability_set_is_invalid_rather_than_absent() {
let raw = request_advertising(serde_json::json!("not-an-object"));
assert_eq!(
observe_client_capabilities(&raw),
Some(CapabilityObservation::Malformed)
);
assert_eq!(
conclude(
&EraResolution::Known("2026-07-28".to_string()),
&ResultObservation::Unrecognized,
Some(&CapabilityObservation::Malformed),
),
ResultConclusion::Invalid(InvalidReason::MalformedCapabilities),
"more evidence does not make an unreadable value readable"
);
}
#[test]
fn capabilities_bind_to_their_own_call_across_interleaved_responses() {
let vector_id = "per-request-capabilities-interleaved";
let rows = vector(vector_id)["per_call"]
.as_array()
.expect("per_call")
.clone();
assert_eq!(rows.len(), 2, "the vector is a pair by construction");
let arrival: Vec<CorrelationId> = response_contexts_by_id(vector_id)
.into_iter()
.map(|(key, _)| key)
.collect();
assert_eq!(
arrival,
[
CorrelationId::Num("2".to_string()),
CorrelationId::Num("1".to_string())
],
"the responses must arrive out of order or the test proves nothing"
);
let era_observation = vector(vector_id)["observation"]["era"]
.as_str()
.expect("observation.era")
.to_string();
for row in rows {
let key = manifest_correlation_id(vector_id, &row["jsonrpc_id"]);
let label = row["conclusion"].as_str().expect("conclusion");
let ctx = context_for_call(vector_id, &key);
assert_eq!(
ctx.result_observation,
Some(ResultObservation::Unrecognized),
"{vector_id}/{key:?}: both responses carry the same unrecognized token"
);
let expected_capability = match row["capabilities"].as_str().expect("capabilities") {
"core_only" => CapabilityObservation::CoreOnly,
"extension_not_understood" => CapabilityObservation::ExtensionNotUnderstood,
"absent" => CapabilityObservation::Absent,
other => panic!("{vector_id}/{key:?}: unrecognised capability {other:?}"),
};
assert_eq!(
ctx.capability_observation,
Some(expected_capability),
"{vector_id}/{key:?}: this response carries its own request's capability set"
);
let Expected::Exact(want) = decode_conclusion(vector_id, label, &era_observation);
assert_eq!(
actual_conclusion(&ctx),
want,
"{vector_id}/{key:?}: this response must be judged against its own request's \
capabilities, not the transcript's or the most recent one's"
);
}
}
#[test]
fn the_interleaved_vector_retains_no_capability_or_token_bytes() {
for (key, ctx) in response_contexts_by_id("per-request-capabilities-interleaved") {
let rendered = format!("{ctx:?}");
assert!(
!rendered.contains("io.vendor/partial-results"),
"call {key:?}: the token and the extension name are attacker-chosen and must not be \
retained: {rendered}"
);
}
}
#[test]
fn the_capability_observation_vocabulary_is_closed() {
fn check(id: &str, c: &Value) {
match c.as_str() {
Some("core_only" | "absent" | "extension_not_understood") => {}
other => panic!("{id}: unrecognised capability observation {other:?}"),
}
}
let mut carriers: Vec<String> = Vec::new();
for v in vectors() {
let id = v["id"].as_str().expect("id").to_string();
if let Some(rows) = v.get("per_call").and_then(|p| p.as_array()) {
for r in rows {
check(&id, &r["capabilities"]);
}
carriers.push(id);
continue;
}
if let Some(c) = v["observation"].get("capabilities") {
check(&id, c);
carriers.push(id);
}
}
carriers.sort();
assert_eq!(
carriers,
[
"capabilities-absent-noncore-token",
"capabilities-core-only-noncore-token",
"capabilities-unknown-extension",
"correlation-id-type-collision",
"modern-unrecognized-token",
"per-request-capabilities-interleaved",
],
"the capability arm is exactly these vectors"
);
}
#[test]
fn no_capability_state_echoes_an_advertised_extension_name() {
let rendered = format!("{:?}", sole_context("capabilities-unknown-extension"));
assert!(
!rendered.contains("io.vendor/partial-results"),
"the extension name is attacker-chosen and value-free by design: {rendered}"
);
}
#[test]
fn a_schema_valid_contradiction_is_not_terminal() {
for (id, member) in [
("complete-with-input-requests", "inputRequests"),
("complete-with-request-state", "requestState"),
] {
let ctx = sole_context(id);
assert_eq!(
ctx.result_observation,
Some(ResultObservation::CompleteWithContinuation),
"{id}: a completion claim beside {member} is observed as its own state, not as plain \
completion"
);
assert_eq!(
actual_conclusion(&ctx),
ResultConclusion::Incomplete(IncompleteReason::ContradictoryResult),
"{id}: it concludes with its own reason"
);
}
}
#[test]
fn an_input_required_result_with_no_continuation_is_invalid() {
for (id, why) in [
(
"input-required-without-continuation",
"neither member present",
),
(
"input-required-null-continuation",
"both members explicitly null",
),
] {
let payload = result_payloads(id).remove(0);
assert_eq!(payload["resultType"], "input_required", "{id}");
let ctx = sole_context(id);
assert_eq!(
ctx.result_observation,
Some(ResultObservation::InputRequiredWithoutContinuation),
"{id}: {why}"
);
assert_eq!(
actual_conclusion(&ctx),
ResultConclusion::Invalid(InvalidReason::UncontinuableInputRequired),
"{id}: a request for input that cannot be answered is not a valid interim result"
);
}
assert!(
schema_accepts(
"2026-07-28",
"InputRequiredResult",
&result_payloads("input-required-without-continuation").remove(0)
),
"InputRequiredResult was expected to accept a result with no continuation member"
);
}
#[test]
fn a_continuation_member_of_the_wrong_type_is_invalid() {
for (why, member) in [
("requestState must be a string", r#""requestState":7"#),
("inputRequests must be an object", r#""inputRequests":[]"#),
(
"a broken member is a fault even beside a well-formed sibling",
r#""requestState":"s1","inputRequests":[]"#,
),
] {
let ctx = sole_context_from(&interim_transcript(member));
assert_eq!(
ctx.result_observation,
Some(ResultObservation::InputRequiredWithMalformedContinuation),
"{why}"
);
assert_eq!(
actual_conclusion(&ctx),
ResultConclusion::Invalid(InvalidReason::MalformedContinuation),
"{why}: a member that cannot be read is not a way to continue"
);
}
let complete = sole_context_from(
&interim_transcript(r#""requestState":7"#).replace("input_required", "complete"),
);
assert_eq!(
complete.result_observation,
Some(ResultObservation::CompleteWithContinuation)
);
assert_eq!(
actual_conclusion(&complete),
ResultConclusion::Incomplete(IncompleteReason::ContradictoryResult),
"a completion claim carrying an unreadable continuation must not reach terminal"
);
}
fn interim_transcript(members: &str) -> String {
format!(
r#"{{"transport":"streamable-http",
"transport_context":{{"headers":{{"MCP-Protocol-Version":"2026-07-28"}}}},
"entries":[
{{"timestamp_ms":1000,"request":{{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{{"name":"Calc","arguments":{{}},"_meta":{{
"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientCapabilities":{{}}}}}}}}}},
{{"timestamp_ms":1001,"response":{{"jsonrpc":"2.0","id":1,
"result":{{"resultType":"input_required","content":[],{members}}}}}}}]}}"#
)
}
fn sole_context_from(text: &str) -> McpEraContext {
let mut all: Vec<McpEraContext> =
parse_mcp_transcript_detailed(text, McpInputFormat::StreamableHttp)
.unwrap_or_else(|e| panic!("must parse: {e:?}"))
.into_iter()
.filter(|p| p.context.result_observation.is_some())
.map(|p| p.context)
.collect();
assert_eq!(all.len(), 1, "one response");
all.remove(0)
}
#[test]
fn either_continuation_member_alone_is_the_contradiction() {
let observe = |result: Value| {
observe_result(&serde_json::json!({"jsonrpc": "2.0", "id": 1, "result": result}))
};
for (why, result) in [
(
"inputRequests alone",
serde_json::json!({"resultType": "complete", "content": [],
"inputRequests": {"elicitation": {"method": "elicitation/create",
"params": {}}}}),
),
(
"requestState alone",
serde_json::json!({"resultType": "complete", "content": [], "requestState": "s1"}),
),
(
"an empty continuation value still disagrees with the completion claim",
serde_json::json!({"resultType": "complete", "content": [], "requestState": ""}),
),
] {
assert_eq!(
observe(result),
Some(ResultObservation::CompleteWithContinuation),
"{why}"
);
}
for (why, result) in [
(
"neither member is plain completion",
serde_json::json!({"resultType": "complete", "content": []}),
),
(
"an explicit null is silence, not a continuation",
serde_json::json!({"resultType": "complete", "content": [],
"inputRequests": null, "requestState": null}),
),
] {
assert_eq!(observe(result), Some(ResultObservation::Complete), "{why}");
}
}
#[test]
fn recognition_is_capability_relative() {
let known = sole_context("capabilities-core-only-noncore-token");
let undeterminable = sole_context("capabilities-absent-noncore-token");
let unknown_ext = sole_context("capabilities-unknown-extension");
assert_eq!(
actual_conclusion(&known),
ResultConclusion::Incomplete(IncompleteReason::UnrecognizedResultType),
"a present core-only capability set makes the token known-unrecognized"
);
for (id, ctx) in [
("capabilities-absent-noncore-token", &undeterminable),
("capabilities-unknown-extension", &unknown_ext),
] {
assert_eq!(
actual_conclusion(ctx),
ResultConclusion::Incomplete(IncompleteReason::RecognitionUndeterminable),
"{id}: recognition is undeterminable here and carries its own reason"
);
}
assert_eq!(
actual_conclusion(&undeterminable),
actual_conclusion(&unknown_ext),
"an advertised extension this build does not read is undeterminable for the same reason: \
no mapping from extension name to result token may be invented"
);
}
#[test]
fn an_interim_result_never_lifts_delivery_out_of_incomplete() {
let ctx = sole_context("modern-input-required");
let observed = ctx.result_observation.clone().expect("a result");
assert_eq!(observed, ResultObservation::InputRequired);
assert_eq!(actual_conclusion(&ctx), ResultConclusion::NonTerminal);
}
#[test]
fn multi_hop_keeps_two_records_and_infers_no_pairing() {
let all = contexts("multi-hop-input-required");
assert_eq!(all.len(), 2, "each hop is its own response record");
for (i, ctx) in all.iter().enumerate() {
let observed = ctx.result_observation.clone().expect("a result");
assert_eq!(observed, ResultObservation::InputRequired, "hop {i}");
assert_eq!(
actual_conclusion(ctx),
ResultConclusion::NonTerminal,
"hop {i}"
);
}
let doc = transcript("multi-hop-input-required");
let ids: Vec<&Value> = doc["entries"]
.as_array()
.expect("entries")
.iter()
.filter_map(|e| e.get("response").and_then(|r| r.get("id")))
.collect();
assert_eq!(ids.len(), 2);
assert_ne!(
ids[0], ids[1],
"the hops are distinct calls, not one call twice"
);
assert!(
vector("multi-hop-input-required")["observation"]["inferred_pairing"] == false,
"the corpus must not claim a pairing it does not have"
);
}
#[test]
fn traceparent_changes_no_conclusion() {
let plain = sole_context("modern-complete");
let traced = sole_context("traceparent-present");
assert_eq!(plain.era, traced.era);
assert_eq!(plain.result_observation, traced.result_observation);
assert_eq!(
actual_conclusion(&plain),
actual_conclusion(&traced),
"a traceparent is a correlation hint and never evidence"
);
assert_eq!(
vector("modern-complete")["profile_baseline"],
vector("traceparent-present")["profile_baseline"]
);
}
#[test]
fn unknown_and_conflicting_eras_stay_distinguishable() {
let unknown = sole_context("unknown-era");
let conflicting = sole_context("conflicting-era-signals");
assert!(matches!(unknown.era, EraResolution::Unknown(_)));
assert!(matches!(conflicting.era, EraResolution::Conflicting { .. }));
assert_ne!(
conclude(&unknown.era, &ResultObservation::Missing, None),
conclude(&conflicting.era, &ResultObservation::Missing, None),
"unknown may become conclusive with more evidence; contradicted will not"
);
}
#[test]
fn equivalent_calls_conclude_alike() {
for pair in manifest()["equivalence_pairs"].as_array().expect("pairs") {
let ids = pair["pair"].as_array().expect("pair");
let (a, b) = (ids[0].as_str().expect("a"), ids[1].as_str().expect("b"));
let (ca, cb) = (
actual_conclusion(&sole_context(a)),
actual_conclusion(&sole_context(b)),
);
assert_eq!(
ca, cb,
"{a} and {b} are declared equivalent and must conclude alike"
);
assert_eq!(
vector(a)["conclusion"],
vector(b)["conclusion"],
"{a} and {b} are declared equivalent but the manifest gives them different conclusions"
);
let Expected::Exact(want) = expected_conclusion(&vector(a));
assert_eq!(ca, want, "{a}/{b}");
assert_eq!(vector(a)["profile_baseline"], vector(b)["profile_baseline"]);
}
}