The Unknown Outcome and Postcondition Verification
An accepted command becomes complete only when the system can prove the intended business state, with a stable identity, a single committed effect and an authoritative readback after uncertainty.
TLDR
- A successful hand-off is not proof of a successful effect. A timeout after a state-changing command can mean not applied, still running or already committed.
- Exactly-once business behaviour requires a stable command identity, an intent fingerprint, an atomic duplicate fence and a postcondition that can be checked against authoritative state.
- When the reply is lost, the workflow must enter UNKNOWN, stop dependent actions and ask an outcome observer for an effect receipt before retrying.
- The claim is bounded. If an external system cannot deduplicate, expose state by command identity or provide adequate consistency, global exactly-once behaviour cannot be proved.
- The design decision changes from “retry on error” to observe, resolve, then reuse the same command. A fresh command identifier after uncertainty creates fresh authority to cause harm.
The packet that disappeared after the money moved
At 14:03:11, a relationship manager approves a £250,000 facility drawdown. A model has prepared the case, a human has confirmed the amount, and a deterministic authority service has checked the customer, facility, purpose, limit and approval. The service sends one typed command to the ledger gateway.
The ledger receives the command, posts the debit and credit, commits the transaction and starts returning a result. The return connection fails. At 14:03:16, the gateway records DEADLINE_EXCEEDED. That status does not say that the transaction failed. The gRPC specification explicitly warns that a deadline can expire even when a state-changing operation completed successfully.2 Distributed databases expose the same condition: a connection can close during COMMIT after the durable decision has already been made, leaving the caller unable to tell which side of the commit boundary the failure occupied.3
The workflow now has two tempting choices. It can treat the timeout as failure and submit a fresh transfer, risking a second £250,000 movement. Or it can stop, risking a drawdown that the customer believes was approved but that never reached the ledger. Both choices force a binary answer from evidence that supports three possibilities: not applied, still in flight, or already applied.
This distinction is especially important in an agentic system. A language model may describe the timeout, propose a recovery plan or explain the evidence to an operator. It should not improvise whether the command is safe to repeat. Retry semantics belong in a deterministic authority and recovery protocol, because the question is no longer linguistic. It is a claim about identity, state, atomicity and consequence.
Figure 1. The ambiguous-commit timeline
A timeout creates a third outcome
Application code often inherits the vocabulary of transport: success, error, timeout, retry. Business operations need a different vocabulary. A socket error describes what the caller learned from a communication attempt. It does not describe whether a payment posted, a customer record changed, a policy was issued or an account was closed. HTTP makes retry safer for methods whose intended effect is idempotent, but it also cautions against automatically retrying a non-idempotent request unless the client knows the semantics are idempotent or can establish that the original request was never applied.1
Three truths therefore have to remain separate. Transport truth records whether bytes and acknowledgements were exchanged. Command truth records whether a named intent was accepted, applied, rejected or remains unresolved. Business-state truth records what the authoritative system now says about the customer, account or asset. Collapsing these truths into one Boolean is the design mistake.
Figure 2. Three instruments observe three different truths
Transport instrument
Connection opened, request sent, response received, deadline exceeded. It observes communication, not the business postcondition.
Command instrument
Command ID, intent fingerprint, acceptance, terminal status and replay result. It observes one semantic instruction across attempts.
State instrument
Ledger entry, resource version, settlement reference or other authoritative evidence. It observes the effect that matters.
Thought experiment: move the acknowledgement
Imagine the ledger sends its acknowledgement just before the database commit. The caller receives a clean success, then the ledger process crashes and rolls back. The acknowledgement is early enough to be responsive but cannot prove the effect. Move the acknowledgement to just after commit. The ledger now avoids false success, but a network break can still hide a genuine effect from the caller. Only one causal feature changed, the location of the acknowledgement, yet neither location eliminates uncertainty.
The classic end-to-end argument explains why. Lower layers can make communication more reliable and suppress some duplicates, but the complete correctness condition belongs at the application endpoints that understand what the data or action means.4 In this case, only the application can ask the decisive question: does authoritative state contain the one posting attributable to this approved drawdown?
UNKNOWN is therefore a legitimate operational state, not a softer spelling of failure. It means that available evidence cannot yet distinguish relevant worlds. Treating it explicitly prevents two damaging transformations: converting uncertainty into a fresh command, and allowing downstream steps to behave as though the effect were settled.
Figure 3. Attempts may rise while the permitted effect count stays flat
Exactly once is a boundary claim
The phrase “exactly once” is often attached to a queue, broker, workflow engine or database as though it were a transferable property. It is not. The useful claim names three things: which semantic command, which business effect and which system boundary. A broker can guarantee one committed record in its log while an external payment service charges twice. A workflow engine can record one completion while an activity function executes more than once. An application can deduplicate API calls while a notification or fee outside the transaction duplicates.
Apache Kafka, for example, can atomically combine consumed offsets and produced records inside its transactional model, and its idempotent producer suppresses duplicate log entries for retried sends. Its own design discussion makes the boundary visible: end-to-end exactly-once processing depends on where the output is stored and whether that destination cooperates.8 Temporal similarly tells application authors that an Activity may be retried and should be idempotent; the platform can preserve workflow history without making an arbitrary external side effect execute once.9
k is the stable command identifier, H is the committed history inside the named boundary, and E counts business effects attributable to that command. Repeated attempts may increase, but the attributable committed effect count must never exceed one.
The same identifier must always denote the same semantic intent. A repeated key with a different amount, subject or action is a conflict, not a retry.
observe(k, S_authoritative) ∈ {VERIFIED_APPLIED, VERIFIED_REJECTED, UNKNOWN, CONFLICT}The observer resolves the command from authoritative state S. It does not infer application merely from a successful hand-off or infer non-application merely from silence.
This gives three proof obligations. Safety prevents a duplicate effect. Intent integrity prevents a duplicate fence from joining two different instructions. Observability determines which terminal state is actually supported after a lost reply. A fourth property, liveness, asks whether an unresolved command eventually reaches a terminal state when communication and services recover. Safety without liveness creates commands that never duplicate but can remain frozen forever. Liveness without safety can resolve them by doing the harmful thing twice.
A command identity is not a payload hash
Suppose a customer intentionally makes two £100 transfers to the same beneficiary on the same day. The payloads can be identical while the intents are distinct. Conversely, one transfer can be retried with inconsequential formatting differences while the intent remains one. The service therefore needs a caller-supplied command identifier that names the business intention, plus a canonical fingerprint that prevents the identifier from being reused with changed meaning.
AWS describes the same design pressure in idempotent APIs: a caller-provided request identifier is more reliable than inferring sameness from parameters, because identical parameters can represent separate intended creations.5 Stripe persists the first result for an idempotency key and rejects parameter changes under the same key, but it also documents a retention boundary after which a reused key can be treated as new.6 The architectural implication is easy to miss: deduplication retention must exceed every legitimate retry, replay and delayed-delivery horizon, or expiry silently converts an old retry into fresh authority.
The duplicate fence and the effect must commit together
A separate “seen this key” cache is not enough. If the cache is written before the business mutation, a crash can leave a durable duplicate marker for an effect that never happened. Every retry will be suppressed, producing at-most-once execution with a missing outcome. If the mutation is written first, a crash can leave a business effect with no duplicate marker, allowing the retry to repeat it.
Inside one database, the clean pattern is to place the command record, its intent fingerprint, the business mutation and the terminal result in one atomic transaction. Across participants, transaction-commit protocols coordinate whether a distributed transaction commits or aborts. Gray and Lamport’s analysis makes clear that the commit problem is an agreement problem and that classic two-phase commit can block after coordinator failure.7 Even a correct commit protocol does not guarantee that the original client received the decision. The client still needs a durable way to ask later.
Figure 4. The eight-layer proof stack
The boundary must include every effect in the sentence
“The drawdown happened exactly once” may mean only that the core ledger has one posting group. It may also be understood to mean that a payment instruction was sent once, a customer notification was sent once, fees were assessed once and downstream case state advanced once. Those are different claims. If a ledger transaction also writes an outbox row, change-data capture can publish the event without the classic gap between committed state and message emission.11 The consumers still need their own duplicate fences.
When an external provider cannot accept the stable command ID, cannot expose outcome by that ID, and cannot participate in an atomic protocol, the system cannot honestly claim global exactly-once behaviour. It can narrow the boundary, use reservations, reconcile on a provider reference, or place a human decision at the irreversible edge. The boundary is part of the guarantee, not a footnote beneath it.
flowchart TD A["The packet that disappeared<br/>after the money moved"] --> B["A timeout creates a third outcome"] B --> C["Exactly once is a boundary claim"] C --> D["Build an outcome observer, not<br/>a retry loop"] D -. "re-test" .-> A classDef first fill:#0a1424,stroke:#5B5CF6,stroke-width:2px,color:#e6f3ff classDef step fill:#0a1424,stroke:#3f7da0,stroke-width:1px,color:#e6f3ff class A first class B,C,D step
Build an outcome observer, not a retry loop
An outcome observer is not a dashboard that notices failures after the fact. It is an application-level protocol that accepts the original command identity and intended postcondition, reads the best available authoritative evidence, and returns a status that constrains what the workflow may do next. It sits between uncertain execution and any dependent action.
The observer needs at least the command ID, intent fingerprint, subject, action type, expected postcondition, authority boundary and required consistency. It should return one of a small number of typed outcomes. VERIFIED_APPLIED means that the intended effect exists once and is attributable to the command. VERIFIED_REJECTED means that the authority recorded a terminal refusal. CONFLICT means that evidence exists but disagrees with the expected intent or state. UNKNOWN means that neither application nor safe non-application is proved.
A useful implementation may also expose NOT_APPLIED, but only when absence is definitive. A missing row on a lagging replica is not definitive. A missing row while the original request might still be executing is not definitive. A missing row after the executor has fenced the command, reached a terminal transaction status, and served a consistency-qualified read can be. Google Cloud Spanner documents the practical version of this distinction: a rare unknown commit result should be followed by another read to inspect current database state.10
Figure 5. Outcome is a governed state machine
PROPOSED
Intent exists, but no authority to cause the effect has yet been consumed.
ACCEPTED
The executor has received the named command. This is not a terminal business outcome.
UNKNOWN
The caller lacks enough evidence. Dependent actions and fresh commands are blocked.
VERIFIED
Applied once or terminally rejected, with an effect or rejection receipt.
ESCALATED
Conflict, expired evidence or an uncooperative external boundary needs controlled resolution.
Separate the decision receipt from the effect receipt
The system should preserve two different records. A decision receipt proves why the command was authorised: requesting principal, approving principal, purpose, policy version, limits, subject, parameters and the command ID created from that decision. An effect receipt proves what happened afterwards: terminal outcome, effect identifier, committed state version, postcondition checked, observation time and evidence pointer.
The first receipt prevents execution from outrunning authority. The second prevents authority from being mistaken for execution. This separation is useful in agentic systems because a model proposal, a human approval, an API acceptance and a verified business outcome otherwise collapse into one narrative trace. A fluent trace can then claim completion while the authoritative system remains unchanged, or claim failure while a hidden commit has already occurred.
Figure 6. Authority and effect require different receipts
Decision receipt
Answers: who approved which intent, under which rule, for which subject and limit?
Effect receipt
Answers: did that named intent produce the expected authoritative state exactly once?
Observe the postcondition, not a convenient proxy
A postcondition is a predicate over authoritative business state. For the drawdown, it might require exactly one posting group with the original command ID, the expected facility and settlement accounts, the approved amount, a committed ledger version and no reversal linked to that effect. Merely observing that the destination balance increased by £250,000 is weaker. Another legitimate transfer could have produced the same balance change. Amount, time and customer are useful reconciliation clues, but they are not causal attribution.
The postcondition also needs a version. A rule such as “status equals PAID” can become wrong when the product adds an intermediate PAID_PENDING_FINALITY state. Versioning the predicate makes the effect receipt interpretable later. Where legal or network finality occurs after the initial commit, the observer can issue a provisional receipt and later a final receipt, each with a distinct claim. It should not silently upgrade “instruction accepted” into “funds settled”.
Absence needs a consistency and time argument
Positive evidence is often easier than negative evidence. One durable, unique effect row with the correct identity can prove application. The absence of that row may mean not applied, not yet visible, still executing, written elsewhere, rolled back, or lost from the observer’s query path. The observer therefore needs to know which store is authoritative, whether the read is linearizable or replica-lagged, whether the executor can still commit, and how long command records remain queryable.
Thought experiment: move only the observer
Keep the command, executor and committed transaction unchanged. In the first world, the outcome observer reads the writer-side command index. It finds the effect row and returns VERIFIED_APPLIED. In the second world, the observer reads an asynchronous replica that is 800 milliseconds behind. At 600 milliseconds, the same query returns no row. The business reality is identical in both worlds. Only the observer’s evidence path changed.
If “not found” means “safe to issue a new command”, the second world can create a duplicate because replica lag has been converted into fresh authority. If “not found” means UNKNOWN, the workflow waits, reads from a stronger source or replays through the original duplicate fence. This single-variable comparison shows why a postcondition needs a consistency contract as well as a query.
A stronger model cannot repair the missing evidence. It can estimate that replica lag is likely, but probability does not establish whether this particular transfer committed. For an irreversible action, the observer should use likelihood only to choose the next information-gathering step, never to manufacture a terminal outcome. Presence can be proved by one causally linked durable effect; absence requires both an authoritative read and a reason the original command can no longer become visible.
A disciplined recovery loop follows this order:
- Freeze every dependent step that assumes the effect.
- Read the command and business effect by the original identity from an authorised, consistency-qualified source.
- Verify the intent fingerprint and the full postcondition, not only the presence of a row.
- If applied, emit the effect receipt and resume. If rejected, preserve the terminal reason.
- If no terminal evidence exists but in-flight execution remains possible, stay
UNKNOWNand observe again with bounded backoff. - Retry only through the original command identity after the authority can guarantee that the duplicate fence will return the first result or safely execute the still-unapplied intent.
- Escalate conflicts, evidence expiry and unbounded ambiguity. Do not repair them with a new identifier.
Compensation is not proof that the first effect never happened. It is a second, explicitly authorised effect linked to the first. A reversal may restore an economic balance while leaving fees, notifications, market exposure or legal consequences. The ledger should therefore preserve both receipts: original application and compensating action.
Experiments that expose the mechanism
A reliability claim becomes useful when a fault can try to break it. The decisive fault for this paper is simple: commit the business mutation, then lose the response before the caller records success. A second attempt should not create a second business effect, and the system should still be able to prove what happened.
Worked example one: one transfer, two implementations
The minimal experiment starts with account A holding 100,000 pence and account B holding zero. It asks to transfer 25,000 pence. The naive implementation treats each delivery attempt as a new instruction. It commits the first transfer and then raises a synthetic lost-reply exception. The caller retries with a new attempt identifier. The final balances are A = 50,000 and B = 50,000, with two ledger rows. The transport retried once; the business effect happened twice.
The controlled implementation gives the transfer one command ID and one canonical intent hash. Inside one SQLite transaction it inserts the command record, moves the balances, inserts a ledger row whose command ID is unique, and stores the terminal result. The synthetic reply is again lost after commit. When the caller invokes the operation again with the same ID and same intent, the transaction returns the stored result. The final balances are A = 75,000 and B = 25,000, with one ledger row. Reusing the command ID with a different amount raises an intent conflict.
Figure 7. The atomic transaction closes the duplicate gap
command_id is unique; intent_hash fixes meaning.Debit and credit move together or both roll back.
One ledger entry is causally attributable to the command.
A replay returns the original outcome instead of executing again.
Negative control: change the operation, keep the failure
Now replace “increment the balance by 25,000” with “set case status to REVIEWED”. Replaying the state-setting operation converges on the same primary state. The second execution may be harmless even without a duplicate table. This is a serious negative control because it shows that the full mechanism is not always necessary. Algebraically idempotent operations can make duplicate execution acceptable when every material side effect shares the same property.
The qualification matters. The status may converge while each attempt sends an email, charges a fee, appends a non-deduplicated audit event or triggers another workflow. HTTP defines idempotence in terms of the intended effect requested by the user, while allowing servers to have other per-request side effects.1 The architect must inventory the full effect surface before declaring the operation safe to repeat.
Figure 8. Duplicate execution is sometimes harmless, and sometimes cumulative
Cumulative operation: increment by 1
Each retry changes the result again. Duplicate execution violates the intended postcondition.
1
2
3
Convergent operation: set to REVIEWED
Repeated primary writes reach the same state, provided secondary effects are also controlled.
REVIEWED
REVIEWED
REVIEWED
Worked example two: a production-shaped drawdown
Consider the same drawdown inside a compound AI workflow. A model extracts facility terms and proposes an amount. Deterministic checks retrieve the current facility version, remaining limit, currency and settlement accounts. A human approves. The authority kernel creates cmd-drawdown-042, fingerprints the approved parameters and stores the decision receipt. Only then does an executor call the core ledger.
The ledger’s idempotency record and posting group commit together. The response is lost. The workflow records UNKNOWN, freezes customer notification, cash-management updates and any next-step recommendation that assumes available funds. The outcome observer queries the writer-side command index by the original ID, verifies the fingerprint, confirms exactly one posting group, checks the facility and settlement accounts, and captures the ledger state version. It emits VERIFIED_APPLIED and an effect receipt. The workflow can now publish a drawdown event through an outbox and release dependent steps.
Suppose the next step sends an instruction to an external payment rail that neither accepts the command ID nor provides a query by client reference. The guarantee stops at the ledger boundary. The rail step needs its own correlation reference, duplicate controls and reconciliation. Without them, the system may claim “ledger posting exactly once” but must not claim “customer funds settled exactly once”. If the rail remains ambiguous beyond its operating window, the appropriate route may be an operations queue, not another model-generated attempt.
Figure 9. The outcome observer sits between authority and dependent effects
Model proposal
Draft amount, evidence and rationale. No direct effect authority.
Authority kernel
Identity, policy, approval, command ID and intent fingerprint.
Executor and ledger
Atomic duplicate fence plus posting. Reply may still be lost.
Outcome observer
Authoritative readback, postcondition check and effect receipt.
Failure injection is the evidence
The happy path proves little. A release gate should inject faults at the seams where state and knowledge can diverge. The expected invariant is not “every request succeeds”; it is that no test creates a second effect for one command, silently advances on uncertainty, or loses the evidence needed to recover.
| Injected fault | Required observation | Forbidden behaviour |
|---|---|---|
| Drop the response after commit | Command becomes UNKNOWN, then resolves by authoritative readback | Fresh command ID or duplicate effect |
| Crash between duplicate marker and mutation | Both roll back because they share one transaction | Permanent marker with no effect, or effect with no marker |
| Replay the same ID with changed amount | CONFLICT and security or integrity alert | Overwrite the original intent or apply the new amount |
| Read from a lagging replica | UNKNOWN until consistency is sufficient | Interpret missing data as safe non-application |
| Deliver an old retry after retention expiry | Reject or reconcile under an explicit late-arrival policy | Treat the old instruction as a fresh command |
| Commit ledger state but fail event publication | Outbox retains a replayable event tied to the same transaction | Repeat the ledger mutation to regenerate the event |
Choose the recovery mechanism from consequence and evidence
The strongest objection to this architecture is that it can be excessive. A low-value cache refresh, a repeatable read or a convergent “set preference” call does not need the same machinery as a payment, entitlement change or external communication. That objection is correct. The mechanism should be proportionate to the operation’s algebra, consequence, reversibility and observability.
The mistake is to make the decision from transport reliability alone. A service with five nines of availability can still produce one ambiguous commit at the worst possible moment. A less available service may be safe to retry if its operation is genuinely idempotent and its secondary effects are controlled. The design question is therefore: what can repetition do, and what evidence can later distinguish the possible worlds?
Figure 10. Recovery decision by duplicate safety and outcome evidence
Retry with the same command identity. Preserve a receipt when attribution, audit or secondary effects matter.
Automatic replay is acceptable through the original fence. Resume only after a terminal effect receipt.
Remain UNKNOWN, reconcile and repair the missing control. Do not convert absence or timeout into a fresh command.
Stop automatic retry. Use a reservation, manual disposition, provider reconciliation or a redesigned integration boundary.
The explicit failure boundary
The preferred mechanism ceases to prove its claim under any of the following conditions:
- No stable semantic identity: each retry receives a fresh key, or identical payloads are incorrectly assumed to be the same business intent.
- No atomicity: the duplicate record, business mutation and stored result can diverge under crash or partial commit.
- No causal postcondition: the observer can see a balance or status but cannot tie it to the command amid concurrent legitimate changes.
- Insufficient consistency: a lagging or incomplete read is allowed to prove non-application while the original executor can still commit.
- Short retention: command and result records expire before delayed messages, disaster replay, manual resubmission or legal investigation can arrive.
- Uncooperative external effects: a provider cannot deduplicate, expose a client reference or participate in a protocol that joins its result to the claimed boundary.
- Receipt-state divergence: an effect receipt is generated from a separate, fallible write rather than reconstructed from or committed with authoritative state.
Under these conditions, the architecture can still reduce risk, but it should change its language. It may offer at-least-once delivery with deduplication best effort, manual reconciliation, or compensated completion. It should not advertise unqualified exactly-once behaviour. Research on stream-processing guarantees has criticised the term precisely because delivery, consistency and determinism are often collapsed into one label while carrying different costs and properties.12
Late retries are delayed authority
The retention horizon cannot be copied from an HTTP timeout or a queue’s ordinary redelivery window. It must cover the longest path by which the same intent can return: client retries, broker delays, regional recovery, workflow replay, operator resubmission, restored backups and exceptional reconciliation. A command that reappears after its duplicate record has been deleted is operationally indistinguishable from a new instruction unless another durable identity ledger survives.
Long retention has costs, including storage, privacy exposure and lookup performance. The answer is not casual expiry. Sensitive payloads can be reduced to a keyed fingerprint and minimum audit fields, while retention is tiered by consequence. High-impact commands may need a compact tombstone for much longer than their full response body. The late-arrival policy must be tested before deletion: reject, quarantine, or reconstruct from an authoritative archive. Deleting the fence does not delete the original authority; it deletes the system’s ability to recognise its return.
What the operating model should measure
Ordinary service metrics count calls, errors and latency. Outcome verification needs a second family of measures: the number and age of commands in UNKNOWN, time from acceptance to verified outcome, percentage of consequential actions with effect receipts, conflicts caused by key reuse, late retries arriving beyond retention, postcondition read failures, duplicate effects found by reconciliation, and compensations initiated after an ambiguous commit.
These measures should be segmented by action type and consequence. A five-minute unknown outcome for a document tag may be unimportant; five minutes for a market order or customer disbursement may halt a process. The service-level objective should therefore govern time to verified business outcome, not only API response time. A fast acknowledgement can make the transport graph look healthy while the business backlog accumulates unresolved commands.
Release evidence should include the fault-injection cases above, a documented command-retention horizon, consistency tests for the observer, a schema for decision and effect receipts, and a boundary statement listing every downstream effect excluded from the guarantee. The companion paper on trajectory assurance treats observed effect as one layer of model-risk evidence; this mechanism supplies the command-level proof and recovery protocol that layer needs.13
stateDiagram-v2 [*] --> Claim state "Figure 1. The ambiguous-commit<br/>timeline" as Claim state "Figure 2. Three instruments<br/>observe three different truths" as Test state "Thought experiment: move the<br/>acknowledgement" as Revision Claim --> Test: expose mechanism Test --> Decision: evidence survives Test --> Revision: rival survives Revision --> Claim: narrow or rebuild Decision --> [*]
A fault-injected outcome observer in SQLite
The artefact below uses only Python’s standard library. It creates two accounts, a command table and a ledger. The negative path deliberately commits a transfer, loses the reply and retries under a fresh identity. The positive path commits the command record, transfer, unique ledger effect and stored result in one transaction, then loses the same reply and replays through the original identity.
Its expected output is discriminating rather than decorative. The naive path must show two effects. The controlled path must show one effect, a VERIFIED_APPLIED receipt and rejection when the same command ID is reused with a changed amount. Assertions make the script fail if any of those properties change.
Runnable Python 3 artefact
"""Run with Python 3.11+. Uses only sqlite3 from the standard library."""
import hashlib
import json
import sqlite3
class LostReply(Exception):
pass
class IntentConflict(Exception):
pass
def fingerprint(payload):
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode()).hexdigest()
def database():
db = sqlite3.connect(":memory:")
db.row_factory = sqlite3.Row
db.executescript(
"""
CREATE TABLE accounts(
id TEXT PRIMARY KEY,
balance INTEGER NOT NULL CHECK(balance >= 0)
);
CREATE TABLE commands(
command_id TEXT PRIMARY KEY,
intent_hash TEXT NOT NULL,
status TEXT NOT NULL,
result_json TEXT,
committed_at TEXT
);
CREATE TABLE ledger(
effect_id INTEGER PRIMARY KEY AUTOINCREMENT,
command_id TEXT NOT NULL UNIQUE,
debit_id TEXT NOT NULL,
credit_id TEXT NOT NULL,
amount INTEGER NOT NULL CHECK(amount > 0)
);
INSERT INTO accounts VALUES ('A', 100000), ('B', 0);
"""
)
return db
def snapshot(db):
rows = dict(db.execute("SELECT id, balance FROM accounts"))
effects = db.execute("SELECT COUNT(*) FROM ledger").fetchone()[0]
return rows["A"], rows["B"], effects
def apply_once(db, command_id, payload, lose_reply=False):
"""Duplicate fence, business mutation and result commit atomically."""
intent_hash = fingerprint(payload)
db.execute("BEGIN IMMEDIATE")
old = db.execute(
"SELECT intent_hash, result_json FROM commands WHERE command_id = ?",
(command_id,),
).fetchone()
if old:
if old["intent_hash"] != intent_hash:
db.rollback()
raise IntentConflict("same command_id, different intent")
result = json.loads(old["result_json"])
db.commit()
return result
db.execute(
"INSERT INTO commands VALUES (?, ?, 'ACCEPTED', NULL, NULL)",
(command_id, intent_hash),
)
debit = db.execute(
"""UPDATE accounts SET balance = balance - ?
WHERE id = ? AND balance >= ?""",
(payload["amount"], payload["debit_id"], payload["amount"]),
)
if debit.rowcount != 1:
result = {"status": "REJECTED", "reason": "insufficient funds"}
db.execute(
"""UPDATE commands SET status='REJECTED', result_json=?,
committed_at=CURRENT_TIMESTAMP WHERE command_id=?""",
(json.dumps(result), command_id),
)
db.commit()
return result
db.execute(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
(payload["amount"], payload["credit_id"]),
)
effect_id = db.execute(
"INSERT INTO ledger(command_id, debit_id, credit_id, amount) "
"VALUES (?, ?, ?, ?)",
(
command_id,
payload["debit_id"],
payload["credit_id"],
payload["amount"],
),
).lastrowid
result = {"status": "APPLIED", "effect_id": effect_id}
db.execute(
"""UPDATE commands SET status='APPLIED', result_json=?,
committed_at=CURRENT_TIMESTAMP WHERE command_id=?""",
(json.dumps(result), command_id),
)
db.commit()
if lose_reply:
raise LostReply("commit succeeded; response disappeared")
return result
def observe(db, command_id, expected):
"""Return an effect receipt from authoritative command and ledger state."""
command = db.execute(
"SELECT * FROM commands WHERE command_id = ?", (command_id,)
).fetchone()
if not command:
return {"outcome": "UNKNOWN", "reason": "no durable command record"}
if command["intent_hash"] != fingerprint(expected):
return {"outcome": "CONFLICT", "reason": "intent hash mismatch"}
if command["status"] == "REJECTED":
return {"outcome": "VERIFIED_REJECTED", "command_id": command_id}
effects = db.execute(
"SELECT * FROM ledger WHERE command_id = ?", (command_id,)
).fetchall()
if command["status"] != "APPLIED" or len(effects) != 1:
return {"outcome": "UNKNOWN", "reason": "postcondition not proved"}
effect = effects[0]
actual = {
"debit_id": effect["debit_id"],
"credit_id": effect["credit_id"],
"amount": effect["amount"],
}
if actual != expected:
return {"outcome": "CONFLICT", "reason": "effect differs from intent"}
return {
"outcome": "VERIFIED_APPLIED",
"command_id": command_id,
"effect_id": effect["effect_id"],
"intent_hash": command["intent_hash"],
"committed_at": command["committed_at"],
}
def naive_retry_demo(payload):
"""A fresh ID per attempt turns one retry into two business effects."""
db = database()
for attempt in ("attempt-1", "attempt-2"):
try:
apply_once(db, attempt, payload, lose_reply=(attempt == "attempt-1"))
except LostReply:
pass
return snapshot(db)
def safe_retry_demo(payload):
db = database()
command_id = "cmd-drawdown-042"
try:
apply_once(db, command_id, payload, lose_reply=True)
except LostReply:
pass
apply_once(db, command_id, payload)
receipt = observe(db, command_id, payload)
return db, snapshot(db), receipt
def main():
transfer = {"debit_id": "A", "credit_id": "B", "amount": 25000}
naive = naive_retry_demo(transfer)
db, safe, receipt = safe_retry_demo(transfer)
assert naive == (50000, 50000, 2)
assert safe == (75000, 25000, 1)
assert receipt["outcome"] == "VERIFIED_APPLIED"
try:
changed = {**transfer, "amount": 30000}
apply_once(db, "cmd-drawdown-042", changed)
except IntentConflict:
conflict = "rejected reused key with different intent"
else:
raise AssertionError("intent conflict was not detected")
print(f"naive: A={naive[0]}, B={naive[1]}, effects={naive[2]}")
print(f"safe: A={safe[0]}, B={safe[1]}, effects={safe[2]}")
print(
f"observer: {receipt['outcome']}, "
f"command={receipt['command_id']}, effect={receipt['effect_id']}"
)
print(f"conflict: {conflict}")
if __name__ == "__main__":
main()
Run the file as python3 outcome_observer_demo.py. The first call to each implementation commits and raises LostReply. In the naive path, the caller invents attempt-2, so the service correctly treats it as a second authorised command. In the controlled path, the caller reuses cmd-drawdown-042, so the service returns the result already stored under that semantic identity.
naive: A=50000, B=50000, effects=2
safe: A=75000, B=25000, effects=1
observer: VERIFIED_APPLIED, command=cmd-drawdown-042, effect=1
conflict: rejected reused key with different intentThe observer intentionally returns UNKNOWN when no durable command record exists. In a real distributed system, “not found” cannot prove non-application while an executor may still be in flight. A production implementation would add an execution lease or terminal transaction inquiry, consistency metadata, a bounded retry schedule, record retention, signed or tamper-evident receipts, and separate observers for external command boundaries.
The most important line is not the unique constraint by itself. It is the transaction boundary around command identity, intent, business mutation and result. Moving any one of those writes outside the boundary creates a crash point the test suite should inject. The artefact can be extended by killing the process after each statement, replaying the command, and asserting that the effect count remains zero or one while every committed command remains observable.
Terms that must not collapse into “success”
| Term | Meaning in this paper |
|---|---|
| Attempt | One delivery or execution try. Several attempts may belong to one command. |
| Command | A stable, named business intent whose meaning is fixed by an intent fingerprint. |
| Acceptance | Evidence that a component received or queued the command, not that the postcondition holds. |
| Commit | The durable decision that mutations inside a named transaction boundary take effect. |
| Postcondition | A versioned predicate over authoritative business state that defines successful effect. |
| Effect receipt | A record binding the command and intent to the verified terminal business state. |
| Unknown outcome | An evidence state in which application and safe non-application are both still possible. |
| Compensation | A new authorised effect that addresses a prior effect; it does not erase history. |
Sources and the boundary of each claim
- OfficialRFC 9110, HTTP Semantics. Defines idempotent request semantics and the conditions under which automatic retry is appropriate.
- OfficialgRPC status codes. States that a deadline can expire even when a state-changing operation completed successfully.
- OfficialCockroachDB common errors. Documents ambiguous commit results and distinguishes idempotent from cumulative retries.
- PrimaryJ. H. Saltzer, D. P. Reed and D. D. Clark, End-to-End Arguments in System Design. Grounds the claim that application-level knowledge is needed for complete correctness checks.
- PractitionerMalcolm Featonby, Making retries safe with idempotent APIs. Explains caller-provided identifiers, semantic equivalence and late-arriving requests.
- OfficialStripe idempotent requests. Documents stored first results, parameter mismatch checks and key-retention limits.
- PrimaryJim Gray and Leslie Lamport, Consensus on Transaction Commit. Frames distributed commit as agreement and analyses blocking in classic two-phase commit.
- OfficialApache Kafka design. Describes idempotent production, transactions and the boundary of exactly-once processing.
- OfficialTemporal Activity definition. Clarifies that Activities may execute more than once and should be made idempotent.
- OfficialCloud Spanner commit method. Documents rare unknown commit outcomes and recommends reading current state.
- OfficialDebezium Outbox Event Router. Describes the outbox pattern for avoiding divergence between internal state and published events.
- PrimaryArtem Trofimov and colleagues, Delivery, consistency, and determinism. Challenges underspecified exactly-once labels in stream processing and separates their underlying properties.
- PractitionerRajesh Ranjan Mahapatra, Model-Risk Evidence for Agentic Systems. Provides the adjacent trajectory-assurance frame; this paper narrows its effect-correctness layer into a command protocol.
sequenceDiagram participant Q as Question participant M as Mechanism participant E as Evidence participant D as Decision Q->>M: The packet that disappeared after the money moved M->>E: Figure 1. The ambiguous-commit timeline E-->>M: compare rival explanations E->>D: Do not advance on acceptance D-->>Q: revise scope or act
Do not advance on acceptance
The practical change is small enough to state as one rule: a consequential workflow may advance only on a verified business outcome, not on command acceptance or a missing error. A normal reply can carry the effect receipt immediately. A lost reply must create UNKNOWN, preserve the original identity and block every dependency that assumes success or failure.
This rule turns retry from a generic resilience reflex into a governed state transition. Stable identity says which intent is being recovered. The atomic duplicate fence prevents a second effect. The postcondition says what successful completion means. The outcome observer connects that definition to authoritative state. Retention, consistency and boundary declarations keep the claim honest over time.
No mechanism here makes arbitrary distributed action magically occur once. It offers something more defensible: within a named boundary and stated assumptions, every approved command can produce at most one attributable effect, and uncertainty can be resolved without issuing fresh authority. Where those assumptions fail, the system must narrow its claim, reconcile, compensate or ask a human. The unknown outcome is not an inconvenience to hide. It is the point at which reliable systems prove what they know before they act again.