use super::RequirementKind;
use crate::contract::canonical_policy::CanonicalPolicy;
use crate::contract::capability::{
Capability, EnvEntry, EnvPolicy, FdPolicy, NetDest, NetPolicy, SpawnPolicy,
};
fn samples() -> Vec<(&'static str, RequirementKind, CanonicalPolicy)> {
let dest = |host: &str, port| NetDest {
host: host.to_string(),
port,
};
vec![
(
"fd-none",
RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::None,
}),
CanonicalPolicy::of_fd(&FdPolicy::None),
),
(
"fd-only-empty",
RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::Only(vec![]),
}),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![])),
),
(
"fd-only-13",
RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::Only(vec![1, 3]),
}),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1, 3])),
),
(
"fd-only-13-alias",
RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::Only(vec![3, 1, 3]),
}),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![3, 1, 3])),
),
(
"spawn-deny-new-tasks",
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::DenyNewTasks,
}),
CanonicalPolicy::of_spawn(&SpawnPolicy::DenyNewTasks),
),
(
"spawn-allow-threads",
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::AllowThreadsWithinBoundary,
}),
CanonicalPolicy::of_spawn(&SpawnPolicy::AllowThreadsWithinBoundary),
),
(
"spawn-allow-descendants",
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::AllowDescendantsWithinBoundary,
}),
CanonicalPolicy::of_spawn(&SpawnPolicy::AllowDescendantsWithinBoundary),
),
(
"env-empty",
RequirementKind::of_capability_for_test(&Capability::Environment {
policy: EnvPolicy::Exact(vec![]),
}),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![])),
),
(
"env-entries",
RequirementKind::of_capability_for_test(&Capability::Environment {
policy: EnvPolicy::Exact(vec![EnvEntry::literal("PATH", "/usr/bin")]),
}),
CanonicalPolicy::of_env(&EnvPolicy::Exact(vec![EnvEntry::literal(
"PATH", "/usr/bin",
)])),
),
(
"net-deny",
RequirementKind::of_capability_for_test(&Capability::Network {
policy: NetPolicy::DenyAll,
}),
CanonicalPolicy::of_net(&NetPolicy::DenyAll),
),
(
"net-allow",
RequirementKind::of_capability_for_test(&Capability::Network {
policy: NetPolicy::AllowList(vec![dest("example.com", 443)]),
}),
CanonicalPolicy::of_net(&NetPolicy::AllowList(vec![dest("example.com", 443)])),
),
]
}
fn variant(policy: &CanonicalPolicy) -> &[u8] {
let bytes = policy.as_bytes();
&bytes[..bytes.len().min(2)]
}
#[test]
fn equal_canonical_variants_map_to_one_key() {
let samples = samples();
let mut violations = Vec::new();
for (na, ka, ca) in &samples {
for (nb, kb, cb) in &samples {
if variant(ca) == variant(cb) && ka != kb {
violations.push(format!(
"{na} and {nb} share a canonical variant but map to {ka:?} vs {kb:?}"
));
}
}
}
assert!(
violations.is_empty(),
"policy→key is not a function (one canonical variant, two keys): {violations:?}"
);
}
#[test]
fn one_key_implies_one_canonical_variant() {
let samples = samples();
let mut collisions = Vec::new();
for (na, ka, ca) in &samples {
for (nb, kb, cb) in &samples {
if ka == kb && variant(ca) != variant(cb) {
collisions.push(format!(
"{na} and {nb} share key {ka:?} but have DISTINCT canonical variants"
));
}
}
}
assert!(
collisions.is_empty(),
"a key is shared by two distinct canonical variants: {collisions:?}"
);
}
#[test]
fn the_policy_aware_splits_are_real_and_aliases_collapse() {
assert_ne!(
RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::None
}),
RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::Only(vec![])
}),
"InheritedFds None vs Only must be distinct keys"
);
assert_ne!(
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::DenyNewTasks
}),
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::AllowThreadsWithinBoundary
}),
"ChildSpawn DenyNewTasks vs AllowThreads must be distinct keys"
);
assert_ne!(
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::AllowThreadsWithinBoundary
}),
RequirementKind::of_capability_for_test(&Capability::ChildSpawn {
policy: SpawnPolicy::AllowDescendantsWithinBoundary
}),
"ChildSpawn AllowThreads vs AllowDescendants must be distinct keys"
);
let canon = RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::Only(vec![1, 3]),
});
let alias = RequirementKind::of_capability_for_test(&Capability::InheritedFds {
policy: FdPolicy::Only(vec![3, 1, 3]),
});
assert_eq!(canon, alias, "fd-list aliases must share one key");
assert_eq!(
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![1, 3])).as_bytes(),
CanonicalPolicy::of_fd(&FdPolicy::Only(vec![3, 1, 3])).as_bytes(),
"fd-list aliases must share canonical bytes"
);
}