canon-ir/1, the compiled form
Generated from the module documentation of crates/canon/src/ir/mod.rs.
canon-ir/1, the normalized form evaluators consume (design § 30).
compile is a pure function of a protocol/1 document: it validates the document and, when it
is valid, returns its Ir. The IR differs from the source in exactly these ways:
- Canonical ordering. Declarations are keyed and ordered by identifier, not by the order they
are written in. The members of an
alloranypredicate are sorted bycanonical_order, and an action's capability requirements, the evidence kinds it may produce, the claims an invalidation rule invalidates, the values a case input declares, the values an input test lists and the dimensions an evidence match demands independence on by identifier, and the outcomes achildrenform lists by code point. - No repetition. Each of those nine lists holds every member once: in three-valued logic
allandanyare idempotent, and the two action lists, the invalidated claims, the two lists of input values, the dimensions ofindependent_onand the outcomes ofchildrenare sets. - Explicit defaults. Every field is present. An absent description or effect is
null; an evidence match without a result has"result": null; a claim test withoutishas"is": "true"; an action without a precondition has the precondition{"all": []}, which is true; absent lists and sections are empty. The nine exceptions are an evidence kind'smax_age, thesubjectof an evidence match or an order form, an evidence match'sindependent_on, an obligation'sapplicable_when, an action'sarguments, an action'scase_open, theinvalidationsection, thecasesection and theindependence_dimensionssection: each written only when it is declared (a section only when it holds a rule, an input or a dimension), so the IR of a protocol that declares none of them is the IR it was before they existed. Theindependence_dimensionssection is{<id>: {"description": …}}. Thecasesection is{"inputs": {<id>: {"description": …, "values": [...]}}}. An order form is written as authored:{"first": {"kind": …, "result": …}},{"before": {"first": …, "then": …}},{"after": {"first": …, "then": {"kind": …, "result": …}}}and{"since": {"last": …, "then": {"kind": …, "result": …}}}. The form over the child cases is{"children": {"in": [...]}}, and an action'scase_openis{"protocol": …, "subject": …}, as declared (story:case-composition). An action'sargumentsis its JSON Schema as declared, the same JSON value, its object keys sorted like every object's (story:action-arguments). - No authoring sugar. A claim test is always
{"claim": {"id": …, "is": …}}, whichever way it was written, and an input test{"input": {"id": …, "in": [...]}}. An outcome that requires an explicit decision has the requirement{"decision": <name>}, as written; any other requirement is its predicate. - Resolved references. Only a document the validator accepts compiles, so every claim and
evidence kind the IR references is declared in it (
case_outcomewherever achildrenform reads it), and so is every artifact an evidence match or an order form names as its subject, an action opens a case about (case_open) or an invalidation rule names as its upstream artifact, every input an input test names, with every value it lists, and every dimension an evidence match demands independence on.
The IR carries the protocol id and revision (design § 37) and nothing about where the document
came from: no path, no working directory, no time. Ir::canonical_json is its one
serialization, and the bytes are suitable for hashing: documents that differ only in the ways
listed above give the same bytes. Other logical equivalences are not normalized, so they give
different bytes: a one-member all or any and its member, a nested all inside an all and
the flattened list, not of not and what it negates, and maximum ages of the same length
written in different units (60m and 1h).
check_openings takes a set of compiled protocols and refuses it when their actions'
case_open openings form a cycle (case-open-cycle, D206): a protocol opens a case under
another when one of its actions names that protocol in case_open, and a cycle is a path of
openings back to where it began. The refusal names the first cycle a walk from the least
protocol id on any cycle finds, in the order its openings go, back to that id
(protocols open cases under each other in a cycle: audit -> review -> audit). An opening
under a protocol outside the set closes no cycle; a chain of openings is accepted.