Skip to main content
Your progress
0 of 5 lessons complete0%
T2 / M2.13 / L5 OF 5 / Engineer TIER / 9 min

Rule versions, retirement and supersede

Outcome

By the end of this lesson, you will be able to explain what cuts a new rule version, predict how existing recommendations are routed when one does, and distinguish a catalog change from a customer outcome.


TierEngineer
JTBD”Understand why a batch of my recommendations changed status when nobody in my org touched them.”
PersonasPlatform Engineer · FinOps Lead
PrerequisitesM2.13.L2 · M2.13.L4
Time9 minutes
Bloom verbExplain (Understand), Predict (Analyze), Distinguish (Analyze)

1. Concept

Rules change. When one does, every open recommendation it raised was raised under a definition that no longer exists. Versioning is how that is made visible rather than silent.

What cuts a version, and what does not

Terminal window
A BEHAVIOUR CHANGE CUTS A VERSION
a threshold moved
a lookback window changed
a condition added or removed
The author implements ContractRevisioned and increments:
// RC-1704: expiry threshold 30 -> 60 days.
func (*acmExpiringRule) ContractRevision() int { return 1 }
A DECLARATION CHANGE DOES NOT
renaming a rule
changing its severity
recategorising it
Those reach the customer through the registry at render
time. Nothing about what the rule DOES has moved.

Not implementing the interface is revision 0, so a rule that never changes behaviour never versions.

The asymmetry this fixed

The declaration used to be what got hashed, and it had the logic exactly backwards:

Terminal window
BEFORE
a copy-edit to a TITLE -> withdrew and re-raised
every open recommendation
for that rule
a THRESHOLD change -> no version at all
AFTER
title edit -> nothing
threshold change -> a version, if the author
bumps

The hash is over (rule_id, revision) separated by a NUL byte. Rule ids are free-form and several end in digits, so plain concatenation would let ("RC-1", 2) collide with ("RC-12", 0).

A reverted contract returns to its original number, because the hash is the key rather than a counter. An accidental edit and its undo do not relabel a customer’s work twice.

The gap, named rather than hidden

Terminal window
WHAT IS STILL NOT DETECTED
The body of Evaluate, when the author does not bump.
A threshold moving 5% -> 10% behind an unchanged
declaration produces the same hash and no new version.
It is MANUAL and nothing verifies it was bumped.

The alternatives were hashing the compiled function, which is unavailable at runtime and unstable across builds, or a constant every author remembers. The gap is documented rather than papered over with the word “version”.

Routing: by prior status and verdict, not by the edit

When a version is cut, existing recommendations are routed by what they were and what we measured, never by the rule edit alone.

Terminal window
PRIOR STATUS VERDICT OUTCOME
─────────────────────────────────────────────────────────────
open any rule_retired
(reason:
rule_version_
changed), then
re-raised under
the new version
in the same pass
dismissed any stays dismissed,
re-stamped to the
new version
applied verified_adopted UNTOUCHED. Keeps
applied AND its
original version.
applied verified_unchanged / rule_retired,
verified_diverged / re-raised as open
verified_reverted

An applied recommendation with no verdict (NULL, pending or abstained) is left alone. Those mean we never looked, cannot yet tell, or never will. None is evidence against the customer, so withdrawing on them would retract an adoption claim on the strength of our own instruments being quiet.

That sentence is the whole ethic of this module in one line.

Why rule_retired rather than optimised

Terminal window
optimised is served in the customer's own tab row and
reads as an outcome THEY achieved
rule_retired is hidden from that surface entirely and
stays visible internally, rendered as
"Withdrawn"

Writing optimised for a withdrawal would credit the customer with a saving that came from us editing our own catalogue. The withdrawn row is not frozen by the upsert either, so it comes back open under the new version in the same pass. The audit trail keeps both halves: a close, then a reopen.

Ordering matters here: the routing runs before the engine’s upsert. The upsert re-stamps every non-frozen row to the current version, so running the routing afterwards would find no stale open recommendations at all and the open arm would silently no-op.

Supersede is off by default, for a sharper reason

The applied transition is gated behind a flag, default off, because it overwrites a status the customer set.

Terminal window
WHEN ENABLED, THE SUPERSEDE DOES
status applied -> optimised
closure_reason_code rule_superseded (its own reason)
updated_at PINNED. Nothing about the resource
was checked; our catalog moved.
resolution_state UNTOUCHED. Carried, never written.

The candidate count is measured and logged per org whether or not the flag is set. With it unset the log line is a warning, because those recommendations are attributed to a definition that no longer exists and nothing will correct them until someone turns it on.

“Verified” is carried, not written. A recommendation that was already verified stays verified through the transition; one that was never verified does not acquire a verification from a rule edit. Stamping one would manufacture a saving out of a catalog change, which is precisely the over-claim the two-column model exists to prevent.

rule_superseded is its own closure reason rather than a borrowed one. Borrowing rule_not_applicable put superseded rows inside the auto-dismiss scope, where the next pass would freeze them as zop_dismissed stamped rule_not_fired: a customer’s applied claim laundered into an auto-dismissal over a rule they had nothing to do with.


2. Demo

A threshold change, and what each population did:

