Skip to content

tutorial

Chapter 5 of 6

Chapter 5 — Survive a dropped confirmation

by Rod Rivera Published

Voice is a lossy channel and the model will retry. Give an irreversible action a fingerprint so the same request twice posts one card.

Here is a sequence in which every component behaves correctly and the outcome is still wrong.

  1. The caller says “yes, send it.”
  2. The tool posts the card and returns ok.
  3. The confirmation is cut off — a dropped packet, or the caller talking over it.
  4. The model, having no evidence the tool succeeded, does the reasonable thing and calls it again.
  5. Two cards are in the post.

Nothing there is a bug in any single part. It is a bug in the assumption that calling a tool twice is the same as calling it twice as much. For a read, it is. For something that leaves the building, it is not.

Why not a boolean

The tempting fix is a flag: already_reissued. It is wrong, and the reason is worth stating because it generalises.

A caller genuinely can need two cards replaced on one call — the debit card and the credit card were both in the stolen wallet. A boolean cannot tell “the same request again” from “a second, different request”, so it either blocks the legitimate second card or permits the duplicate first one.

What must not happen twice is the same request, not any second request. So the key has to describe the request:

def request_fingerprint(card_id: str, postcode: str, line1: str) -> str:
    canonical = "|".join(
        part.strip().casefold() for part in (card_id, postcode, line1)
    )
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:16]

Same fingerprint, same reference, no second card. Different fingerprint, a real second order.

The destination is part of the identity

Note that the fingerprint includes the address, not just the card.

Replacing card 9931 to the home address, and then to a newly-stated address, are different requests — and the second must go through the guard again rather than inheriting the first one’s success. If the fingerprint were just card_id, a caller could obtain a cheap approval to an on-file address and then reuse it to redirect. The idempotency key would have laundered the guard.

Why it is hashed

The fingerprint is written to conversation memory, and conversation memory is a place transcripts come from. A hash of an address is not an address.

Truncated to 16 hex characters — 64 bits. Within a single phone call, where the number of distinct requests is in the single digits, collision risk is not a real quantity.

What it looks like

The same request twice posts one card:
  ✓ second attempt reports duplicate
      -> ok=True result=duplicate
  ✓ and returns the SAME reference (RC-BB083296)

duplicate is deliberately a success — ok=True — because from the caller’s point of view the card is coming, and telling them otherwise would prompt them to ask for another one. What it must not do is mint a new reference, which would be a promise that a second card exists.

What breaks

_PLACED in tools/cards.py is a dictionary in process memory. Restart the process and the guarantee resets. That is honest for a tutorial and wrong for production, where the card issuer’s own idempotency key is the mechanism you want. The shape of the lookup is what transfers; the storage is not.