- Architecture
Prevent duplicate bookings across redials and concurrent calls
Prevent duplicate bookings with stable intent identity and atomic capacity checks. Test redials, concurrent requests and unknown outcomes.
- Author
Udit Goenka- Editorial date
- Reading time
- 27 min

TL;DR: Keep one immutable business-intent identity across redials and retries, then reserve the intended resource atomically. Intent deduplication prevents repeated execution of the same request; a separate capacity constraint prevents different requests from claiming the same slot, and an unknown remote outcome requires reconciliation.
A caller asks for a booking, loses the connection and calls again. The second conversation has a fresh call ID, but the requested booking may be the same business intent. If your application creates a new operation for every call, a harmless redial can become another reservation.
There is a second race even when intent identities are correct. Two different callers can read the same available slot before either request commits. A deduplication key for each request does not stop both from reserving the resource unless the capacity decision is enforced at the authoritative write boundary.
The webhook projection contract separates delivery identity from business operations. This guide takes the next step: a local SQLite model that keeps intent identity, exact payload, current resource permission and slot reservation in one transaction. It is an executable contract demonstration, not a booking provider or distributed exactly-once system for 60db Agent.
AWS's idempotent API design discussion explains why caller-supplied request identity matters when retries express the same intent. Your implementation must still decide what constitutes the intent and which store can enforce the associated business constraint.
Table of contents
- Give business intent a stable identity
- Freeze exact values before allowing retries
- Protect resource capacity separately
- Commit permission and reservation together
- Run a local booking contract
- Keep unknown remote outcomes unresolved
- Make cancellation terminal for that intent
- Diagnose redials without inventing success
- Frequently asked questions
- Validate identity and capacity independently
Give business intent a stable identity
A call attempt is a transport and conversation identity. A business intent identifies the exact proposed operation that a principal wants performed within a tenant. One intent can survive several call attempts, and one call can contain several distinct intents. Treating those identities as interchangeable loses both retry safety and useful diagnostic evidence.
Issue the intent identity at a trusted server boundary and retain it with the approved values. Do not derive it solely from caller ID, a phone number, an ASR transcript or the current call ID. Those values can be ambiguous, change between attempts or fail to establish the business principal.
The caller-authentication guide explains why reaching a familiar number does not prove identity. An authenticated returning principal may be allowed to locate an existing intent, but that lookup still needs tenant and resource authorization. A matching voice or repeated phrase should not silently join two operations.
For a redial, recover the existing intent only through a documented authenticated workflow. Ask whether the user is continuing the prior request when necessary, preserve its exact values, and expose its current disposition. If the user wants a different resource or time, create an explicitly revised proposal rather than altering the old retry payload.
A deterministic hash of booking values is not always a sufficient intent identity. Two legitimate separate bookings can have identical visible values, while one ongoing intent can acquire several conversational descriptions. Use a server-issued operation identity and bind it to the admitted payload, rather than assuming semantic equality of text proves sameness of intent.
The teaching schema scopes an identity by tenant and principal. It stores call-attempt labels only in the fixture report. Production schemas may use different scope owners, but they need an equally precise rule for who can create, locate, retry and disclose an operation.
Keep the original identity stable during transport recovery. Generating a new UUID after each timeout defeats an idempotent provider's ability to recognize a retry. A new identity should mean a new authorized logical operation, not merely that the previous request produced an inconvenient response.
Current business-state checks remain necessary even when the intent is stable. A remembered availability result can become stale while the conversation continues. Stable operation identity preserves retry meaning; it does not freeze the external inventory forever.

