use std::collections::BTreeMap;
use rto_graph::{Edge, EdgeKind, FactSet, Node, NodeKind, Provenance};
use yaml_rust2::Yaml;
use super::{Actor, INDEX_FILE, LOG_FILE, Origin, section_for, short_digest, slug};
pub const OKF_REF_PREFIX: &str = "import:okf/";
pub const OKF_KEY_PREFIX: &str = "okf:";
#[must_use]
pub fn import_ref(peer: &str) -> String {
format!("{OKF_REF_PREFIX}{peer}")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Trust {
Trust,
Acknowledge,
}
impl Trust {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Trust => "trust",
Self::Acknowledge => "acknowledge",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SkipReason {
NoFrontmatter,
UnterminatedFrontmatter,
UnparsableFrontmatter,
MissingType,
}
impl SkipReason {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::NoFrontmatter => "no YAML frontmatter block",
Self::UnterminatedFrontmatter => "frontmatter block is never closed",
Self::UnparsableFrontmatter => "frontmatter block is not parseable YAML",
Self::MissingType => "no non-empty `type` (OKF's one required key)",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Skipped {
pub path: String,
pub reason: SkipReason,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct OkfReport {
pub okf_version: Option<String>,
pub files_total: usize,
pub reserved_skipped: usize,
pub concepts_read: usize,
pub concepts_by_type: BTreeMap<String, usize>,
pub concepts_by_provenance: BTreeMap<String, usize>,
pub skipped: Vec<SkippedRow>,
pub links_total: usize,
pub edges_read: usize,
pub links_reciprocal: usize,
pub links_unresolved: usize,
pub links_outside_relationships: usize,
pub extrefs_filled: Vec<(String, String)>,
pub extrefs_ambiguous: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub struct SkippedRow {
pub path: String,
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct OkfImport {
pub facts: FactSet,
pub report: OkfReport,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OkfError {
#[error("no markdown files under {0}: an OKF bundle is a directory of concept documents")]
Empty(String),
#[error(
"{path} holds {files} markdown file(s) and no readable concept among them, so it is \
not an OKF bundle. First failures: {detail}"
)]
NoConcepts {
path: String,
files: usize,
detail: String,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct ParsedFrontmatter {
type_: String,
title: Option<String>,
description: Option<String>,
resource: Option<String>,
status: Option<String>,
tags: Vec<String>,
sources: Vec<String>,
generated: Option<(String, String)>,
verified: Vec<(String, String)>,
}
impl ParsedFrontmatter {
fn claimed_tier(&self) -> Provenance {
match self.verified.first() {
None => Provenance::Inferred,
Some((by, _)) if by.starts_with("human:") => Provenance::Authored,
Some(_) => Provenance::Derived,
}
}
fn effective_origin(&self, trust: Trust) -> Option<Origin> {
let confirmed = self.verified.first();
let (by, at) = confirmed.or(self.generated.as_ref())?;
Some(Origin {
by: parse_actor(by),
at: at.clone(),
confirms: confirmed.is_some() && trust == Trust::Trust,
})
}
}
fn parse_actor(token: &str) -> Actor {
if let Some(id) = token.strip_prefix("human:") {
return Actor::Human(id.to_owned());
}
if let Some(id) = token.strip_prefix("process:") {
return Actor::Process(id.to_owned());
}
match token.split_once('/') {
Some((producer, version)) => Actor::Tool(producer.to_owned(), version.to_owned()),
None => Actor::Process(token.to_owned()),
}
}
#[must_use]
pub fn peer_origin(meta: &serde_json::Value) -> Option<Origin> {
let origin = meta.get("okf")?.get("origin")?;
Some(Origin {
by: parse_actor(origin.get("by")?.as_str()?),
at: origin.get("at")?.as_str()?.to_owned(),
confirms: origin
.get("confirms")
.and_then(serde_json::Value::as_bool)
.unwrap_or(false),
})
}
fn split_frontmatter(text: &str) -> Result<(&str, &str), SkipReason> {
let rest = text
.strip_prefix("---\n")
.or_else(|| text.strip_prefix("---\r\n"))
.ok_or(SkipReason::NoFrontmatter)?;
let mut offset = 0usize;
for line in rest.split_inclusive('\n') {
if line.trim_end_matches(['\r', '\n']) == "---" {
let body = rest[offset + line.len()..].trim_start_matches(['\r', '\n']);
return Ok((&rest[..offset], body));
}
offset += line.len();
}
Err(SkipReason::UnterminatedFrontmatter)
}
fn parse_frontmatter(block: &str) -> Result<ParsedFrontmatter, SkipReason> {
let mut fm = ParsedFrontmatter::default();
let docs = yaml_rust2::YamlLoader::load_from_str(block)
.map_err(|_| SkipReason::UnparsableFrontmatter)?;
let Some(first) = docs.first() else {
return Ok(fm);
};
let Some(map) = first.as_hash() else {
return Ok(fm);
};
let get = |key: &str| map.get(&Yaml::String(key.to_owned()));
if let Some(v) = get("type").and_then(scalar_text) {
fm.type_ = v;
}
fm.title = get("title").and_then(scalar_text);
fm.description = get("description").and_then(scalar_text);
fm.resource = get("resource").and_then(scalar_text);
fm.status = get("status").and_then(scalar_text);
if let Some(tags) = get("tags") {
match tags {
Yaml::Array(items) => fm.tags.extend(items.iter().filter_map(scalar_text)),
other => fm.tags.extend(scalar_text(other)),
}
}
match get("sources") {
Some(Yaml::Array(items)) => {
for item in items {
fm.sources.extend(source_resource(item));
}
}
Some(single @ Yaml::Hash(_)) => fm.sources.extend(source_resource(single)),
_ => {}
}
fm.generated = get("generated").and_then(by_at);
fm.verified = get("verified").map(verified_entries).unwrap_or_default();
Ok(fm)
}
fn source_resource(entry: &Yaml) -> Option<String> {
entry
.as_hash()?
.get(&Yaml::String("resource".to_owned()))
.and_then(scalar_text)
.filter(|r| !r.trim().is_empty())
}
fn scalar_text(v: &Yaml) -> Option<String> {
match v {
Yaml::String(s) | Yaml::Real(s) => Some(s.clone()),
Yaml::Integer(i) => Some(i.to_string()),
Yaml::Boolean(b) => Some(b.to_string()),
_ => None,
}
}
fn by_at(node: &Yaml) -> Option<(String, String)> {
let map = node.as_hash()?;
let by = map
.get(&Yaml::String("by".to_owned()))
.and_then(scalar_text)?;
if by.trim().is_empty() {
return None;
}
let at = map
.get(&Yaml::String("at".to_owned()))
.and_then(scalar_text)
.unwrap_or_default();
Some((by, at))
}
fn verified_entries(node: &Yaml) -> Vec<(String, String)> {
match node {
Yaml::Array(items) => items.iter().filter_map(by_at).collect(),
other => by_at(other).into_iter().collect(),
}
}
struct RelLink {
kind: String,
target: String,
reciprocal: bool,
}
fn parse_relationships(body: &str) -> (Vec<RelLink>, usize) {
let mut links = Vec::new();
let mut outside = 0usize;
let mut in_section = false;
let mut kind = EdgeKind::Related.as_str().to_owned();
for line in body.lines() {
let trimmed = line.trim();
if let Some(heading) = trimmed.strip_prefix("## ") {
in_section = heading.trim().eq_ignore_ascii_case("relationships");
EdgeKind::Related.as_str().clone_into(&mut kind);
continue;
}
if trimmed.starts_with("# ") {
in_section = false;
continue;
}
if let Some(heading) = trimmed.strip_prefix("### ")
&& in_section
{
heading.trim().clone_into(&mut kind);
continue;
}
for target in markdown_link_targets(trimmed) {
if in_section {
links.push(RelLink {
kind: kind.clone(),
reciprocal: trimmed.contains('\u{2190}'),
target,
});
} else {
outside += 1;
}
}
}
(links, outside)
}
fn markdown_link_targets(line: &str) -> Vec<String> {
let mut out = Vec::new();
let bytes = line.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] != b'[' {
i += 1;
continue;
}
let Some(close) = line[i..].find("](") else {
break;
};
let after = i + close + 2;
let Some(end) = line[after..].find(')') else {
break;
};
let target = line[after..after + end].trim();
if !target.is_empty() {
out.push(target.to_owned());
}
i = after + end + 1;
}
out
}
fn resolve_target(from: &str, target: &str) -> Option<String> {
if target.contains("://") || target.starts_with('#') {
return None;
}
let target = target.split('#').next().unwrap_or(target);
if target.is_empty() {
return None;
}
if target.starts_with('/') {
return Some(normalise(target));
}
let dir = from.rsplit_once('/').map_or("", |(d, _)| d);
Some(normalise(&format!("{dir}/{target}")))
}
fn normalise(path: &str) -> String {
let mut parts: Vec<&str> = Vec::new();
for seg in path.split('/') {
match seg {
"" | "." => {}
".." => {
parts.pop();
}
other => parts.push(other),
}
}
format!("/{}", parts.join("/"))
}
fn bundle_path(raw: &str) -> String {
normalise(&raw.replace('\\', "/"))
}
fn is_reserved(path: &str) -> bool {
let name = path.rsplit('/').next().unwrap_or(path);
name == INDEX_FILE || name == LOG_FILE
}
struct Concept {
path: String,
fm: ParsedFrontmatter,
body: String,
links: Vec<RelLink>,
}
pub struct ReadOptions<'a> {
pub trust: Trust,
pub peer: &'a str,
pub extref_keys: &'a [String],
}
pub fn read_bundle(
root: &str,
files: &[(String, String)],
opts: &ReadOptions<'_>,
) -> Result<OkfImport, OkfError> {
let mut report = OkfReport {
files_total: files.len(),
..OkfReport::default()
};
if files.is_empty() {
return Err(OkfError::Empty(root.to_owned()));
}
let (concepts, skipped) = collect_concepts(files, &mut report);
if concepts.is_empty() {
let considered = files.len() - report.reserved_skipped;
if considered == 0 {
return Err(OkfError::Empty(root.to_owned()));
}
let detail = skipped
.iter()
.take(3)
.map(|s| format!("{} ({})", s.path, s.reason.as_str()))
.collect::<Vec<_>>()
.join("; ");
return Err(OkfError::NoConcepts {
path: root.to_owned(),
files: considered,
detail,
});
}
report.skipped = skipped
.into_iter()
.map(|s| SkippedRow {
path: s.path,
reason: s.reason.as_str().to_owned(),
})
.collect();
let (stub_for, ambiguous) = extref_fills(&concepts, opts.extref_keys);
report.extrefs_ambiguous = ambiguous;
let keys: BTreeMap<&str, String> = concepts
.iter()
.map(|c| {
let key = stub_for.get(c.path.as_str()).cloned().unwrap_or_else(|| {
format!(
"{OKF_KEY_PREFIX}{peer}{path}",
peer = opts.peer,
path = c.path
)
});
(c.path.as_str(), key)
})
.collect();
for (path, key) in &stub_for {
report
.extrefs_filled
.push((key.clone(), (*path).to_owned()));
}
report.extrefs_filled.sort();
let src_ref = import_ref(opts.peer);
let mut facts = FactSet::new();
for c in &concepts {
push_concept(c, opts, &src_ref, &keys, &stub_for, &mut facts, &mut report);
}
Ok(OkfImport { facts, report })
}
fn collect_concepts(
files: &[(String, String)],
report: &mut OkfReport,
) -> (Vec<Concept>, Vec<Skipped>) {
let mut concepts: Vec<Concept> = Vec::new();
let mut skipped: Vec<Skipped> = Vec::new();
for (raw_path, content) in files {
let path = bundle_path(raw_path);
if is_reserved(&path) {
report.reserved_skipped += 1;
if path == format!("/{INDEX_FILE}") {
report.okf_version = root_okf_version(content);
}
continue;
}
match split_frontmatter(content) {
Err(reason) => skipped.push(Skipped { path, reason }),
Ok((block, body)) => {
let fm = match parse_frontmatter(block) {
Ok(fm) => fm,
Err(reason) => {
skipped.push(Skipped { path, reason });
continue;
}
};
if fm.type_.trim().is_empty() {
skipped.push(Skipped {
path,
reason: SkipReason::MissingType,
});
continue;
}
let (links, outside) = parse_relationships(body);
report.links_outside_relationships += outside;
concepts.push(Concept {
path,
fm,
body: body.to_owned(),
links,
});
}
}
}
concepts.sort_by(|a, b| a.path.cmp(&b.path));
skipped.sort_by(|a, b| a.path.cmp(&b.path));
(concepts, skipped)
}
fn push_concept(
c: &Concept,
opts: &ReadOptions<'_>,
src_ref: &str,
keys: &BTreeMap<&str, String>,
stub_for: &BTreeMap<&str, String>,
facts: &mut FactSet,
report: &mut OkfReport,
) {
let key = &keys[c.path.as_str()];
let provenance = match opts.trust {
Trust::Trust => c.fm.claimed_tier().externalise(),
Trust::Acknowledge => Provenance::ExternalInferred,
};
let name =
c.fm.title
.clone()
.filter(|t| !t.trim().is_empty())
.unwrap_or_else(|| {
c.path
.rsplit('/')
.next()
.unwrap_or(&c.path)
.trim_end_matches(".md")
.to_owned()
});
let mut node =
Node::new(key.clone(), NodeKind::from_token(&c.fm.type_), name).with_provenance(provenance);
node.meta = concept_meta(
c,
opts,
src_ref,
stub_for.get(c.path.as_str()).map(String::as_str),
);
facts.nodes.push(node);
*report
.concepts_by_type
.entry(c.fm.type_.clone())
.or_default() += 1;
*report
.concepts_by_provenance
.entry(provenance.as_str().to_owned())
.or_default() += 1;
report.concepts_read += 1;
for link in &c.links {
report.links_total += 1;
if link.reciprocal {
report.links_reciprocal += 1;
continue;
}
let target =
resolve_target(&c.path, &link.target).and_then(|t| keys.get(t.as_str()).cloned());
let Some(dst) = target else {
report.links_unresolved += 1;
continue;
};
let mut edge = Edge::derived(key.clone(), dst, EdgeKind::from_token(&link.kind));
edge.provenance = provenance;
edge.src_ref = Some(src_ref.to_owned());
facts.edges.push(edge);
report.edges_read += 1;
}
}
fn root_okf_version(content: &str) -> Option<String> {
let (block, _) = split_frontmatter(content).ok()?;
yaml_rust2::YamlLoader::load_from_str(block)
.ok()?
.first()?
.as_hash()?
.get(&Yaml::String("okf_version".to_owned()))
.and_then(scalar_text)
.map(|v| v.trim().to_owned())
}
fn concept_meta(
c: &Concept,
opts: &ReadOptions<'_>,
src_ref: &str,
fills: Option<&str>,
) -> serde_json::Value {
let mut meta = serde_json::json!({
"okf": {
"source": src_ref,
"peer": opts.peer,
"path": c.path,
"type": c.fm.type_,
"trust": opts.trust.as_str(),
"claimed": {
"tier": c.fm.claimed_tier().as_str(),
"verified": !c.fm.verified.is_empty(),
},
"resource": c.fm.resource,
"status": c.fm.status,
"tags": c.fm.tags,
"sources": c.fm.sources,
},
});
if let Some(origin) = c.fm.effective_origin(opts.trust) {
meta["okf"]["origin"] = serde_json::json!({
"by": origin.by.as_token(),
"at": origin.at,
"confirms": origin.confirms,
});
}
if let Some(desc) = &c.fm.description {
meta["okf"]["description"] = serde_json::Value::from(desc.clone());
}
let content = rto_graph::cap_content(&c.body);
if !content.is_empty() {
meta["content"] = serde_json::Value::from(content);
}
if let Some(qualified) = fills.and_then(|stub| stub.strip_prefix("extref:")) {
meta["qualified"] = serde_json::Value::from(qualified);
}
meta
}
fn extref_fills<'a>(
concepts: &'a [Concept],
extref_keys: &[String],
) -> (BTreeMap<&'a str, String>, Vec<String>) {
let mut by_stub: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
let mut by_path: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
for stub in extref_keys {
let Some(qualified) = stub.strip_prefix("extref:") else {
continue;
};
let Some((_project, bare)) = rto_graph::parse_qualified(qualified) else {
continue;
};
let bare_slug = slug(bare);
let with_digest = format!("{bare_slug}-{}", short_digest(bare));
for c in concepts {
let (dir, file) = c.path.rsplit_once('/').unwrap_or(("", &c.path));
let name = file.trim_end_matches(".md");
let section = dir.rsplit('/').next().unwrap_or("");
if section != section_for(&c.fm.type_) {
continue;
}
if name == bare_slug || name == with_digest {
by_stub.entry(stub).or_default().push(&c.path);
by_path.entry(&c.path).or_default().push(stub);
}
}
}
let mut fills = BTreeMap::new();
let mut ambiguous: Vec<String> = Vec::new();
for (stub, paths) in &by_stub {
match paths.as_slice() {
[only] if by_path.get(*only).is_some_and(|s| s.len() == 1) => {
fills.insert(*only, (*stub).to_owned());
}
_ => ambiguous.push((*stub).to_owned()),
}
}
ambiguous.sort();
ambiguous.dedup();
(fills, ambiguous)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::okf::{Concept as RenderConcept, Frontmatter, OKF_VERSION, assemble, origin_for};
use rto_graph::{EdgeRef, Explanation, NodeSummary};
fn opts(trust: Trust, extref_keys: &[String]) -> ReadOptions<'_> {
ReadOptions {
trust,
peer: "acme",
extref_keys,
}
}
fn read(files: &[(&str, &str)], trust: Trust) -> OkfImport {
let owned: Vec<(String, String)> = files
.iter()
.map(|(p, c)| ((*p).to_owned(), (*c).to_owned()))
.collect();
read_bundle("okf/", &owned, &opts(trust, &[])).expect("read")
}
fn node_named<'a>(import: &'a OkfImport, key: &str) -> &'a rto_graph::Node {
import
.facts
.nodes
.iter()
.find(|n| n.key == key)
.unwrap_or_else(|| panic!("no node {key} in {:?}", keys(import)))
}
fn keys(import: &OkfImport) -> Vec<&str> {
import.facts.nodes.iter().map(|n| n.key.as_str()).collect()
}
fn summary(key: &str, kind: &str, name: &str) -> NodeSummary {
NodeSummary {
key: key.to_owned(),
kind: kind.to_owned(),
name: name.to_owned(),
path: None,
lang: None,
}
}
fn explanation(
key: &str,
kind: &str,
name: &str,
out: Vec<EdgeRef>,
inc: Vec<EdgeRef>,
) -> Explanation {
Explanation {
schema: rto_graph::SCHEMA,
node: summary(key, kind, name),
meta: serde_json::Value::Null,
outgoing: out,
incoming: inc,
}
}
fn edge_ref(to: &str) -> EdgeRef {
EdgeRef {
kind: "references".to_owned(),
provenance: "authored",
confidence: None,
node: to.to_owned(),
}
}
#[test]
fn a_roteiro_bundle_round_trips_at_the_external_tier() {
let at = "2026-09-01T10:00:00Z";
let tool = Actor::Tool("roteiro".to_owned(), "5.0.0".to_owned());
let alice = Actor::Human("alice".to_owned());
let adr = explanation(
"adr:0021",
"adr",
"OKF bundle",
vec![edge_ref("file:src/lib.rs")],
Vec::new(),
);
let file = explanation(
"file:src/lib.rs",
"file",
"lib.rs",
Vec::new(),
vec![edge_ref("adr:0021")],
);
let guess = explanation("sym:rust:src/lib.rs#f", "fn", "f", Vec::new(), Vec::new());
let rendered = assemble(
vec![
RenderConcept {
explanation: &adr,
frontmatter: Frontmatter {
type_: "adr".to_owned(),
title: Some("OKF bundle".to_owned()),
origin: Some(origin_for(Provenance::Authored, at, &tool, Some(&alice))),
..Frontmatter::default()
},
body: Some("The decision text.".to_owned()),
member: None,
},
RenderConcept {
explanation: &file,
frontmatter: Frontmatter {
type_: "file".to_owned(),
title: Some("lib.rs".to_owned()),
origin: Some(origin_for(Provenance::Derived, at, &tool, None)),
..Frontmatter::default()
},
body: None,
member: None,
},
RenderConcept {
explanation: &guess,
frontmatter: Frontmatter {
type_: "fn".to_owned(),
title: Some("f".to_owned()),
origin: Some(origin_for(Provenance::Inferred, at, &tool, None)),
..Frontmatter::default()
},
body: None,
member: None,
},
],
"acme",
&[],
);
let files: Vec<(String, String)> = rendered
.iter()
.map(|f| (f.path.clone(), f.content.clone()))
.collect();
let import = read_bundle("okf/", &files, &opts(Trust::Trust, &[])).expect("read");
assert_round_trip(&import, at);
}
fn assert_round_trip(import: &OkfImport, at: &str) {
assert_eq!(import.report.concepts_read, 3, "{:?}", keys(import));
assert_eq!(import.report.okf_version.as_deref(), Some(OKF_VERSION));
let by_prov: BTreeMap<&str, &str> = import
.facts
.nodes
.iter()
.map(|n| (n.key.as_str(), n.provenance.as_str()))
.collect();
assert_eq!(
by_prov,
BTreeMap::from([
("okf:acme/decisions/adr-0021.md", "external-authored"),
("okf:acme/files/file-src-lib-rs.md", "external-derived"),
(
"okf:acme/symbols/sym-rust-src-lib-rs-f.md",
"external-inferred"
),
]),
"a flat `External` would collapse these three into one"
);
assert_eq!(import.facts.edges.len(), 1);
let e = &import.facts.edges[0];
assert_eq!(e.src, "okf:acme/decisions/adr-0021.md");
assert_eq!(e.dst, "okf:acme/files/file-src-lib-rs.md");
assert_eq!(e.kind.as_str(), "references");
assert_eq!(e.provenance, Provenance::ExternalAuthored);
assert_eq!(
e.confidence, None,
"an imported edge carries no confidence this graph never computed"
);
assert!(e.is_valid(), "and must still satisfy the store's invariant");
assert_eq!(
import.report.links_reciprocal, 1,
"the `left-arrow` half of the same edge is skipped, not reversed"
);
let adr_node = node_named(import, "okf:acme/decisions/adr-0021.md");
assert_eq!(adr_node.name, "OKF bundle");
assert_eq!(adr_node.kind.as_str(), "adr");
assert!(
adr_node.meta["content"]
.as_str()
.expect("content")
.contains("The decision text."),
"{:?}",
adr_node.meta["content"]
);
assert_eq!(
peer_origin(&adr_node.meta),
Some(Origin {
by: Actor::Human("alice".to_owned()),
at: at.to_owned(),
confirms: true,
}),
"Alice's confirmation is re-emitted naming Alice, not re-tiered"
);
}
const AUTHORED: &str = "---\ntype: \"adr\"\ntitle: \"A decision\"\ngenerated:\n by: \"human:alice\"\n at: \"2026-09-01T10:00:00Z\"\nverified:\n - by: \"human:alice\"\n at: \"2026-09-01T10:00:00Z\"\n---\n\n# A decision\n\nBody.\n";
#[test]
fn trust_preserves_the_peers_tier_and_acknowledge_replaces_it() {
let trusted = read(&[("/decisions/a.md", AUTHORED)], Trust::Trust);
let node = &trusted.facts.nodes[0];
assert_eq!(node.provenance, Provenance::ExternalAuthored);
assert!(peer_origin(&node.meta).expect("origin").confirms);
let acked = read(&[("/decisions/a.md", AUTHORED)], Trust::Acknowledge);
let node = &acked.facts.nodes[0];
assert_eq!(
node.provenance,
Provenance::ExternalInferred,
"acknowledge takes their information without their confirmation"
);
assert_eq!(node.meta["okf"]["claimed"]["tier"], "authored");
assert_eq!(node.meta["okf"]["trust"], "acknowledge");
assert!(
!peer_origin(&node.meta).expect("origin").confirms,
"and re-rendering must not put the confirmation back"
);
}
#[test]
fn a_concept_with_no_verified_key_is_unverified_not_unknown() {
let doc = "---\ntype: \"doc\"\ngenerated:\n by: \"roteiro/5.0.0\"\n at: \"2026-09-01T00:00:00Z\"\n---\n\n# D\n";
let import = read(&[("/docs/d.md", doc)], Trust::Trust);
assert_eq!(
import.facts.nodes[0].provenance,
Provenance::ExternalInferred
);
}
#[test]
fn a_tool_verifier_is_machine_confirmed() {
let doc = "---\ntype: \"file\"\nverified:\n - by: \"roteiro/5.0.0\"\n at: \"2026-09-01T00:00:00Z\"\n---\n\n# F\n";
let import = read(&[("/files/f.md", doc)], Trust::Trust);
assert_eq!(
import.facts.nodes[0].provenance,
Provenance::ExternalDerived
);
}
#[test]
fn an_unrecognised_type_is_imported_as_an_other_kind() {
let doc = "---\ntype: \"dataset\"\ntitle: \"Sales\"\n---\n\n# Sales\n";
let import = read(&[("/things/s.md", doc)], Trust::Trust);
assert_eq!(
import.facts.nodes[0].kind,
NodeKind::Other("dataset".to_owned())
);
assert_eq!(import.report.concepts_by_type["dataset"], 1);
}
#[test]
fn a_partly_readable_bundle_reports_what_it_skipped() {
let import = read(
&[
("/decisions/good.md", AUTHORED),
("/decisions/plain.md", "# Just markdown\n"),
("/decisions/open.md", "---\ntype: \"adr\"\nnever closed\n"),
("/decisions/typeless.md", "---\ntitle: \"x\"\n---\n\nBody\n"),
("/decisions/broken.md", "---\ntype: [adr\n---\n\nBody\n"),
],
Trust::Trust,
);
assert_eq!(import.report.concepts_read, 1);
let rows: Vec<(&str, &str)> = import
.report
.skipped
.iter()
.map(|s| (s.path.as_str(), s.reason.as_str()))
.collect();
assert_eq!(
rows,
vec![
(
"/decisions/broken.md",
"frontmatter block is not parseable YAML"
),
("/decisions/open.md", "frontmatter block is never closed"),
("/decisions/plain.md", "no YAML frontmatter block"),
(
"/decisions/typeless.md",
"no non-empty `type` (OKF's one required key)"
),
],
"unparseable YAML and a missing `type` are separate reasons: both end \
with no type, but one means *add a key* and the other means *the \
block does not parse*"
);
}
#[test]
fn an_off_spec_shape_is_read_where_a_real_producer_writes_one() {
let bare_tags = "---\ntype: \"adr\"\ntags: stackoverflow, posts, deprecated\n---\n\nB\n";
let one_source =
"---\ntype: \"adr\"\nsources:\n resource: \"/tables/orders.md\"\n---\n\nB\n";
let scalar_source = "---\ntype: \"adr\"\nsources: \"/tables/orders.md\"\n---\n\nB\n";
let no_resource =
"---\ntype: \"adr\"\nsources:\n - id: \"x\"\n title: \"T\"\n---\n\nB\n";
let tags_of = |doc: &str| {
let (block, _) = split_frontmatter(doc).expect("split");
parse_frontmatter(block).expect("parse").tags
};
let sources_of = |doc: &str| {
let (block, _) = split_frontmatter(doc).expect("split");
parse_frontmatter(block).expect("parse").sources
};
assert_eq!(
tags_of(bare_tags),
vec!["stackoverflow, posts, deprecated".to_owned()],
"a bare `tags` string is kept verbatim as one tag: nothing is lost, \
and no comma convention is invented"
);
assert_eq!(
sources_of(one_source),
vec!["/tables/orders.md".to_owned()],
"a single `sources` entry written without the list dash is read, \
mirroring the shorthand §5.2 sanctions for `verified`"
);
assert_eq!(
sources_of(scalar_source),
Vec::<String>::new(),
"a scalar `sources` is not read: it cannot be told from a typo, and \
provenance is the one field where a guess is worse than silence"
);
assert_eq!(
sources_of(no_resource),
Vec::<String>::new(),
"§5.1 makes `resource` REQUIRED within an entry; an entry without \
one names nothing a consumer could follow"
);
}
#[test]
fn a_directory_with_no_readable_concept_is_refused_whole() {
let files = vec![
("okf/a.md".to_owned(), "# no frontmatter\n".to_owned()),
("okf/b.md".to_owned(), "plain text\n".to_owned()),
];
let err = read_bundle("okf/", &files, &opts(Trust::Trust, &[])).expect_err("refuse");
assert_eq!(
err.to_string(),
"okf/ holds 2 markdown file(s) and no readable concept among them, so it is not \
an OKF bundle. First failures: /okf/a.md (no YAML frontmatter block); \
/okf/b.md (no YAML frontmatter block)",
);
}
#[test]
fn an_empty_directory_is_refused_by_name() {
let err = read_bundle("okf/", &[], &opts(Trust::Trust, &[])).expect_err("refuse");
assert_eq!(
err.to_string(),
"no markdown files under okf/: an OKF bundle is a directory of concept documents",
);
}
#[test]
fn a_bundle_of_only_reserved_files_is_empty_rather_than_unreadable() {
let files = vec![
(
format!("/{INDEX_FILE}"),
format!("---\nokf_version: \"{OKF_VERSION}\"\n---\n\n# Index\n"),
),
(format!("/{LOG_FILE}"), "# Log\n".to_owned()),
];
let err = read_bundle("okf/", &files, &opts(Trust::Trust, &[])).expect_err("refuse");
assert_eq!(
err.to_string(),
"no markdown files under okf/: an OKF bundle is a directory of concept documents",
);
}
const A_DOC: &str = "---\ntype: \"doc\"\n---\n\n# A\n\nSee [B](/docs/b.md) in prose.\n\n## Relationships\n\n### references\n\n* \u{2192} [b](/docs/b.md)\n* \u{2192} [gone](/docs/gone.md)\n* \u{2190} [c](/docs/c.md)\n";
#[test]
fn only_links_under_relationships_become_edges() {
let import = read(
&[
("/docs/a.md", A_DOC),
("/docs/b.md", "---\ntype: \"doc\"\n---\n\n# B\n"),
("/docs/c.md", "---\ntype: \"doc\"\n---\n\n# C\n"),
],
Trust::Trust,
);
assert_eq!(import.facts.edges.len(), 1, "{:?}", import.facts.edges);
assert_eq!(import.facts.edges[0].dst, "okf:acme/docs/b.md");
assert_eq!(
import.report.links_outside_relationships, 1,
"the prose citation is counted, not imported as a relationship"
);
assert_eq!(
import.report.links_unresolved, 1,
"an edge to a concept the bundle does not contain is dropped and said so"
);
assert_eq!(import.report.links_reciprocal, 1);
}
#[test]
fn a_scalar_round_trips_through_the_writers_escaper() {
let hostile = "line one\nkey: forged\t\"quoted\" \\ back \u{1}";
let fm = Frontmatter {
type_: "doc".to_owned(),
title: Some(hostile.to_owned()),
..Frontmatter::default()
};
let doc = format!("{}\n# x\n", fm.render());
let (block, _) = split_frontmatter(&doc).expect("split");
let fm = parse_frontmatter(block).expect("the writer emits parseable YAML");
assert_eq!(fm.title.as_deref(), Some(hostile));
}
#[test]
fn an_imported_concept_fills_the_matching_placeholder() {
let stub = rto_graph::external_ref_key("acme::adr:0021");
let stubs = vec![stub.clone()];
let files = vec![("/decisions/adr-0021.md".to_owned(), AUTHORED.to_owned())];
let import = read_bundle("okf/", &files, &opts(Trust::Trust, &stubs)).expect("read");
assert_eq!(keys(&import), vec![stub.as_str()]);
let node = node_named(&import, &stub);
assert_eq!(node.name, "A decision");
assert_eq!(node.provenance, Provenance::ExternalAuthored);
assert!(node.meta.get("content").is_some(), "a stub gained content");
assert_eq!(node.meta["qualified"], "acme::adr:0021");
assert_eq!(
import.report.extrefs_filled,
vec![(stub, "/decisions/adr-0021.md".to_owned())]
);
}
#[test]
fn an_ambiguous_correspondence_fills_nothing_and_says_so() {
assert_eq!(slug("adr:0021"), slug("adr/0021"));
let a = rto_graph::external_ref_key("acme::adr:0021");
let b = rto_graph::external_ref_key("acme::adr/0021");
let stubs = vec![a.clone(), b.clone()];
let files = vec![("/decisions/adr-0021.md".to_owned(), AUTHORED.to_owned())];
let import = read_bundle("okf/", &files, &opts(Trust::Trust, &stubs)).expect("read");
assert_eq!(
keys(&import),
vec!["okf:acme/decisions/adr-0021.md"],
"the concept is still imported, just not attached to a placeholder"
);
assert!(import.report.extrefs_filled.is_empty());
assert_eq!(import.report.extrefs_ambiguous, vec![b, a]);
}
#[test]
fn a_concept_outside_the_layout_the_naming_rule_assumes_is_not_a_match() {
let stubs = vec![rto_graph::external_ref_key("acme::adr:0021")];
assert_eq!(section_for("adr"), "decisions");
let files = vec![(
"/notes/adr-0021.md".to_owned(),
"---\ntype: \"adr\"\n---\n\n# x\n".to_owned(),
)];
let import = read_bundle("okf/", &files, &opts(Trust::Trust, &stubs)).expect("read");
assert!(import.report.extrefs_filled.is_empty());
assert!(import.report.extrefs_ambiguous.is_empty());
assert_eq!(keys(&import), vec!["okf:acme/notes/adr-0021.md"]);
}
#[test]
fn the_answer_does_not_depend_on_the_order_the_files_arrive_in() {
let files: Vec<(String, String)> = vec![
("/decisions/a.md", AUTHORED),
("/docs/b.md", "---\ntype: \"doc\"\n---\n\n# B\n"),
("/docs/plain.md", "# no frontmatter\n"),
("/docs/typeless.md", "---\ntitle: \"x\"\n---\n\nB\n"),
("/symbols/c.md", "---\ntype: \"fn\"\n---\n\n# C\n"),
]
.into_iter()
.map(|(p, c)| (p.to_owned(), c.to_owned()))
.collect();
let forwards = read_bundle("okf/", &files, &opts(Trust::Trust, &[])).expect("read");
let mut backwards_input = files;
backwards_input.reverse();
let backwards =
read_bundle("okf/", &backwards_input, &opts(Trust::Trust, &[])).expect("read");
assert_eq!(
keys(&forwards),
keys(&backwards),
"node order is what reaches the persisted import layer"
);
assert_eq!(
forwards
.report
.skipped
.iter()
.map(|s| s.path.as_str())
.collect::<Vec<_>>(),
backwards
.report
.skipped
.iter()
.map(|s| s.path.as_str())
.collect::<Vec<_>>(),
);
assert_eq!(
serde_json::to_string(&forwards.facts).expect("json"),
serde_json::to_string(&backwards.facts).expect("json"),
);
}
#[test]
fn the_peers_own_record_survives_the_import_intact() {
let doc = "---\ntype: \"adr\"\ntitle: \"A decision\"\ndescription: \"One sentence.\"\nresource: \"https://example.test/blob/abc/docs/adr/0001.md\"\nstatus: \"Accepted\"\ntags:\n - \"architecture\"\n - \"storage\"\nverified:\n - by: \"human:alice\"\n at: \"2026-09-01T10:00:00Z\"\nsources:\n - resource: \"/docs/adr/0001.md\"\n---\n\n# A decision\n\nThe prose.\n";
let import = read(&[("/decisions/a.md", doc)], Trust::Trust);
let meta = &import.facts.nodes[0].meta;
let mut okf = meta["okf"].clone();
let origin = okf["origin"].take();
assert_eq!(
okf,
serde_json::json!({
"source": "import:okf/acme",
"peer": "acme",
"path": "/decisions/a.md",
"type": "adr",
"trust": "trust",
"claimed": { "tier": "authored", "verified": true },
"resource": "https://example.test/blob/abc/docs/adr/0001.md",
"status": "Accepted",
"tags": ["architecture", "storage"],
"sources": ["/docs/adr/0001.md"],
"description": "One sentence.",
"origin": serde_json::Value::Null,
}),
);
assert_eq!(
origin,
serde_json::json!({
"by": "human:alice",
"at": "2026-09-01T10:00:00Z",
"confirms": true,
}),
);
assert_eq!(meta["content"], "# A decision The prose.");
assert_eq!(meta.get("qualified"), None);
}
#[test]
fn a_relative_link_resolves_against_its_own_directory() {
assert_eq!(
resolve_target("/a/b/c.md", "../d/e.md").as_deref(),
Some("/a/d/e.md")
);
assert_eq!(
resolve_target("/a/b/c.md", "/x/y.md").as_deref(),
Some("/x/y.md")
);
assert_eq!(resolve_target("/a/b/c.md", "https://x/y").as_deref(), None);
assert_eq!(resolve_target("/a/b/c.md", "#anchor").as_deref(), None);
}
#[test]
fn an_actor_token_round_trips_and_never_loses_the_attribution() {
for token in ["human:alice", "roteiro/5.0.0", "process:sync"] {
assert_eq!(parse_actor(token).as_token(), token);
}
assert_eq!(parse_actor("mystery").as_token(), "process:mystery");
}
#[test]
fn an_unknown_frontmatter_key_takes_its_children_with_it() {
let block = "type: \"doc\"\nvendor_thing:\n by: \"not-an-actor\"\n nested:\n - x\ntitle: \"kept\"\n";
let fm = parse_frontmatter(block).expect("parseable YAML");
assert_eq!(fm.type_, "doc");
assert_eq!(fm.title.as_deref(), Some("kept"));
assert_eq!(
fm.generated, None,
"a `by:` nested under an unknown key is not the document's origin"
);
}
}