use super::*;
fn package() -> super::super::PluginPackageId {
super::super::PluginPackageId::parse("acme/assistant").unwrap()
}
fn surface(kind: PluginSurfaceKind, id: &str) -> PluginSurfaceRef {
PluginSurfaceRef {
kind,
id: id.to_owned(),
}
}
fn digest(letter: char) -> String {
format!("sha256:{}", letter.to_string().repeat(64))
}
fn schema() -> Value {
serde_json::json!({
"type": "object",
"additionalProperties": false,
"properties": {
"query": {"type": "string", "maxLength": 128}
},
"required": ["query"]
})
}
fn tool() -> CapabilityDescriptor {
let package = package();
let surface = surface(PluginSurfaceKind::Tool, "search");
CapabilityDescriptor {
schema: CAPABILITY_DESCRIPTOR_SCHEMA_V1.to_owned(),
invocation_ref: InvocationRef::derive(&package, &surface, 7, &digest('a')).unwrap(),
artifact_ref: Some(ArtifactRef::derive(&package, &surface, 7, &digest('b')).unwrap()),
endpoint_ref: Some(EndpointRef::derive(&package, &surface, 7, &digest('c')).unwrap()),
package_id: package,
surface,
generation: 7,
package_digest: digest('d'),
manifest_digest: digest('e'),
title: "Search".to_owned(),
description: "Search verified knowledge.".to_owned(),
dependencies: Vec::new(),
publication: CapabilityPublicationEvidence {
catalog_record_digest: digest('f'),
signature_digest: digest('0'),
},
capability: CapabilityDescriptorKind::Tool {
name: "search".to_owned(),
input_schema: schema(),
output_schema: schema(),
annotations: CapabilityToolAnnotations::new(true, false, true, false),
},
}
}
#[test]
fn opaque_references_are_deterministic_and_domain_separated() {
let package = package();
let surface = surface(PluginSurfaceKind::Tool, "search");
let invocation = InvocationRef::derive(&package, &surface, 1, &digest('a')).unwrap();
assert_eq!(
invocation,
InvocationRef::derive(&package, &surface, 1, &digest('a')).unwrap()
);
assert_ne!(
invocation.as_str(),
ArtifactRef::derive(&package, &surface, 1, &digest('a'))
.unwrap()
.as_str()
);
assert!(InvocationRef::parse("/tmp/private").is_err());
}
#[test]
fn descriptor_round_trips_without_private_authority_fields() {
let descriptor = tool();
descriptor.validate().unwrap();
let encoded = serde_json::to_value(&descriptor).unwrap();
assert!(encoded.get("path").is_none());
assert!(encoded.get("packageRoot").is_none());
assert!(encoded.get("executable").is_none());
assert!(encoded.get("secret").is_none());
let decoded: CapabilityDescriptor = serde_json::from_value(encoded).unwrap();
assert_eq!(decoded, descriptor);
assert_eq!(
decoded.descriptor_digest().unwrap(),
descriptor.descriptor_digest().unwrap()
);
let mut unknown = serde_json::to_value(&descriptor).unwrap();
unknown["path"] = serde_json::json!("/private");
assert!(serde_json::from_value::<CapabilityDescriptor>(unknown).is_err());
}
#[test]
fn descriptor_rejects_executable_only_and_external_schema_authority() {
let mut descriptor = tool();
descriptor.capability = CapabilityDescriptorKind::Tool {
name: "bad/name".to_owned(),
input_schema: schema(),
output_schema: schema(),
annotations: CapabilityToolAnnotations::new(false, true, false, true),
};
assert!(descriptor.validate().is_err());
let mut descriptor = tool();
descriptor.capability = CapabilityDescriptorKind::Tool {
name: "search".to_owned(),
input_schema: serde_json::json!({
"type": "object",
"additionalProperties": false,
"$ref": "https://example.invalid/schema"
}),
output_schema: schema(),
annotations: CapabilityToolAnnotations::new(true, false, true, false),
};
assert!(descriptor.validate().is_err());
for reference_keyword in ["$dynamicRef", "$recursiveRef", "$id"] {
let mut descriptor = tool();
let mut input_schema = serde_json::Map::new();
input_schema.insert("type".to_owned(), Value::String("object".to_owned()));
input_schema.insert("additionalProperties".to_owned(), Value::Bool(false));
input_schema.insert(
reference_keyword.to_owned(),
Value::String("https://example.invalid/schema".to_owned()),
);
descriptor.capability = CapabilityDescriptorKind::Tool {
name: "search".to_owned(),
input_schema: Value::Object(input_schema),
output_schema: schema(),
annotations: CapabilityToolAnnotations::new(true, false, true, false),
};
assert!(
descriptor.validate().is_err(),
"{reference_keyword} escaped"
);
}
}
#[test]
fn catalog_is_immutable_publication_and_revision_checked() {
let installation =
InstallationId::new(super::super::InstallationKind::User, "user/current").unwrap();
let first = tool();
let mut second = tool();
second.surface.id = "z-last".to_owned();
if let CapabilityDescriptorKind::Tool { name, .. } = &mut second.capability {
*name = "z-last".to_owned();
}
second.invocation_ref = InvocationRef::derive(
&second.package_id,
&second.surface,
second.generation,
&digest('a'),
)
.unwrap();
let catalog =
CapabilityGatewayCatalog::new(installation.clone(), 7, vec![second.clone(), first.clone()])
.unwrap();
assert_eq!(catalog.descriptors()[0].surface.id, "search");
assert_eq!(catalog.find_tool("search").unwrap().surface.id, "search");
let mut tampered = serde_json::to_value(&catalog).unwrap();
tampered["generation"] = serde_json::json!(8);
assert!(CapabilityGatewayCatalog::from_json(&serde_json::to_vec(&tampered).unwrap()).is_err());
let mut foreign = catalog.clone();
foreign.installation = InstallationId::new(
super::super::InstallationKind::Workspace,
"workspace/current",
)
.unwrap();
assert!(foreign.validate().is_err());
}
#[test]
fn catalog_publication_generation_is_independent_from_package_lifecycle_generation() {
let installation =
InstallationId::new(super::super::InstallationKind::User, "user/current").unwrap();
let first = tool();
let mut second = tool();
second.package_id = super::super::PluginPackageId::parse("acme/second").unwrap();
second.surface = surface(PluginSurfaceKind::Tool, "convert");
second.generation = 11;
second.invocation_ref = InvocationRef::derive(
&second.package_id,
&second.surface,
second.generation,
&digest('a'),
)
.unwrap();
second.artifact_ref = Some(
ArtifactRef::derive(
&second.package_id,
&second.surface,
second.generation,
&digest('b'),
)
.unwrap(),
);
second.endpoint_ref = Some(
EndpointRef::derive(
&second.package_id,
&second.surface,
second.generation,
&digest('c'),
)
.unwrap(),
);
if let CapabilityDescriptorKind::Tool { name, .. } = &mut second.capability {
*name = "convert".to_owned();
}
let catalog = CapabilityGatewayCatalog::new(installation, 42, vec![second, first]).unwrap();
assert_eq!(catalog.generation(), 42);
assert_eq!(catalog.descriptors()[0].generation, 7);
assert_eq!(catalog.descriptors()[1].generation, 11);
}
#[test]
fn catalog_rejects_two_lifecycle_generations_of_one_surface() {
let installation =
InstallationId::new(super::super::InstallationKind::User, "user/current").unwrap();
let first = tool();
let mut second = first.clone();
second.generation = 8;
second.invocation_ref = InvocationRef::derive(
&second.package_id,
&second.surface,
second.generation,
&digest('a'),
)
.unwrap();
assert!(CapabilityGatewayCatalog::new(installation, 42, vec![first, second]).is_err());
}
#[test]
fn description_proof_binds_the_exact_agent_visible_descriptor() {
let proof = CapabilityDescriptionProof::from_verified(tool(), "registry/official").unwrap();
proof.validate().unwrap();
let encoded = serde_json::to_vec(&proof).unwrap();
let decoded = CapabilityDescriptionProof::from_json(&encoded).unwrap();
assert_eq!(decoded, proof);
let mut tampered = proof.clone();
tampered.descriptor.title = "Different title".to_owned();
assert!(tampered.validate().is_err());
let mut tampered = proof;
tampered.signer_id = "../untrusted".to_owned();
assert!(tampered.validate().is_err());
}
#[test]
fn verified_descriptions_are_the_only_inputs_to_the_proof_catalog_constructor() {
let installation =
InstallationId::new(super::super::InstallationKind::User, "user/current").unwrap();
let proof = CapabilityDescriptionProof::from_verified(tool(), "registry/official").unwrap();
let catalog =
CapabilityGatewayCatalog::from_verified_descriptions(installation.clone(), 7, vec![proof])
.unwrap();
assert_eq!(catalog.descriptors().len(), 1);
let mut invalid =
CapabilityDescriptionProof::from_verified(tool(), "registry/official").unwrap();
invalid.descriptor_digest = digest('e');
assert!(
CapabilityGatewayCatalog::from_verified_descriptions(installation, 7, vec![invalid])
.is_err()
);
}
#[test]
fn public_gateway_contracts_are_send_and_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<InvocationRef>();
assert_send_sync::<ArtifactRef>();
assert_send_sync::<EndpointRef>();
assert_send_sync::<CapabilityDescriptionProof>();
assert_send_sync::<CapabilityDescriptor>();
assert_send_sync::<CapabilityGatewayCatalog>();
}