use serde_json::Value;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use std::sync::OnceLock;
const AUTHORITATIVE_REGISTRY: &str = include_str!("../data/REGISTRY.md");
const CPB_SNAPSHOT: &str = include_str!("../data/cpb_provisional.json");
pub const REGISTRY_NAMES: [&str; 7] = [
"verdict_class",
"disposition.decision",
"effect.type",
"irreversibility_class",
"effect_attestation",
"chain.relation",
"citation_purpose",
];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RegistryError(String);
impl fmt::Display for RegistryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for RegistryError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Registries {
seeded: BTreeMap<String, BTreeSet<String>>,
}
impl Registries {
pub fn authoritative() -> Result<&'static Registries, RegistryError> {
static TABLE: OnceLock<Result<Registries, RegistryError>> = OnceLock::new();
TABLE
.get_or_init(|| Registries::parse(AUTHORITATIVE_REGISTRY))
.as_ref()
.map_err(Clone::clone)
}
pub fn parse(text: &str) -> Result<Registries, RegistryError> {
let mut sections: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
let mut current: Option<&str> = None;
for line in text.lines() {
if let Some(name) = section_header(line) {
sections.insert(name, Vec::new());
current = Some(name);
} else if let Some(name) = current {
if line.starts_with("## ") {
current = None;
} else if let Some(lines) = sections.get_mut(name) {
lines.push(line);
}
}
}
let mut seeded = BTreeMap::new();
for name in REGISTRY_NAMES {
let values = match sections.get(name) {
Some(lines) => seeded_values(lines),
None if name == "citation_purpose" => BTreeSet::new(),
None => {
return Err(RegistryError(format!(
"registry {name:?} not found in REGISTRY.md"
)))
}
};
if values.is_empty() && sections.contains_key(name) {
return Err(RegistryError(format!(
"registry {name:?} parsed with no seeded values"
)));
}
seeded.insert(name.to_string(), values);
}
Ok(Registries { seeded })
}
pub fn contains(&self, registry: &str, value: &str) -> bool {
self.seeded
.get(registry)
.is_some_and(|values| values.contains(value))
}
pub fn values(&self, registry: &str) -> impl Iterator<Item = &str> {
self.seeded
.get(registry)
.into_iter()
.flatten()
.map(String::as_str)
}
}
pub type ProvisionalValues = BTreeMap<String, BTreeMap<String, String>>;
pub fn provisional_values() -> Result<&'static ProvisionalValues, RegistryError> {
static TABLE: OnceLock<Result<ProvisionalValues, RegistryError>> = OnceLock::new();
TABLE
.get_or_init(|| parse_provisional(CPB_SNAPSHOT))
.as_ref()
.map_err(Clone::clone)
}
fn parse_provisional(text: &str) -> Result<ProvisionalValues, RegistryError> {
let snapshot: Value = serde_json::from_str(text)
.map_err(|e| RegistryError(format!("decode embedded CPB provisional registry: {e}")))?;
let mut out = ProvisionalValues::new();
let Some(types) = snapshot
.get("provisional_artifact_types")
.and_then(Value::as_object)
else {
return Ok(out);
};
let classes: BTreeMap<&String, &Value> = types.iter().collect();
for (class, entry) in classes {
for registry in REGISTRY_NAMES {
let values = entry
.get("capsule_field_values")
.and_then(|v| v.get(registry))
.and_then(Value::as_array);
for value in values.into_iter().flatten().filter_map(Value::as_str) {
out.entry(registry.to_string())
.or_default()
.entry(value.to_string())
.or_insert_with(|| class.clone());
}
}
}
Ok(out)
}
fn section_header(line: &str) -> Option<&str> {
let rest = line.strip_prefix("##")?;
let rest = strip_required_ws(rest)?;
let rest = strip_ordinal(rest)?;
let rest = strip_required_ws(rest)?;
let (token, tail) = backtick_token(rest)?;
tail.trim_matches(is_re2_space).is_empty().then_some(token)
}
fn ordered_list_item(line: &str) -> Option<&str> {
let rest = strip_ordinal(line.trim_start_matches(is_re2_space))?;
let rest = strip_required_ws(rest)?;
let (token, tail) = backtick_token(rest)?;
tail.trim_matches(is_re2_space).is_empty().then_some(token)
}
fn is_re2_space(c: char) -> bool {
matches!(c, ' ' | '\t' | '\n' | '\x0c' | '\r')
}
fn strip_required_ws(s: &str) -> Option<&str> {
let trimmed = s.trim_start_matches(is_re2_space);
(trimmed.len() < s.len()).then_some(trimmed)
}
fn strip_ordinal(s: &str) -> Option<&str> {
let digits = s.bytes().take_while(u8::is_ascii_digit).count();
if digits == 0 {
return None;
}
s[digits..].strip_prefix('.')
}
fn backtick_token(s: &str) -> Option<(&str, &str)> {
let rest = s.strip_prefix('`')?;
let end = rest.find('`')?;
(end > 0).then(|| (&rest[..end], &rest[end + 1..]))
}
fn backtick_tokens(s: &str) -> Vec<&str> {
let mut out = Vec::new();
let mut rest = s;
while let Some(start) = rest.find('`') {
let after = &rest[start + 1..];
match after.find('`') {
Some(0) => rest = after,
Some(end) => {
out.push(&after[..end]);
rest = &after[end + 1..];
}
None => break,
}
}
out
}
fn seeded_values(lines: &[&str]) -> BTreeSet<String> {
let mut values = BTreeSet::new();
let mut i = 0;
while i < lines.len() {
let line = lines[i];
let stripped = line.trim();
if stripped.starts_with('|') {
let first = stripped
.trim_matches('|')
.split('|')
.next()
.unwrap_or("")
.trim();
let separator = first.chars().all(|c| matches!(c, '-' | ':' | ' '));
if !first.is_empty() && first != "Value" && !separator {
if let Some((token, "")) = backtick_token(first) {
values.insert(token.to_string());
}
}
i += 1;
continue;
}
if let Some(token) = ordered_list_item(line) {
values.insert(token.to_string());
i += 1;
continue;
}
if let Some(at) = stripped.find("Initial contents") {
let mut text = &stripped[at..];
while i < lines.len() && !lines[i].trim().is_empty() {
for token in backtick_tokens(text) {
values.insert(token.to_string());
}
i += 1;
text = lines.get(i).copied().unwrap_or("");
}
continue;
}
i += 1;
}
values
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn authoritative_table_has_every_registry() {
let regs = Registries::authoritative().expect("embedded REGISTRY.md parses");
for name in REGISTRY_NAMES {
assert!(regs.values(name).next().is_some(), "{name} is empty");
}
assert!(regs.contains("chain.relation", "supersedes"));
assert!(!regs.contains("chain.relation", "not-a-relation"));
}
#[test]
fn parses_structured_loci_only() {
let text = "\
## 1. `verdict_class`
Prose mentioning `not_a_value`.
| Value | Meaning |
|---|---|
| `executed` | ran |
1. `blocked`
Initial contents: `denied`,
`expired`
## 2. `disposition.decision`
| `accept` | |
## 3. `effect.type`
| `write` | |
## 4. `irreversibility_class`
| `two_way` | |
## 5. `effect_attestation`
| `gate_executed` | |
## 6. `chain.relation`
| `supersedes` | |
";
let regs = Registries::parse(text).expect("parses");
let verdicts: Vec<&str> = regs.values("verdict_class").collect();
assert_eq!(verdicts, ["blocked", "denied", "executed", "expired"]);
assert_eq!(regs.values("citation_purpose").count(), 0);
}
#[test]
fn missing_registry_is_an_error() {
assert!(Registries::parse("## 1. `verdict_class`\n| `x` |\n").is_err());
}
#[test]
fn provisional_snapshot_parses() {
provisional_values().expect("embedded CPB snapshot parses");
}
}