Every code review of a remediation system spends its time in the wrong file.
The reviewer opens restart.py, reads twelve lines that call the Kubernetes API, and asks sensible questions about error handling. Meanwhile the file that determines whether this system is safe — the one wrapping every handler, deciding what may run, checking whether the world got better, and writing the record you will hand an auditor — gets a glance because it “hasn’t changed.”
The handlers are the least interesting code in a remediator. The runner is the product.
Where the safety actually lives
A remediator is not “code that restarts pods.” It is a sequence that every action passes through, without exception:
gate → act → verify → audit, with rollback as a first-class branch rather than an exception path.
Handlers only own the act step, and only the mechanical part of it. Everything that makes the system defensible is in the wrapper:
- Is this action’s reversibility tier within the current autonomy ceiling?
- Has the kill switch been tripped, globally or for this tenant?
- Is there budget left for this class of action in this window?
- Did the post-action probe say the world got better — and did it have the ability to say no?
- If not, did the inverse run, and did that get verified?
- Did exactly one audit event get written, with the tier, the outcome, and the human whose authority this ran under?
Put those in the handlers and you have written them once. You will not write them the same way ten handlers later, and the one that forgets the kill-switch check will be the one that runs during the incident where you needed it.
The contract, concretely
The runner’s contract is the API of the system. Everything else is an implementation detail behind it.
What the runner guarantees to every action:
- Nothing executes without a reversibility tier and a policy decision.
- Every execution is followed by verification, or it is not considered complete.
- Every failed verification triggers the inverse operation, which is itself verified.
- Exactly one audit event is emitted per action, whatever the outcome.
- Every terminal state is one of a closed, named set — not a free-text string, and not an exception that escaped.
What the runner demands from every handler:
- Do one thing.
- Declare your reversibility tier.
- Expose an inverse.
- Be safe to run twice.
- Return a structured outcome. Do not raise to signal a business failure.
- Contain no policy, no audit, no retry.
Point 4 has become non-negotiable rather than merely wise. Retry is now the normal recovery path in enough protocols and transports that a handler which is not idempotent will eventually be executed twice by infrastructure you do not control. If running your handler twice produces two of something, that is a bug in the handler, not bad luck.
Point 6 is the one people argue with. A handler author reaches for a policy check because they can see a case the runner does not cover. That instinct is correct and the response is wrong: extend the runner. A safety check that lives in one handler is a safety check nine other handlers do not have.
The dangerous pattern
The most damaging line in a remediation codebase is a handler that constructs its own client:
# in a handler — do not do this
client = KubernetesClient(token=os.environ["SA_TOKEN"])
client.delete_namespaced_pod(name, namespace)
That handler has silently opted out of every guarantee above. The gate never ran. The probe never ran. The audit event does not exist. The kill switch is irrelevant, because nothing consulted it. Your architecture diagram still shows a runner in the middle; production disagrees.
This does not usually arrive as sabotage. It arrives as a Friday afternoon and a handler that needs one extra call the runner does not expose yet.
Make it structurally hard, then test for it. Handlers should receive a scoped client from the runner — one that cannot perform ungated mutations — rather than building their own from ambient credentials. And back it with a test that asserts the property directly:
def test_no_handler_constructs_its_own_client():
for name, handler in registry.items():
src = inspect.getsource(handler.__module__)
assert "KubernetesClient(" not in src, f"{name} bypasses the runner"
That test is crude and it works. An architectural rule that lives only in a design document is a rule the third contributor has not read.
Why this makes reviews cheap
The payoff is not elegance, it is review economics.
When the runner owns the cross-cutting properties, adding a handler stops being a safety conversation. The reviewer asks: does it do one thing, is the tier right, does the inverse work, is it safe to run twice? Four questions, all local, all answerable by reading one short file.
Compare a codebase where each handler carries its own policy checks and audit emission. Now every new handler is a full safety review, every reviewer has to hold the whole system in their head, and the answer to “is this safe” is “read all eleven handlers and hope.”
Centralising the contract is what lets you add the twelfth action without re-auditing the eleven that came before. That is the property that makes a remediation system something you can keep extending rather than something you freeze because nobody wants to touch it.
What to do Monday
- Find the file that wraps your actions. If there is no such file, that is the finding — you have scripts with a scheduler, not a remediator.
- Write the contract down, both directions: what the runner guarantees, what it demands. One page.
- Grep for handlers that construct their own clients or credentials. Every hit is a live bypass.
- Add the bypass test. Crude is fine.
- Move one safety check out of a handler and into the runner. Usually the audit emission, because it is the one most often forgotten and the one you will most regret missing.
The handler that restarts the pod is fifteen lines and will be right. The runner is the part that decides whether being wrong is survivable, and it deserves the review attention you have been spending on the handlers.
Comments