Skip to main content

Concepts · 1 of 5

The protocol/1 language

A protocol is a YAML document. It names itself with an id and a revision, then declares six sections.

Shippedcrates/canon/src/model/, the protocol/1 source model

The protocol/1 language

Each section is a map from an identifier to a declaration. This page explains what the sections are for; the protocol/1 reference lists every key, generated from Canon's model.

format: protocol/1
protocol:
id: investigation
revision: 1
artifacts: {} # what evidence is about
evidence_kinds: {} # what can be observed
claims: {} # propositions, established by evidence
obligations: {} # what is owed
actions: {} # what may be done
outcomes: {} # how a case may legitimately end

Canon reads a document strictly. A key it does not know is refused. A key written with no value is refused too, so a forgotten value never silently becomes a default: to take a default, leave the key out.

Artifacts​

An artifact is a thing that has revisions, such as an explanation, a design or a deployment. Evidence is bound to the revision of an artifact it was observed on; see evidence bound to revisions.

Evidence kinds​

An evidence kind names a class of observation the protocol admits. Claims match evidence by kind, and actions say which kinds they may produce. Both must name a declared kind.

An evidence match may also name the artifact its record must be about, with subject. Only records about that artifact then count; a record about another declared artifact does not match, even at that artifact's current revision. A match without subject counts records about any declared artifact. A subject must name a declared artifact.

claims:
explanation.survived:
true_when:
evidence: {kind: falsification_attempt, result: survived, subject: explanation}
ShippedSubject-bound evidence matches

Conformance scenario CANON-EVIDENCE-003 holds this; see evidence bound to revisions.

Claims​

A claim is a proposition whose value is TRUE, FALSE or UNKNOWN (three-valued truth). Its predicate, true_when, says what evidence and which other claims establish it. Claims must not test each other in a cycle.

Obligations​

An obligation is something that must be done before the case can be complete. Its discharged_when predicate says when it is done; it may test only claim values, never evidence directly.

obligations:
establish.explanation:
discharged_when:
claim: explanation.supported
ShippedOpen or discharged

canon evaluate reports every declared obligation as open or discharged. It is discharged only when the predicate is TRUE; UNKNOWN and FALSE both leave it open. Whether deciding a claim or establishing it discharges an obligation is the author's choice, written with the claim tests of the predicate: {claim: c} needs c to be TRUE, and {not: {claim: c, is: unknown}} needs it decided either way.

Actions​

An action is a semantically named possibility, not a workflow step. It says when it applies (precondition), which capabilities it needs authority for (requires), what class of effect it has (effect), which evidence kinds it may produce (may_produce) and what arguments it takes (arguments, below). A person, planner, agent or workflow engine decides how to carry it out.

actions:
rollback.recent_release:
precondition:
claim: impact.mitigated
is: false
requires:
- capability: production.rollback
effect: reversible_change
may_produce:
- evidence: rollback_record

Canon checks that capabilities and effect classes are well-formed identifiers. It does not resolve who holds a capability: whoever runs the evaluation passes the authority decisions in, as a canon-authority/1 list of capabilities granted or denied (canon evaluate --authority). It gives effect classes no built-in meaning today.

ShippedAction admissibility

canon evaluate reports every declared action as blocked when its precondition is not TRUE or a capability it requires is denied, approval-required when the precondition is TRUE, nothing is denied and some required capability is not decided, and admissible when the precondition is TRUE and every required capability is granted. A status other than admissible comes with the claims, evidence or capabilities that decide it. Conformance scenario CANON-AUTHORITY-001 holds this.

Argument schemas​

An action may declare one argument schema, arguments: a JSON Schema saying what arguments the action is called with. A runner reads it from the protocol it loads and validates each call's arguments against it. Absent: the action takes no arguments; a runner accepts only {}. Canon itself never validates arguments: it evaluates no call.

actions:
inspect:
arguments:
type: object
properties:
source: {type: string, minLength: 1}
limit: {type: integer, minimum: 1, maximum: 50}
required: [source]
additionalProperties: false

The schema is written in Canon's argument-schema dialect, a subset of JSON Schema 2020-12. Every schema in it is a valid 2020-12 schema with the same meaning, so a runner can hand it to any 2020-12 validator. Protocols load offline, so the dialect has no reference at all.

  • Every schema, the top level and each nested one, names exactly one type: object, array, string, integer, number, boolean or null. The top level is type: object.
  • title and description, strings, anywhere.
  • enum, on any type: a non-empty list of distinct JSON scalars, each of the schema's type.
  • object: properties (a map of name to schema), required (a list of distinct names, each declared in properties), additionalProperties (a boolean only).
  • array: items (one schema), minItems, maxItems.
  • string: minLength, maxLength.
  • integer and number: minimum, maximum, integers.
  • Every length and count bound is a non-negative integer, and a minimum is at most its maximum.
  • Schemas nest at most 32 deep: the top level is one level, and each schema in properties or items one more.

