30-second answer

A rule carries its own version, separate from the engine's. Ids are never reused. When a rule changes, comparisons between assessments distinguish a tenant that moved from a rule that moved.

Why the rule version is separate

An assessment records which rules decided it and at what version. That is what lets diff answer the question that matters when a pass becomes a fail:

  • the tenant changed. Somebody turned something on. That is a finding.
  • the rule changed. A newer version tightened what the rule establishes. The tenant may be untouched.

A comparison that reported only outcomes would present the second as the first, and a team would go looking for a change nobody made.

Ids are never reused

Layer 4 of the validator enforces it across the set:

Text
[L4 duplicate-id] <path>: id 'SPO-LIST-001' is already used by <other path>.
Ids are never reused

An id is what a finding names and what an audit trail refers to a year later. Reusing one silently rewrites history.

What changing a source means

Every source carries checked_at. A rule whose basis rests on a page that has since moved or changed is a rule whose checked_at no longer describes reality, and re-checking is part of maintaining it rather than an optional tidy up.

The version bump is the visible half. SPO-LIST-001 ships at v2.0 while most of the set is v1.0, which is a fact any reader can see in list-rules.

Deprecating rather than deleting

A rule that is withdrawn does not free its id. The engine has no id recycling path, by construction.