30-second answer
Four layers, run in order, each assuming the one before it passed. A problem
prints as [L<layer> <code>] <location>: <message>, and the layers are sorted
so a shape error appears above the semantic errors it caused.
The rule the layering follows
Each constraint has exactly one owner. Where a constraint could live in more than one layer, it belongs to the lowest layer that can express it completely.
That is what stops the same check existing twice, disagreeing, and nobody knowing which one is authoritative.
The layers
Layer 1: the document loads. YAML that does not parse never reaches a schema.
Layer 2: the schema. Required fields, allowed values, types, patterns. Layer 2 never follows a reference: it can see that an evidence path is well formed, and not whether anything declares it.
One check sits here that looks semantic and is not. The unknown,
not_applicable and invalid_evidence messages may not contain {. Those
three are printed precisely when evidence is missing, out of scope or
malformed, so a field cannot be both the reason the rule could not decide and a
value the rule prints. The check is purely lexical, so it belongs to the lowest
layer that can express it.
Layer 3: the rule is coherent with itself. This is where references are followed, and it produces four codes:
| Code | What it caught |
|---|---|
undeclared-evidence | The condition or applicability reads a path evidence_requirements does not declare |
undeclared-interpolation | A pass or fail message interpolates a path that is not declared |
unused-required-evidence | A path is declared required and never consumed. It manufactures unknown on resources the rule could decide |
undeclared-dependency | The rule decides using a path without declaring it required |
unused-required-evidence is the subtle one. A path declared and never used
makes the rule demand evidence it does not need, so a resource the rule could
have decided comes back unknown.
Layer 4: the set together. duplicate-id: an id already used by another
file. Ids are never reused.
Running it
m365-governance validate
20 rules validated. No problems found.The count is printed on success so a silent pass cannot be confused with having read nothing.
Exit codes
0 clean. 1 problems, on stderr.