30-second answer
A collector reads a tenant and writes evidence as JSON. It evaluates nothing, scores nothing, and has no write path. There are ten slices, each answering one question, and every one of them is a mode of a single PowerShell entry point.
Read only, and proved rather than promised
Nothing under the collector tree calls a mutating cmdlet against a tenant. No
Set-, New-, Remove-, Add-, Grant- or Revoke-. That is enforced in
CI by parsing every file in the tree and failing on any mutating verb.
The word "every" is the load-bearing part. Naming one path was true while there was one file, and would have gone on passing while proving nothing about the rest. What the gate establishes, and what it does not, is set out in what a read only gate proves.
When a collection fails, the failure message says so explicitly:
collection failed after 12.4s. Nothing was written to the tenant;
a collector has no write path.A collector never judges
It returns what it observed. It never returns is_compliant, a risk level, a
score or a recommended action.
That is not modesty. A collector that judges has made the rule unreviewable: the judgement moves out of a rule file that somebody can read and diff, and into code that nobody reviews line by line. Keeping the two apart is what makes a finding arguable.
Every fact carries a collection state
A fact the collector could not read says so, with a reason, rather than appearing as a zero or an empty list. The states come from the collector's own resolver:
| State | Meaning |
|---|---|
observed | The collector read this |
missing | It could not be read. The default when a failure is not recognised |
not-supported | The path does not support this, or the run did not ask for it |
The distinction that matters most is not-supported. Run permissions without
--count-unique-scopes and the unique scope count comes back not-supported,
which is the truth about this run rather than a zero. A rule that needs the
number then returns unknown instead of passing on a count nobody took.
One document per resource
A mode that reads many resources writes many files. --output is a file for
the single-resource modes and a directory for the rest, and that shape is why
evaluate treats a directory as a run set even when it holds one document.
What has to be installed
Only collection needs PowerShell 7. The engine, the rules and the tests do not,
and they run offline against fixtures. collect checks before reaching the
network:
PowerShell 7 is not installed. Only collection needs it; the engine,
the rules and the tests do not.Validated against PnP.PowerShell 3.3.0, run read only against a live tenant.
The ten slices
| Slice | Mode | Needs | Profile |
|---|---|---|---|
agents | Agents | site | default |
sites | TenantSites | tenant | capacity |
owners | SiteOwners | site | ownership |
modernity | Modernity | site | modernisation |
sharing | SiteSharing | site and tenant | sharing |
tenant-sharing | TenantSharing | tenant | tenant-sharing |
activity | Activity | site and tenant | activity |
classification | Classification | site | default |
permissions | UniquePermissions | site | capacity |
spfx | SpfxCatalog | site | spfx |
The profile column is not decoration. Evaluating a collection against the wrong
profile produces a wall of unknown for facts nobody asked to be collected,
which is why collect prints the pairing when it finishes.
A collection path no rule can consume should not exist
That is the standing rule, and it holds for nine of the ten. agents is the
one deliberate exception, recorded rather than smuggled past a test: see
agents.