30-second answer
A rule is a YAML file. Thirteen fields are required, and the required set is chosen so that a rule cannot exist without saying what authority it has, what it needs to decide, and what it fails to establish when it passes.
The required fields
From the schema, rule.schema.json:
schema_version id version title description
service resource_type basis severity
evidence_requirements condition outcomes limitationsapplicability and remediation are optional. Everything else above is not.
Field by field
schema_version, version. Both are strings matching ^[0-9]+\.[0-9]+$,
and the schema states why they are quoted: unquoted, 1.10 parses as the float
1.1.
id. The identifier a finding names. Ids are never reused; the validator
enforces it across the whole set.
service, resource_type. What the rule is about. resource_type is what
makes a rule apply to a list rather than to a site.
basis. The authority behind the rule. It has
its own page, because it is
the field that decides whether a finding is a defect or a decision.
severity. An object, not a word: a default of low, medium, high
or critical, a rationale of at least 40 characters, and an optional
configurable. The rationale is mandatory because a severity nobody argued for
is a number somebody will argue with later.
evidence_requirements. A list of paths, each with a type and required: true. The constant is deliberate: optional evidence has no role in this
version. Every declared path must be consumed, and every consumed path is
required.
condition and the optional applicability. Both are comparisons: an
operator, an evidence path and usually a value. The twelve operators are
equals, not-equals, less-than, less-than-or-equal, greater-than,
greater-than-or-equal, contains, not-contains, exists, not-exists,
in, not-in. exists and not-exists take no value; the rest require one.
outcomes. Five messages: pass, fail, unknown, not_applicable,
invalid_evidence. error is absent on purpose, and the schema says why:
it describes the engine and not the resource, so a rule may not author its
message.
limitations. Required, with a mandatory
passes_without_resolving of at least 40 characters.
The one field that makes this engine different
limitations.passes_without_resolving is what the rule admits when it passes.
SPO-LIST-002 puts it plainly:
A list at 49,000 scopes passes and is one sharing operation away from the ceiling, and a list at 6,000 passes this rule while sitting well above the number Microsoft recommends for performance. This rule measures only the supported ceiling, and passing it says nothing about whether the list is usable.
A rule cannot be merged without writing that paragraph. It is why a pass from
this engine can be read as a bounded statement rather than as reassurance.