Anything else is refused: $ref, $defs, $schema, $id, allOf, anyOf, oneOf, not, if/then/else, patternProperties, pattern, format, const, exclusiveMinimum, exclusiveMaximum, a list-valued type, a schema-valued additionalProperties, a keyword on a type it does not apply to, and any unknown keyword. canon validate reports each offending keyword as its own action-arguments problem, naming the action, the keyword's JSON Pointer inside the schema and why it is refused:

action `inspect` arguments at `/properties/source/pattern`: `pattern` is outside the dialect

canon compile carries each schema into canon-ir/1 as declared, its object keys sorted like every object's, and writes no arguments for an action without one.

Every change to an action's arguments is made under a new protocol.revision, like every other change to a protocol. canon diff tells a runner what the change does to calls:

ChangeCategoryLine
arguments introduced on an action (it took only {})BREAKINGarguments introduced
arguments removed from an action (it now takes only {})BREAKINGarguments removed
only title or description changed, anywhere in the schemaNO SEMANTIC CHANGEarguments rewritten
the new schema accepts only argument documents the old one acceptedTIGHTENINGarguments narrowed
the old schema accepts only argument documents the new one acceptsRELAXATIONarguments widened
anything else, or bothBREAKINGarguments changed

Narrowing and widening are decided structurally and soundly, never guessed. Per keyword: integer to number widens and the reverse narrows, and any other type change is changed; an enum introduced or a value removed narrows, and removed or a value added widens; a lower bound introduced or raised, or an upper bound introduced or lowered, narrows, and the reverse widens; a name added to required narrows, and one removed widens. For an object, each property name is compared by its effective schema: the declared one, else any value when additionalProperties is absent or true and no value when it is false; items and each property recurse. The directions of all keywords are combined: none is rewritten, only narrowing is narrowed, only widening is widened, both is changed.

ShippedAction argument schemas

Shipped in Canon 0.5.0 (story:action-arguments): arguments on an action, its dialect check in canon validate, its carry into canon-ir/1 and its classification by canon diff.

Outcomes​

An outcome is a declared terminal interpretation of a case, such as supported, restored or abandoned, together with what it requires: a predicate, or an explicit decision. A case ends only through an outcome its protocol declares.

ShippedLegitimate or blocked

canon evaluate reports every declared outcome as legitimate when its requirement is TRUE and blocked, with the reasons, otherwise. A case snapshot that records termination through an outcome the protocol does not declare is refused (undeclared-outcome), and so is one through a blocked outcome (illegitimate-termination). Conformance scenario CANON-OUTCOME-001 holds this.

ShippedOutcomes that require an explicit decision

An outcome may require an explicit decision instead of a predicate:

outcomes:
inconclusive:
requires:
decision: explicitly_inconclusive

It is legitimate only when canon evaluate --decisions is given a canon-decisions/1 decision of that name, for that outcome, taken at the case snapshot's revision. A decision taken at an earlier case revision has been superseded and does not apply; the outcome is then blocked, naming the decision. A legitimate decided outcome records who decided: the decision and every principal whose decision applied. A decision for an outcome that does not require it, or the same decision given twice, is refused. Conformance scenario CANON-OUTCOME-002 holds this.

Predicates​

Claims, discharge predicates, preconditions and outcome requirements are written as predicates: a small, total expression language with five forms, all, any, not, an evidence match and a claim test. There are no variables, loops or function calls. An outcome requirement may instead be a decision requirement, requires: decision: <name>: it is not a predicate form, so it stands alone as the whole requirement and cannot appear inside all, any or not, or in a claim, obligation or action.

true_when:
any:
- claim: explanation.supported
- all:
- evidence: {kind: falsification_attempt, result: survived}
- not:
claim: explanation.refuted
is: unknown
ShippedHow three values combine

all is false when a member is false, true when every member is true, and unknown otherwise; any is the mirror image; not swaps true and false and keeps unknown. The evaluation reference gives every rule, including evidence matches.

Validation and the compiled form​

canon validate checks the format, identifiers, duplicates, argument schemas, unresolved references and claim cycles, and reports every problem in a stable order (validation). A valid document compiles into canon-ir/1: declarations sorted by identifier, defaults written out, authoring shortcuts removed, so that documents meaning the same thing compile to the same bytes.