ADR-010. A write is checked, not announced
Status: accepted, 7 September 2026. Still in force.
Context
The chain was already sound:
READ -> COMPARE -> WRITE -> READ
The second read is a good decision, and the runtime said why it exists: the API
normalises, completes, and sometimes refuses in silence. But after that read,
the module announced changed=true without looking at what it had just read.
A value the API did not apply therefore went unnoticed. It came back on the next
playbook as a changed=true that never settles, and nobody connects that symptom
to its cause: the module writes, the API ignores, the module writes again, for
as long as the playbook runs.
Decision
READ -> NORMALIZE -> COMPARE -> WRITE -> READ -> NORMALIZE -> VERIFY
After the second read, every field the playbook asked for is compared against what the API returns. A divergence fails the module, naming the field, the requested value and the observed one:
failed: true
changed: true
failed_postconditions:
tags:
requested: [production, web]
observed: [production]
changed stays true, and that is the delicate part. The API accepted the
request: the resource moved. A failure that hid changed would tell a replayed
playbook that nothing happened while the machine had in fact changed.
The check uses the same comparison as the outbound decision (ADR-009). Comparing as a set to decide whether to write and then strictly to verify would fail on an ordering the first comparison had just accepted.
Two families of field stay out, for two different reasons:
secrets, which the API never returns. Comparing them would compare a value to
Noneand conclude “not honoured” on every run, which looks like a measurement and is not one;fields an override excludes, with its reason. The case this anticipates is an API that applies a field asynchronously: failing on it would fail a correct playbook on a delay rather than on an error.
Consequences
No field is excluded today. Nothing of the sort has been observed, and the mechanism exists so that the day it is, the answer is an override with a reason rather than an edit to the runtime under pressure.
The exclusion carries a reason like every other decision here. Without it, the
escape hatch would become the convenient way to silence an inconvenient check,
and the check would be decorative.
A module can now fail where it used to succeed. That is the point: the failure was already there, spread over every future run of the playbook, in a form nobody could diagnose.
What this record does not decide
Whether the module should retry. A retry policy depends on the nature of the operation, and it is its own question.