Freeze exact values before allowing retries
An exact retry needs an exact payload. Store the approved resource, canonical slot and relevant inventory version as an owned value snapshot. Do not retain a mutable caller dictionary that can change after review while keeping the same intent identity.
The artifact uses a frozen Intent dataclass containing only strings and an integer version. The local payload serializer emits an explicit list of resource, slot and expected version. That binding survives inventory and capacity conflicts as well as successful commits; denied access and malformed input do not create an admitted intent. It is a schema-specific equality representation, not a universal JSON canonicalization standard or a semantic comparison of arbitrary objects.
The slot is an opaque server-defined label in the model. It is not a date parser or a promise that arbitrary overlapping intervals are protected. A production adapter must resolve a requested time against the declared calendar, timezone and authoritative inventory before producing this canonical slot identity.
The locale and timezone guide covers that earlier resolution boundary. A date-only utterance, a repeated local time or an omitted zone should not be guessed into a final booking value. Confirmation should describe the resolved instant and resource in terms the caller can inspect.
Preserve the original evidence behind the proposal separately from the canonical booking values. That evidence may include permitted transcript references and the exact clarification that selected a slot. It should not be overwritten by the normalized representation or copied wholesale into an idempotency key.
The number-capture contract illustrates the general preservation rule. Formatting changes can support presentation while a canonical value remains owned by its field contract. Booking identity should not depend on whatever spoken formatting the latest call happened to produce.
When an existing identity receives a changed payload, return an explicit intent conflict. An authorized intent that reaches an inventory or capacity conflict is still admitted evidence: persist its exact payload and conflict disposition before returning. Do not merge the new values into the first request, return the previous success as if it applied to the change, or generate another provider key behind the caller's back. A material repair requires a newly reviewed logical operation.
This distinction also applies after a failure. A changed request can be valid, but it is not an exact retry of the old one. Keep the old operation's evidence available and make the new proposal's relation to it explicit, especially if the old remote outcome remains unknown.
Strict scalar validation belongs before serialization and database work. The model rejects booleans as inventory versions and bounds supported integers to SQLite's range. A larger application should similarly validate resource identifiers and field-specific values before comparing or sorting them.
Pronunciation dictionary snapshots provide a useful parallel for reviewed content: approval binds a particular immutable representation. A reviewed booking must bind the same exactness to resource, time, principal scope and intended action, even though its business semantics differ from a dictionary release.

Protect resource capacity separately
Intent uniqueness and resource uniqueness solve different problems. The first says that one scoped operation has one recorded result. The second says that the authoritative capacity unit cannot be reserved beyond its allowed occupancy. You usually need both for a single-capacity booking service.
Imagine two different authenticated callers choosing the same slot. Each has a distinct intent identity, so both pass an intent uniqueness check legitimately. If availability is checked outside the reservation transaction, both can observe an empty slot and then create separate bookings.
A real concurrent-booking issue report describes overlapping requests being accepted after availability checks. It supplies a concrete developer question, not an independently reproduced defect rate or a claim about every release. The relevant design question is where the final capacity constraint is enforced.
The local model uses a reservation primary key over tenant, resource and slot. Its transaction serializes the check and insertion in one database, and the primary key is the final structural guard. Two different intents cannot both own that exact capacity unit in this store.
PostgreSQL's constraints documentation describes uniqueness and exclusion constraints. Choose the constraint that matches the real inventory model. A unique start-time string cannot protect arbitrary overlapping intervals, and a single-row reservation model cannot represent every multi-capacity resource.
For variable-duration appointments, define overlap semantics precisely. Include setup time, buffers, resource pools and interval endpoint rules if the business requires them. The local example intentionally avoids those features and does not advertise that its opaque slot label solves a general scheduling problem.
A slot revision answers another question: whether the caller approved against the expected inventory definition. Capacity and revision are independent. A slot can still be empty while its time or terms changed, so a matching capacity key alone may not make the approved operation valid.
The artifact's configuration helper creates initial slot versions for fixtures and can update the local permission row. It does not implement inventory editing. A production inventory service must advance versions under its owning transaction and define what happens to existing reservations when a slot changes.
The admission-control boundary offers a related principle. Checking that work appears acceptable before enqueueing is weaker than atomically reserving the capacity you intend to own. Apply that reasoning to the resource itself rather than relying on an earlier availability display.
Partitioned regional stores complicate the rule. If each disconnected region can independently reserve the same real-world slot, local uniqueness constraints do not establish global uniqueness. The regional failover contract requires an authoritative owner and protected write boundary rather than a hopeful merge after both regions act.

