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:

CodeWhat it caught
undeclared-evidenceThe condition or applicability reads a path evidence_requirements does not declare
undeclared-interpolationA pass or fail message interpolates a path that is not declared
unused-required-evidenceA path is declared required and never consumed. It manufactures unknown on resources the rule could decide
undeclared-dependencyThe 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

PowerShell
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.