30-second answer
A finding without provenance is an opinion with formatting. The minimum that must travel with it: when it was collected, what collected it and at which version, which identity kind produced the view, and which rule version judged it.
{
"collected_at": "2026-08-06T12:24:26Z",
"collector": "spo-collector",
"collector_version": "0.10.0",
"identity_kind": "delegated",
"scopes": ["AllSites.Read"]
}What this proves
With provenance attached, a finding is a checkable statement: anyone can re-run the same collector, under the same identity kind, against the same resource, and compare. Without it, the same sentence cannot be distinguished from a guess made on a different day by a different pair of eyes.
What it does not prove
- That the finding is still true. Provenance dates a finding; it does not keep it fresh. "573 days inactive" was true at the collection moment, and a report read a year later describes that day, not today.
- That the collection was complete. Completeness is its own field. Provenance says who looked and when; coverage says what they managed to see.
PowerShell
# Stamp provenance at collection time, from facts, not from intentions
[pscustomobject]@{
collected_at = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
collector_version = '0.10.0'
identity_kind = 'delegated'
module = (Get-Module PnP.PowerShell).Version.ToString()
}Example output
collected_at : 2026-08-06T12:24:26Z
collector_version : 0.10.0
identity_kind : delegated
module : 3.3.0Explanation
Each field answers a failure that actually happens. collected_at is what
lets a day-count mean something after the report is old. identity_kind
is the difference between "the tenant has 47 sites" and "one person could
see 47 sites". The collector version is what tells you whether a defect
fixed last month affects this document. And the rule version, carried on
the finding itself, is what separates "the tenant changed" from "the rule
changed" when two reports disagree.
Provenance is only worth carrying if something checks it. The engine
learned this on itself: 27 of its shipped fixtures declared
source_api: Microsoft Graph v1.0, and the collector has never used
Graph. It reads lists through PnP.PowerShell and CSOM, and its tenant
paths go through SharePoint Admin. Every document validated; the field
was simply wrong, copied forward from an assumption. The fix (merged as
the engine's PR 12) corrected each fixture to the path that actually
produced it, left collector_version untouched because the collector's
version and the PnP dependency's version are different facts, and added
a CI gate that ties every published source_api to a collection path
the collector declares. The gate's own first draft accused correct
evidence because it read only the modules and not the orchestrator, and
that error is recorded too.
The same fix separated two sentences that had been allowed to blur: a result computed from constructed evidence is a true result about a construction. The engine's public example carried a collection timestamp that read as a tenant observation at 14:02 on 5 August; the number was right and the evidence was never observed anywhere. What kind of thing the input was is now recorded in a registry outside the evidence documents, so a production Assessment can never validate a construction as collected.
Production considerations
- Imported evidence (an export from another tool) carries provenance too: the source tool, its version, when it exported. What it can never carry is verifiable completeness, and a report over imported evidence should say so in as many words.
- Provenance is written at collection time or never; reconstructing it afterwards is authorship, not observation.
- The tenant in the envelope is
{id, host}, and the id is required and null until a collection path is proven to populate it. A null that means "nobody read this" is honest; a hostname promoted to an identity is not, because a tenant is not a hostname.
Related governance rules
SPO-ACTIVITY-001: a day count only true relative to collected_at
References
- Evidence schema (m365-governance-as-code)
- Trust model (m365-governance-as-code)