Commit permission and reservation together
Authorization must apply to the exact action and resource at the point where the store changes. A successful authentication earlier in the call is not a perpetual grant to book any resource. Current access is also required before disclosing a historical receipt.
OWASP's authorization guidance recommends deny-by-default behavior and permission validation on every request. The model implements a small local permission table keyed by tenant, principal and resource. It checks that row inside the same write transaction as the intent and reservation decisions.
This arrangement makes the demonstration's authority boundary explicit. The database owns both its fixture permissions and its reservations. It does not claim that a cached boolean from a separate CRM service becomes current merely because your booking transaction reads it.
If authorization lives elsewhere, the adapter must define a versioned capability, transactional gateway or other protocol that makes its freshness meaningful at action time. A preflight check can still be useful for user experience, but it is not a substitute for the owning resource service's enforcement.
CRM failures during a call examine the case where a refresh succeeds after access has been revoked. A new token does not restore missing permission. The booking workflow should retain the intent while refusing the action or receipt disclosure until current authorization permits it.
The local transaction begins before checking permission, looking up intent history and inspecting slot capacity. An exact authorized retry of a committed intent returns the stored receipt. An exact retry of a stored inventory or resource conflict returns that original conflict with no receipt and makes no new reservation, even if availability later changes. A new attempt after changed conditions requires a newly reviewed intent identity; it must not silently convert the old conflict into a write. A denied retry returns no receipt, even when a prior booking exists, so historical success cannot bypass a newer access decision.
The database connection is owned by each method and closed on success or exception. Python's sqlite3 reference documents transaction and connection behavior; a transaction context is not automatically a connection-lifetime guarantee. The example uses explicit rollback in finally and a separate closing context.
The injected failure occurs after local capacity insertion but before the intent receipt is written. The fixture checks that the transaction rolls back both partial changes, then permits a later ordinary attempt. This proves the local atomic boundary under an exception; it does not simulate an external provider commit.
When porting the pattern, use the target database's actual concurrency contract. PostgreSQL's isolation documentation describes serialization and transaction retry concerns. Retrying a failed transaction means rerunning the required checks, not replaying only the final insert against stale values.
The following criteria table uses ✓ for a protection supplied by the named boundary under its stated assumptions, and ✗ when it is absent. The marks do not quantify uptime, customer outcomes or distributed reliability.
| Criterion | Call-ID deduplication | Intent ledger only | Atomic intent and capacity model |
|---|---|---|---|
| Same intent across a new call ID | ✗ | ✓ with stable identity | ✓ with stable identity |
| Changed payload under one identity conflicts | ✗ unless implemented | ✓ when payload is bound | ✓ |
| Different intents cannot share the exact slot | ✗ | ✗ | ✓ in one local store |
| Current local resource permission is checked | ✗ | ✗ unless added | ✓ inside transaction |
| Remote provider outcome is established | ✗ | ✗ | ✗ separate evidence required |
Run a local booking contract
Save the complete fence as booking_contract.py and run it with Python 3.12 or later. It uses temporary SQLite storage and independent connections for concurrent calls. A barrier releases both attempts together so the test exercises the authoritative transaction rather than simply calling the function twice in sequence.
The fixture models exact pre-defined single-capacity slots. It does not parse calendars, issue authorization tokens, contact a scheduling vendor, send confirmation messages or persist real customer details. Its receipt prefix identifies local evidence, and the emitted report explicitly states that no remote action was attempted.
The same-intent race should yield one committed result and one historical retry. The different-intent race should yield one committed result and one resource conflict. The losing identity remains bound to its original payload, and an exact retry replays the conflict instead of trying again against newly free capacity. These are different assertions, and a test suite that covers only the first can miss double booking between legitimate distinct requests.
from concurrent.futures import ThreadPoolExecutor
from contextlib import closing
from dataclasses import dataclass
import json
from pathlib import Path
import sqlite3
import tempfile
import threading
@dataclass(frozen=True)
class Intent:
tenant: str
principal: str
identity: str
resource: str
slot: str
expected_version: int
def label(value):
return type(value) is str and 0 < len(value) <= 80 and value.isascii() and all(33 <= ord(c) <= 126 for c in value)
def checked(intent):
if type(intent) is not Intent or not all(label(getattr(intent,k)) for k in ('tenant','principal','identity','resource','slot')):
raise ValueError('intent fields')
if type(intent.expected_version) is not int or not 0 <= intent.expected_version <= 2**63-1:
raise ValueError('version')
return json.dumps([intent.resource,intent.slot,intent.expected_version],separators=(',',':'))
class Store:
def __init__(self,path):
self.path = str(path)
with closing(sqlite3.connect(self.path)) as db:
db.executescript('''
CREATE TABLE IF NOT EXISTS permission(tenant TEXT,principal TEXT,resource TEXT,
allowed INTEGER,PRIMARY KEY(tenant,principal,resource));
CREATE TABLE IF NOT EXISTS slot(tenant TEXT,resource TEXT,slot TEXT,version INTEGER,
PRIMARY KEY(tenant,resource,slot));
CREATE TABLE IF NOT EXISTS intent(tenant TEXT,principal TEXT,identity TEXT,payload TEXT,
state TEXT,receipt TEXT,PRIMARY KEY(tenant,principal,identity));
CREATE TABLE IF NOT EXISTS reservation(tenant TEXT,resource TEXT,slot TEXT,
principal TEXT,identity TEXT,PRIMARY KEY(tenant,resource,slot));
''')
db.commit()
def configure(self,tenant,principal,resource,slot,version,allowed=True):
checked(Intent(tenant,principal,'configuration',resource,slot,version))
if type(allowed) is not bool:
raise ValueError('permission')
with closing(sqlite3.connect(self.path)) as db:
db.execute('INSERT OR REPLACE INTO permission VALUES(?,?,?,?)',
(tenant,principal,resource,int(allowed)))
db.execute('INSERT OR IGNORE INTO slot VALUES(?,?,?,?)',(tenant,resource,slot,version))
db.commit()
def act(self,intent,cancel=False,inject_failure=False):
payload = checked(intent)
if type(cancel) is not bool or type(inject_failure) is not bool:
raise ValueError('flags')
key = (intent.tenant,intent.principal,intent.identity)
slot = (intent.tenant,intent.resource,intent.slot)
with closing(sqlite3.connect(self.path,timeout=5,isolation_level=None)) as db:
try:
db.execute('BEGIN IMMEDIATE')
allowed = db.execute('SELECT allowed FROM permission WHERE tenant=? AND principal=? AND resource=?',
(intent.tenant,intent.principal,intent.resource)).fetchone()
if allowed != (1,):
result = ('denied',None)
else:
prior = db.execute('SELECT payload,state,receipt FROM intent WHERE tenant=? AND principal=? AND identity=?',key).fetchone()
if prior and prior[0] != payload:
result = ('intent_conflict',None)
elif prior and prior[1] == 'canceled':
result = ('canceled',prior[2])
elif prior and cancel:
db.execute('UPDATE intent SET state=? WHERE tenant=? AND principal=? AND identity=?',('canceled',)+key)
db.execute('DELETE FROM reservation WHERE tenant=? AND resource=? AND slot=? AND principal=? AND identity=?',
slot+(intent.principal,intent.identity))
result = ('canceled',prior[2])
elif prior:
result = ('historical_retry' if prior[1] == 'committed' else prior[1],prior[2])
elif cancel:
# Cancel-before-create creates a tombstone for this exact intent.
db.execute('INSERT INTO intent VALUES(?,?,?,?,?,?)',key+(payload,'canceled',None))
result = ('canceled',None)
else:
version = db.execute('SELECT version FROM slot WHERE tenant=? AND resource=? AND slot=?',slot).fetchone()
occupied = db.execute('SELECT 1 FROM reservation WHERE tenant=? AND resource=? AND slot=?',slot).fetchone()
if version != (intent.expected_version,):
result = ('inventory_conflict',None)
elif occupied:
result = ('resource_conflict',None)
else:
receipt = 'local:' + json.dumps(key,separators=(',',':'))
db.execute('INSERT INTO reservation VALUES(?,?,?,?,?)',slot+(intent.principal,intent.identity))
if inject_failure:
raise RuntimeError('synthetic failure before receipt')
db.execute('INSERT INTO intent VALUES(?,?,?,?,?,?)',key+(payload,'committed',receipt))
result = ('committed',receipt)
if result[0] in {'inventory_conflict','resource_conflict'}:
db.execute('INSERT INTO intent VALUES(?,?,?,?,?,?)',key+(payload,result[0],None))
db.commit()
return result
finally:
if db.in_transaction:
db.rollback()
def counts(self):
with closing(sqlite3.connect(self.path)) as db:
return tuple(db.execute('SELECT (SELECT COUNT(*) FROM intent),(SELECT COUNT(*) FROM reservation)').fetchone())
def remote_disposition(observed):
if type(observed) is not str or observed not in {'not_submitted','submitted_response_lost','confirmed_success'}:
raise ValueError('remote observation')
return {'not_submitted':'not_attempted','submitted_response_lost':'pending_reconciliation',
'confirmed_success':'provider_receipt_required'}[observed]
def main():
with tempfile.TemporaryDirectory() as directory:
path = Path(directory)/'booking.db'
store = Store(path)
store.configure('tenant-a','principal-a','room-a','slot-a',7)
intent = Intent('tenant-a','principal-a','intent-a','room-a','slot-a',7)
barrier = threading.Barrier(2)
def call_attempt(attempt):
barrier.wait()
return {'attempt':attempt,'result':Store(path).act(intent)}
with ThreadPoolExecutor(max_workers=2) as pool:
reports = list(pool.map(call_attempt,['synthetic-call-a','synthetic-redial-b']))
assert sorted(r['result'][0] for r in reports) == ['committed','historical_retry']
assert store.counts() == (1,1)
assert store.act(Intent('tenant-a','principal-a','intent-a','room-a','slot-b',7))[0] == 'intent_conflict'
assert Store(path).act(intent)[0] == 'historical_retry'
store.configure('tenant-a','principal-a','room-a','slot-a',7,False)
assert store.act(intent) == ('denied',None)
store.configure('tenant-a','principal-a','room-a','slot-a',7,True)
assert store.act(intent,cancel=True)[0] == 'canceled'
assert store.act(intent)[0] == 'canceled'
assert store.counts() == (1,0)
canceled = Intent('tenant-a','principal-a','never-start','room-a','slot-a',7)
assert store.act(canceled,cancel=True)[0] == 'canceled'
assert store.act(canceled)[0] == 'canceled'
stale = Intent('tenant-a','principal-a','stale','room-a','slot-a',6)
assert store.act(stale) == ('inventory_conflict',None)
assert Store(path).act(stale) == ('inventory_conflict',None)
assert store.act(Intent('tenant-a','principal-a','stale','room-a','slot-a',7))[0] == 'intent_conflict'
left = Intent('tenant-a','principal-a','intent-left','room-a','slot-a',7)
right = Intent('tenant-a','principal-a','intent-right','room-a','slot-a',7)
gate = threading.Barrier(2)
def reserve(value):
gate.wait()
return Store(path).act(value)[0]
with ThreadPoolExecutor(max_workers=2) as pool:
assert sorted(pool.map(reserve,[left,right])) == ['committed','resource_conflict']
loser = next(value for value in (left,right) if store.act(value)[0] == 'resource_conflict')
winner = right if loser == left else left
store.configure('tenant-a','principal-a','room-a','slot-available',7)
changed = Intent(loser.tenant,loser.principal,loser.identity,loser.resource,'slot-available',7)
assert Store(path).act(changed)[0] == 'intent_conflict'
assert store.act(winner,cancel=True)[0] == 'canceled'
assert Store(path).act(loser) == ('resource_conflict',None)
assert store.act(loser,cancel=True) == ('canceled',None)
assert store.act(loser) == ('canceled',None)
store.configure('tenant-b','principal-a','room-a','slot-a',7)
other_tenant = Intent('tenant-b','principal-a','intent-a','room-a','slot-a',7)
assert store.act(other_tenant)[0] == 'committed'
store.configure('tenant-a','principal-a','room-a','slot-fault',7)
fault = Intent('tenant-a','principal-a','intent-fault','room-a','slot-fault',7)
before = store.counts()
try:
store.act(fault,inject_failure=True)
raise AssertionError('fault was not injected')
except RuntimeError:
pass
assert store.counts() == before
assert store.act(fault)[0] == 'committed'
try:
store.act(Intent('tenant-a','principal-a','bad','room-a','slot-a',True))
raise AssertionError('bool admitted as version')
except ValueError:
pass
assert remote_disposition('submitted_response_lost') == 'pending_reconciliation'
print(json.dumps({'fixture_class':'Synthetic example','call_attempts':reports,
'local_rows':store.counts(),'remote_outcome':'not_attempted',
'lost_remote_response_policy':remote_disposition('submitted_response_lost')},indent=2))
print('All local booking identity and capacity fixtures passed')
if __name__ == '__main__':
main()
The restart assertion constructs another Store object over the same file and obtains historical retry behavior. This demonstrates that the ledger is in the local database rather than an object-local dictionary. It does not establish durability through power loss, replication lag, backup restore or storage corruption.
The cancellation-before-create fixture writes a tombstone for the exact intent. That prevents a delayed first attempt from creating a booking after cancellation. A later genuinely new request can receive a new intent identity, but the old identity remains terminal and its original payload remains bound.
The permission fixture removes local access after a booking and verifies that the historical receipt is not returned. Restoring permission allows the cancellation fixture to proceed under a fresh local authorization check. The old receipt itself never acts as a reusable authorization credential.
The regression fixture guide helps organize these cases by contract. Preserve separate assertions for identity, capacity, permission, terminal state and rollback. A single successful booking count cannot tell you which of those rules actually survived a change.
The model's capacity test concerns identical slot keys. If you extend it to overlapping intervals, pooled resources or partial capacity, replace the constraint and write new concurrency fixtures. Do not keep the old test name while quietly broadening the claim beyond the schema it exercised.
Keep unknown remote outcomes unresolved
A network timeout after submission does not reveal whether the provider committed. The request may have failed before reaching the service, succeeded with a lost response, or remained in progress. Classifying all these paths as failure invites another booking under a new identity.
The artifact includes a small observation policy that maps a lost response after submission to pending reconciliation. It deliberately performs no network call. That returned status is a contract reminder, not a simulation proving how a particular provider handles retries.
For a real provider, retain the original operation key, exact payload, submission evidence and any provider operation identifier. Reconcile using the provider's documented lookup or retry protocol. Do not generate a new business intent merely because the caller redialed while the original result remained unknown.
Stripe's idempotency documentation currently distinguishes API v1 from API v2. Its v1 response replay and v2 recovery behavior are different contracts. Neither can be assumed for an unspecified booking service, and a provider's key-retention policy is not your complete business operation lifetime.
Before retrying a mutation, identify the selected API and endpoint, its key scope, payload comparison rules, execution-start behavior and recovery guarantees. Record the version or documentation basis in the adapter. A generic retry library cannot infer these semantics from an HTTP method alone.
An outbox can make local intent and planned delivery durable in one database transaction. It does not atomically commit the external provider's state. After sending, the worker still needs a stable provider operation identity and a policy for unknown outcomes, duplicate delivery and acknowledgment loss.
Conditional writes address yet another requirement. RFC 9110's conditional request semantics define If-Match against a selected representation. A provider must enforce that precondition at its mutation boundary; adding the header locally does not create support or turn an earlier read into a reserved slot.
Waiting speech for slow tools should reflect this uncertainty. The agent can explain that it is checking the existing request. It should not say a booking failed, succeeded or was safely canceled without the corresponding authoritative observation.
Keep the unknown state visible during handoff. A human operator needs the original intent, frozen values, submission evidence and permitted provider references to reconcile safely. A handoff message that says only retry booking can cause the next actor to create the very duplicate your ledger was intended to prevent.

