Checks: where a check comes from¶
What it is for¶
kac declares every check it can report against a corpus (one repository of knowledge records kept in git) in the
schema, and prints that declaration back with kac checks. The schema
decides what runs and against which type, so nothing is hard-coded per type.
Only an error fails the build. info is for a check declared in _checks.yaml. A type's own rules report at error
or warning, which is what
meta/type.schema.json limits
a severity: there to.
Read this page when you are adding a check, or deciding whether the check you want already exists.
validate says which files a check runs against.
What it is not¶
It is not validate. kac checks reads the schema and prints what could fire. It opens no record.
It is not the table on a type page. That table is grouped and worded by hand, and several catalogue ids fold into
one row of it. kac checks exits non-zero once the two have drifted.
It is not a prose reviewer. A check that reads prose matches a closed set of words: the hedges no-hedged-ordering
warns on, the modals a type declares, a heading title, a literal link target. An open-class rule, such as "a step opens
with an imperative verb", belongs in a standard and in the skill an agent loads.
How it works¶
A check is defined once, in the schema.
.schema/_checks.yaml declares the
checks that run against every document, and the ones that read the schema or the corpus. An entry gives its severity,
what it proves correct, and whether it belongs on a type page. A type's own rules are declared beside the type, in
.schema/<type>.yaml. Between them they are every check the validator can report.
The schema itself¶
kac checks the schema before it reads any document, and a finding there names the schema file and the key.
What the schema is held to lists what that pass reports.
A type's own rules¶
A rule fires against the documents of the type whose schema declares it. A rule written as an expr:, a one-line
condition the schema states and the tool evaluates, reports under its own id. Adding one is adding YAML rather than
editing this tool, and Rule expressions is the reference for what one may say.
A rule written in C# names the check id it reports under: no-dependency-cycles reports as dependency-cycle.
Checks in C# says when a rule needs one, and how it is written.
A list the schema asks an author to fill takes a floor, set with min-items:. list-order warns on a list out of
alphabetical order. A list whose order means something declares order: as-written, and list-order passes over it.
A report's sources declares it, because kac report writes the corpus first. A rule with no severity: runs nothing,
as A rule with no severity: says.
A rule that counts words, links or days sets a ratio, a ceiling or a window. Three windows are fixed:
| Rule | Window |
|---|---|
high-risk-review-window |
184 days from accepted-on to review-by, on a deviation whose risk is high |
staleness |
92 days for quarterly and 366 for annual, the longest quarter and the longest year |
staleness-loud |
92 days for a runbook, whatever its rehearsal-frequency |
staleness measures quarterly and annual alone. on-change and per-release state an event, so a process using
either is reported only where last-rehearsed is never. staleness-loud reads last-rehearsed.at alone, so a
tabletop walk earns the same 92 days as a live one.
A rule that matches text keeps its pattern in .schema/. Most such rules read the document as written, so a
credential pasted into a fenced block is still found. target-is-measurable reads a frontmatter value instead.
Code is excluded from every link and marker check, because a fenced or indented block parses to no link and no marker.
The key-order rule¶
Two files declare field order, and both name status. _universal.yaml orders
id, type, tier, status, owner, sources, tags. The type file orders the rest, which for an ADR is
status, decided-on, supersedes, superseded-by, deciders, related. Neither file states one order for the merged set.
kac checks a document against both chains at once. Every pair the schema orders must hold, and a pair it leaves
alone, such as owner against decided-on, is free. tags before id, or related before status, fails.
Which check reports a fault¶
One fault is reported once, by whichever check owns it:
required-fieldreports an absent field, so arequired-when:naming that field asks for nothing further.reciprocalleaves a dangling or wrong-type reference toref-resolves.clause-tablereports a broken table and stops the pass, and a missing section belongs torequired-section.date-formatreports a day the calendar does not have under the same id as a malformed one.
Faults nothing else would catch¶
| Check | Refuses |
|---|---|
part-empty |
a part declared by a heading with nothing under it, which would export with no words |
label-canonical |
a reference label spelled differently from the id it names, as [ADR-0013] |
ref-resolves |
an id pointing at a record of the wrong type, as well as one pointing at nothing |
Known limits¶
Some checks run only under validate --since. immutable-after-accepted, immutable-after-published,
clause-id-kept, rule-id-kept and retire-not-delete compare a record with its state at an earlier revision, so a
run that names none skips them. Comparing with a base revision
says what each reports.
A clause or a rule can change its meaning and keep its id. clause-id-kept and rule-id-kept read the id alone,
so a reworded part passes whatever the new words commit to. A citation of the id then reaches an obligation its author
never read.
Changing the id of a rejected ADR raises no finding. A record is paired across the two revisions by its id, so a
new id reads as one record deleted and another added. retire-not-delete warns only where the deleted record's status
was its type's in-force: value, and an ADR is in force at accepted, not rejected.
The typo allowance can change what a sentence says. Each changed word may sit up to two edits from the word it
replaced, so not can become now and 12 can become 42 with no finding.