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:
[L4 duplicate-id] <path>: id 'SPO-LIST-001' is already used by <other path>.
Ids are never reusedAn 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.