Terminal window
CHANGE: RC-1704 certificate-expiry threshold 30 -> 60 days
Author bumps ContractRevision to 1. New version cut.
POPULATION BEFORE THE PASS: 214 recommendations
open, 96
-> rule_retired (rule_version_changed), re-raised
under v1 in the same pass. Net effect for the
customer: the finding is still there, and now
reflects the 60-day threshold. Audit trail shows
closed + reopened.
dismissed, 44
-> stay dismissed, re-stamped to v1. Nobody's
dismissal is undone by our threshold moving. NOT
gated by the flag: it changes one metadata column
on rows whose status nobody is touching.
applied + verified_adopted, 31
-> UNTOUCHED. Still applied, still stamped v0. They
did the work under the old definition and it held.
Relabelling it would erase that.
applied + verified_unchanged, 12
-> rule_retired, re-raised open. They claimed it and
the estate never agreed, so under a new definition
it is simply an open finding again.
applied + no verdict, 24
-> LEFT ALONE. We never looked. Withdrawing would
retract their claim on the strength of our own
silence.
optimised / other, 7
-> unaffected.
WHAT THE CUSTOMER SEES
96 findings refreshed. 44 dismissals intact. 31
adoptions intact. 12 findings back on the board. No
mention of "version" anywhere: that is internal.
WHAT WOULD HAVE HAPPENED UNDER THE OLD SCHEME
Nothing, because a threshold change cut no version at
all. All 214 rows would still be attributed to a
definition that no longer existed, and the 12 false
applied claims would have stayed hidden.

3. Hands-on (6 min)

Terminal window
1. On the internal console, find a recommendation showing
"Raised under rule vN" with N > 0.
Rule: ____________ version: ______
Is that string visible on the customer surface? Y / N
2. Find a recommendation with status `rule_retired`.
What reason code? ______________________
Would a customer see this row at all? Y / N
3. Look at your `applied` recommendations. How many have
NO verdict (NULL/pending/abstained)? ______
Those are the population a version change deliberately
leaves alone. Explain in one line why:
______________________________________________
4. Is the supersede flag enabled in your environment?
Y / N
If N, find the per-org warning log line. It names the
candidate count. What is it? ______
5. Write the difference, in your own words, between a
recommendation closing because the customer fixed
something and one closing because we edited a rule:
______________________________________________

4. Knowledge check

Q1

A rule author renames a rule and changes its severity from medium to high. This:

A. Cuts a new version and re-raises every open recommendation
B. Cuts no version
C. Cuts a version only if the severity increases
D. Retires the rule

Show answer

Correct: B. Renaming, re-severity and recategorising are declaration changes that reach the customer through the registry at render time; only a behaviour change (a threshold, a lookback window, an added or removed condition) implements ContractRevisioned and increments. This fixed an asymmetry that ran exactly backwards: a copy-edit to a title used to withdraw and re-raise every open recommendation, while a threshold change produced no version at all.

Q2

A version is cut. A recommendation with status = applied and no verdict (NULL, pending or abstained) is:

A. Retired and re-raised as open
B. Superseded to optimised
C. Re-stamped to the new version and kept applied
D. Left alone

Show answer

Correct: D. NULL, pending and abstained mean we never looked, cannot yet tell, or never will, and none of those is evidence against the customer. Withdrawing would retract an adoption claim on the strength of our own instruments being quiet. Contrast with applied + verified_unchanged, which is retired and re-raised: there we have positive evidence that the estate never agreed with the claim.

Q3

When a withdrawal happens, the row is written as rule_retired rather than optimised because:

A. optimised is reserved for auto-remediation
B. optimised is served in the customer’s own tab row and reads as an outcome they achieved
C. rule_retired rows are simply deleted once 30 days have passed since the withdrawal
D. optimised cannot be reopened

Show answer

Correct: B. Crediting a customer with a saving that came from us editing our own catalogue would be an over-claim, so rule_retired is hidden from that surface and stays visible internally as “Withdrawn”. The same reasoning gives rule_superseded its own closure reason. Borrowing an existing one would put superseded rows inside the auto-dismiss scope. A customer’s applied claim would then be quietly turned into an auto-dismissal, over a rule change they had nothing to do with.


5. Apply

When a batch of recommendations changes state and nobody in your org acted, check whether a rule version was cut before opening a support ticket. The audit trail will show a close with rule_version_changed followed by a reopen in the same pass.

If you author rules, treat ContractRevision as a required part of any behaviour change. Nothing verifies it was bumped, which makes it a review-time habit rather than a compiler guarantee.


Glossary terms touched

ContractRevision · rule_retired · rule_superseded · Rule version


Start with the bill.

Foundations takes about five hours. The first lesson is nine minutes.

Open curriculum. No login. No paywall. 290 lessons across 7 courses, three publicly verifiable credentials. Read it on the train, take the exam on a Saturday, list the credential on your résumé Monday.

5h median time to finish Foundations
0 logins, paywalls, or marketing forms
open curriculum, public credential verifier
Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console· Multi-cloud automation· Production-ready in 30 min· SOC 2 · ISO 27001· 20–60% off the bill, first month· 4 platforms · 1 console·