use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use gray_matter::engine::YAML;
use gray_matter::Matter;
use include_dir::{include_dir, Dir};
use serde::Deserialize;
static EMBEDDED_KNOWLEDGE: Dir<'_> =
include_dir!("$CARGO_MANIFEST_DIR/knowledge");
#[derive(Debug, Clone)]
pub struct Primitive {
pub name: String,
pub summary: String,
pub category: Category,
pub top_level: bool,
pub grammar: String,
pub since: String,
pub body: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Category {
Cognition,
CognitiveIo,
DataPlane,
SessionTypes,
Wire,
Operators,
}
impl Category {
pub fn as_str(self) -> &'static str {
match self {
Category::Cognition => "cognition",
Category::CognitiveIo => "cognitive_io",
Category::DataPlane => "data_plane",
Category::SessionTypes => "session_types",
Category::Wire => "wire",
Category::Operators => "operators",
}
}
}
#[derive(Debug, Deserialize)]
struct Frontmatter {
name: String,
summary: String,
category: Category,
top_level: bool,
#[serde(default)]
grammar: String,
#[serde(default)]
since: String,
}
#[derive(Debug, Clone)]
pub struct Reference {
pub kind: ReferenceKind,
pub slug: String,
pub summary: String,
pub title: String,
pub body: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum ReferenceKind {
Grammar,
Logic,
Compliance,
}
impl ReferenceKind {
pub fn as_str(self) -> &'static str {
match self {
ReferenceKind::Grammar => "grammar",
ReferenceKind::Logic => "logic",
ReferenceKind::Compliance => "compliance",
}
}
pub const fn all() -> &'static [ReferenceKind] {
&[
ReferenceKind::Grammar,
ReferenceKind::Logic,
ReferenceKind::Compliance,
]
}
}
#[derive(Debug, Deserialize)]
struct ReferenceFrontmatter {
name: String,
summary: String,
title: String,
}
#[derive(Debug, Clone)]
pub struct Template {
pub slug: String,
pub source: String,
}
#[derive(Debug, Clone)]
pub struct Example {
pub name: String,
pub title: String,
pub summary: String,
pub topic: ExampleTopic,
pub primitives: Vec<String>,
pub source: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum ExampleTopic {
Composition,
SessionTypes,
Shields,
Effects,
Streaming,
Data,
Agents,
Endpoints,
Memory,
Validation,
}
impl ExampleTopic {
pub fn as_str(self) -> &'static str {
match self {
ExampleTopic::Composition => "composition",
ExampleTopic::SessionTypes => "session_types",
ExampleTopic::Shields => "shields",
ExampleTopic::Effects => "effects",
ExampleTopic::Streaming => "streaming",
ExampleTopic::Data => "data",
ExampleTopic::Agents => "agents",
ExampleTopic::Endpoints => "endpoints",
ExampleTopic::Memory => "memory",
ExampleTopic::Validation => "validation",
}
}
pub const fn all() -> &'static [ExampleTopic] {
&[
ExampleTopic::Composition,
ExampleTopic::SessionTypes,
ExampleTopic::Shields,
ExampleTopic::Effects,
ExampleTopic::Streaming,
ExampleTopic::Data,
ExampleTopic::Agents,
ExampleTopic::Endpoints,
ExampleTopic::Memory,
ExampleTopic::Validation,
]
}
pub fn parse(s: &str) -> Option<Self> {
let norm = s.trim().to_ascii_lowercase().replace('-', "_");
ExampleTopic::all()
.iter()
.copied()
.find(|t| t.as_str() == norm)
}
}
#[derive(Debug, Deserialize)]
struct ExampleFrontmatter {
name: String,
title: String,
summary: String,
topic: ExampleTopic,
#[serde(default)]
primitives: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct Prompt {
pub name: String,
pub title: String,
pub summary: String,
pub arguments: Vec<PromptArgument>,
pub body: String,
}
#[derive(Debug, Clone, Deserialize, serde::Serialize)]
pub struct PromptArgument {
pub name: String,
pub description: String,
#[serde(default)]
pub required: bool,
}
#[derive(Debug, Deserialize)]
struct PromptFrontmatter {
name: String,
title: String,
summary: String,
#[serde(default)]
arguments: Vec<PromptArgument>,
}
#[derive(Debug, Clone, Default)]
pub struct Catalog {
primitives: BTreeMap<String, Primitive>,
references: BTreeMap<(ReferenceKind, String), Reference>,
templates: BTreeMap<String, Template>,
prompts: BTreeMap<String, Prompt>,
examples: BTreeMap<String, Example>,
}
impl Catalog {
pub fn load_default() -> Result<Self, LoadError> {
if let Ok(env) = std::env::var("AXON_EMCP_KNOWLEDGE_DIR") {
let path = PathBuf::from(env);
if !path.is_dir() {
return Err(LoadError::MissingDir(path));
}
return Self::load_from(&path);
}
let dev_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("knowledge");
if dev_path.is_dir() {
return Self::load_from(&dev_path);
}
Self::load_embedded()
}
pub fn load_embedded() -> Result<Self, LoadError> {
let prims_dir = EMBEDDED_KNOWLEDGE.get_dir("primitives").ok_or_else(|| {
LoadError::MissingDir(PathBuf::from("(embedded)/primitives"))
})?;
let mut primitives = BTreeMap::new();
for file in prims_dir.files() {
if !is_markdown(file.path()) {
continue;
}
let raw = file.contents_utf8().ok_or_else(|| {
LoadError::BadFrontmatter(
PathBuf::from(file.path()),
"embedded file is not valid UTF-8".into(),
)
})?;
let prim = parse_primitive(raw, file.path())?;
if primitives.insert(prim.name.clone(), prim.clone()).is_some() {
return Err(LoadError::DuplicateName(
prim.name,
PathBuf::from(file.path()),
));
}
}
let mut references = BTreeMap::new();
for kind in ReferenceKind::all() {
let Some(dir) = EMBEDDED_KNOWLEDGE.get_dir(kind.as_str()) else {
continue;
};
for file in dir.files() {
if !is_markdown(file.path()) {
continue;
}
let raw = file.contents_utf8().ok_or_else(|| {
LoadError::BadFrontmatter(
PathBuf::from(file.path()),
"embedded file is not valid UTF-8".into(),
)
})?;
let refr = parse_reference(raw, file.path(), *kind)?;
let key = (refr.kind, refr.slug.clone());
if references.insert(key, refr.clone()).is_some() {
return Err(LoadError::DuplicateName(
format!("{}/{}", refr.kind.as_str(), refr.slug),
PathBuf::from(file.path()),
));
}
}
}
let mut templates = BTreeMap::new();
if let Some(dir) = EMBEDDED_KNOWLEDGE.get_dir("templates") {
for file in dir.files() {
if !is_axon(file.path()) {
continue;
}
let slug = match file.path().file_stem().and_then(|s| s.to_str()) {
Some(s) => s.to_string(),
None => continue,
};
let source = file.contents_utf8().ok_or_else(|| {
LoadError::BadFrontmatter(
PathBuf::from(file.path()),
"embedded template is not valid UTF-8".into(),
)
})?;
let tpl = Template { slug: slug.clone(), source: source.to_string() };
if templates.insert(slug.clone(), tpl).is_some() {
return Err(LoadError::DuplicateName(
slug,
PathBuf::from(file.path()),
));
}
}
}
let mut prompts = BTreeMap::new();
if let Some(dir) = EMBEDDED_KNOWLEDGE.get_dir("prompts") {
for file in dir.files() {
if !is_markdown(file.path()) {
continue;
}
let raw = file.contents_utf8().ok_or_else(|| {
LoadError::BadFrontmatter(
PathBuf::from(file.path()),
"embedded prompt is not valid UTF-8".into(),
)
})?;
let p = parse_prompt(raw, file.path())?;
if prompts.insert(p.name.clone(), p.clone()).is_some() {
return Err(LoadError::DuplicateName(
p.name,
PathBuf::from(file.path()),
));
}
}
}
let mut examples = BTreeMap::new();
if let Some(dir) = EMBEDDED_KNOWLEDGE.get_dir("examples") {
for file in dir.files() {
if !is_markdown(file.path()) {
continue;
}
let raw = file.contents_utf8().ok_or_else(|| {
LoadError::BadFrontmatter(
PathBuf::from(file.path()),
"embedded example is not valid UTF-8".into(),
)
})?;
let ex = parse_example(raw, file.path())?;
if examples.insert(ex.name.clone(), ex.clone()).is_some() {
return Err(LoadError::DuplicateName(
ex.name,
PathBuf::from(file.path()),
));
}
}
}
Ok(Catalog { primitives, references, templates, prompts, examples })
}
pub fn load_from(root: &Path) -> Result<Self, LoadError> {
let primitives_dir = root.join("primitives");
if !primitives_dir.is_dir() {
return Err(LoadError::MissingDir(primitives_dir));
}
let mut primitives = BTreeMap::new();
for entry in std::fs::read_dir(&primitives_dir)
.map_err(|e| LoadError::Io(primitives_dir.clone(), e))?
{
let entry = entry.map_err(|e| LoadError::Io(primitives_dir.clone(), e))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("md") {
continue;
}
let raw = std::fs::read_to_string(&path)
.map_err(|e| LoadError::Io(path.clone(), e))?;
let prim = parse_primitive(&raw, &path)?;
if primitives.insert(prim.name.clone(), prim.clone()).is_some() {
return Err(LoadError::DuplicateName(prim.name, path));
}
}
let mut references = BTreeMap::new();
for kind in ReferenceKind::all() {
let dir = root.join(kind.as_str());
if !dir.is_dir() {
continue;
}
for entry in std::fs::read_dir(&dir).map_err(|e| LoadError::Io(dir.clone(), e))? {
let entry = entry.map_err(|e| LoadError::Io(dir.clone(), e))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("md") {
continue;
}
let raw = std::fs::read_to_string(&path)
.map_err(|e| LoadError::Io(path.clone(), e))?;
let refr = parse_reference(&raw, &path, *kind)?;
let key = (refr.kind, refr.slug.clone());
if references.insert(key, refr.clone()).is_some() {
return Err(LoadError::DuplicateName(
format!("{}/{}", refr.kind.as_str(), refr.slug),
path,
));
}
}
}
let mut templates = BTreeMap::new();
let tpl_dir = root.join("templates");
if tpl_dir.is_dir() {
for entry in std::fs::read_dir(&tpl_dir).map_err(|e| LoadError::Io(tpl_dir.clone(), e))? {
let entry = entry.map_err(|e| LoadError::Io(tpl_dir.clone(), e))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("axon") {
continue;
}
let slug = match path.file_stem().and_then(|s| s.to_str()) {
Some(s) => s.to_string(),
None => continue,
};
let source = std::fs::read_to_string(&path)
.map_err(|e| LoadError::Io(path.clone(), e))?;
let tpl = Template { slug: slug.clone(), source };
if templates.insert(slug.clone(), tpl).is_some() {
return Err(LoadError::DuplicateName(slug, path));
}
}
}
let mut prompts = BTreeMap::new();
let pr_dir = root.join("prompts");
if pr_dir.is_dir() {
for entry in std::fs::read_dir(&pr_dir).map_err(|e| LoadError::Io(pr_dir.clone(), e))? {
let entry = entry.map_err(|e| LoadError::Io(pr_dir.clone(), e))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("md") {
continue;
}
let raw = std::fs::read_to_string(&path)
.map_err(|e| LoadError::Io(path.clone(), e))?;
let p = parse_prompt(&raw, &path)?;
if prompts.insert(p.name.clone(), p.clone()).is_some() {
return Err(LoadError::DuplicateName(p.name, path));
}
}
}
let mut examples = BTreeMap::new();
let ex_dir = root.join("examples");
if ex_dir.is_dir() {
for entry in std::fs::read_dir(&ex_dir).map_err(|e| LoadError::Io(ex_dir.clone(), e))? {
let entry = entry.map_err(|e| LoadError::Io(ex_dir.clone(), e))?;
let path = entry.path();
if path.extension().and_then(|s| s.to_str()) != Some("md") {
continue;
}
let raw = std::fs::read_to_string(&path)
.map_err(|e| LoadError::Io(path.clone(), e))?;
let ex = parse_example(&raw, &path)?;
if examples.insert(ex.name.clone(), ex.clone()).is_some() {
return Err(LoadError::DuplicateName(ex.name, path));
}
}
}
Ok(Catalog { primitives, references, templates, prompts, examples })
}
pub fn empty_for_tests() -> Self {
Catalog::default()
}
pub fn primitive_count(&self) -> usize {
self.primitives.len()
}
pub fn primitive(&self, name: &str) -> Option<&Primitive> {
self.primitives.get(name)
}
pub fn primitives(&self) -> impl Iterator<Item = &Primitive> {
self.primitives.values()
}
pub fn reference_count(&self) -> usize {
self.references.len()
}
pub fn reference_count_of(&self, kind: ReferenceKind) -> usize {
self.references.keys().filter(|(k, _)| *k == kind).count()
}
pub fn reference(&self, kind: ReferenceKind, slug: &str) -> Option<&Reference> {
self.references.get(&(kind, slug.to_string()))
}
pub fn references(&self) -> impl Iterator<Item = &Reference> {
self.references.values()
}
pub fn references_of(&self, kind: ReferenceKind) -> impl Iterator<Item = &Reference> {
self.references
.iter()
.filter(move |((k, _), _)| *k == kind)
.map(|(_, v)| v)
}
pub fn template_count(&self) -> usize {
self.templates.len()
}
pub fn template(&self, slug: &str) -> Option<&Template> {
self.templates.get(slug)
}
pub fn templates(&self) -> impl Iterator<Item = &Template> {
self.templates.values()
}
pub fn prompt_count(&self) -> usize {
self.prompts.len()
}
pub fn prompt(&self, name: &str) -> Option<&Prompt> {
self.prompts.get(name)
}
pub fn prompts(&self) -> impl Iterator<Item = &Prompt> {
self.prompts.values()
}
pub fn example_count(&self) -> usize {
self.examples.len()
}
pub fn example(&self, name: &str) -> Option<&Example> {
self.examples.get(name)
}
pub fn examples(&self) -> impl Iterator<Item = &Example> {
self.examples.values()
}
pub fn examples_of(&self, topic: ExampleTopic) -> impl Iterator<Item = &Example> {
self.examples.values().filter(move |e| e.topic == topic)
}
pub fn examples_using<'a>(&'a self, primitive: &'a str) -> impl Iterator<Item = &'a Example> + 'a {
self.examples
.values()
.filter(move |e| e.primitives.iter().any(|p| p == primitive))
}
}
fn is_markdown(p: &Path) -> bool {
p.extension()
.and_then(|s| s.to_str())
.map(|s| s.eq_ignore_ascii_case("md"))
.unwrap_or(false)
}
fn is_axon(p: &Path) -> bool {
p.extension()
.and_then(|s| s.to_str())
.map(|s| s.eq_ignore_ascii_case("axon"))
.unwrap_or(false)
}
#[derive(Debug)]
pub enum LoadError {
MissingDir(PathBuf),
Io(PathBuf, std::io::Error),
BadFrontmatter(PathBuf, String),
NoFrontmatter(PathBuf),
DuplicateName(String, PathBuf),
}
impl std::fmt::Display for LoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LoadError::MissingDir(p) => write!(f, "expected directory not found: {}", p.display()),
LoadError::Io(p, e) => write!(f, "I/O error reading {}: {}", p.display(), e),
LoadError::BadFrontmatter(p, msg) => {
write!(f, "frontmatter error in {}: {}", p.display(), msg)
}
LoadError::NoFrontmatter(p) => {
write!(f, "missing YAML frontmatter block in {}", p.display())
}
LoadError::DuplicateName(n, p) => {
write!(f, "primitive `{n}` already exists (second definition at {})", p.display())
}
}
}
}
impl std::error::Error for LoadError {}
fn parse_primitive(raw: &str, path: &Path) -> Result<Primitive, LoadError> {
let matter = Matter::<YAML>::new();
let parsed = matter.parse(raw);
let fm: Frontmatter = parsed
.data
.ok_or_else(|| LoadError::NoFrontmatter(path.to_path_buf()))?
.deserialize()
.map_err(|e| LoadError::BadFrontmatter(path.to_path_buf(), e.to_string()))?;
Ok(Primitive {
name: fm.name,
summary: fm.summary,
category: fm.category,
top_level: fm.top_level,
grammar: fm.grammar,
since: fm.since,
body: parsed.content,
})
}
fn parse_prompt(raw: &str, path: &Path) -> Result<Prompt, LoadError> {
let matter = Matter::<YAML>::new();
let parsed = matter.parse(raw);
let fm: PromptFrontmatter = parsed
.data
.ok_or_else(|| LoadError::NoFrontmatter(path.to_path_buf()))?
.deserialize()
.map_err(|e| LoadError::BadFrontmatter(path.to_path_buf(), e.to_string()))?;
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
if !stem.is_empty() && fm.name != stem {
return Err(LoadError::BadFrontmatter(
path.to_path_buf(),
format!(
"frontmatter name `{}` does not match file stem `{}` \
— the prompt slug must equal the filename",
fm.name, stem
),
));
}
Ok(Prompt {
name: fm.name,
title: fm.title,
summary: fm.summary,
arguments: fm.arguments,
body: parsed.content,
})
}
fn parse_example(raw: &str, path: &Path) -> Result<Example, LoadError> {
let matter = Matter::<YAML>::new();
let parsed = matter.parse(raw);
let fm: ExampleFrontmatter = parsed
.data
.ok_or_else(|| LoadError::NoFrontmatter(path.to_path_buf()))?
.deserialize()
.map_err(|e| LoadError::BadFrontmatter(path.to_path_buf(), e.to_string()))?;
let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
if !stem.is_empty() && fm.name != stem {
return Err(LoadError::BadFrontmatter(
path.to_path_buf(),
format!(
"frontmatter name `{}` does not match file stem `{}` \
— the example slug must equal the filename",
fm.name, stem
),
));
}
let source = parsed.content.trim_start().to_string();
Ok(Example {
name: fm.name,
title: fm.title,
summary: fm.summary,
topic: fm.topic,
primitives: fm.primitives,
source,
})
}
fn parse_reference(
raw: &str,
path: &Path,
expected_kind: ReferenceKind,
) -> Result<Reference, LoadError> {
let matter = Matter::<YAML>::new();
let parsed = matter.parse(raw);
let fm: ReferenceFrontmatter = parsed
.data
.ok_or_else(|| LoadError::NoFrontmatter(path.to_path_buf()))?
.deserialize()
.map_err(|e| LoadError::BadFrontmatter(path.to_path_buf(), e.to_string()))?;
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("");
if !stem.is_empty() && fm.name != stem {
return Err(LoadError::BadFrontmatter(
path.to_path_buf(),
format!(
"frontmatter name `{}` does not match file stem `{}` \
— the URL slug must equal the filename",
fm.name, stem
),
));
}
Ok(Reference {
kind: expected_kind,
slug: fm.name,
summary: fm.summary,
title: fm.title,
body: parsed.content,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::io::Write;
fn write_primitive(dir: &Path, name: &str, content: &str) {
let path = dir.join(format!("{name}.md"));
let mut f = fs::File::create(path).unwrap();
f.write_all(content.as_bytes()).unwrap();
}
#[test]
fn loader_parses_a_well_formed_primitive() {
let tmp = tempdir();
let prim_dir = tmp.join("primitives");
fs::create_dir_all(&prim_dir).unwrap();
write_primitive(
&prim_dir,
"socket",
r#"---
name: socket
summary: typed WebSocket transport (Fase 41)
category: session_types
top_level: true
grammar: |
socket Name { protocol: SessionRef, ... }
since: Fase 41.b
---
# `socket`
Body prose.
"#,
);
let cat = Catalog::load_from(&tmp).unwrap();
assert_eq!(cat.primitive_count(), 1);
let p = cat.primitive("socket").unwrap();
assert_eq!(p.summary, "typed WebSocket transport (Fase 41)");
assert!(p.top_level);
assert_eq!(p.category, Category::SessionTypes);
assert!(p.body.contains("Body prose"));
}
#[test]
fn loader_rejects_missing_frontmatter() {
let tmp = tempdir();
let prim_dir = tmp.join("primitives");
fs::create_dir_all(&prim_dir).unwrap();
write_primitive(&prim_dir, "bad", "# no frontmatter\n");
let err = Catalog::load_from(&tmp).expect_err("must reject");
assert!(matches!(err, LoadError::NoFrontmatter(_)));
}
#[test]
fn loader_rejects_duplicate_names() {
let tmp = tempdir();
let prim_dir = tmp.join("primitives");
fs::create_dir_all(&prim_dir).unwrap();
let body = |n| {
format!(
"---\nname: {n}\nsummary: x\ncategory: cognition\ntop_level: true\n---\n"
)
};
write_primitive(&prim_dir, "a", &body("dup"));
write_primitive(&prim_dir, "b", &body("dup"));
let err = Catalog::load_from(&tmp).expect_err("dup name must fail");
assert!(matches!(err, LoadError::DuplicateName(name, _) if name == "dup"));
}
#[test]
fn embedded_corpus_loads_and_contains_at_least_the_socket_primitive() {
let cat = Catalog::load_embedded().expect("embedded corpus must load");
assert!(
cat.primitive_count() >= 1,
"embedded corpus is empty — include_dir! resolved no files"
);
let socket = cat
.primitive("socket")
.expect("Phase 0 ships the socket primitive — must be embedded");
assert!(socket.top_level);
assert_eq!(socket.category, Category::SessionTypes);
assert!(socket.body.contains("socket"));
assert!(socket.body.contains("WebSocket"));
}
#[test]
fn registry_and_corpus_coverage_is_closed_on_documented_entries() {
use axon_frontend::{DocStatus, PRIMITIVE_REGISTRY};
use std::collections::HashSet;
let cat = Catalog::load_embedded().expect("embedded corpus must load");
let documented: Vec<&'static str> = PRIMITIVE_REGISTRY
.iter()
.filter(|i| i.doc_status == DocStatus::Documented)
.map(|i| i.name)
.collect();
let missing_docs: Vec<&'static str> = documented
.iter()
.copied()
.filter(|name| cat.primitive(name).is_none())
.collect();
let documented_set: HashSet<&'static str> = documented.iter().copied().collect();
let orphan_docs: Vec<String> = cat
.primitives()
.filter(|p| !documented_set.contains(p.name.as_str()))
.map(|p| p.name.clone())
.collect();
if !missing_docs.is_empty() || !orphan_docs.is_empty() {
let summary = axon_frontend::coverage_summary();
panic!(
"§Fase 6.a coverage gate failed:\n\
\n\
missing docs (in registry as Documented, no .md):\n \
{missing_docs:?}\n\
orphan docs (.md but not in registry as Documented):\n \
{orphan_docs:?}\n\
\n\
catalog summary: {} total, {} documented, {} pending.\n\
\n\
Fix:\n \
- For each missing doc, run: \
`axon-emcp scaffold-primitive <name>` + fill in the body.\n \
- For each orphan doc, either add the entry to \
axon_frontend::PRIMITIVE_REGISTRY as DocStatus::Documented, \
or delete the .md if the primitive was removed.",
summary.total, summary.documented, summary.pending,
);
}
for info in PRIMITIVE_REGISTRY {
let parsed: Result<Category, _> = serde_json::from_value(
serde_json::Value::String(info.category.to_string()),
);
assert!(
parsed.is_ok(),
"primitive `{}` registry category `{}` does not deserialise \
into axon-emcp's Category enum — closed-catalog drift",
info.name, info.category
);
}
}
#[test]
fn registry_coverage_summary_is_internally_consistent() {
let s = axon_frontend::coverage_summary();
assert_eq!(s.total, s.documented + s.pending);
assert_eq!(s.total, 45);
}
#[test]
fn embedded_corpus_contains_every_phase_5_prompt() {
let cat = Catalog::load_embedded().expect("embedded corpus must load");
let expected = ["flow_design", "shield_design", "session_design"];
for name in expected {
let p = cat
.prompt(name)
.unwrap_or_else(|| panic!("prompt `{name}` must be embedded"));
assert_eq!(p.name, name, "{name}: slug drift");
assert!(!p.title.is_empty(), "{name}: title empty");
assert!(!p.summary.is_empty(), "{name}: summary empty");
assert!(!p.body.is_empty(), "{name}: body empty");
assert!(
!p.arguments.is_empty(),
"{name}: at least one argument must be declared so the host can render a picker"
);
for arg in &p.arguments {
assert!(!arg.name.is_empty(), "{name}: empty argument name");
assert!(!arg.description.is_empty(), "{name}: empty argument description for `{}`", arg.name);
}
}
assert_eq!(
cat.prompt_count(),
expected.len(),
"prompt count drift — add the new prompt to the expected list"
);
}
#[test]
fn embedded_corpus_contains_every_phase_4_template() {
let cat = Catalog::load_embedded().expect("embedded corpus must load");
let expected = [
"generic", "healthcare", "banking", "government",
"legal", "chat", "retrieval", "multi_agent",
"legaltech", "fintech", "pharmatech", "medic_research",
"chat_research", "chat_tools", "chat_skills", "whatsapp",
"voice", "dev", "sales_consultive", "sales_widget",
"workflow_automation", "business_intelligence",
"corporate_integration", "self_learning",
"document_analysis", "ticket_triage",
"content_moderation", "knowledge_extraction",
"compliance_monitoring", "recruitment",
"education", "financial_advisor", "data_pipeline",
];
for slug in expected {
let t = cat
.template(slug)
.unwrap_or_else(|| panic!("template `{slug}` must be embedded"));
assert_eq!(t.slug, slug);
assert!(
!t.source.is_empty(),
"template `{slug}`: empty source — compose would return nothing"
);
assert!(
t.source.contains("flow "),
"template `{slug}`: missing `flow` declaration"
);
}
assert_eq!(
cat.template_count(),
expected.len(),
"template count drift — add the new template to the expected list"
);
}
#[test]
fn embedded_corpus_contains_every_phase_3_reference_doc() {
let cat = Catalog::load_embedded().expect("embedded corpus must load");
let expected: &[(ReferenceKind, &str)] = &[
(ReferenceKind::Grammar, "top_level"),
(ReferenceKind::Grammar, "composition"),
(ReferenceKind::Grammar, "ebnf"),
(ReferenceKind::Logic, "flow_composition"),
(ReferenceKind::Logic, "session_duality"),
(ReferenceKind::Compliance, "hipaa"),
(ReferenceKind::Compliance, "gdpr"),
(ReferenceKind::Compliance, "pci_dss"),
(ReferenceKind::Compliance, "sox"),
(ReferenceKind::Compliance, "soc2"),
(ReferenceKind::Compliance, "fedramp"),
(ReferenceKind::Compliance, "gxp"),
(ReferenceKind::Compliance, "fisma"),
(ReferenceKind::Compliance, "nist_800_53"),
];
for (kind, slug) in expected {
let r = cat
.reference(*kind, slug)
.unwrap_or_else(|| panic!("{}/{slug} must be embedded", kind.as_str()));
assert_eq!(r.kind, *kind, "{}/{slug}: kind drift", kind.as_str());
assert_eq!(&r.slug, slug, "{}/{slug}: slug drift", kind.as_str());
assert!(
!r.title.is_empty(),
"{}/{slug}: title empty — resources/list label would be unhelpful",
kind.as_str()
);
assert!(
!r.summary.is_empty(),
"{}/{slug}: summary empty — discovery would have no description",
kind.as_str()
);
assert!(
!r.body.is_empty(),
"{}/{slug}: body empty — resources/read would return nothing",
kind.as_str()
);
}
assert_eq!(
cat.reference_count_of(ReferenceKind::Grammar),
3,
"grammar family count drift"
);
assert_eq!(
cat.reference_count_of(ReferenceKind::Logic),
2,
"logic family count drift"
);
assert_eq!(
cat.reference_count_of(ReferenceKind::Compliance),
9,
"compliance family count drift"
);
}
#[test]
fn embedded_corpus_contains_the_six_core_cognitive_primitives() {
let cat = Catalog::load_embedded().expect("embedded corpus must load");
let expected: &[(&str, Category, bool)] = &[
("persona", Category::Cognition, true),
("flow", Category::Cognition, true),
("step", Category::Cognition, false),
("anchor", Category::Cognition, true),
("tool", Category::Cognition, true),
("reason", Category::Cognition, false),
];
for (name, category, top_level) in expected {
let p = cat
.primitive(name)
.unwrap_or_else(|| panic!("`{name}` must be embedded after Phase 2"));
assert_eq!(
p.category, *category,
"{name}: category drift (catalogue says {:?}, expected {category:?})",
p.category
);
assert_eq!(
p.top_level, *top_level,
"{name}: top_level drift (catalogue says {}, expected {top_level})",
p.top_level
);
assert!(
!p.summary.is_empty(),
"{name}: summary is empty — agent listings would be unhelpful"
);
assert!(
!p.body.is_empty(),
"{name}: body is empty — `axon.primitive_doc({name})` would return nothing"
);
let opener = format!("# `{name}`");
assert!(
p.body.contains(&opener),
"{name}: body must open with `{opener}` — missing canonical header"
);
}
}
fn tempdir() -> PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
let n = N.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"axon-emcp-test-{}-{n}",
std::process::id()
));
fs::create_dir_all(&path).unwrap();
path
}
}