use std::collections::BTreeSet;
use serde_json::json;
use harn_serve::adapters::acp::{
HARN_AGENT_EVENT_KINDS, HARN_AGENT_EVENT_METHOD, HARN_CONTENT_EXTENSION_FIELDS,
HARN_PROVIDER_CATALOG_METHOD, HARN_SESSION_UPDATE_EXTENSIONS,
HARN_TOOL_LIFECYCLE_EXTENSION_FIELDS,
};
use harn_vm::llm::receipts::{
TOOL_CALL_RECEIPT_SCHEMA_ARTIFACT, TOOL_CALL_RECEIPT_SCHEMA_VERSION, TOOL_CALL_RECEIPT_STATUSES,
};
use super::constants::*;
use super::go::*;
use super::manifest::*;
use super::python::*;
use super::rust::*;
use super::support::*;
use super::swift::*;
use super::typescript::*;
use super::values::*;
use super::*;
#[test]
fn typescript_artifact_has_no_dangling_type_references() {
let ts = generate_typescript();
let declared: std::collections::HashSet<&str> = regex::Regex::new(
r"(?m)^\s*(?:export\s+)?(?:declare\s+)?(?:const\s+)?(?:interface|type|enum|class)\s+([A-Za-z_][A-Za-z0-9_]*)",
)
.unwrap()
.captures_iter(&ts)
.map(|c| c.get(1).unwrap().as_str())
.collect();
let mut dangling: Vec<&str> = regex::Regex::new(r"\b((?:ACP|Harn|A2A|MCP)[A-Za-z0-9]+)\b")
.unwrap()
.captures_iter(&ts)
.map(|c| c.get(1).unwrap().as_str())
.filter(|name| !declared.contains(name))
.collect();
dangling.sort_unstable();
dangling.dedup();
assert!(
dangling.is_empty(),
"emitted TypeScript references undeclared protocol type(s): {dangling:?}. Every \
ACP/Harn/A2A/MCP-prefixed type used in the bindings must be declared in the same \
artifact; this guard shift-lefts the burin-code `tsc` failure into harn's build."
);
}
#[test]
fn generated_types_include_harn_wire_vocabularies() {
let ts = generate_typescript();
assert!(ts.contains("export type JsonRpcId = number | string | null"));
assert!(ts.contains("export const MCP_DRAFT_PROTOCOL_VERSION = \"DRAFT-2026-v1\""));
assert!(ts.contains("export interface MCPRequestMeta"));
assert!(ts.contains("export interface MCPDiscoverResult"));
assert!(ts.contains("export interface MCPInputRequiredResult"));
assert!(ts.contains("export interface MCPOAuthDiscoveryResult"));
assert!(ts.contains("MCP_OAUTH_CLIENT_REGISTRATION_MODES"));
assert!(ts.contains("MCP_OAUTH_AUTH_MODES"));
assert!(ts.contains("application_type: MCPOAuthApplicationType"));
assert!(ts.contains("MCP_UNSUPPORTED_PROTOCOL_VERSION_ERROR"));
assert!(ts.contains("server/discover"));
assert!(ts.contains("io.modelcontextprotocol/protocolVersion"));
assert!(ts.contains("MCP-Protocol-Version"));
assert!(ts.contains("ttlMs"));
assert!(ts.contains("cacheScope"));
assert!(ts.contains("sessionClose: \"session/close\""));
assert!(ts.contains("@deprecated Use session/close; session/stop will be removed"));
for value in HARN_SESSION_UPDATE_EXTENSIONS
.iter()
.chain(HARN_AGENT_EVENT_KINDS.iter())
.chain(HARN_TOOL_LIFECYCLE_EXTENSION_FIELDS.iter())
{
assert!(ts.contains(value), "TypeScript artifact missing {value}");
}
let swift = generate_swift();
assert!(swift.contains("public enum HarnACPAgentMethod"));
assert!(swift.contains("mcpDraftProtocolVersion = \"DRAFT-2026-v1\""));
assert!(swift.contains("public struct HarnMCPRequestMeta"));
assert!(swift.contains("public struct HarnMCPDiscoverResult"));
assert!(swift.contains("public struct HarnMCPInputRequiredResult"));
assert!(swift.contains("public struct HarnMCPOAuthDiscoveryResult"));
assert!(swift.contains("public enum HarnMCPOAuthClientRegistrationMode"));
assert!(swift.contains("public enum HarnMCPOAuthAuthMode"));
assert!(swift.contains("case applicationType = \"application_type\""));
assert!(swift.contains("HarnMCPUnsupportedProtocolVersionError"));
assert!(swift.contains("case protocolVersion = \"io.modelcontextprotocol/protocolVersion\""));
assert!(swift.contains("case protocolVersion = \"MCP-Protocol-Version\""));
assert!(swift.contains("case sessionClose = \"session/close\""));
assert!(swift.contains("@available(*, deprecated"));
assert!(swift.contains("public static let allCases: [Self]"));
assert!(swift.contains("public enum HarnACPClientMethod"));
assert!(swift.contains("public enum HarnACPAgentNotification"));
assert!(swift.contains("public enum HarnJsonRpcId"));
assert!(swift.contains("public var id: HarnJsonRpcId"));
assert!(swift.contains("public init?(jsonObject: Any)"));
assert!(swift.contains("case let value as NSNumber: return jsonNumber(value)"));
assert!(swift.contains("CFGetTypeID(value) == CFBooleanGetTypeID()"));
assert!(swift.contains("public static func success(id: HarnJsonRpcId"));
assert!(swift.contains("public struct HarnToolCallReceipt"));
for value in tool_kind_values()
.into_iter()
.chain(tool_call_status_values())
.chain(tool_call_error_category_values())
.chain(worker_status_values())
.chain(
TOOL_CALL_RECEIPT_STATUSES
.iter()
.map(|value| (*value).to_string()),
)
{
assert!(swift.contains(&value), "Swift artifact missing {value}");
}
assert!(swift.contains("public enum HarnWorkerStatus"));
assert!(ts.contains("export type HarnWorkerStatus"));
assert!(ts.contains("HARN_WORKER_STATUSES"));
assert!(ts.contains("export interface ToolCallReceipt"));
assert!(ts.contains("HARN_TOOL_CALL_RECEIPT_STATUSES"));
}
#[test]
fn swift_case_name_escapes_reserved_keywords() {
assert_eq!(swift_case_name("private"), "`private`");
assert_eq!(swift_case_name("public"), "`public`");
assert_eq!(swift_case_name("class"), "`class`");
assert_eq!(swift_case_name("application_type"), "applicationType");
assert_eq!(swift_case_name("session/close"), "sessionClose");
assert_eq!(swift_case_name("private_room"), "privateRoom");
let swift = generate_swift();
assert!(
!swift.contains("case private = "),
"Swift artifact contains unescaped `case private = ...`"
);
assert!(
!swift.contains("case public = "),
"Swift artifact contains unescaped `case public = ...`"
);
assert!(swift.contains("case `private` = \"private\""));
assert!(swift.contains("case `public` = \"public\""));
}
#[test]
fn generated_rust_includes_harn_wire_vocabularies() {
let rust = generate_rust();
assert!(
rust.starts_with("// GENERATED by `harn dump-protocol-artifacts` - do not edit by hand."),
"Rust artifact missing provenance header"
);
assert!(rust.contains("pub const HARN_PROTOCOL_ARTIFACT_VERSION: &str ="));
assert!(rust.contains(&format!(
"pub const HARN_AGENT_EVENT_METHOD: &str = {};",
json_string_literal(HARN_AGENT_EVENT_METHOD)
)));
assert!(rust.contains(&format!(
"pub const HARN_PROVIDER_CATALOG_METHOD: &str = {};",
json_string_literal(HARN_PROVIDER_CATALOG_METHOD)
)));
assert!(rust.contains("pub const ACP_AGENT_METHOD_SESSION_PROMPT: &str = \"session/prompt\""));
assert!(rust.contains("pub const ACP_AGENT_METHODS: &[&str] = &["));
assert!(rust.contains("pub const ACP_DISPATCHED_METHODS: &[&str] = &["));
assert!(rust.contains("pub const ACP_CLIENT_METHODS: &[&str] = &["));
assert!(rust.contains("pub const ACP_SESSION_UPDATES: &[&str] = &["));
assert!(rust.contains("pub const HARN_ACP_SESSION_UPDATE_EXTENSIONS: &[&str] = &["));
assert!(rust
.contains("pub const HARN_CONTENT_EXTENSION_FIELD_VISIBLE_TEXT: &str = \"visible_text\""));
assert!(rust.contains(
"pub const HARN_CONTENT_EXTENSION_FIELD_VISIBLE_DELTA: &str = \"visible_delta\""
));
assert!(rust.contains("pub const HARN_CONTENT_EXTENSION_FIELDS: &[&str] = &["));
assert!(rust.contains(
"pub const ACP_DISPATCHED_METHOD_HARN_HITL_RESPOND: &str = \"harn.hitl.respond\""
));
assert!(rust.contains("pub const ACP_DISPATCHED_METHOD_AGENT_RESUME: &str = \"agent/resume\""));
for value in ACP_AGENT_METHODS
.iter()
.chain(ACP_DISPATCHED_METHODS.iter())
.chain(ACP_CLIENT_METHODS.iter())
.chain(HARN_SESSION_UPDATE_EXTENSIONS.iter())
.chain(HARN_AGENT_EVENT_KINDS.iter())
.chain(HARN_CONTENT_EXTENSION_FIELDS.iter())
.chain(HARN_TOOL_LIFECYCLE_EXTENSION_FIELDS.iter())
{
assert!(rust.contains(value), "Rust artifact missing {value}");
}
for value in all_acp_session_updates() {
assert!(rust.contains(&value), "Rust artifact missing {value}");
}
}
#[test]
fn rust_const_name_sanitizes_wire_values() {
assert_eq!(
rust_const_name("ACP_AGENT_METHOD", "session/prompt"),
"ACP_AGENT_METHOD_SESSION_PROMPT"
);
assert_eq!(
rust_const_name("ACP_DISPATCHED_METHOD", "harn.workflow.signal"),
"ACP_DISPATCHED_METHOD_HARN_WORKFLOW_SIGNAL"
);
assert_eq!(
rust_const_name("ACP_CLIENT_METHOD", "fs/read_text_file"),
"ACP_CLIENT_METHOD_FS_READ_TEXT_FILE"
);
assert_eq!(rust_const_name("X", "/"), "X");
assert_eq!(rust_const_name("V", "2026-07-28"), "V_2026_07_28");
}
#[test]
fn dispatched_acp_methods_match_artifact() {
let dispatch =
read_repo_text("crates/harn-serve/src/adapters/acp/mod.rs").expect("read acp adapter");
let body = dispatch
.split_once("match method.as_str() {")
.expect("dispatch match block")
.1
.split_once("\n _ => {")
.expect("dispatch wildcard arm")
.0;
let mut dispatched = BTreeSet::new();
for line in body.lines() {
let trimmed = line.trim();
if !trimmed.contains("=>") || !trimmed.starts_with('"') {
if trimmed.starts_with("HARN_PROVIDER_CATALOG_METHOD") {
dispatched.insert(HARN_PROVIDER_CATALOG_METHOD.to_string());
}
continue;
}
let arm = trimmed.split("=>").next().unwrap_or("");
for literal in arm.split('|') {
let name = literal.trim().trim_matches('"');
if !name.is_empty() {
dispatched.insert(name.to_string());
}
}
}
let published: BTreeSet<String> = ACP_DISPATCHED_METHODS
.iter()
.map(|m| m.to_string())
.collect();
assert_eq!(
published,
dispatched,
"ACP_DISPATCHED_METHODS is out of sync with the ACP adapter dispatch arms.\n\
missing from artifact: {:?}\n\
stale in artifact: {:?}",
dispatched.difference(&published).collect::<Vec<_>>(),
published.difference(&dispatched).collect::<Vec<_>>(),
);
}
#[test]
fn generated_python_includes_harn_wire_vocabularies() {
let py = generate_python();
assert!(py.contains("MCP_DRAFT_PROTOCOL_VERSION: str = \"DRAFT-2026-v1\""));
assert!(py.contains("MCP_REQUIRED_METADATA_KEYS: tuple"));
assert!(py.contains("class MCPDiscoverResult(_HarnDataclass):"));
assert!(py.contains("class MCPInputRequiredResult(_HarnDataclass):"));
assert!(py.contains("class MCPCacheScope(str, Enum):"));
assert!(py.contains("class ACPSessionUpdate(str, Enum):"));
assert!(py.contains("class HarnToolCallErrorCategory(str, Enum):"));
assert!(py.contains("class HarnWorkerStatus(str, Enum):"));
assert!(py.contains("class ToolCallReceipt(_HarnDataclass):"));
assert!(py.contains("class ToolCallReceiptStatus(str, Enum):"));
assert!(py.contains("class _HarnDataclass:"));
assert!(py.contains("def is_request("));
for value in HARN_SESSION_UPDATE_EXTENSIONS
.iter()
.chain(HARN_AGENT_EVENT_KINDS.iter())
.chain(ACP_AGENT_METHODS.iter())
{
assert!(py.contains(value), "Python artifact missing {value}");
}
assert!(
py.contains(HARN_PROVIDER_CATALOG_METHOD),
"Python artifact missing {HARN_PROVIDER_CATALOG_METHOD}"
);
for value in worker_status_values() {
assert!(py.contains(&value), "Python artifact missing {value}");
}
}
#[test]
fn generated_go_includes_harn_wire_vocabularies() {
let go = generate_go();
assert!(go.contains("package harnprotocol"));
assert!(go.contains("const MCPDraftProtocolVersion = \"DRAFT-2026-v1\""));
assert!(go.contains("type MCPRequestMeta struct"));
assert!(go.contains("type MCPDiscoverResult struct"));
assert!(go.contains("type MCPInputRequiredResult struct"));
assert!(go.contains("MCPUnsupportedProtocolVersionErrorCode"));
assert!(go.contains("type JSONRPCID struct"));
assert!(go.contains("type ACPSessionUpdateNotification struct"));
assert!(go.contains("func IsRequest(envelope map[string]json.RawMessage)"));
assert!(go.contains("type HarnWorkerStatus = string"));
assert!(go.contains("var HarnWorkerStatuses = []HarnWorkerStatus"));
assert!(go.contains("type ToolCallReceipt struct"));
assert!(go.contains("var ToolCallReceiptStatuses = []ToolCallReceiptStatus"));
for value in HARN_SESSION_UPDATE_EXTENSIONS
.iter()
.chain(HARN_AGENT_EVENT_KINDS.iter())
.chain(ACP_AGENT_METHODS.iter())
{
assert!(go.contains(value), "Go artifact missing {value}");
}
assert!(
go.contains(HARN_PROVIDER_CATALOG_METHOD),
"Go artifact missing {HARN_PROVIDER_CATALOG_METHOD}"
);
for value in worker_status_values() {
assert!(go.contains(&value), "Go artifact missing {value}");
}
}
#[test]
fn round_trip_fixture_matches_python_and_go_field_set() {
let fixture: serde_json::Value =
serde_json::from_str(&generate_round_trip_fixture().expect("fixture"))
.expect("fixture json");
assert_eq!(
fixture["envelopes"]["sessionUpdateNotification"]["params"]["update"]["sessionUpdate"],
json!("tool_call")
);
assert_eq!(
fixture["envelopes"]["agentEventNotification"]["method"],
json!(HARN_AGENT_EVENT_METHOD)
);
assert_eq!(
fixture["harnProviderCatalogMethod"],
json!(HARN_PROVIDER_CATALOG_METHOD)
);
assert_eq!(
fixture["envelopes"]["agentEventNotification"]["params"]["kind"],
json!("composition_child_call")
);
assert_eq!(fixture["a2aTask"]["status"]["state"], json!("working"));
assert_eq!(
fixture["mcpDiscoverResult"]["supportedVersions"][0],
json!(MCP_DRAFT_PROTOCOL_VERSION)
);
assert_eq!(
fixture["mcpInputRequiredResult"]["resultType"],
json!(MCP_INPUT_REQUIRED_RESULT_TYPE)
);
assert_eq!(
fixture["mcpUnsupportedProtocolVersionError"]["error"]["code"],
json!(MCP_UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE)
);
assert_eq!(fixture["toolCallReceipt"]["schema_version"], json!(1));
}
#[test]
fn manifest_advertises_python_and_go_bindings() {
let manifest: serde_json::Value =
serde_json::from_str(&generate_manifest().expect("manifest")).expect("manifest json");
assert!(manifest["bindings"]["python"]["artifact"].is_string());
assert!(manifest["bindings"]["go"]["artifact"].is_string());
assert!(manifest["bindings"]["go"]["modulePath"].is_string());
assert_eq!(
manifest["bindings"]["rust"]["artifact"],
json!("harn-protocol.rs")
);
assert_eq!(
manifest["bindings"]["rust"]["vendorPath"],
json!("protocol/src/generated.rs")
);
assert_eq!(manifest["bindings"]["rust"]["stability"], json!("stable"));
assert_eq!(
manifest["bindings"]["typescript"]["stability"],
json!("stable")
);
assert_eq!(
manifest["mcp"]["draftProtocolVersion"],
json!("DRAFT-2026-v1")
);
assert_eq!(
manifest["mcp"]["unsupportedProtocolVersionError"]["code"],
json!(MCP_UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE)
);
assert_eq!(
manifest["mcp"]["jsonSchemaDialect"],
json!(MCP_JSON_SCHEMA_2020_12_DIALECT)
);
assert_eq!(manifest["bindings"]["python"]["stability"], json!("stable"));
assert_eq!(manifest["bindings"]["go"]["stability"], json!("stable"));
assert_eq!(
manifest["receipts"]["toolCallReceiptSchemaVersion"],
json!(TOOL_CALL_RECEIPT_SCHEMA_VERSION)
);
assert_eq!(
manifest["acp"]["deprecatedAgentMethods"]["session/stop"]["replacement"],
json!("session/close")
);
}
#[test]
fn generated_manifest_references_schema_artifacts() {
let manifest: serde_json::Value =
serde_json::from_str(&generate_manifest().expect("manifest")).expect("manifest json");
for schema in SCHEMA_COPIES {
assert!(
manifest["schemas"]
.as_array()
.expect("schema array")
.iter()
.any(|entry| entry["artifact"] == schema.artifact),
"manifest missing {}",
schema.artifact
);
}
assert!(
manifest["schemas"]
.as_array()
.expect("schema array")
.iter()
.any(|entry| entry["artifact"] == TOOL_CALL_RECEIPT_SCHEMA_ARTIFACT),
"manifest missing {TOOL_CALL_RECEIPT_SCHEMA_ARTIFACT}"
);
}
#[test]
fn committed_protocol_artifacts_match_generator() {
let artifacts = generate_artifacts().expect("artifacts");
let output_root = repo_root().join("spec/protocol-artifacts");
for artifact in artifacts {
let path = output_root.join(&artifact.relative_path);
let on_disk = fs::read_to_string(&path).unwrap_or_else(|error| {
panic!(
"failed to read {}: {error}\n\
hint: run `make gen-protocol-artifacts` to regenerate.",
path.display()
)
});
assert_eq!(
normalize_line_endings(&on_disk),
normalize_line_endings(&artifact.contents),
"{} is stale. Run `make gen-protocol-artifacts` to regenerate.",
path.display()
);
}
}