Skip to content

screwyprof/ordered-policy

Repository files navigation

ordered-policy

A small example of representing an operational decision as explicit, ordered, observable policy.

This is not a rules engine or a framework. It is the smallest thing that shows one idea from the essay Making Operational Decisions Explainable: a controller's decision does not have to hide inside a ladder of nested ifs. It can be an ordered list of named rules that reads like the policy it is, can be evaluated without the system it controls, and can explain itself.

The idea, in one contrast

The decision the way it tends to grow, as control flow:

if state.InMaintenanceWindow() {
    return NoAction
}
if state.BackupRunning() {
    return Wait
}
if state.BackupExpired() || state.RetentionExceeded() {
    if !state.IsLastBackup() {
        return DeleteBackup
    }
    return Wait
}
if state.ScheduleReached() {
    return StartBackup
}
return Wait

If the last backup is expired and a backup is scheduled, does it start a new one? You have to trace the returns to know — the expired branch returns first, so no, it waits.

The same policy as an ordered list (policy.go):

var Rules = []Rule{
    Case("Do nothing during maintenance").
        Matches(State.InMaintenanceWindow).
        Execute(NoAction),

    Case("Let a running backup finish").
        Matches(State.BackupRunning).
        Execute(Wait),

    Case("Keep the last backup, even if it's due for cleanup").
        Matches(And(State.IsLastBackup, Or(State.BackupExpired, State.RetentionExceeded))).
        Execute(Wait),

    Case("Delete a backup that is expired or over retention").
        Matches(Or(State.BackupExpired, State.RetentionExceeded)).
        Execute(DeleteBackup),

    Case("Start a scheduled backup").
        Matches(State.ScheduleReached).
        Execute(StartBackup),
}

Now the answer is in the list: "Keep the last backup" sits above "Start a scheduled backup". Priority is the order.

Three properties

  • Explicit. The policy is a value you can read in precedence order, not control flow you have to execute in your head.
  • Testable. DetermineNextAction(State) Action is pure — it reads nothing and changes nothing — so the decision logic is table-testable with no cluster and no clock. Every rule and the precedence between them shows up in the rows of strategy_test.go: each a state and the action policy must produce.
  • Observable. An optional Observer is handed each decision — the action and the rule that fired — alongside the state whose facts decided it. Most software logs the action; this logs the decision (example_test.go):
decision: DeleteBackup (rule "Delete a backup that is expired or over retention")
facts: maintenance=false running=false expired=true retention=false lastBackup=false scheduleReached=false

What this is not

No Kubernetes, no framework, no config — this repository shows the representation and nothing else. It's an illustration kept deliberately simple, not production-ready code to depend on: take the idea, not the file. If your reaction is "that's surprisingly small," that's the point: the hard part was never the code. It was deciding where the complexity belongs.

License

MIT. See LICENSE.

Releases

Packages

Contributors

Languages