pub mod adoption;
pub mod anchors;
pub mod basis;
pub mod cache;
pub mod change;
pub mod claim;
pub mod command;
pub mod context;
pub mod coverage;
pub mod declaration;
pub mod dependency;
pub mod duplicate;
pub mod endpoint;
pub mod facet_blank;
pub mod facet_required;
pub mod facet_value;
pub mod fill;
pub mod finding;
pub mod fragment;
pub mod frontmatter;
pub mod gate;
pub mod identifier;
pub mod identity;
pub mod initial_dependency;
pub mod instance;
pub mod language;
pub mod lifecycle_state;
pub mod link_path;
pub mod observation;
pub mod outside_root;
pub mod paint;
pub mod participation;
pub mod patch;
pub mod placement;
pub mod promotion;
pub mod readset;
pub mod reciprocity;
pub mod register;
pub mod retention;
pub mod retired;
pub mod scope;
pub mod sections;
pub mod shape;
pub mod source_form;
pub mod state_set_twice;
pub mod suppression;
pub mod surface;
pub mod suspect;
pub mod target;
pub mod transition;
pub mod verification;
pub mod voice;
pub use adoption::Ledger;
#[doc(hidden)]
pub use cache::cached_form;
pub use cache::{rules_digest, Cache};
pub use context::{Context, Date};
pub use coverage::Coverage;
pub use fill::{filled, WIDTH};
pub use finding::{Finding, Severity};
pub use gate::{Recorded, Verdict};
pub use instance::{Input, Instance, Outcome};
pub use observation::{Observation, Observations};
pub use patch::Patch;
pub use readset::{ReadSet, Rule};
pub use register::{Bound, Register};
pub use scope::{
CorpusCheck, CorpusView, DocumentCheck, DocumentView, EdgeCheck, EdgeUnit, EdgeView, Grain,
NeighbourhoodCheck, NeighbourhoodView, Scope,
};
pub use shape::{Purpose, Shape};
pub use suppression::Inventory;
use headwater_census::census::Census;
use headwater_census::shelves::Taxonomy;
use headwater_graph::{Declarations, Graph};
pub const RULES: [&str; 39] = [
facet_required::RULE,
facet_value::RULE,
facet_blank::RULE,
identifier::RULE,
placement::RULE,
target::RULE,
suspect::RULE,
reciprocity::RULE,
endpoint::RULE,
dependency::RULE,
initial_dependency::RULE,
basis::RULE,
participation::RULE,
state_set_twice::RULE,
declaration::RULE,
identity::RULE,
duplicate::RULE,
claim::MISSING,
claim::STALE,
voice::RULE,
language::RULE,
retired::RULE,
surface::RULE,
command::RULE,
source_form::RULE,
sections::RULE,
fragment::RULE,
link_path::RULE,
promotion::RULE,
transition::RULE,
lifecycle_state::RULE,
retention::RULE,
coverage::RULE,
register::DISPOSITION,
register::MECHANISM,
register::OBSERVATION,
adoption::RULE,
outside_root::RULE,
verification::RULE,
];
pub struct Declared<'a> {
pub lock: &'a str,
pub taxonomy: &'a Taxonomy,
pub shape: &'a Shape,
pub relations: &'a Declarations,
pub config: &'a headwater_graph::Config,
pub register: &'a Register,
pub observations: &'a Observations,
pub adoption: Option<&'a headwater_yaml::Mapping>,
pub source: &'a str,
}
#[derive(Clone, Debug)]
pub struct Run {
pub instances: Vec<Instance>,
pub coverage: Coverage,
pub findings: Vec<Finding>,
pub adoption: Ledger,
pub suppressions: Inventory,
pub served: Vec<Serves>,
pub read_set: ReadSet,
pub register: register::Projection,
pub verifications: verification::Block,
pub change: Option<Scoped>,
pub cache: cache::Report,
}
#[derive(Clone, Debug)]
pub struct Serves {
pub rule: &'static str,
pub scope: Scope,
pub version: u32,
pub obligation: Bound,
pub exportable_as: scope::ExportTargets,
}
fn registry() -> [(&'static str, Scope, u32, scope::ExportTargets); RULES.len()] {
[
(
facet_required::RULE,
scope::document_scope::<facet_required::Required>(),
scope::document_version::<facet_required::Required>(),
scope::document_exports::<facet_required::Required>(),
),
(
facet_value::RULE,
scope::document_scope::<facet_value::Values>(),
scope::document_version::<facet_value::Values>(),
scope::document_exports::<facet_value::Values>(),
),
(
facet_blank::RULE,
scope::document_scope::<facet_blank::Blank>(),
scope::document_version::<facet_blank::Blank>(),
scope::document_exports::<facet_blank::Blank>(),
),
(
identifier::RULE,
scope::document_scope::<identifier::Identifier>(),
scope::document_version::<identifier::Identifier>(),
scope::document_exports::<identifier::Identifier>(),
),
(
placement::RULE,
scope::document_scope::<placement::Placement>(),
scope::document_version::<placement::Placement>(),
scope::document_exports::<placement::Placement>(),
),
(
target::RULE,
scope::edge_scope::<target::Targets<'_>>(),
scope::edge_version::<target::Targets<'_>>(),
scope::edge_exports::<target::Targets<'_>>(),
),
(
suspect::RULE,
scope::edge_scope::<suspect::Suspect<'_>>(),
scope::edge_version::<suspect::Suspect<'_>>(),
scope::edge_exports::<suspect::Suspect<'_>>(),
),
(
reciprocity::RULE,
scope::edge_scope::<reciprocity::Reciprocity>(),
scope::edge_version::<reciprocity::Reciprocity>(),
scope::edge_exports::<reciprocity::Reciprocity>(),
),
(
endpoint::RULE,
scope::edge_scope::<endpoint::Endpoints<'_>>(),
scope::edge_version::<endpoint::Endpoints<'_>>(),
scope::edge_exports::<endpoint::Endpoints<'_>>(),
),
(
dependency::RULE,
scope::edge_scope::<dependency::Dependency<'_>>(),
scope::edge_version::<dependency::Dependency<'_>>(),
scope::edge_exports::<dependency::Dependency<'_>>(),
),
(
initial_dependency::RULE,
scope::edge_scope::<initial_dependency::InitialDependency<'_>>(),
scope::edge_version::<initial_dependency::InitialDependency<'_>>(),
scope::edge_exports::<initial_dependency::InitialDependency<'_>>(),
),
(
basis::RULE,
scope::edge_scope::<basis::Basis<'_>>(),
scope::edge_version::<basis::Basis<'_>>(),
scope::edge_exports::<basis::Basis<'_>>(),
),
(
participation::RULE,
scope::neighbourhood_scope::<participation::Participation<'_>>(),
scope::neighbourhood_version::<participation::Participation<'_>>(),
scope::neighbourhood_exports::<participation::Participation<'_>>(),
),
(
state_set_twice::RULE,
scope::corpus_scope::<state_set_twice::StateSetTwice<'_>>(),
scope::corpus_version::<state_set_twice::StateSetTwice<'_>>(),
scope::corpus_exports::<state_set_twice::StateSetTwice<'_>>(),
),
(
declaration::RULE,
scope::document_scope::<declaration::Unusable<'_>>(),
scope::document_version::<declaration::Unusable<'_>>(),
scope::document_exports::<declaration::Unusable<'_>>(),
),
(
identity::RULE,
scope::document_scope::<identity::Identity<'_>>(),
scope::document_version::<identity::Identity<'_>>(),
scope::document_exports::<identity::Identity<'_>>(),
),
(
duplicate::RULE,
scope::corpus_scope::<duplicate::Duplicate>(),
scope::corpus_version::<duplicate::Duplicate>(),
scope::corpus_exports::<duplicate::Duplicate>(),
),
(
claim::MISSING,
scope::corpus_scope::<claim::Missing<'_>>(),
scope::corpus_version::<claim::Missing<'_>>(),
scope::corpus_exports::<claim::Missing<'_>>(),
),
(
claim::STALE,
scope::corpus_scope::<claim::Stale<'_>>(),
scope::corpus_version::<claim::Stale<'_>>(),
scope::corpus_exports::<claim::Stale<'_>>(),
),
(
voice::RULE,
scope::document_scope::<voice::Voice>(),
scope::document_version::<voice::Voice>(),
scope::document_exports::<voice::Voice>(),
),
(
language::RULE,
scope::document_scope::<language::Language>(),
scope::document_version::<language::Language>(),
scope::document_exports::<language::Language>(),
),
(
retired::RULE,
scope::document_scope::<retired::Retired>(),
scope::document_version::<retired::Retired>(),
scope::document_exports::<retired::Retired>(),
),
(
surface::RULE,
scope::document_scope::<surface::LocalPath>(),
scope::document_version::<surface::LocalPath>(),
scope::document_exports::<surface::LocalPath>(),
),
(
command::RULE,
scope::document_scope::<command::Undeclared>(),
scope::document_version::<command::Undeclared>(),
scope::document_exports::<command::Undeclared>(),
),
(
source_form::RULE,
scope::document_scope::<source_form::SourceForm>(),
scope::document_version::<source_form::SourceForm>(),
scope::document_exports::<source_form::SourceForm>(),
),
(
sections::RULE,
scope::document_scope::<sections::Sections>(),
scope::document_version::<sections::Sections>(),
scope::document_exports::<sections::Sections>(),
),
(
fragment::RULE,
scope::corpus_scope::<fragment::Fragments>(),
scope::corpus_version::<fragment::Fragments>(),
scope::corpus_exports::<fragment::Fragments>(),
),
(
link_path::RULE,
scope::corpus_scope::<link_path::Paths>(),
scope::corpus_version::<link_path::Paths>(),
scope::corpus_exports::<link_path::Paths>(),
),
(
promotion::RULE,
scope::document_scope::<promotion::Promoted>(),
scope::document_version::<promotion::Promoted>(),
scope::document_exports::<promotion::Promoted>(),
),
(
transition::RULE,
scope::document_scope::<transition::Transition<'_>>(),
scope::document_version::<transition::Transition<'_>>(),
scope::document_exports::<transition::Transition<'_>>(),
),
(
lifecycle_state::RULE,
scope::document_scope::<lifecycle_state::StateAdmitted<'_>>(),
scope::document_version::<lifecycle_state::StateAdmitted<'_>>(),
scope::document_exports::<lifecycle_state::StateAdmitted<'_>>(),
),
(
retention::RULE,
scope::corpus_scope::<retention::Retention<'_>>(),
scope::corpus_version::<retention::Retention<'_>>(),
scope::corpus_exports::<retention::Retention<'_>>(),
),
(
coverage::RULE,
coverage::SCOPE,
coverage::VERSION,
coverage::EXPORTABLE_AS,
),
(
register::DISPOSITION,
register::SCOPE,
register::VERSION,
register::EXPORTABLE_AS,
),
(
register::MECHANISM,
register::SCOPE,
register::VERSION,
register::EXPORTABLE_AS,
),
(
register::OBSERVATION,
register::SCOPE,
register::VERSION,
register::EXPORTABLE_AS,
),
(
adoption::RULE,
adoption::SCOPE,
adoption::VERSION,
adoption::EXPORTABLE_AS,
),
(
outside_root::RULE,
outside_root::SCOPE,
outside_root::VERSION,
outside_root::EXPORTABLE_AS,
),
(
verification::RULE,
scope::edge_scope::<verification::Verified<'_>>(),
scope::edge_version::<verification::Verified<'_>>(),
scope::edge_exports::<verification::Verified<'_>>(),
),
]
}
#[derive(Clone, Debug)]
pub struct Partition {
pub target: String,
pub exported: Vec<&'static str>,
pub unexported: Vec<&'static str>,
}
pub fn partition(target: &str) -> Partition {
let mut exported = Vec::new();
let mut unexported = Vec::new();
for (rule, _, _, targets) in registry() {
match targets.contains(&target) {
true => exported.push(rule),
false => unexported.push(rule),
}
}
Partition {
target: target.to_string(),
exported,
unexported,
}
}
pub fn run(
census: &Census,
graph: &Graph,
declared: &Declared<'_>,
claims: &claim::Claims,
ctx: &Context,
cache: &mut Cache,
) -> Run {
let required = facet_required::Required::over(declared.shape);
let values = facet_value::Values::over(declared.shape);
let blank = facet_blank::Blank::over(declared.shape);
let identifiers =
identifier::Identifier::over(declared.shape, &declared.config.identifier_facet);
let placement = placement::Placement::over(declared.taxonomy);
let targets = target::Targets::over(declared.relations);
let suspect = suspect::Suspect::over(declared.relations, declared.shape);
let reciprocity = reciprocity::Reciprocity::over(declared.relations, declared.shape);
let endpoints = endpoint::Endpoints::over(declared.relations, declared.shape);
let dependency = dependency::Dependency::over(declared.relations, declared.shape);
let initial_dependency =
initial_dependency::InitialDependency::over(declared.relations, declared.shape);
let basis = basis::Basis::over(declared.relations);
let verified = verification::Verified::over(declared.relations, declared.observations);
let participation = participation::Participation::over(declared.shape, declared.relations);
let set_twice = state_set_twice::StateSetTwice::over(declared.relations, declared.shape);
let declarations = declaration::Unusable::over(declared.relations, declared.shape);
let identities = identity::Identity::over(
declared.relations,
declared.shape,
&declared.config.identifier_facet,
);
let duplicates = duplicate::Duplicate::over(&declared.config.identifier_facet);
let claim_missing = claim::Missing::over(declared.shape, declared.taxonomy, &graph.index);
let claim_stale = claim::Stale::over(
&declared.config.identifier_facet,
declared.shape,
&graph.index,
);
let voice = voice::Voice::over(declared.shape);
let language = language::Language::over(declared.shape);
let retired = retired::Retired::over(declared.shape);
let surface = surface::LocalPath::over(declared.shape);
let commands = command::Undeclared::over(declared.shape);
let source_form = source_form::SourceForm::over(declared.shape);
let sections = sections::Sections::over(declared.shape);
let fragments = fragment::Fragments;
let link_paths = link_path::Paths;
let promoted = promotion::Promoted;
let transitions = transition::Transition::over(declared.shape);
let standing = lifecycle_state::StateAdmitted::over(declared.shape);
let retention = retention::Retention::over(declared.taxonomy, declared.shape);
let digests = scope::Digests::of(census);
let mut instances = scope::over_documents(&required, census, graph, ctx, cache);
instances.extend(scope::over_documents(&values, census, graph, ctx, cache));
instances.extend(scope::over_documents(&blank, census, graph, ctx, cache));
instances.extend(scope::over_documents(
&identifiers,
census,
graph,
ctx,
cache,
));
instances.extend(scope::over_documents(&placement, census, graph, ctx, cache));
instances.extend(scope::over_edges(
&targets,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&suspect,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&reciprocity,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&endpoints,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&dependency,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&initial_dependency,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&basis,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_edges(
&verified,
census,
graph,
&digests,
declared.observations,
ctx,
cache,
));
instances.extend(scope::over_neighbourhoods(
&participation,
census,
graph,
&digests,
ctx,
cache,
));
if set_twice.can_clash() {
instances.extend(scope::over_corpus(
&set_twice, census, graph, claims, ctx, cache,
));
}
instances.extend(scope::over_documents(
&declarations,
census,
graph,
ctx,
cache,
));
instances.extend(scope::over_documents(
&identities,
census,
graph,
ctx,
cache,
));
instances.extend(scope::over_corpus(
&duplicates,
census,
graph,
claims,
ctx,
cache,
));
instances.extend(scope::over_corpus(
&claim_missing,
census,
graph,
claims,
ctx,
cache,
));
instances.extend(scope::over_corpus(
&claim_stale,
census,
graph,
claims,
ctx,
cache,
));
instances.extend(scope::over_documents(&voice, census, graph, ctx, cache));
instances.extend(scope::over_documents(&language, census, graph, ctx, cache));
instances.extend(scope::over_documents(&retired, census, graph, ctx, cache));
instances.extend(scope::over_documents(&surface, census, graph, ctx, cache));
instances.extend(scope::over_documents(&commands, census, graph, ctx, cache));
instances.extend(scope::over_documents(
&source_form,
census,
graph,
ctx,
cache,
));
instances.extend(scope::over_outside_root(&language, census, cache));
instances.extend(scope::over_outside_root(&retired, census, cache));
instances.extend(scope::over_outside_root(&source_form, census, cache));
instances.extend(scope::over_documents(§ions, census, graph, ctx, cache));
instances.extend(scope::over_corpus(
&fragments, census, graph, claims, ctx, cache,
));
instances.extend(scope::over_corpus(
&link_paths,
census,
graph,
claims,
ctx,
cache,
));
instances.extend(scope::over_documents(&promoted, census, graph, ctx, cache));
instances.extend(scope::over_documents(
&transitions,
census,
graph,
ctx,
cache,
));
instances.extend(scope::over_documents(&standing, census, graph, ctx, cache));
instances.extend(scope::over_corpus(
&retention, census, graph, claims, ctx, cache,
));
let coverage = Coverage::of(census, &instances);
let mut register = register::Projection::of(declared.register, declared.observations);
let declared_payload = match declared.adoption {
Some(block) => adoption::read(block, &RULES),
None => adoption::Declared::default(),
};
let mut findings: Vec<Finding> = instances
.iter()
.flat_map(|instance| instance.findings().iter().cloned())
.collect();
findings.extend(coverage.findings());
findings.extend(register.findings(declared.source));
findings.extend(adoption::expired(
&declared_payload,
declared.source,
ctx.now(),
));
findings.extend(outside_root::findings(census, declared.source));
let served: Vec<Serves> = registry()
.into_iter()
.map(|(rule, scope, version, exportable_as)| Serves {
rule,
scope,
version,
obligation: declared.register.bound(rule),
exportable_as,
})
.collect();
for finding in &mut findings {
finding.obligation = match served.iter().find(|served| served.rule == finding.rule) {
Some(Serves {
obligation: Bound::To(obligation),
..
}) => Some(obligation.clone()),
_ => None,
};
}
let (findings, adoption) =
adoption::apply(finding::sorted(findings), declared_payload, ctx.now());
let (declared_suppressions, refused) = suppression::declared(census, &RULES);
let (findings, suppressions) =
suppression::apply(findings, declared_suppressions, refused, ctx.now());
register.escaped_from(declared.register, &suppressions);
register.pending_from(declared.register, &adoption);
let rules: Vec<Rule> = served
.iter()
.map(|served| Rule {
name: served.rule,
version: served.version,
needs_clock: served.scope.needs_clock(),
needs_prior: served.scope.needs_prior(),
})
.collect();
let mut read_set = ReadSet::of(declared.lock, ctx.now(), &rules, &instances);
if let Some(digest) = declared.observations.read_set_digest() {
if let Err(at) = read_set
.inputs
.binary_search_by(|known| known.path.as_str().cmp(observation::PATH))
{
read_set
.inputs
.insert(at, Input::new(observation::PATH, digest));
}
}
let change = ctx.change().map(|change| Scoped {
named: change.named(),
unmatched: change.unmatched().into_iter().map(str::to_string).collect(),
promotions: findings
.iter()
.filter(|finding| finding.rule == promotion::RULE)
.count(),
});
Run {
instances,
coverage,
findings,
adoption,
suppressions,
served,
read_set,
register,
verifications: verification::Block::of(
declared.relations,
census,
graph,
declared.observations,
),
change,
cache: cache.report(),
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Scoped {
pub named: change::Named,
pub unmatched: Vec<String>,
pub promotions: usize,
}
impl Scoped {
pub fn render(&self) -> String {
use std::fmt::Write;
let mut out = String::new();
let _ = writeln!(
out,
"scoped to a change: {} documents named, {} added, {} with a prior version this run \
read",
self.named.documents, self.named.added, self.named.carried
);
if self.named.unreadable > 0 {
let _ = writeln!(
out,
" {:5} prior versions did not read, and every instance over one is skipped with \
its reason rather than passed",
self.named.unreadable
);
}
if !self.unmatched.is_empty() {
let _ = writeln!(
out,
" {:5} named no row of this corpus, so nothing was checked over them:",
self.unmatched.len()
);
for path in &self.unmatched {
let _ = writeln!(out, " {path}");
}
}
let _ = writeln!(
out,
" {:5} promoted from `{}` to `{}`. Nothing declares how many promotions in one \
change is too many",
self.promotions,
promotion::FROM,
promotion::TO
);
out
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Detail {
EveryInstance,
Findings,
Totals,
}
impl Run {
pub fn has_errors(&self) -> bool {
self.findings
.iter()
.any(|finding| finding.severity == Severity::Error)
}
pub fn counts(&self) -> Vec<(Severity, usize)> {
[Severity::Error, Severity::Warn, Severity::Info]
.into_iter()
.map(|severity| {
(
severity,
self.findings
.iter()
.filter(|finding| finding.severity == severity)
.count(),
)
})
.collect()
}
pub fn per_rule(&self) -> Vec<(&'static str, usize)> {
RULES
.iter()
.map(|rule| {
(
*rule,
self.instances
.iter()
.filter(|instance| instance.rule == *rule)
.count(),
)
})
.collect()
}
pub fn render(&self, detail: Detail, mode: crate::paint::ColorMode) -> String {
use std::fmt::Write;
let mut out = String::new();
if let Some(change) = &self.change {
out.push_str(&change.render());
}
out.push_str(&self.coverage.render());
out.push_str(&self.adoption.render(mode));
let _ = writeln!(out, "{}", self.read_set.summary());
for (rule, count) in self.per_rule() {
if count > 0 {
let _ = writeln!(out, " {count:5} instances of {rule}");
}
}
out.push_str(&self.suppressions.render());
let _ = writeln!(
out,
"{}",
crate::paint::paint(
crate::paint::Role::Heading,
"rules, and for each the scope that binds it",
mode
)
);
for served in &self.served {
let _ = writeln!(out, " {}\n {}", served.rule, served.scope.render());
}
out.push_str(&self.register.render());
out.push_str(&self.verifications.render());
if detail != Detail::Totals {
let _ = writeln!(out, "{} findings", self.findings.len());
for (severity, count) in self.counts() {
if count > 0 {
let _ = writeln!(
out,
" {count:5} {}",
crate::paint::severity_word(severity, mode)
);
}
}
}
if detail == Detail::EveryInstance {
out.push_str("\ninstances\n");
for instance in &self.instances {
let _ = writeln!(
out,
" {} {}\n {}",
instance.rule,
instance.paths().join(" + "),
match &instance.outcome {
Outcome::Passed => "passed".to_string(),
Outcome::Failed(_) => "failed".to_string(),
Outcome::Skipped(reason) => format!("skipped: {reason}"),
}
);
}
}
if !self.findings.is_empty() && detail != Detail::Totals {
out.push('\n');
for finding in &self.findings {
out.push_str(&finding.render(mode));
}
}
out
}
}