use super::{PackGateDecl, PACK_MANIFEST};
use sha2::{Digest as _, Sha256};
use std::collections::{BTreeMap, HashSet};
use std::path::{Path, PathBuf};
pub const MAX_STANDARDS_FILE_BYTES: u64 = 256 * 1024;
pub const MAX_STANDARDS_FILES: usize = 512;
pub const MAX_STANDARDS_RULES: usize = 256;
pub const MAX_STANDARDS_NORMALIZED_BYTES: usize = 1024 * 1024;
const CANONICAL_HEADER: &str = "kranz-standards-manifest v1";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StandardsTrust {
RepoTracked,
External,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RfcStatus {
Draft,
Approved,
Enforced,
Retired,
}
impl RfcStatus {
fn parse(raw: &str) -> Option<Self> {
match raw {
"draft" => Some(Self::Draft),
"approved" => Some(Self::Approved),
"enforced" => Some(Self::Enforced),
"retired" => Some(Self::Retired),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Draft => "draft",
Self::Approved => "approved",
Self::Enforced => "enforced",
Self::Retired => "retired",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleLevel {
Must,
Should,
}
impl RuleLevel {
fn parse(raw: &str) -> Option<Self> {
match raw {
"must" => Some(Self::Must),
"should" => Some(Self::Should),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Must => "must",
Self::Should => "should",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleStatus {
Active,
Retired,
}
impl RuleStatus {
fn parse(raw: &str) -> Option<Self> {
match raw {
"active" => Some(Self::Active),
"retired" => Some(Self::Retired),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Active => "active",
Self::Retired => "retired",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleStage {
Planning,
Implementation,
Validation,
Merge,
}
impl RuleStage {
pub(crate) fn parse(raw: &str) -> Option<Self> {
match raw {
"planning" => Some(Self::Planning),
"implementation" => Some(Self::Implementation),
"validation" => Some(Self::Validation),
"merge" => Some(Self::Merge),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Planning => "planning",
Self::Implementation => "implementation",
Self::Validation => "validation",
Self::Merge => "merge",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Checker {
Gate(String),
AgentJudgement,
ManualAttestation,
}
impl Checker {
fn parse(raw: &str) -> Result<Self, String> {
match raw {
"agent-judgement" => Ok(Self::AgentJudgement),
"manual-attestation" => Ok(Self::ManualAttestation),
_ => match raw.strip_prefix("gate:") {
Some(id) if !id.is_empty() => Ok(Self::Gate(id.to_string())),
_ => Err(format!(
"supported checker forms are `gate:<id>` (a declared pack gate), \
`agent-judgement`, and `manual-attestation`, got `{raw}`"
)),
},
}
}
pub fn render(&self) -> String {
match self {
Self::Gate(id) => format!("gate:{id}"),
Self::AgentJudgement => "agent-judgement".to_string(),
Self::ManualAttestation => "manual-attestation".to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RfcMeta {
pub id: String,
pub title: String,
pub owner: String,
pub status: RfcStatus,
pub effective_at: Option<String>,
pub supersedes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuleMeta {
pub id: String,
pub revision: u64,
pub rfc: String,
pub level: RuleLevel,
pub status: RuleStatus,
pub statement: String,
pub domains: Vec<String>,
pub stages: Vec<RuleStage>,
pub when_paths: Vec<String>,
pub task_classes: Vec<String>,
pub checker: Option<Checker>,
pub waivable: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StandardsManifest {
pub root: String,
pub rfcs: Vec<RfcMeta>,
pub rules: Vec<RuleMeta>,
pub gate_bindings: Vec<PackGateDecl>,
pub pack_gates: Vec<PackGateDecl>,
pub digest: String,
canonical: String,
}
impl StandardsManifest {
pub fn canonical_text(&self) -> &str {
&self.canonical
}
pub fn rule(&self, id: &str) -> Option<&RuleMeta> {
self.rules.iter().find(|r| r.id == id)
}
pub fn rfc(&self, id: &str) -> Option<&RfcMeta> {
self.rfcs.iter().find(|r| r.id == id)
}
pub fn effective_status(&self, rule: &RuleMeta) -> RfcStatus {
if rule.status == RuleStatus::Retired {
return RfcStatus::Retired;
}
self.rfc(&rule.rfc)
.map(|rfc| rfc.status)
.unwrap_or(RfcStatus::Retired)
}
}
pub(crate) fn load_from_pack_dir(
pack_dir: &Path,
root: &str,
gates: &[PackGateDecl],
trust: StandardsTrust,
) -> Result<StandardsManifest, String> {
use cap_fs_ext::DirExt as _;
let display_root = pack_dir.join(root);
let mut dir = cap_std::fs::Dir::open_ambient_dir(pack_dir, cap_std::ambient_authority())
.map_err(|e| format!("cannot open pack dir {}: {e}", pack_dir.display()))?;
for name in root.split('/') {
dir = dir.open_dir_nofollow(name).map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
format!(
"[standards] root `{root}` does not exist in the pack ({})",
display_root.display()
)
} else {
format!(
"[standards] root `{root}` resolves through a symlinked or non-directory \
component ({}) — the standards corpus loads no-follow",
display_root.display()
)
}
})?;
}
let source = FsSource {
root_dir: dir,
display_root,
};
load_from_source(&source, root, gates, trust)
}
pub fn load_at_ref(
repo: &crate::git_ops::GitRepo,
refname: &str,
pack_rel_dir: &str,
) -> Result<Option<StandardsManifest>, String> {
let oid = repo
.rev_parse(refname)
.map_err(|e| format!("cannot resolve ref `{refname}`: {e}"))?;
let manifest_rel = join_rel(pack_rel_dir, PACK_MANIFEST);
let Some(bytes) = repo
.show_file(&oid, &manifest_rel)
.map_err(|e| format!("cannot read {manifest_rel} at `{refname}`: {e}"))?
else {
return Ok(None);
};
let text = String::from_utf8(bytes)
.map_err(|_| format!("{manifest_rel} at `{refname}` is not valid UTF-8"))?;
let doc =
super::toml::parse(&text).map_err(|e| format!("{manifest_rel} at `{refname}`: {e}"))?;
let (_name, schema) =
super::manifest_header(&doc).map_err(|e| format!("{manifest_rel} at `{refname}`: {e}"))?;
let Some(root) = super::standards_root_of(&doc, schema)
.map_err(|e| format!("{manifest_rel} at `{refname}`: {e}"))?
else {
return Ok(None);
};
let mut gates = Vec::new();
for (idx, item) in doc.array("gate").iter().enumerate() {
gates.push(
super::load_gate(item, idx)
.map_err(|e| format!("{manifest_rel} at `{refname}`: {e}"))?,
);
}
super::reject_duplicate_names("gate", gates.iter().map(|g| g.name.as_str()))
.map_err(|e| format!("{manifest_rel} at `{refname}`: {e}"))?;
let prefix = join_rel(pack_rel_dir, &root);
let source = GitSource {
repo,
oid: &oid,
refname,
prefix,
};
load_from_source(&source, &root, &gates, StandardsTrust::RepoTracked).map(Some)
}
pub fn trust_for_dir(repo_root: &Path, pack_dir: &Path) -> StandardsTrust {
let Some(rel) = repo_relative_dir(repo_root, pack_dir) else {
return StandardsTrust::External;
};
let manifest_rel = join_rel(&rel, PACK_MANIFEST);
let Ok(repo) = crate::git_ops::GitRepo::open(repo_root) else {
return StandardsTrust::External;
};
match repo.is_tracked(&manifest_rel) {
Ok(true) => StandardsTrust::RepoTracked,
_ => StandardsTrust::External,
}
}
pub fn repo_relative_dir(repo_root: &Path, dir: &Path) -> Option<String> {
let repo_c = std::fs::canonicalize(repo_root).ok()?;
let dir_c = std::fs::canonicalize(dir).ok()?;
let rel = dir_c.strip_prefix(&repo_c).ok()?;
let mut out = String::new();
for part in rel.components() {
let std::path::Component::Normal(name) = part else {
return None;
};
if !out.is_empty() {
out.push('/');
}
out.push_str(name.to_str()?);
}
Some(out)
}
fn join_rel(dir: &str, leaf: &str) -> String {
if dir.is_empty() {
leaf.to_string()
} else {
format!("{dir}/{leaf}")
}
}
pub fn check_transitions(
base: Option<&StandardsManifest>,
proposed: &StandardsManifest,
) -> Vec<String> {
let mut errors = Vec::new();
for rfc in proposed
.rfcs
.iter()
.filter(|r| r.status == RfcStatus::Enforced)
{
match base.and_then(|b| b.rfc(&rfc.id)) {
None => errors.push(format!(
"RFC `{}` is enforced but absent at the base — an RFC may not move \
absent/draft → enforced; land it approved first so the advisory period \
produces real evidence (D-B)",
rfc.id
)),
Some(base_rfc) if base_rfc.status == RfcStatus::Draft => errors.push(format!(
"RFC `{}` is enforced but was draft at the base — absent/draft → enforced \
is refused; promote through approved first (D-B)",
rfc.id
)),
Some(base_rfc) if base_rfc.status == RfcStatus::Retired => errors.push(format!(
"RFC `{}` is enforced but was retired at the base — a tombstone is one-way \
(D-B/D-C)",
rfc.id
)),
Some(_) => {}
}
}
for rule in &proposed.rules {
if proposed.effective_status(rule) != RfcStatus::Enforced {
continue;
}
let base_effective = match base {
Some(base) => base
.rule(&rule.id)
.map(|base_rule| base.effective_status(base_rule)),
None => None,
};
match base_effective {
Some(RfcStatus::Approved | RfcStatus::Enforced) => {}
Some(RfcStatus::Retired) => {}
Some(RfcStatus::Draft) => errors.push(format!(
"rule `{}` is enforced but was draft at the base — absent/draft → enforced \
is refused; an approved advisory period comes first (D-B)",
rule.id
)),
None => errors.push(format!(
"rule `{}` is enforced but absent at the base — new rules enter as draft or \
approved, never directly enforced (D-B)",
rule.id
)),
}
}
let Some(base) = base else {
return errors;
};
for base_rule in &base.rules {
let Some(proposed_rule) = proposed.rule(&base_rule.id) else {
errors.push(format!(
"rule `{}` (base revision {}) is gone — known rule IDs cannot disappear; \
retire the rule as a one-way tombstone instead of deleting it (D-C)",
base_rule.id, base_rule.revision
));
continue;
};
if base_rule.status == RuleStatus::Retired && proposed_rule.status == RuleStatus::Active {
errors.push(format!(
"rule `{}` was retired at the base and cannot be reactivated — retirement \
is a one-way tombstone; a successor rule needs a new ID (D-B/D-C)",
base_rule.id
));
continue;
}
if base_rule.status == RuleStatus::Active && proposed_rule.status == RuleStatus::Active {
if proposed_rule.revision < base_rule.revision {
errors.push(format!(
"rule `{}` revision moved backwards ({} → {}) — revisions are monotonic \
(D-C)",
base_rule.id, base_rule.revision, proposed_rule.revision
));
} else if proposed_rule.revision == base_rule.revision {
if let Some(field) = semantic_change(base_rule, proposed_rule) {
errors.push(format!(
"rule `{}` changed `{field}` without a revision increment (still \
{}) — a semantic change to statement, level, scope, checker, or \
waiver posture requires a bump (D-C)",
base_rule.id, base_rule.revision
));
}
}
}
}
errors
}
fn semantic_change(base: &RuleMeta, proposed: &RuleMeta) -> Option<&'static str> {
if base.statement != proposed.statement {
Some("statement")
} else if base.level != proposed.level {
Some("level")
} else if base.stages != proposed.stages {
Some("stages")
} else if base.when_paths != proposed.when_paths {
Some("when-paths")
} else if base.task_classes != proposed.task_classes {
Some("task-classes")
} else if base.checker != proposed.checker {
Some("checker")
} else if base.waivable != proposed.waivable {
Some("waivable")
} else {
None
}
}
pub fn render_registration(manifest: &StandardsManifest) -> String {
let tally = |status: RfcStatus| manifest.rfcs.iter().filter(|r| r.status == status).count();
let active_rules = manifest
.rules
.iter()
.filter(|r| r.status == RuleStatus::Active)
.count();
format!(
"standards (schema {} root `{}`):\n digest: sha256:{}\n RFCs: {} (draft {}, approved \
{}, enforced {}, retired {}); rules: {} (active {}, retired {}); gate bindings: {}\n",
super::SCHEMA_STANDARDS,
manifest.root,
manifest.digest,
manifest.rfcs.len(),
tally(RfcStatus::Draft),
tally(RfcStatus::Approved),
tally(RfcStatus::Enforced),
tally(RfcStatus::Retired),
manifest.rules.len(),
active_rules,
manifest.rules.len() - active_rules,
manifest.gate_bindings.len(),
)
}
pub fn render_manifest(manifest: &StandardsManifest, trust: StandardsTrust) -> String {
let mut out = format!(
"standards root `{}` — {} RFC(s), {} rule(s)\ndigest: sha256:{}\n",
manifest.root,
manifest.rfcs.len(),
manifest.rules.len(),
manifest.digest
);
out.push_str(match trust {
StandardsTrust::RepoTracked => "trust: repo-tracked — enforced rules may activate\n",
StandardsTrust::External => {
"trust: external/untracked — advisory only; enforced rules are refused at load \
(D-A/D-J)\n"
}
});
out.push_str("RFCs:\n");
if manifest.rfcs.is_empty() {
out.push_str(" (none)\n");
}
for rfc in &manifest.rfcs {
let effective = rfc
.effective_at
.as_deref()
.map(|ts| format!(", effective {ts}"))
.unwrap_or_default();
let supersedes = if rfc.supersedes.is_empty() {
String::new()
} else {
format!(", supersedes {}", rfc.supersedes.join(", "))
};
out.push_str(&format!(
" - {} \"{}\" — {}, owner {}{}{}\n",
rfc.id,
rfc.title,
rfc.status.as_str(),
rfc.owner,
effective,
supersedes
));
}
out.push_str("rules:\n");
if manifest.rules.is_empty() {
out.push_str(" (none)\n");
}
for rule in &manifest.rules {
let checker = rule
.checker
.as_ref()
.map(Checker::render)
.unwrap_or_else(|| "-".to_string());
out.push_str(&format!(
" - {} r{} — {}, {}; checker {}; waivable: {}\n statement: {}\n",
rule.id,
rule.revision,
rule.level.as_str(),
manifest.effective_status(rule).as_str(),
checker,
rule.waivable,
rule.statement
));
let list = |items: &[String]| {
if items.is_empty() {
"-".to_string()
} else {
items.join(", ")
}
};
let stages = rule
.stages
.iter()
.map(RuleStage::as_str)
.collect::<Vec<_>>()
.join(", ");
out.push_str(&format!(
" stages: {}; domains: {}; when-paths: {}; task-classes: {}\n",
stages,
list(&rule.domains),
list(&rule.when_paths),
list(&rule.task_classes)
));
}
out
}
pub fn render_transition_report(
refname: &str,
base: Option<&StandardsManifest>,
errors: &[String],
) -> String {
let base_desc = match base {
Some(base) => format!(
"base digest sha256:{}, {} RFC(s), {} rule(s)",
base.digest,
base.rfcs.len(),
base.rules.len()
),
None => "no standards at the base ref".to_string(),
};
let mut out = format!("transition check against `{refname}` ({base_desc}):\n");
if errors.is_empty() {
out.push_str(" ok — no lifecycle violations\n");
} else {
for error in errors {
out.push_str(&format!(" REFUSED: {error}\n"));
}
}
out
}
struct SourceFile {
rel: String,
size: u64,
display: String,
}
trait CorpusSource {
fn list_files(&self) -> Result<Vec<SourceFile>, String>;
fn read_bytes(&self, file: &SourceFile) -> Result<Vec<u8>, String>;
}
struct FsSource {
root_dir: cap_std::fs::Dir,
display_root: PathBuf,
}
impl CorpusSource for FsSource {
fn list_files(&self) -> Result<Vec<SourceFile>, String> {
use cap_fs_ext::DirExt as _;
let mut out = Vec::new();
for (name, ftype) in sorted_entries(&self.root_dir, &self.display_root)? {
let display = self.display_root.join(&name);
check_entry_name(&name, &display)?;
if ftype.is_symlink() {
return Err(format!(
"{} resolves through a symlink — the standards corpus never follows \
symlinks (D-J)",
display.display()
));
}
if !ftype.is_dir() {
return Err(format!(
"{} is not an RFC directory — the standards root holds one directory \
per RFC, nothing else",
display.display()
));
}
let rfc_dir = self.root_dir.open_dir_nofollow(&name).map_err(|_| {
format!(
"{} resolves through a symlinked or non-directory component — the \
standards corpus never follows symlinks (D-J)",
display.display()
)
})?;
list_rfc_dir(&rfc_dir, &name, &display, &mut out)?;
}
Ok(out)
}
fn read_bytes(&self, file: &SourceFile) -> Result<Vec<u8>, String> {
use cap_fs_ext::{DirExt as _, FollowSymlinks, OpenOptionsFollowExt as _};
use std::io::Read as _;
let mut dir = self
.root_dir
.try_clone()
.map_err(|e| format!("{} cannot be opened: {e}", file.display))?;
let mut names = file.rel.split('/').peekable();
while let Some(name) = names.next() {
if names.peek().is_some() {
dir = dir.open_dir_nofollow(name).map_err(|_| {
format!(
"{} resolves through a symlinked or non-directory component — the \
standards corpus never follows symlinks (D-J)",
file.display
)
})?;
continue;
}
let meta = dir
.symlink_metadata(name)
.map_err(|e| format!("{} cannot be stat'ed: {e}", file.display))?;
let ftype = meta.file_type();
if ftype.is_symlink() {
return Err(format!(
"{} is a symlink — the standards corpus never follows symlinks (D-J)",
file.display
));
}
if !ftype.is_file() {
return Err(format!(
"{} is not a regular file (FIFO/device/socket) — the standards corpus \
accepts regular files only (D-J)",
file.display
));
}
let mut options = cap_std::fs::OpenOptions::new();
options.read(true).follow(FollowSymlinks::No);
let opened = dir
.open_with(name, &options)
.map_err(|e| format!("{} cannot be read: {e}", file.display))?;
let mut bytes = Vec::new();
opened
.take(MAX_STANDARDS_FILE_BYTES + 1)
.read_to_end(&mut bytes)
.map_err(|e| format!("{} cannot be read: {e}", file.display))?;
if bytes.len() as u64 > MAX_STANDARDS_FILE_BYTES {
return Err(format!(
"{} is {} bytes, over the {}-byte per-file cap",
file.display,
bytes.len(),
MAX_STANDARDS_FILE_BYTES
));
}
return Ok(bytes);
}
Err(format!("{} resolves to no file", file.display))
}
}
fn list_rfc_dir(
dir: &cap_std::fs::Dir,
rel_prefix: &str,
display: &Path,
out: &mut Vec<SourceFile>,
) -> Result<(), String> {
use cap_fs_ext::DirExt as _;
for (name, ftype) in sorted_entries(dir, display)? {
let entry_display = display.join(&name);
if ftype.is_symlink() {
return Err(format!(
"{} resolves through a symlink — the standards corpus never follows \
symlinks (D-J)",
entry_display.display()
));
}
if name == "rfc.md" {
if !ftype.is_file() {
return Err(format!(
"{} must be a regular file",
entry_display.display()
));
}
out.push(SourceFile {
rel: format!("{rel_prefix}/rfc.md"),
size: dir.symlink_metadata(&name).map(|m| m.len()).unwrap_or(0),
display: entry_display.display().to_string(),
});
continue;
}
if name == "rules" {
if !ftype.is_dir() {
return Err(format!(
"{} must be a directory holding rule files",
entry_display.display()
));
}
let rules_dir = dir.open_dir_nofollow(&name).map_err(|_| {
format!(
"{} resolves through a symlinked or non-directory component — the \
standards corpus never follows symlinks (D-J)",
entry_display.display()
)
})?;
for (rule_name, rule_ftype) in sorted_entries(&rules_dir, &entry_display)? {
let rule_display = entry_display.join(&rule_name);
if rule_ftype.is_symlink() {
return Err(format!(
"{} resolves through a symlink — the standards corpus never \
follows symlinks (D-J)",
rule_display.display()
));
}
if rule_ftype.is_dir() {
return Err(format!(
"{} is a directory — rules/ holds rule Markdown files only, no \
nested directories",
rule_display.display()
));
}
if !rule_ftype.is_file() {
return Err(format!(
"{} is not a regular file (FIFO/device/socket) — the standards \
corpus accepts regular files only (D-J)",
rule_display.display()
));
}
if !rule_name.ends_with(".md") {
return Err(format!(
"{} is not a `.md` rule file — rules/ holds rule Markdown files \
only",
rule_display.display()
));
}
check_entry_name(&rule_name, &rule_display)?;
out.push(SourceFile {
rel: format!("{rel_prefix}/rules/{rule_name}"),
size: rules_dir
.symlink_metadata(&rule_name)
.map(|m| m.len())
.unwrap_or(0),
display: rule_display.display().to_string(),
});
}
continue;
}
return Err(format!(
"{} is unexpected — an RFC directory holds `rfc.md` and `rules/`, nothing else",
entry_display.display()
));
}
Ok(())
}
struct GitSource<'a> {
repo: &'a crate::git_ops::GitRepo,
oid: &'a str,
refname: &'a str,
prefix: String,
}
impl CorpusSource for GitSource<'_> {
fn list_files(&self) -> Result<Vec<SourceFile>, String> {
let entries = self
.repo
.ls_tree_recursive(self.oid, &self.prefix)
.map_err(|e| format!("cannot list the standards root at `{}`: {e}", self.refname))?;
if entries.is_empty() {
return Err(format!(
"[standards] root is declared but no tracked files exist under `{}` at \
`{}`",
self.prefix, self.refname
));
}
let mut out = Vec::new();
for entry in entries {
let display = format!("{}:{}", self.refname, entry.path);
if entry.path == self.prefix {
return Err(format!(
"{display} is a file, not a directory tree — the standards root must \
be a directory"
));
}
let Some(rel) = entry.path.strip_prefix(&format!("{}/", self.prefix)) else {
return Err(format!(
"git ls-tree reported {display} outside the standards root `{}`",
self.prefix
));
};
if rel.starts_with('"') {
return Err(format!(
"{display} needed git quoting — corpus names stay inside ASCII \
alphanumerics, `.`, `_`, `-`"
));
}
if entry.mode == "120000" {
return Err(format!(
"{display} is a tracked symlink — the standards corpus never follows \
symlinks (D-J)"
));
}
if entry.kind != "blob" {
return Err(format!(
"{display} is a {} (mode {}) — the standards corpus accepts regular \
files only (D-J)",
entry.kind, entry.mode
));
}
let size = entry.size.unwrap_or(0);
validate_corpus_rel(rel, &display)?;
out.push(SourceFile {
rel: rel.to_string(),
size,
display,
});
}
Ok(out)
}
fn read_bytes(&self, file: &SourceFile) -> Result<Vec<u8>, String> {
let path = format!("{}/{}", self.prefix, file.rel);
let bytes = self
.repo
.show_file(self.oid, &path)
.map_err(|e| format!("{} cannot be read: {e}", file.display))?
.ok_or_else(|| {
format!(
"{} vanished between listing and read — refusing to continue",
file.display
)
})?;
if bytes.len() as u64 > MAX_STANDARDS_FILE_BYTES {
return Err(format!(
"{} is {} bytes, over the {MAX_STANDARDS_FILE_BYTES}-byte per-file cap",
file.display,
bytes.len()
));
}
Ok(bytes)
}
}
fn sorted_entries(
dir: &cap_std::fs::Dir,
display: &Path,
) -> Result<Vec<(String, cap_std::fs::FileType)>, String> {
let mut out = Vec::new();
let entries = dir
.entries()
.map_err(|e| format!("cannot list {}: {e}", display.display()))?;
for entry in entries {
let entry = entry.map_err(|e| format!("cannot list {}: {e}", display.display()))?;
let name = entry.file_name().into_string().map_err(|_| {
format!(
"{} holds a non-UTF-8 file name — the standards corpus requires UTF-8 names",
display.display()
)
})?;
let meta = dir
.symlink_metadata(&name)
.map_err(|e| format!("{} cannot be stat'ed: {e}", display.join(&name).display()))?;
out.push((name, meta.file_type()));
}
out.sort_by(|a, b| a.0.cmp(&b.0));
Ok(out)
}
fn check_entry_name(name: &str, display: &Path) -> Result<(), String> {
if name.starts_with('.')
|| !name
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
{
return Err(format!(
"{}: corpus entry names stay inside ASCII alphanumerics, `.`, `_`, `-` and \
never start with `.`",
display.display()
));
}
Ok(())
}
fn validate_corpus_rel(rel: &str, display: &str) -> Result<(), String> {
let parts: Vec<&str> = rel.split('/').collect();
for part in &parts {
if part.starts_with('.')
|| part.is_empty()
|| !part
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
{
return Err(format!(
"{display}: corpus entry names stay inside ASCII alphanumerics, `.`, `_`, \
`-` and never start with `.`"
));
}
}
let well_formed = match parts.as_slice() {
[_dir, file] => *file == "rfc.md",
[_dir, rules, file] => *rules == "rules" && file.ends_with(".md"),
_ => false,
};
if !well_formed {
return Err(format!(
"{display}: unexpected path shape — the corpus holds `<RFC-dir>/rfc.md` and \
`<RFC-dir>/rules/<rule>.md` only"
));
}
Ok(())
}
fn load_from_source<S: CorpusSource>(
source: &S,
root: &str,
gates: &[PackGateDecl],
trust: StandardsTrust,
) -> Result<StandardsManifest, String> {
let mut listing = source.list_files()?;
listing.sort_by(|a, b| a.rel.cmp(&b.rel));
if listing.len() > MAX_STANDARDS_FILES {
return Err(format!(
"[standards] root `{root}` holds {} files, over the {}-file cap",
listing.len(),
MAX_STANDARDS_FILES
));
}
for file in &listing {
if file.size > MAX_STANDARDS_FILE_BYTES {
return Err(format!(
"{} is {} bytes, over the {}-byte per-file cap",
file.display, file.size, MAX_STANDARDS_FILE_BYTES
));
}
}
let mut groups: BTreeMap<String, (Option<&SourceFile>, Vec<&SourceFile>)> = BTreeMap::new();
for file in &listing {
let parts: Vec<&str> = file.rel.split('/').collect();
let (dir, is_rfc) = match parts.as_slice() {
[dir, name] if *name == "rfc.md" => ((*dir).to_string(), true),
[dir, rules, name] if *rules == "rules" && name.ends_with(".md") => {
((*dir).to_string(), false)
}
_ => {
return Err(format!(
"{}: unexpected path shape — the corpus holds `<RFC-dir>/rfc.md` and \
`<RFC-dir>/rules/<rule>.md` only",
file.display
))
}
};
let group = groups.entry(dir).or_insert_with(|| (None, Vec::new()));
if is_rfc {
if group.0.is_some() {
return Err(format!(
"{}: duplicate rfc.md in one RFC directory",
file.display
));
}
group.0 = Some(file);
} else {
group.1.push(file);
}
}
let mut rfcs = Vec::new();
let mut rules = Vec::new();
for (dir, (rfc_file, rule_files)) in &groups {
let Some(rfc_file) = rfc_file else {
return Err(format!(
"standards RFC directory `{dir}` has rules but no rfc.md ({})",
rule_files[0].display
));
};
let text = read_text(source, rfc_file)?;
let fm = parse_frontmatter(&text, &rfc_file.display)?;
rfcs.push(load_rfc(&fm, &rfc_file.display)?);
for rule_file in rule_files {
if rules.len() >= MAX_STANDARDS_RULES {
return Err(format!(
"{}: the rule count exceeds the {}-rule cap",
rule_file.display, MAX_STANDARDS_RULES
));
}
let text = read_text(source, rule_file)?;
let fm = parse_frontmatter(&text, &rule_file.display)?;
let rule = load_rule(&fm, &rule_file.display)?;
if let Some(Checker::Gate(id)) = &rule.checker {
if !gates.iter().any(|g| &g.name == id) {
return Err(format!(
"{}: field `checker`: rule `{}` checker `gate:{id}` names no \
declared [[gate]] in this pack — the checker must resolve to a \
pack gate at load (D-F)",
rule_file.display, rule.id
));
}
}
rules.push(rule);
}
}
assemble(root, rfcs, rules, gates, trust)
}
fn read_text<S: CorpusSource>(source: &S, file: &SourceFile) -> Result<String, String> {
let bytes = source.read_bytes(file)?;
String::from_utf8(bytes).map_err(|_| {
format!(
"{} is not valid UTF-8 — corpus files are UTF-8 text",
file.display
)
})
}
fn assemble(
root: &str,
mut rfcs: Vec<RfcMeta>,
mut rules: Vec<RuleMeta>,
gates: &[PackGateDecl],
trust: StandardsTrust,
) -> Result<StandardsManifest, String> {
let mut ids = HashSet::new();
for rfc in &rfcs {
if !ids.insert(rfc.id.as_str()) {
return Err(format!(
"duplicate standards id `{}` — RFC and rule IDs are pack-wide unique (D-C)",
rfc.id
));
}
}
for rule in &rules {
if !ids.insert(rule.id.as_str()) {
return Err(format!(
"duplicate standards id `{}` — RFC and rule IDs are pack-wide unique (D-C)",
rule.id
));
}
}
let status_of = |id: &str| rfcs.iter().find(|r| r.id == id).map(|r| r.status);
let mut gate_bindings: BTreeMap<&str, &PackGateDecl> = BTreeMap::new();
for rule in &rules {
let Some(rfc_status) = status_of(&rule.rfc) else {
return Err(format!(
"rule `{}` names parent RFC `{}`, which does not exist in this pack — \
orphan rules fail the load (D-C)",
rule.id, rule.rfc
));
};
let effective = if rule.status == RuleStatus::Retired {
RfcStatus::Retired
} else {
rfc_status
};
if let Some(Checker::Gate(id)) = &rule.checker {
let Some(gate) = gates.iter().find(|g| &g.name == id) else {
return Err(format!(
"rule `{}` checker `gate:{id}` names no declared [[gate]] in this pack \
— the checker must resolve to a pack gate at load (D-F)",
rule.id
));
};
gate_bindings.insert(gate.name.as_str(), gate);
}
if matches!(effective, RfcStatus::Approved | RfcStatus::Enforced) && rule.checker.is_none()
{
return Err(format!(
"rule `{}` is effectively {} (RFC `{}` is {}) but declares no checker — \
promotion to approved requires a valid typed binding; only draft rules \
may omit one (D-F)",
rule.id,
effective.as_str(),
rule.rfc,
rfc_status.as_str()
));
}
if trust == StandardsTrust::External && effective == RfcStatus::Enforced {
return Err(format!(
"rule `{}` is effectively enforced but this pack is external/untracked — \
an external pack may supply approved advisory rules, never enforced ones, \
in this slice (D-A/D-J). Remedy: vendor the pack into the repo as a \
tracked, repo-relative packDir so its lifecycle is provable from base \
history",
rule.id
));
}
}
rfcs.sort_by(|a, b| a.id.cmp(&b.id));
rules.sort_by(|a, b| a.id.cmp(&b.id));
let gate_bindings: Vec<PackGateDecl> =
gate_bindings.values().map(|gate| (*gate).clone()).collect();
let canonical = canonical_text(&rfcs, &rules, &gate_bindings);
if canonical.len() > MAX_STANDARDS_NORMALIZED_BYTES {
return Err(format!(
"the normalized standards manifest is {} bytes, over the {}-byte cap",
canonical.len(),
MAX_STANDARDS_NORMALIZED_BYTES
));
}
let digest = Sha256::digest(canonical.as_bytes());
let digest = digest
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>();
Ok(StandardsManifest {
root: root.to_string(),
rfcs,
rules,
gate_bindings,
pack_gates: gates.to_vec(),
digest,
canonical,
})
}
fn canonical_text(rfcs: &[RfcMeta], rules: &[RuleMeta], gates: &[PackGateDecl]) -> String {
fn list_lines(out: &mut String, indent: &str, items: &[String]) {
for item in items {
out.push_str(&format!("{indent}- {item}\n"));
}
}
let mut out = format!("{CANONICAL_HEADER}\n");
for rfc in rfcs {
out.push_str(&format!("rfc {}\n", rfc.id));
out.push_str(&format!(" title: {}\n", rfc.title));
out.push_str(&format!(" owner: {}\n", rfc.owner));
out.push_str(&format!(" status: {}\n", rfc.status.as_str()));
out.push_str(&format!(
" effective-at: {}\n",
rfc.effective_at.as_deref().unwrap_or("-")
));
out.push_str(" supersedes:\n");
list_lines(&mut out, " ", &rfc.supersedes);
}
for rule in rules {
out.push_str(&format!("rule {}\n", rule.id));
out.push_str(&format!(" revision: {}\n", rule.revision));
out.push_str(&format!(" rfc: {}\n", rule.rfc));
out.push_str(&format!(" level: {}\n", rule.level.as_str()));
out.push_str(&format!(" status: {}\n", rule.status.as_str()));
out.push_str(&format!(" statement: {}\n", rule.statement));
out.push_str(" domains:\n");
list_lines(&mut out, " ", &rule.domains);
out.push_str(" stages:\n");
let stages: Vec<String> = rule.stages.iter().map(|s| s.as_str().to_string()).collect();
list_lines(&mut out, " ", &stages);
out.push_str(" when-paths:\n");
list_lines(&mut out, " ", &rule.when_paths);
out.push_str(" task-classes:\n");
list_lines(&mut out, " ", &rule.task_classes);
out.push_str(&format!(
" checker: {}\n",
rule.checker
.as_ref()
.map(Checker::render)
.unwrap_or_else(|| "-".to_string())
));
out.push_str(&format!(" waivable: {}\n", rule.waivable));
}
for gate in gates {
out.push_str(&format!("gate {}\n", gate.name));
out.push_str(&format!(" command: {}\n", gate.command));
out.push_str(" when-paths:\n");
list_lines(&mut out, " ", &gate.when_paths);
}
out
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum FieldValue {
Scalar(String),
List(Vec<String>),
}
struct Frontmatter {
fields: Vec<(String, FieldValue)>,
}
impl Frontmatter {
fn get(&self, key: &str) -> Option<&FieldValue> {
self.fields.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
fn check_unknown(&self, display: &str, known: &[&str]) -> Result<(), String> {
for (key, _) in &self.fields {
if !known.contains(&key.as_str()) {
return Err(format!(
"{display}: unknown frontmatter field `{key}` (declared fields: {})",
known.join(", ")
));
}
}
Ok(())
}
fn scalar(&self, key: &str, display: &str) -> Result<Option<&str>, String> {
match self.get(key) {
Some(FieldValue::Scalar(value)) => Ok(Some(value.as_str())),
Some(FieldValue::List(_)) => Err(format!(
"{display}: field `{key}` must be a scalar, got a list"
)),
None => Ok(None),
}
}
fn required_scalar(&self, key: &str, display: &str) -> Result<String, String> {
self.scalar(key, display)?
.map(str::to_string)
.ok_or_else(|| format!("{display}: missing required field `{key}`"))
}
fn list(&self, key: &str, display: &str) -> Result<Option<&[String]>, String> {
match self.get(key) {
Some(FieldValue::List(items)) => Ok(Some(items.as_slice())),
Some(FieldValue::Scalar(_)) => Err(format!(
"{display}: field `{key}` must be a list (`{key}: [a, b]`), got a scalar"
)),
None => Ok(None),
}
}
fn required_list(&self, key: &str, display: &str) -> Result<Vec<String>, String> {
self.list(key, display)?
.map(<[String]>::to_vec)
.ok_or_else(|| format!("{display}: missing required field `{key}`"))
}
}
fn parse_frontmatter(text: &str, display: &str) -> Result<Frontmatter, String> {
let text = text.strip_prefix('\u{feff}').unwrap_or(text);
let mut lines = text.lines().enumerate();
let Some((_, first)) = lines.next() else {
return Err(format!(
"{display}: empty file — expected a `---` frontmatter fence"
));
};
if first != "---" {
return Err(format!(
"{display}: line 1 must be the `---` frontmatter fence, got `{first}`"
));
}
let mut fields: Vec<(String, FieldValue)> = Vec::new();
for (idx, line) in lines {
let line_no = idx + 1;
if line == "---" {
return Ok(Frontmatter { fields });
}
if line.trim().is_empty() {
continue;
}
if line.starts_with(char::is_whitespace) {
return Err(format!(
"{display}: line {line_no}: unexpected indentation — the frontmatter \
subset has no nested or block values"
));
}
if line.contains('\t') {
return Err(format!(
"{display}: line {line_no}: tab characters are not in the frontmatter \
subset"
));
}
let Some(colon) = line.find(':') else {
return Err(format!(
"{display}: line {line_no}: expected `key: value`, got `{line}`"
));
};
let key = &line[..colon];
if !is_kebab_key(key) {
return Err(format!(
"{display}: line {line_no}: unsupported field name `{key}` (lowercase \
kebab-case keys only)"
));
}
if fields.iter().any(|(k, _)| k == key) {
return Err(format!(
"{display}: line {line_no}: duplicate field `{key}`"
));
}
let raw = line[colon + 1..].trim();
if raw.is_empty() {
return Err(format!(
"{display}: line {line_no}: field `{key}` has an empty value — omit \
optional fields instead (implicit null is not in the subset)"
));
}
let value = parse_value(raw, display, line_no, key)?;
fields.push((key.to_string(), value));
}
Err(format!(
"{display}: missing the closing `---` frontmatter fence"
))
}
fn is_kebab_key(key: &str) -> bool {
let mut parts = key.split('-');
let valid_part = |part: &str| {
!part.is_empty()
&& part
.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
};
match parts.next() {
Some(first) if first.chars().next().is_some_and(|c| c.is_ascii_lowercase()) => {
valid_part(first) && parts.all(valid_part)
}
_ => false,
}
}
fn parse_value(raw: &str, display: &str, line_no: usize, key: &str) -> Result<FieldValue, String> {
let refusal = |what: &str| {
format!(
"{display}: line {line_no}: field `{key}`: {what} is not in the frontmatter \
subset"
)
};
match raw.chars().next() {
Some('[') => parse_inline_list(raw, display, line_no, key).map(FieldValue::List),
Some('"') => {
let (value, rest) = parse_quoted(&raw[1..], display, line_no, key)?;
check_trailing(rest, display, line_no, key)?;
Ok(FieldValue::Scalar(value))
}
Some('\'') => Err(refusal("single-quoted strings (use double quotes)")),
Some('&') => Err(refusal("anchors")),
Some('*') => Err(refusal("aliases")),
Some('!') => Err(refusal("tags")),
Some('|' | '>') => Err(refusal(
"block scalars (values are single-line; the statement is one line)",
)),
Some('{') => Err(refusal("flow mappings")),
_ => {
let cut = raw.find(" #").unwrap_or(raw.len());
let value = raw[..cut].trim();
if value.is_empty() {
return Err(format!(
"{display}: line {line_no}: field `{key}` has an empty value — omit \
optional fields instead"
));
}
Ok(FieldValue::Scalar(value.to_string()))
}
}
}
fn check_trailing(rest: &str, display: &str, line_no: usize, key: &str) -> Result<(), String> {
let rest = rest.trim();
if rest.is_empty() || rest.starts_with('#') {
Ok(())
} else {
Err(format!(
"{display}: line {line_no}: field `{key}` has trailing text after the value"
))
}
}
fn parse_quoted<'a>(
text: &'a str,
display: &str,
line_no: usize,
key: &str,
) -> Result<(String, &'a str), String> {
let mut out = String::new();
let mut chars = text.char_indices();
while let Some((idx, c)) = chars.next() {
match c {
'"' => return Ok((out, &text[idx + 1..])),
'\\' => match chars.next() {
Some((_, '"')) => out.push('"'),
Some((_, '\\')) => out.push('\\'),
Some((_, other)) => {
return Err(format!(
"{display}: line {line_no}: field `{key}`: unsupported escape \
`\\{other}` (only `\\\"` and `\\\\` are in the subset)"
))
}
None => break,
},
c => out.push(c),
}
}
Err(format!(
"{display}: line {line_no}: field `{key}`: unterminated quoted string"
))
}
fn parse_inline_list(
raw: &str,
display: &str,
line_no: usize,
key: &str,
) -> Result<Vec<String>, String> {
let mut items = Vec::new();
let mut rest = &raw[1..];
loop {
rest = rest.trim_start();
if let Some(after) = rest.strip_prefix(']') {
check_trailing(after, display, line_no, key)?;
return Ok(items);
}
if rest.is_empty() {
return Err(format!(
"{display}: line {line_no}: field `{key}`: unterminated `[` in list value"
));
}
if let Some(after) = rest.strip_prefix('"') {
let (value, after) = parse_quoted(after, display, line_no, key)?;
items.push(value);
rest = after;
} else {
let end = rest.find([',', ']']).ok_or_else(|| {
format!(
"{display}: line {line_no}: field `{key}`: unterminated `[` in \
list value"
)
})?;
let element = rest[..end].trim();
if element.is_empty() {
return Err(format!(
"{display}: line {line_no}: field `{key}`: empty list element"
));
}
if element.contains(['#', '"', '[', '\'']) {
return Err(format!(
"{display}: line {line_no}: field `{key}`: bare list element \
`{element}` contains a character the subset does not allow (quote \
the element)"
));
}
items.push(element.to_string());
rest = &rest[end..];
}
rest = rest.trim_start();
match rest.strip_prefix(',') {
Some(after) => rest = after,
None => {
if !rest.starts_with(']') {
return Err(format!(
"{display}: line {line_no}: field `{key}`: expected `,` or `]` in \
list value"
));
}
}
}
}
}
fn is_valid_id(raw: &str) -> bool {
!raw.is_empty()
&& raw
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-'))
}
fn required_id(fm: &Frontmatter, key: &str, display: &str) -> Result<String, String> {
let id = fm.required_scalar(key, display)?;
if !is_valid_id(&id) {
return Err(format!(
"{display}: field `{key}` is `{id}` — IDs use ASCII alphanumerics, `.`, `_`, \
`-` only"
));
}
Ok(id)
}
fn required_revision(fm: &Frontmatter, display: &str) -> Result<u64, String> {
let raw = fm.required_scalar("revision", display)?;
if raw.is_empty() || !raw.chars().all(|c| c.is_ascii_digit()) {
return Err(format!(
"{display}: field `revision` must be a positive integer, got `{raw}`"
));
}
match raw.parse::<u64>() {
Ok(n) if n >= 1 => Ok(n),
_ => Err(format!(
"{display}: field `revision` must be a positive integer, got `{raw}`"
)),
}
}
fn optional_bool(fm: &Frontmatter, key: &str, display: &str) -> Result<bool, String> {
match fm.scalar(key, display)? {
Some("true") => Ok(true),
Some("false") | None => Ok(false),
Some(other) => Err(format!(
"{display}: field `{key}` must be exactly `true` or `false`, got `{other}`"
)),
}
}
fn normalized_list(mut items: Vec<String>) -> Vec<String> {
items.sort();
items.dedup();
items
}
fn load_rfc(fm: &Frontmatter, display: &str) -> Result<RfcMeta, String> {
fm.check_unknown(
display,
&[
"id",
"title",
"owner",
"status",
"effective-at",
"supersedes",
],
)?;
let id = required_id(fm, "id", display)?;
let title = fm.required_scalar("title", display)?;
let owner = fm.required_scalar("owner", display)?;
let status_raw = fm.required_scalar("status", display)?;
let status = RfcStatus::parse(&status_raw).ok_or_else(|| {
format!(
"{display}: field `status` is `{status_raw}` — RFC statuses are draft, \
approved, enforced, retired (D-B)"
)
})?;
let effective_at = match fm.scalar("effective-at", display)? {
Some(raw) => {
let parsed = chrono::DateTime::parse_from_rfc3339(raw).map_err(|_| {
format!(
"{display}: field `effective-at` must be an RFC3339 timestamp, got \
`{raw}`"
)
})?;
Some(
parsed
.with_timezone(&chrono::Utc)
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
)
}
None => None,
};
let mut supersedes = Vec::new();
if let Some(items) = fm.list("supersedes", display)? {
for item in items {
if !is_valid_id(item) {
return Err(format!(
"{display}: field `supersedes` element `{item}` is not a valid RFC id"
));
}
supersedes.push(item.clone());
}
}
Ok(RfcMeta {
id,
title,
owner,
status,
effective_at,
supersedes: normalized_list(supersedes),
})
}
fn load_rule(fm: &Frontmatter, display: &str) -> Result<RuleMeta, String> {
fm.check_unknown(
display,
&[
"id",
"revision",
"rfc",
"level",
"status",
"statement",
"domains",
"stages",
"when-paths",
"task-classes",
"checker",
"waivable",
],
)?;
let id = required_id(fm, "id", display)?;
let revision = required_revision(fm, display)?;
let rfc = required_id(fm, "rfc", display)?;
let level_raw = fm.required_scalar("level", display)?;
let level = RuleLevel::parse(&level_raw).ok_or_else(|| {
format!(
"{display}: field `level` is `{level_raw}` — RFC-2119 levels are must and \
should (D-B has no `may` row)"
)
})?;
let status_raw = fm.required_scalar("status", display)?;
let status = RuleStatus::parse(&status_raw).ok_or_else(|| {
format!(
"{display}: field `status` is `{status_raw}` — rule statuses are active and \
retired (D-B)"
)
})?;
let statement = fm.required_scalar("statement", display)?;
let domains = normalized_list(fm.required_list("domains", display)?);
let mut stages = Vec::new();
for raw in fm.required_list("stages", display)? {
let Some(stage) = RuleStage::parse(&raw) else {
return Err(format!(
"{display}: field `stages` element `{raw}` — stages are planning, \
implementation, validation, merge"
));
};
stages.push(stage);
}
if stages.is_empty() {
return Err(format!(
"{display}: field `stages` must list at least one stage — a rule applying \
nowhere is not a rule"
));
}
stages.sort_by_key(|s| s.as_str());
stages.dedup();
let mut when_paths = Vec::new();
if let Some(items) = fm.list("when-paths", display)? {
for item in items {
let path = Path::new(item);
if item.trim().is_empty()
|| path.is_absolute()
|| path.components().any(|part| {
!matches!(
part,
std::path::Component::CurDir | std::path::Component::Normal(_)
)
})
{
return Err(format!(
"{display}: field `when-paths` entries must be repo-relative paths \
without parent components: {item:?}"
));
}
let normalized = crate::merge_gate::normalize_relative_path(item, false);
if normalized.is_empty() || normalized == "." {
return Err(format!(
"{display}: field `when-paths` entries must name a repo path — omit \
the field to leave the rule unscoped"
));
}
when_paths.push(normalized);
}
}
let mut task_classes = Vec::new();
if let Some(items) = fm.list("task-classes", display)? {
task_classes.extend(items.iter().cloned());
}
let checker = match fm.scalar("checker", display)? {
Some(raw) => {
Some(Checker::parse(raw).map_err(|e| format!("{display}: field `checker`: {e}"))?)
}
None => None,
};
let waivable = optional_bool(fm, "waivable", display)?;
Ok(RuleMeta {
id,
revision,
rfc,
level,
status,
statement,
domains,
stages,
when_paths: normalized_list(when_paths),
task_classes: normalized_list(task_classes),
checker,
waivable,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pack::{SCHEMA_BASE, SCHEMA_CONTRACT, SCHEMA_STANDARDS};
fn pack_with(manifest: &str, files: &[(&str, &str)]) -> (tempfile::TempDir, PathBuf) {
let tmp = tempfile::tempdir().expect("tempdir");
let dir = tmp.path().join("pack");
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join(PACK_MANIFEST), manifest).unwrap();
for (rel, body) in files {
let path = dir.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, body).unwrap();
}
(tmp, dir)
}
const PACK_TOML: &str = r#"
[pack]
name = "zz-standards-pack"
schema = 4
[standards]
root = "standards"
[[gate]]
name = "zz-gate-one"
command = "cd ."
"#;
const RFC_MD: &str = "\
---
id: RFC-001
title: zz synthetic safety standard
status: approved
owner: zz-platform
effective-at: 2026-09-01T02:00:00+02:00
---
Prose rationale — never hashed.
";
const RULE_ONE: &str = "\
---
id: ZZ-RULE-001
revision: 1
rfc: RFC-001
level: must
status: active
statement: zz synthetic must statement one.
domains: [zz-domain]
stages: [implementation, validation]
checker: gate:zz-gate-one
waivable: false
---
Rule one prose.
";
const RULE_TWO: &str = "\
---
id: ZZ-RULE-002
revision: 2
rfc: RFC-001
level: should
status: active
statement: zz synthetic should statement two.
domains: [zz-other, zz-domain]
stages: [planning, implementation, validation, merge]
when-paths: [crates/]
task-classes: [implementation]
checker: agent-judgement
---
Rule two prose.
";
const CORPUS: &[(&str, &str)] = &[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md", RULE_ONE),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
];
fn synthetic_pack() -> (tempfile::TempDir, PathBuf) {
pack_with(PACK_TOML, CORPUS)
}
fn load_trusted(dir: &Path) -> StandardsManifest {
crate::pack::Pack::load_with_trust(dir, StandardsTrust::RepoTracked)
.expect("load")
.expect("a pack")
.standards
.expect("a standards manifest")
}
const EXPECTED_CANONICAL: &str = "\
kranz-standards-manifest v1
rfc RFC-001
title: zz synthetic safety standard
owner: zz-platform
status: approved
effective-at: 2026-09-01T00:00:00Z
supersedes:
rule ZZ-RULE-001
revision: 1
rfc: RFC-001
level: must
status: active
statement: zz synthetic must statement one.
domains:
- zz-domain
stages:
- implementation
- validation
when-paths:
task-classes:
checker: gate:zz-gate-one
waivable: false
rule ZZ-RULE-002
revision: 2
rfc: RFC-001
level: should
status: active
statement: zz synthetic should statement two.
domains:
- zz-domain
- zz-other
stages:
- implementation
- merge
- planning
- validation
when-paths:
- crates
task-classes:
- implementation
checker: agent-judgement
waivable: false
gate zz-gate-one
command: cd .
when-paths:
";
const EXPECTED_DIGEST: &str =
"8dfb505b203fea5f66286353defce0ef46118331fc45946b00b27c6bacd01df7";
#[test]
fn flight_rules_contract_synthetic_pack_loads_byte_stable_manifest_and_digest() {
let (_tmp, dir) = synthetic_pack();
let manifest = load_trusted(&dir);
assert_eq!(manifest.root, "standards");
assert_eq!(manifest.rfcs.len(), 1);
assert_eq!(manifest.rules.len(), 2);
assert_eq!(manifest.gate_bindings.len(), 1);
assert_eq!(manifest.canonical_text(), EXPECTED_CANONICAL);
assert_eq!(manifest.digest, EXPECTED_DIGEST);
let rfc = &manifest.rfcs[0];
assert_eq!(rfc.id, "RFC-001");
assert_eq!(rfc.status, RfcStatus::Approved);
assert_eq!(
rfc.effective_at.as_deref(),
Some("2026-09-01T00:00:00Z"),
"effective-at normalizes to UTC seconds"
);
let rule = manifest.rule("ZZ-RULE-002").expect("rule two");
assert_eq!(rule.revision, 2);
assert_eq!(rule.level, RuleLevel::Should);
assert_eq!(rule.domains, vec!["zz-domain", "zz-other"], "sorted");
assert_eq!(rule.when_paths, vec!["crates"], "normalized, slash-free");
assert_eq!(rule.checker, Some(Checker::AgentJudgement));
assert!(!rule.waivable);
assert_eq!(
manifest.effective_status(rule),
RfcStatus::Approved,
"an active rule inherits its RFC's lifecycle (D-B)"
);
}
#[test]
fn flight_rules_contract_renaming_files_and_dirs_preserves_identity_and_digest() {
let (_tmp, dir) = synthetic_pack();
let before = load_trusted(&dir);
let renamed = &[
("standards/RFC-001-renamed/rfc.md", RFC_MD),
(
"standards/RFC-001-renamed/rules/ZZ-RENAMED-001.md",
RULE_ONE,
),
("standards/RFC-001-renamed/rules/ZZ-RULE-002.md", RULE_TWO),
];
let (_tmp2, dir2) = pack_with(PACK_TOML, renamed);
let after = load_trusted(&dir2);
assert_eq!(before.digest, after.digest);
assert_eq!(before, after, "paths are not identity (D-C)");
}
#[test]
fn flight_rules_contract_prose_edits_do_not_churn_the_digest() {
let (_tmp, dir) = synthetic_pack();
let before = load_trusted(&dir);
let edited_rfc = RFC_MD.replace("never hashed", "EDITED rationale");
let edited_rule = RULE_ONE.replace("Rule one prose.", "COMPLETELY NEW PROSE.");
let corpus = &[
("standards/RFC-001-zz-safety/rfc.md", edited_rfc.as_str()),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
edited_rule.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
];
let (_tmp2, dir2) = pack_with(PACK_TOML, corpus);
let after = load_trusted(&dir2);
assert_eq!(
before.digest, after.digest,
"prose is rationale, not a second machine authority (D-C)"
);
}
#[test]
fn flight_rules_contract_changing_a_referenced_gate_declaration_changes_the_digest() {
let (_tmp, dir) = synthetic_pack();
let before = load_trusted(&dir);
let manifest_toml = PACK_TOML.replace("command = \"cd .\"", "command = \"cd ..\"");
let (_tmp2, dir2) = pack_with(&manifest_toml, CORPUS);
let after = load_trusted(&dir2);
assert_ne!(before.digest, after.digest);
let with_extra_gate =
format!("{PACK_TOML}\n[[gate]]\nname = \"zz-gate-two\"\ncommand = \"cd /\"\n");
let (_tmp3, dir3) = pack_with(&with_extra_gate, CORPUS);
let third = load_trusted(&dir3);
assert_eq!(before.digest, third.digest);
}
#[test]
fn flight_rules_contract_standards_section_requires_schema_four() {
for schema in [SCHEMA_BASE, SCHEMA_CONTRACT] {
let manifest = format!(
"[pack]\nname = \"x\"\nschema = {schema}\n\n[standards]\nroot = \"standards\"\n"
);
let (_tmp, dir) = pack_with(&manifest, &[]);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("[standards]"), "names the field: {err}");
assert!(err.contains("schema"), "says why: {err}");
}
}
#[test]
fn flight_rules_contract_standards_section_unknown_key_fails_closed() {
let manifest =
"[pack]\nname = \"x\"\nschema = 4\n\n[standards]\nroot = \"standards\"\nbogus = 1\n";
let (_tmp, dir) = pack_with(manifest, &[]);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("unknown field `bogus`"), "{err}");
assert!(err.contains("[standards]"), "{err}");
let manifest = "[pack]\nname = \"x\"\nschema = 4\n\n[standards]\nroot = \"missing\"\n";
let (_tmp2, dir2) = pack_with(manifest, &[]);
let err = crate::pack::Pack::load(&dir2).expect_err("must fail");
assert!(err.contains("root `missing` does not exist"), "{err}");
let manifest = "[pack]\nname = \"x\"\nschema = 4\n\n[standards]\nroot = \"../outside\"\n";
let (_tmp3, dir3) = pack_with(manifest, &[]);
let err = crate::pack::Pack::load(&dir3).expect_err("must fail");
assert!(err.contains("pack-relative path"), "{err}");
}
#[test]
fn flight_rules_contract_missing_and_unknown_frontmatter_fields_fail() {
let rule = RULE_ONE.replace("statement: zz synthetic must statement one.\n", "");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
rule.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("missing required field `statement`"), "{err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
let rule = RULE_ONE.replace("waivable: false", "waivable: false\nbogus: nope");
let (_tmp2, dir2) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
rule.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir2).expect_err("must fail");
assert!(err.contains("unknown frontmatter field `bogus`"), "{err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
let rfc = RFC_MD.replace("owner: zz-platform\n", "");
let (_tmp3, dir3) = pack_with(
PACK_TOML,
&[("standards/RFC-001-zz-safety/rfc.md", rfc.as_str())],
);
let err = crate::pack::Pack::load(&dir3).expect_err("must fail");
assert!(err.contains("missing required field `owner`"), "{err}");
assert!(err.contains("rfc.md"), "names the file: {err}");
}
#[test]
fn flight_rules_contract_invalid_level_status_and_stage_fail() {
for (from, to, needle) in [
("level: must", "level: may", "field `level` is `may`"),
(
"status: active",
"status: limbo",
"field `status` is `limbo`",
),
(
"stages: [implementation, validation]",
"stages: [implementation, guessing]",
"field `stages` element `guessing`",
),
(
"waivable: false",
"waivable: yes",
"field `waivable` must be exactly `true` or `false`",
),
] {
let rule = RULE_ONE.replace(from, to);
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
rule.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains(needle), "{from} → {to}: {err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
}
let rfc = RFC_MD.replace("status: approved", "status: wishful");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[("standards/RFC-001-zz-safety/rfc.md", rfc.as_str())],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("field `status` is `wishful`"), "{err}");
}
#[test]
fn flight_rules_contract_duplicate_ids_fail() {
let dupe = RULE_TWO.replace("ZZ-RULE-002", "ZZ-RULE-001");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md", RULE_ONE),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md",
dupe.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(
err.contains("duplicate standards id `ZZ-RULE-001`"),
"{err}"
);
let dupe = RULE_ONE.replace("id: ZZ-RULE-001", "id: RFC-001");
let (_tmp2, dir2) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
dupe.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir2).expect_err("must fail");
assert!(err.contains("duplicate standards id `RFC-001`"), "{err}");
}
#[test]
fn flight_rules_contract_orphan_rule_fails() {
let orphan = RULE_ONE.replace("rfc: RFC-001", "rfc: RFC-999");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
orphan.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("rule `ZZ-RULE-001`"), "{err}");
assert!(err.contains("RFC-999"), "names the missing parent: {err}");
assert!(err.contains("orphan"), "{err}");
}
#[test]
fn flight_rules_contract_bad_revision_and_checker_fail() {
for (from, to, needle) in [
(
"revision: 1",
"revision: 0",
"field `revision` must be a positive integer",
),
(
"revision: 1",
"revision: -2",
"field `revision` must be a positive integer",
),
(
"revision: 1",
"revision: two",
"field `revision` must be a positive integer",
),
(
"checker: gate:zz-gate-one",
"checker: gate:zz-undeclared",
"names no declared [[gate]]",
),
(
"checker: gate:zz-gate-one",
"checker: run-the-script",
"supported checker forms",
),
(
"checker: gate:zz-gate-one",
"checker: gate:",
"supported checker forms",
),
] {
let rule = RULE_ONE.replace(from, to);
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
rule.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains(needle), "{from} → {to}: {err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
}
}
#[test]
fn flight_rules_contract_approved_rule_requires_a_checker_draft_may_omit() {
let no_checker = RULE_ONE.replace("checker: gate:zz-gate-one\n", "");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
no_checker.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("rule `ZZ-RULE-001`"), "{err}");
assert!(err.contains("declares no checker"), "{err}");
let draft_rfc = RFC_MD.replace("status: approved", "status: draft");
let (_tmp2, dir2) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", draft_rfc.as_str()),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
no_checker.as_str(),
),
],
);
let manifest = load_trusted(&dir2);
assert_eq!(manifest.rules.len(), 1);
assert_eq!(manifest.rules[0].checker, None);
assert_eq!(
manifest.effective_status(&manifest.rules[0]),
RfcStatus::Draft
);
}
#[test]
fn flight_rules_contract_retired_rule_may_omit_its_checker() {
let enforced_rfc = RFC_MD.replace("status: approved", "status: enforced");
let retired = RULE_ONE
.replace("status: active", "status: retired")
.replace("checker: gate:zz-gate-one\n", "");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", enforced_rfc.as_str()),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
retired.as_str(),
),
],
);
let manifest = load_trusted(&dir);
assert_eq!(
manifest.effective_status(&manifest.rules[0]),
RfcStatus::Retired,
"the rule tombstone narrows an enforced RFC (D-B)"
);
}
#[test]
fn flight_rules_contract_caps_fail_promptly() {
let big = format!(
"{RULE_ONE}{}",
"x".repeat(MAX_STANDARDS_FILE_BYTES as usize + 1)
);
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
big.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("per-file cap"), "{err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
let mut files: Vec<(String, String)> = Vec::new();
for i in 0..=(MAX_STANDARDS_FILES) {
files.push((
format!("standards/RFC-D{i:04}/rfc.md"),
RFC_MD.replace("RFC-001", &format!("RFC-D{i:04}")),
));
}
let refs: Vec<(&str, &str)> = files
.iter()
.map(|(p, b)| (p.as_str(), b.as_str()))
.collect();
let (_tmp2, dir2) = pack_with(PACK_TOML, &refs);
let err = crate::pack::Pack::load(&dir2).expect_err("must fail");
assert!(err.contains("file cap"), "{err}");
let mut files: Vec<(String, String)> = vec![(
"standards/RFC-001-zz-safety/rfc.md".to_string(),
RFC_MD.to_string(),
)];
for i in 0..=(MAX_STANDARDS_RULES) {
let rule = RULE_ONE.replace("ZZ-RULE-001", &format!("ZZ-RULE-C{i:04}"));
files.push((
format!("standards/RFC-001-zz-safety/rules/ZZ-RULE-C{i:04}.md"),
rule,
));
}
let refs: Vec<(&str, &str)> = files
.iter()
.map(|(p, b)| (p.as_str(), b.as_str()))
.collect();
let (_tmp3, dir3) = pack_with(PACK_TOML, &refs);
let err = crate::pack::Pack::load(&dir3).expect_err("must fail");
assert!(err.contains("rule cap"), "{err}");
let mut files: Vec<(String, String)> = vec![(
"standards/RFC-001-zz-safety/rfc.md".to_string(),
RFC_MD.to_string(),
)];
for i in 0..250usize {
let rule = RULE_ONE
.replace("ZZ-RULE-001", &format!("ZZ-RULE-N{i:04}"))
.replace("zz synthetic must statement one.", &"s".repeat(4600));
files.push((
format!("standards/RFC-001-zz-safety/rules/ZZ-RULE-N{i:04}.md"),
rule,
));
}
let refs: Vec<(&str, &str)> = files
.iter()
.map(|(p, b)| (p.as_str(), b.as_str()))
.collect();
let (_tmp4, dir4) = pack_with(PACK_TOML, &refs);
let err = crate::pack::Pack::load(&dir4).expect_err("must fail");
assert!(err.contains("normalized standards manifest"), "{err}");
}
#[test]
fn flight_rules_contract_invalid_utf8_fails() {
let (_tmp, dir) = synthetic_pack();
let path = dir.join("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md");
let mut bytes = RULE_ONE.as_bytes().to_vec();
bytes.push(0xFF);
bytes.push(0xFE);
std::fs::write(&path, bytes).unwrap();
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("not valid UTF-8"), "{err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
}
#[test]
fn flight_rules_contract_frontmatter_subset_refuses_yaml_surprises() {
for (line, needle) in [
("statement: &anchor text", "anchors"),
("statement: *alias", "aliases"),
("statement: !!str text", "tags"),
("statement: |", "block scalars"),
("statement: 'single'", "single-quoted strings"),
("statement: {flow: map}", "flow mappings"),
("statement:", "empty value"),
("domains: [zz-domain", "unterminated `[`"),
("domains: [zz-domain,, zz-other]", "empty list element"),
] {
let rule = RULE_ONE.replace("statement: zz synthetic must statement one.", line);
let rule = if line.starts_with("domains:") {
RULE_ONE.replace("domains: [zz-domain]", line)
} else {
rule
};
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
rule.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains(needle), "{line}: {err}");
assert!(err.contains("ZZ-RULE-001.md"), "names the file: {err}");
}
let commented = RULE_ONE.replace(
"statement: zz synthetic must statement one.",
"statement: zz synthetic must statement one. # reviewed",
);
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
commented.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
],
);
let manifest = load_trusted(&dir);
assert_eq!(manifest.digest, EXPECTED_DIGEST, "comments never govern");
for bad_line in [" status: active", "- status: active"] {
let rule = RULE_ONE.replace("status: active", bad_line);
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
rule.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(
err.contains("unexpected indentation") || err.contains("unsupported field name"),
"{bad_line}: {err}"
);
}
let dupe = RULE_ONE.replace("level: must", "level: must\nlevel: should");
let (_tmp, dir) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
dupe.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("duplicate field `level`"), "{err}");
let unfenced = RULE_ONE.replace("---\nRule one prose.", "");
let (_tmp2, dir2) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
unfenced.as_str(),
),
],
);
let err = crate::pack::Pack::load(&dir2).expect_err("must fail");
assert!(err.contains("closing `---`"), "{err}");
}
#[test]
fn flight_rules_contract_external_pack_cannot_activate_enforced_rules() {
let (_tmp, dir) = synthetic_pack();
let pack = crate::pack::Pack::load_with_trust(&dir, StandardsTrust::External)
.expect("advisory loads")
.expect("a pack");
assert_eq!(pack.standards.as_ref().unwrap().rules.len(), 2);
let enforced_rfc = RFC_MD.replace("status: approved", "status: enforced");
let (_tmp2, dir2) = pack_with(
PACK_TOML,
&[
("standards/RFC-001-zz-safety/rfc.md", enforced_rfc.as_str()),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md", RULE_ONE),
],
);
let err = crate::pack::Pack::load_with_trust(&dir2, StandardsTrust::External)
.expect_err("must fail");
assert!(err.contains("external/untracked"), "{err}");
assert!(err.contains("rule `ZZ-RULE-001`"), "names the rule: {err}");
assert!(
err.contains("vendor the pack into the repo"),
"names the remedy: {err}"
);
let pack = crate::pack::Pack::load_with_trust(&dir2, StandardsTrust::RepoTracked)
.expect("tracked loads")
.expect("a pack");
assert_eq!(
pack.standards.as_ref().unwrap().rfcs[0].status,
RfcStatus::Enforced
);
}
#[test]
fn flight_rules_contract_schema_two_and_three_packs_carry_no_standards() {
for schema in [SCHEMA_BASE, SCHEMA_CONTRACT] {
let manifest = format!("[pack]\nname = \"zz-plain\"\nschema = {schema}\n");
let (_tmp, dir) = pack_with(&manifest, &[]);
let pack = crate::pack::Pack::load(&dir)
.expect("load")
.expect("a pack");
assert_eq!(pack.schema, schema);
assert!(pack.standards.is_none());
assert!(!pack.describe().contains("standards"), "byte-identical");
assert!(!crate::pack::render_lint(&pack).contains("digest"));
}
let (_tmp, dir) = pack_with("[pack]\nname = \"zz-bare-four\"\nschema = 4\n", &[]);
let pack = crate::pack::Pack::load(&dir)
.expect("load")
.expect("a pack");
assert_eq!(pack.schema, SCHEMA_STANDARDS);
assert!(pack.standards.is_none());
}
#[test]
fn flight_rules_contract_pack_lint_reports_the_standards_registration() {
let (_tmp, dir) = synthetic_pack();
let pack = crate::pack::Pack::load_with_trust(&dir, StandardsTrust::RepoTracked)
.expect("load")
.expect("a pack");
let report = crate::pack::render_lint(&pack);
assert!(
report.contains("standards (schema 4 root `standards`)"),
"{report}"
);
assert!(report.contains("digest: sha256:"), "{report}");
assert!(
report.contains("RFCs: 1 (draft 0, approved 1, enforced 0, retired 0)"),
"{report}"
);
assert!(
report.contains("rules: 2 (active 2, retired 0)"),
"{report}"
);
let manifest = pack.standards.as_ref().unwrap();
let rendered = render_manifest(manifest, StandardsTrust::RepoTracked);
assert!(rendered.contains("trust: repo-tracked"), "{rendered}");
assert!(
rendered.contains("ZZ-RULE-001 r1 — must, approved; checker gate:zz-gate-one"),
"{rendered}"
);
assert!(rendered.contains("digest: sha256:"), "{rendered}");
}
fn transitions(
base_files: Option<&[(&str, &str)]>,
proposed_files: &[(&str, &str)],
) -> Vec<String> {
let base = base_files.map(|files| {
let (tmp, dir) = pack_with(PACK_TOML, files);
let manifest = load_trusted(&dir);
drop(tmp);
manifest
});
let (_tmp, dir) = pack_with(PACK_TOML, proposed_files);
let proposed = load_trusted(&dir);
check_transitions(base.as_ref(), &proposed)
}
#[test]
fn flight_rules_contract_transition_lint_refuses_absent_or_draft_to_enforced() {
let enforced_rfc = RFC_MD.replace("status: approved", "status: enforced");
let proposed = &[
("standards/RFC-001-zz-safety/rfc.md", enforced_rfc.as_str()),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md", RULE_ONE),
];
let errors = transitions(None, proposed);
assert_eq!(errors.len(), 2, "RFC and rule both refuse: {errors:?}");
assert!(
errors
.iter()
.any(|e| e.contains("RFC `RFC-001`") && e.contains("absent/draft")),
"{errors:?}"
);
assert!(
errors
.iter()
.any(|e| e.contains("rule `ZZ-RULE-001`") && e.contains("absent")),
"{errors:?}"
);
let draft_rfc = RFC_MD.replace("status: approved", "status: draft");
let draft_rule = RULE_ONE.replace("checker: gate:zz-gate-one\n", "");
let base = &[
("standards/RFC-001-zz-safety/rfc.md", draft_rfc.as_str()),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
draft_rule.as_str(),
),
];
let errors = transitions(Some(base), proposed);
assert!(
errors
.iter()
.any(|e| e.contains("RFC `RFC-001`") && e.contains("draft")),
"{errors:?}"
);
assert!(
errors
.iter()
.any(|e| e.contains("rule `ZZ-RULE-001`") && e.contains("draft")),
"{errors:?}"
);
}
#[test]
fn flight_rules_contract_transition_lint_refuses_semantic_change_without_revision_bump() {
let changed = RULE_ONE.replace(
"statement: zz synthetic must statement one.",
"statement: zz REWRITTEN statement.",
);
let errors = transitions(
Some(CORPUS),
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
changed.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
],
);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("rule `ZZ-RULE-001`"), "{errors:?}");
assert!(
errors[0].contains("statement"),
"names the field: {errors:?}"
);
assert!(errors[0].contains("revision increment"), "{errors:?}");
let bumped = changed.replace("revision: 1", "revision: 2");
let errors = transitions(
Some(CORPUS),
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
bumped.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
],
);
assert!(errors.is_empty(), "{errors:?}");
let base_bumped = RULE_ONE.replace("revision: 1", "revision: 5");
let errors = transitions(
Some(&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
base_bumped.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
]),
CORPUS,
);
assert!(
errors.iter().any(|e| e.contains("moved backwards")),
"{errors:?}"
);
}
#[test]
fn flight_rules_contract_transition_lint_refuses_disappearing_ids_and_tombstone_reactivation() {
let errors = transitions(
Some(CORPUS),
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md", RULE_ONE),
],
);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("rule `ZZ-RULE-002`"), "{errors:?}");
assert!(errors[0].contains("cannot disappear"), "{errors:?}");
let retired_base = RULE_ONE.replace("status: active", "status: retired");
let errors = transitions(
Some(&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
retired_base.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
]),
CORPUS,
);
assert!(
errors
.iter()
.any(|e| e.contains("rule `ZZ-RULE-001`") && e.contains("tombstone")),
"{errors:?}"
);
}
#[test]
fn flight_rules_contract_transition_lint_accepts_reviewed_transitions() {
let enforced_rfc = RFC_MD.replace("status: approved", "status: enforced");
let errors = transitions(
Some(CORPUS),
&[
("standards/RFC-001-zz-safety/rfc.md", enforced_rfc.as_str()),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md", RULE_ONE),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
],
);
assert!(errors.is_empty(), "{errors:?}");
let retired = RULE_ONE.replace("status: active", "status: retired");
let errors = transitions(
Some(CORPUS),
&[
("standards/RFC-001-zz-safety/rfc.md", RFC_MD),
(
"standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md",
retired.as_str(),
),
("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md", RULE_TWO),
],
);
assert!(errors.is_empty(), "{errors:?}");
let errors = transitions(Some(CORPUS), CORPUS);
assert!(errors.is_empty(), "{errors:?}");
}
fn git_repo_with_files(
files: &[(String, String)],
) -> (tempfile::TempDir, PathBuf, crate::git_ops::GitRepo) {
let tmp = tempfile::tempdir().unwrap();
let root = tmp.path().join("repo");
std::fs::create_dir_all(&root).unwrap();
let git = |args: &[&str]| {
let out = std::process::Command::new("git")
.args(args)
.current_dir(&root)
.output()
.expect("spawn git");
assert!(out.status.success(), "git {args:?} failed: {out:?}");
};
git(&["init", "-q"]);
git(&["config", "user.email", "t@t"]);
git(&["config", "user.name", "t"]);
for (rel, body) in files {
let path = root.join(rel);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, body).unwrap();
}
git(&["add", "."]);
git(&["commit", "-qm", "pack"]);
let repo = crate::git_ops::GitRepo::open(&root).expect("git repo");
(tmp, root, repo)
}
fn vendored_files() -> Vec<(String, String)> {
let mut files = vec![("vendor/pack/pack.toml".to_string(), PACK_TOML.to_string())];
for (rel, body) in CORPUS {
files.push((format!("vendor/pack/{rel}"), (*body).to_string()));
}
files
}
#[test]
fn flight_rules_contract_load_at_ref_reads_tracked_blobs_not_the_worktree() {
let (_tmp, root, repo) = git_repo_with_files(&vendored_files());
let base = load_at_ref(&repo, "HEAD", "vendor/pack")
.expect("base load")
.expect("standards at HEAD");
let worktree = crate::pack::Pack::load_with_trust(
&root.join("vendor/pack"),
StandardsTrust::RepoTracked,
)
.expect("worktree load")
.expect("a pack");
let worktree = worktree.standards.as_ref().unwrap();
assert_eq!(base.digest, worktree.digest, "identical bytes agree");
let edited = RULE_ONE.replace(
"statement: zz synthetic must statement one.",
"statement: zz WORKTREE-ONLY EDIT.",
);
std::fs::write(
root.join("vendor/pack/standards/RFC-001-zz-safety/rules/ZZ-RULE-001.md"),
&edited,
)
.unwrap();
let base_after = load_at_ref(&repo, "HEAD", "vendor/pack")
.expect("base load")
.expect("standards at HEAD");
assert_eq!(base.digest, base_after.digest, "the base is pinned blobs");
let dirty = crate::pack::Pack::load_with_trust(
&root.join("vendor/pack"),
StandardsTrust::RepoTracked,
)
.expect("worktree load")
.expect("a pack");
let dirty = dirty.standards.as_ref().unwrap();
assert_ne!(
base_after.digest, dirty.digest,
"the worktree moved; the base did not"
);
let errors = check_transitions(Some(&base_after), dirty);
assert_eq!(errors.len(), 1, "{errors:?}");
assert!(errors[0].contains("rule `ZZ-RULE-001`"), "{errors:?}");
assert!(errors[0].contains("revision increment"), "{errors:?}");
let git = |args: &[&str]| {
let out = std::process::Command::new("git")
.args(args)
.current_dir(&root)
.output()
.expect("spawn git");
assert!(out.status.success(), "git {args:?} failed: {out:?}");
};
git(&["rm", "-rqf", "vendor"]);
git(&["commit", "-qm", "drop pack"]);
assert_eq!(
load_at_ref(&repo, "HEAD", "vendor/pack").expect("load"),
None,
"no pack at this ref"
);
}
#[cfg(unix)]
#[test]
fn flight_rules_contract_load_at_ref_refuses_a_tracked_symlink() {
use std::os::unix::fs::symlink;
let mut files = vendored_files();
files.retain(|(p, _)| !p.ends_with("ZZ-RULE-002.md"));
let (_tmp, root, repo) = git_repo_with_files(&files);
symlink(
"ZZ-RULE-001.md",
root.join("vendor/pack/standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md"),
)
.unwrap();
let git = |args: &[&str]| {
let out = std::process::Command::new("git")
.args(args)
.current_dir(&root)
.output()
.expect("spawn git");
assert!(out.status.success(), "git {args:?} failed: {out:?}");
};
git(&["add", "."]);
git(&["commit", "-qm", "add symlink"]);
let err = load_at_ref(&repo, "HEAD", "vendor/pack").expect_err("must fail");
assert!(err.contains("symlink"), "{err}");
assert!(err.contains("ZZ-RULE-002.md"), "names the path: {err}");
}
#[test]
fn flight_rules_contract_trust_for_dir_distinguishes_repo_tracked_from_external() {
let (_tmp, root, _repo) = git_repo_with_files(&vendored_files());
assert_eq!(
trust_for_dir(&root, &root.join("vendor/pack")),
StandardsTrust::RepoTracked
);
std::fs::create_dir_all(root.join("scratch/pack")).unwrap();
std::fs::write(root.join("scratch/pack/pack.toml"), PACK_TOML).unwrap();
assert_eq!(
trust_for_dir(&root, &root.join("scratch/pack")),
StandardsTrust::External
);
let outside = tempfile::tempdir().unwrap();
let pack = outside.path().join("pack");
std::fs::create_dir_all(&pack).unwrap();
std::fs::write(pack.join(PACK_MANIFEST), PACK_TOML).unwrap();
assert_eq!(
trust_for_dir(&root, &pack),
StandardsTrust::External,
"outside the repo is external"
);
}
#[cfg(unix)]
#[test]
fn flight_rules_contract_symlinked_parents_and_leaves_fail_no_follow() {
use std::os::unix::fs::symlink;
let (_tmp, dir) = synthetic_pack();
let target = dir.join("outside.md");
std::fs::write(&target, RULE_ONE).unwrap();
let link = dir.join("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md");
std::fs::remove_file(&link).unwrap();
symlink(&target, &link).unwrap();
let err = crate::pack::Pack::load(&dir).expect_err("must fail");
assert!(err.contains("symlink"), "{err}");
assert!(err.contains("never follows symlinks"), "{err}");
let (_tmp2, dir2) = synthetic_pack();
let real = dir2.join("real-rfc");
std::fs::rename(dir2.join("standards/RFC-001-zz-safety"), &real).unwrap();
symlink(&real, dir2.join("standards/RFC-001-zz-safety")).unwrap();
let err = crate::pack::Pack::load(&dir2).expect_err("must fail");
assert!(err.contains("symlink"), "{err}");
let (_tmp3, dir3) = synthetic_pack();
let fifo = dir3.join("standards/RFC-001-zz-safety/rules/ZZ-RULE-002.md");
std::fs::remove_file(&fifo).unwrap();
let status = std::process::Command::new("mkfifo")
.arg(&fifo)
.status()
.expect("spawn mkfifo");
assert!(status.success());
let err = crate::pack::Pack::load(&dir3).expect_err("must fail");
assert!(err.contains("not a regular file"), "{err}");
assert!(err.contains("ZZ-RULE-002.md"), "names the file: {err}");
let (_tmp4, dir4) = synthetic_pack();
std::fs::write(dir4.join("standards/notes.txt"), "stray").unwrap();
let err = crate::pack::Pack::load(&dir4).expect_err("must fail");
assert!(err.contains("not an RFC directory"), "{err}");
let (_tmp5, dir5) = synthetic_pack();
std::fs::create_dir_all(dir5.join("standards/RFC-001-zz-safety/rules/nested")).unwrap();
let err = crate::pack::Pack::load(&dir5).expect_err("must fail");
assert!(err.contains("no nested directories"), "{err}");
}
}