Make cancellation terminal for that intent
Cancellation is a state transition with its own authorization and evidence. In the local model it changes the intent to canceled and removes its exact slot reservation in the same transaction. A later retry of the old intent returns canceled instead of recreating the reservation.
The stored receipt remains historical evidence when a committed intent is canceled. Returning that receipt with a canceled disposition does not mean the booking is still active. Downstream code must read the disposition, not infer current state from the mere presence of an old successful receipt.
Cancel-before-create needs an explicit rule too. The example records a tombstone for the exact payload even when no reservation exists. Without that row, a delayed creation request could arrive after a successful cancellation response and create work that the caller believes was stopped.
A material change after cancellation requires a new reviewed intent. Reusing the old identity with a different slot conflicts before any reservation is attempted. This keeps immutable retry history separate from a new operation the user may now want to authorize.
Shutdown semantics apply the same terminal rule to runtime work. A cached success or delayed result cannot restore canceled activity. Cancellation should remain visible across redials, queue redelivery and process reconstruction for the period your recovery contract requires.
For remote providers, a local cancellation request and a confirmed provider cancellation are different observations. A timeout while canceling can itself be unknown. Do not release external capacity or promise that no booking exists merely because the local controller stopped waiting.
Separate cancellation from compensation. Canceling an unexecuted operation can prevent a future action; undoing an already completed business effect may require another authorized operation with different consequences. The original creation receipt should remain auditable even when a later compensating action succeeds.
Recording consent transitions provide a related example of why requested, acknowledged and completed states differ. The booking domain has its own rules, but the evidence discipline is the same: preserve what was asked, what was observed and what remains unresolved.
Retention decisions must preserve the operation lifetime you promise. Deleting intent tombstones while accepting old transport retries can reopen canceled work. If data policy requires purging detailed values, design a permitted durable identity or rejection boundary rather than assuming a short cache still supplies the original guarantee.
Diagnose redials without inventing success
Start an incident review by grouping call attempts under their trusted business intent and separating distinct competing intents. A chart of bookings per call can conceal the fact that several calls belong to one request. Conversely, grouping by identical spoken values can incorrectly merge legitimate separate operations.
The cost-per-success method emphasizes outcome denominators. Count confirmed business outcomes against admitted intents, and keep pending or unknown operations visible. A successful local insert is not automatically a completed provider booking or a successful caller task.
Inspect the exact frozen payload associated with each key. If a retry changed the resource or slot, diagnose a proposal-revision problem before blaming the database. If two distinct intents share one capacity unit, inspect the authoritative capacity constraint and transaction boundary.
Use provider evidence to resolve a lost remote response. A local timeout log proves that your caller did not observe a result before its deadline. It does not prove the provider rejected the request, and it should not be used as permission to submit the same business intent under another key.
The timestamp-boundary guide helps distinguish request submission, local deadline, database commit and provider acknowledgment. Preserve those meanings instead of adding overlapping durations or comparing unrelated clocks as if they described one ordered trace.
Keep diagnostic exports narrow. Telemetry redaction should preserve reason codes and scoped operation evidence without exporting credentials, unnecessary caller details or raw conversations. A request key should not encode a phone number or other personal identifier merely to make logs convenient.
Use two kinds of negative control in rollout validation. First, submit exact same-intent requests concurrently and confirm one local result. Then submit distinct intents for the same capacity unit and confirm only the permitted capacity is reserved. Neither test replaces the other, and neither establishes the remote provider's behavior until its adapter is tested separately.
A useful recovery screen shows current authorization, immutable intent values, local disposition and independently observed remote outcome. It should require an explicit new proposal for changed values and retain the original operation identity during reconciliation. Avoid a one-click retry implementation that silently switches keys after every error.
A booking proposal can carry more than the resource and time shown in this narrow model. If price, cancellation terms or another material condition participates in the caller's decision, its exact reviewed value must be bound to the business intent too. An adapter must not reuse an approval after changing those values simply because the resource identifier stayed the same.
The money and quantity contract explains why a spoken amount needs explicit currency, unit and rounding policy. If a booking includes a deposit, preserve that approved monetary representation under its own field grammar. The example omits payment entirely and must not be described as authorizing a charge or testing payment-provider idempotency.
Similarly, a caller's preferred name or pronunciation can improve the interaction without establishing the principal who may book. The surname clarification boundary separates spelling, preference and identity. Keep the authenticated principal reference independent from presentation fields that the conversation may legitimately repair.
Define how a new intent relates to a previous unresolved one. A user may explicitly request a changed appointment while the first provider submission remains unknown. That does not automatically make it safe to submit the second request: the workflow may need to reconcile or cancel the first before admitting another operation against the same real-world need.
An operation graph can retain that relationship without pretending the two payloads are identical. For example, a new reviewed proposal can reference the superseded intent while remaining a separate immutable object. The owning action service must still decide whether both operations can coexist and which evidence is required before the new one may execute.
Avoid an unlimited retry loop around the entire conversation. Recovery should have a bounded execution budget, preserve its original identity and stop when the provider's semantics no longer support safe automatic progress. The caller can receive a truthful pending status and an authorized handoff instead of being kept on the line while the system guesses at outcomes.
The local thread barrier is a scheduling fixture, not a throughput measurement. Its purpose is to make competing attempts contend at one store and verify which invariants survive. Running it repeatedly does not establish a production transaction rate, tail latency or fairness guarantee, and its synthetic receipt count should not appear in a customer-performance chart.
For operational metrics, distinguish a replayed historical response from a new mutation. Both can be successful API responses, but only one creates capacity ownership. Count them separately so a redial recovery improvement does not appear to increase new bookings merely because more retries now receive a useful historical result.
Test scope isolation in addition to contention. A tenant-scoped identifier must not let one principal retrieve another principal's receipt, and a resource permission must not grant access to every resource in the tenant. The sample demonstrates tenant separation and per-resource checks, while a real authorization adapter needs its own cross-principal and cross-resource negative fixtures.
Finally, retain the original fixture output and exact program version with an incident reproduction. If someone changes the slot schema or operation scope, rerun the entire fence before comparing results. A previous green report belongs to the values and implementation that produced it, not to every later adaptation carrying a similar function name.

