use std::collections::BTreeSet;
pub const REGULATORY_CLASSES: &[&str] = &[
"HIPAA",
"PCI_DSS",
"GDPR",
"SOX",
"FINRA",
"ISO27001",
"SOC2",
"FISMA",
"GxP",
"CCPA",
"NIST_800_53",
"NOM151",
"LFPDPPP",
"LGPD",
"LEY1581",
];
pub fn is_known(label: &str) -> bool {
REGULATORY_CLASSES.contains(&label)
}
pub fn unknown_classes<'a>(declared: &'a [String]) -> Vec<&'a str> {
declared
.iter()
.map(|s| s.as_str())
.filter(|s| !is_known(s))
.collect()
}
pub fn nearest_class(label: &str) -> Option<&'static str> {
let mut hit: Option<&'static str> = None;
for candidate in REGULATORY_CLASSES {
if edit_distance_at_most_1(label, candidate) {
if hit.is_some() {
return None; }
hit = Some(candidate);
}
}
hit
}
fn edit_distance_at_most_1(a: &str, b: &str) -> bool {
let (a, b) = (a.as_bytes(), b.as_bytes());
if a == b {
return true;
}
let (long, short) = if a.len() >= b.len() { (a, b) } else { (b, a) };
if long.len() - short.len() > 1 {
return false;
}
let mut i = 0;
let mut j = 0;
let mut edited = false;
while i < long.len() && j < short.len() {
if long[i] == short[j] {
i += 1;
j += 1;
continue;
}
if edited {
return false;
}
edited = true;
if long.len() == short.len() {
i += 1;
j += 1;
} else {
i += 1;
}
}
true
}
pub fn peel_type_constructors(type_ref: &str) -> &str {
let mut t = type_ref.trim();
t = t.strip_suffix('?').unwrap_or(t).trim();
loop {
let peeled = ["FlowEnvelope<", "List<", "Stream<"].iter().find_map(|ctor| {
t.strip_prefix(*ctor)
.and_then(|rest| rest.strip_suffix('>'))
.map(|inner| inner.trim())
});
match peeled {
Some(inner) => t = inner.strip_suffix('?').unwrap_or(inner).trim(),
None => return t,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Coverage {
Shielded {
kappa_from: &'static str,
code: &'static str,
},
NoStaticKappa {
governed_by: Option<&'static str>,
why: &'static str,
},
}
#[derive(Debug, Clone, Copy)]
pub struct Egress {
pub primitive: &'static str,
pub coverage: Coverage,
}
pub const EGRESS_PRIMITIVES: &[Egress] = &[
Egress {
primitive: "axonendpoint",
coverage: Coverage::Shielded {
kappa_from: "body: and output:",
code: "axon-T957",
},
},
Egress {
primitive: "channel",
coverage: Coverage::Shielded {
kappa_from: "message:",
code: "axon-T1215",
},
},
Egress {
primitive: "tool",
coverage: Coverage::Shielded {
kappa_from: "parameters: and output_type:",
code: "axon-T1221",
},
},
Egress {
primitive: "document",
coverage: Coverage::NoStaticKappa {
governed_by: Some("axon-T916"),
why: "binds bare value references (DocScalar::Ref) with no typed binding site",
},
},
Egress {
primitive: "deliver",
coverage: Coverage::NoStaticKappa {
governed_by: Some("axon-T920"),
why: "binds bare value references (DocScalar::Ref) with no typed binding site",
},
},
Egress {
primitive: "notify",
coverage: Coverage::NoStaticKappa {
governed_by: Some("axon-T934"),
why: "binds bare value references (DocScalar::Ref) with no typed binding site",
},
},
Egress {
primitive: "axonstore",
coverage: Coverage::NoStaticKappa {
governed_by: None,
why: "a store schema is a closed catalogue of primitive SQL column types, so a \
regulated value is decomposed into columns before it lands and carries no \
declared type to read a class from",
},
},
];
pub fn shielded_exits() -> impl Iterator<Item = &'static Egress> {
EGRESS_PRIMITIVES
.iter()
.filter(|e| matches!(e.coverage, Coverage::Shielded { .. }))
}
pub fn transitive_kappa(program: &crate::ast::Program, type_ref: &str) -> BTreeSet<String> {
let mut found = BTreeSet::new();
let mut visited: BTreeSet<String> = BTreeSet::new();
let mut queue: Vec<String> = vec![type_ref.to_string()];
while let Some(spelling) = queue.pop() {
let base = peel_type_constructors(&spelling);
if base.is_empty() {
continue;
}
if let Some(open) = base.find('<') {
if let Some(inner) = base.strip_suffix('>').map(|s| &s[open + 1..]) {
for arg in inner.split(',') {
let arg = arg.trim();
if !arg.is_empty() && !visited.contains(arg) {
queue.push(arg.to_string());
}
}
}
}
if !visited.insert(base.to_string()) {
continue;
}
let Some(decl) = program.declarations.iter().find_map(|d| match d {
crate::ast::Declaration::Type(t) if t.name == base => Some(t),
_ => None,
}) else {
continue;
};
found.extend(decl.compliance.iter().cloned());
for field in &decl.fields {
let spelling = if field.type_expr.generic_param.is_empty() {
field.type_expr.name.clone()
} else {
format!("{}<{}>", field.type_expr.name, field.type_expr.generic_param)
};
if !spelling.is_empty() {
queue.push(spelling);
}
}
}
found
}
pub fn peel_channel_payload(spelling: &str) -> &str {
let mut leaf = spelling.trim();
while let Some(inner) = leaf
.strip_prefix("Channel<")
.and_then(|rest| rest.strip_suffix('>'))
{
leaf = inner.trim();
}
peel_type_constructors(leaf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_catalog_is_the_fifteen_the_paper_names() {
assert_eq!(
REGULATORY_CLASSES.len(),
15,
"Κ is the canonical registry from the ESK paper, extended in v4.0.0 with the \
four LATAM jurisdictions. Changing its size is a decision about what an adopter \
may assert, not a refactor — and the paper's own Κ is pinned against this list \
by the paper-matches-compiler gate."
);
for class in [
"HIPAA", "PCI_DSS", "GDPR", "SOX", "FINRA", "ISO27001", "SOC2", "FISMA", "GxP",
"CCPA", "NIST_800_53", "NOM151", "LFPDPPP", "LGPD", "LEY1581",
] {
assert!(is_known(class), "{class} must be in Κ");
}
}
#[test]
fn membership_is_case_sensitive() {
assert!(is_known("HIPAA"));
assert!(!is_known("hipaa"), "case variants are different strings to every consumer that groups by this label");
assert!(!is_known("Hipaa"));
}
#[test]
fn a_typo_is_not_a_class_and_gets_a_suggestion() {
assert!(!is_known("HIPPA"));
assert_eq!(nearest_class("HIPPA"), Some("HIPAA"));
assert_eq!(nearest_class("PCI-DSS"), Some("PCI_DSS"));
}
#[test]
fn a_word_that_is_not_a_framework_suggests_nothing() {
assert!(!is_known("NOT_A_FRAMEWORK"));
assert_eq!(
nearest_class("NOT_A_FRAMEWORK"),
None,
"a suggestion must be a near miss, never the closest of eleven unrelated names"
);
}
#[test]
fn unknown_classes_reports_offenders_in_order() {
let declared = vec!["HIPAA".to_string(), "HIPPA".to_string(), "SOC2".to_string(), "NOPE".to_string()];
assert_eq!(unknown_classes(&declared), vec!["HIPPA", "NOPE"]);
}
#[test]
fn every_class_is_known_and_suggests_itself() {
for class in REGULATORY_CLASSES {
assert!(is_known(class));
assert_eq!(
nearest_class(class),
Some(*class),
"a valid class must resolve to itself — if two members of Κ are one edit apart, \
`nearest_class` goes ambiguous and the diagnostics silently stop suggesting"
);
}
}
}