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.

JSON
{
  "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

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

Text
collected_at      : 2026-08-06T12:24:26Z
collector_version : 0.10.0
identity_kind     : delegated
module            : 3.3.0

Explanation

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.
  • SPO-ACTIVITY-001: a day count only true relative to collected_at

References