Frequently asked questions
Should I use the call ID as the idempotency key?
A call ID identifies a conversation attempt, so a redial normally changes it. Use a trusted business-intent identity that can remain stable across authorized recovery attempts. Retain call IDs as trace evidence rather than allowing each new call to create another logical booking automatically.
How do I recognize the same intent after a redial?
Use an authenticated lookup and explicit continuation workflow within the correct tenant and principal scope. Do not merge requests solely because caller ID, voice or spoken values look similar. Recover the original frozen values and current disposition before deciding whether an exact retry is appropriate.
Can I hash the booking values instead of issuing an identity?
A value hash can help compare admitted payloads, but identical values can describe separate legitimate operations. It also cannot establish caller identity or authorization. Use a trusted operation identity and bind it to an explicit schema-specific payload representation.
What if the caller changes the requested slot?
That is a changed proposal rather than an exact retry. The old identity should conflict if it receives different bound values, including after an inventory or capacity conflict. An exact retry preserves the original admitted conflict; it does not become a new booking attempt when conditions improve. Review the new resource/time and issue a new logical intent while retaining the original operation's history and unresolved remote outcome if one exists.
Does idempotency prevent every double booking?
It prevents repeated execution of the same scoped intent when correctly enforced. Different intents can still compete for one resource, so capacity needs its own authoritative constraint. The local example protects identical single-capacity slot keys, not arbitrary overlapping intervals.
Why are there two uniqueness boundaries?
Intent uniqueness binds one operation identity to one payload and result. Resource uniqueness limits occupancy of the capacity unit itself. Both are necessary because two legitimate distinct operations can race for the same slot even when neither request is a duplicate.
Can an availability read reserve a slot?
Only if the owning service defines that read as a reservation protocol. An ordinary read can become stale immediately. The final mutation must enforce current capacity and any expected inventory version under the authoritative transaction or provider precondition contract.
What does the concurrency fixture prove?
Independent SQLite connections race against one authoritative local database. The test checks same-intent replay and different-intent capacity contention separately. It does not prove behavior across disconnected stores, provider APIs, regional partitions or arbitrary interval-overlap rules.
Why check authorization before returning an old receipt?
Historical success does not grant continuing access to the resource or result. The model reads its local permission row inside the transaction before both mutation and disclosure. A denied retry returns no receipt, even when the ledger contains an earlier successful booking.
Does refreshing a token restore booking permission?
Token refresh can restore a credential under the provider's policy, but it does not add missing action or resource permission. Reauthorize the exact operation against current state. A stale refresh response must not override a newer revocation decision.
What if the provider response is lost after submission?
The remote outcome is unknown until an authoritative observation resolves it. Preserve the original operation identity and exact payload, then use the provider's documented lookup or safe retry protocol. A timeout is not evidence that creating a new booking is safe.
Do Stripe API v1 and v2 replay requests identically?
Current Stripe documentation distinguishes their response and recovery behavior. Read the selected API's contract rather than generalizing one version's cached-response policy. Neither contract automatically applies to a scheduling provider or to an unspecified generic retry wrapper.
Can an outbox guarantee a remote booking exactly once?
An outbox can atomically retain local intent and planned delivery in its owning store. It does not atomically commit an unrelated provider's state. The adapter still needs stable operation identity, documented provider idempotency and reconciliation for unknown outcomes.
What happens when an old retry follows cancellation?
The local model returns the canceled disposition and does not recreate capacity. A historical receipt may remain attached as evidence of the original booking, but it does not indicate current activity. A genuinely new booking needs a new authorized intent identity.
Why store a cancellation before creation arrives?
A cancel-before-create tombstone prevents a delayed original request from creating the same intent later. Without it, the absence of a reservation can be mistaken for permission to create. The tombstone binds the exact payload and keeps the old identity terminal.
Can I release capacity after a cancellation timeout?
A local timeout does not establish remote cancellation. Keep the provider outcome unresolved until its documented observation confirms the relevant state. Local cancellation, remote acknowledgment and actual external resource release should have separate evidence and recovery paths.
What does the injected exception check?
It raises after local reservation insertion and before recording the intent receipt. The assertion verifies that both partial local changes roll back and a subsequent attempt can commit normally. No external service is called, so it does not simulate a provider commit followed by response loss.
Does reopening the database prove crash durability?
It proves that another object can read the committed ledger from the same local database file. It does not test power failure, storage corruption, replication or backup restore. Those deployment properties need explicit infrastructure tests and operational assumptions.
Should a booking key contain a phone number?
Avoid embedding personal identifiers or credentials in operation keys and broad logs. Use opaque trusted identities with separately controlled lookup evidence. Caller ID is also insufficient to establish the business principal, so its inclusion would not solve authorization.
What should the agent tell the caller during reconciliation?
Describe the status that is actually known, such as checking an existing request or waiting for provider confirmation. Do not announce failure, success or cancellation without the matching authoritative evidence. Preserve the same intent across handoff so another actor does not create a duplicate.
Validate identity and capacity independently
Define the business intent, exact approved values and capacity unit before adding automatic retries. Make their identities visible in your schema and keep call attempts as evidence of transport recovery. A convenient correlation key should never quietly become the authority to create another booking.
Run both concurrency fixtures and the changed-payload, revoked-access, cancel-before-create and injected-failure cases. Then test the actual provider adapter with its documented operation-key and conditional-write behavior. Keep local transaction receipts separate from remote outcome evidence throughout the report.
For a production integration, write down what happens after a response is lost, after permission is removed and after a canceled intent is retried. Those paths determine whether recovery preserves the caller's request or accidentally creates another one. When the provider cannot establish a safe result, hold the operation for reconciliation rather than manufacturing certainty.
Use the webhook evidence contract for asynchronous provider observations and the regional ownership boundary when several workers can act. Together, these checks let a 60db Agent integration say precisely which intent was authorized, which local reservation committed and which external result remains unknown.
