headwater-check 0.4.0

Generates the rules from the taxonomy, runs them, computes coverage against the census, and keys each instance on what it read and on the clock it was handed
Documentation

The runner: seventeen checks, coverage against the census, a published read set, a suppression inventory, and text findings in one order.

Spec 12 splits a run in two. Phase A classifies and builds, and it is [headwater_census] and [headwater_graph]. Phase B runs the checks over the graph that Phase A produced, and it is this crate. The order is the answer to the silent pass: the denominator is fixed before any check starts.

Where the rules come from

Fifteen of the seventeen are generated. None of them names a facet, a kind, a relation, an identifier scheme or a number of days: each reads a declaration out of the resolved taxonomy and instantiates itself over whatever that declaration produced. That is what spec 12 means by "a new facet or relation in the taxonomy produces its checks with no code", and it is why the rule list is short while the instance count is not. [coverage] is the runner's own accounting. [fragment] and [duplicate] read no declaration, because the language has no member that turns prose-link resolution or identifier uniqueness on or off: spec 3 states the second of them of every corpus.

Four of the five origins are represented. Shape, Graph and Document are here, and Plugin is not. Corpus in that table is an origin — a rule generated from a declaration that needs many documents — and no such declaration exists yet. [duplicate] is corpus-scoped and Graph-origin, which is the distinction the two lists have always drawn: the origin is what a rule reads a declaration from, and the grain is what one instance covers.

The ten Graph-origin rules span all four grains. [target], [reciprocity], [endpoint], [dependency], [initial_dependency] and [basis] are edge-grained, [participation] is neighbourhood-grained, [duplicate] is corpus-grained, and [declaration] and [identity] are document-grained. The last two are the ones worth stating: they route the phase-A defects that stop an edge from existing, and an edge-scoped instance exists per edge, so no edge-scoped rule reaches a block whose entries produced none. The origin is what a rule reads and the grain is what one instance covers, and the two were never one statement.

The four Document-origin rules are generated from a declaration in the same sense the others are, and two of them meet a limit the language puts there. voice_regime.forbid names categories and states no set of them, and language_regime.controlled names a language and states no set either. So the engine holds a closed set of each, and an instance that meets a name outside it skips with that name in the reason rather than passing.

What is deliberately absent, and where each piece goes

Two parts of the designed check layer are not here, and neither is an oversight. Suppression used to be the third. It is now [suppression]: the runner filters findings, records what it filtered, and feeds the inventory into the coverage report, which is where spec 12 puts it.

Change-scoped evaluation, under the name --changed-only. Every instance is created on every run, and the flag does not exist. #58 declined to build it and measured why: an instance is keyed on the content hashes of what it read, and [cache] serves the ones nothing touched, so a warm run over this repository takes about 57 ms against about 476 ms with no cache (HW-OBL-0080 holds the conditions). The cache derives what moved from the bytes rather than from a list a caller supplies, and spec 6 forbids a flag that puts an input into a verdict which no reviewer sees. What is left unscoped is Phase A, which no flag reaches.

The generated obligation register. Every rule below now names the obligation it serves, because the base package declares obligations and controls and [register] is the path from a rule to its obligation. What is absent is the register as an artifact: coverage by obligation, dispositions, and the control health that spec 4 asks a projection to carry. That is #59.

Scope is a type, and [scope] is where that holds

Spec 12 rules that a check receives a scoped view and cannot ask for a wider one, and that the enforcement is the feature. Each rule below implements one scope trait, and that trait is the only way to receive the matching view. [run] names the seventeen checks it runs, which is the whole of registration. The scope a trait fixes is now read twice: once for the report, and once as a component of the cache key that spec 12 derives from the same fact. The injected clock rides the same declaration, so a rule that reads a date cannot be left out of its own key ([cache]).

Neighbourhood is here, at depth 1, because [participation] needed a grain that Edge does not reach: an edge-scoped instance exists per edge, and a participation expectation is about an edge that nobody declared. Corpus is here for the same kind of reason and one step further out: [duplicate] is about two documents that no edge connects, so no relational grain reaches the pair. Shelf is still absent, and [duplicate] states why it could not have carried this rule either. [coverage] is corpus-grained and is still the runner's accounting rather than a check, and it now says which of the two facts about that grain was the reason.

Three phase-A outcomes stay in phase A, and one of them could not

Spec 12 calls an unparseable file, an unclassifiable path, a dangling edge and an ambiguous shelf match structural findings. Three of the four are a census row. The census accounts for each one with the row that names the author who can act, [coverage] reads that census, and a document with no instance is already a finding. So this runner does not re-report them: a second report of one fact sends its author to two places.

A dangling edge is the member with no row, and that made it the member with no rule. A row is a file and an edge is not one, so nothing in the coverage account reaches it. The graph printed it under its own heading and it answered to no obligation, carried no severity, and left --strict exiting 0 over a corpus whose edges pointed at nothing. [target] closes that, and #59 supplied what it was waiting for: the base package declares the obligation and the control, so the finding travels the same binding as every other one.

The rest of headwater_graph::Problem is routed too, and at the other grain. A relations: block that is not a mapping, an unknown relation name, an unusable entry, an entry with no to and a repeated triple are [declaration]. A source document with no identifier is [identity], with the two identifier-index defects that say the same thing from the other side. Each of those stops an edge from existing, so the unit that survives is the document that wrote the block, and the report the build already produced reaches the check on the view rather than beside it.

Every defect of phase A now reaches a rule, and the last one to arrive is the one that needed a fifth grain. Two documents that claim one identifier are headwater_graph::index::Defect::Duplicate, a document-scoped instance reads one of the two, and [duplicate] reads the corpus. It is the first corpus-scoped check this engine carries, and spec 12 calls those the barriers.