What is missing
No write endpoint accepts a client-supplied idempotency key. A client that retries a POST after a timeout has no way to say "this is the same request", and no way to learn whether the first attempt landed.
The internal outbox makes our side of event delivery idempotent. That does nothing for the caller: a passport create that times out at the network layer may or may not have created a passport, and the only recovery is to list and compare.
Why this is filed rather than built
It is a design pass, not a feature, and it was mis-sized when first raised. Four things need deciding before any code:
- Scope. Every mutating route, or only non-idempotent ones?
PUT and lifecycle transitions are already idempotent by shape; POST /dpp and evidence generation are not. A key on a route that does not need one is noise that still costs a storage row.
- Storage and retention. Keys need a store, which means a migration. How long is a key honoured — long enough to cover a client's retry budget, short enough not to grow without bound? A key retained forever is a slow leak; one retained for a minute does not survive an outage, which is the case it exists for.
- Same key, different body. The interesting failure. Returning the first result silently is wrong — the caller asked for something else.
422 is the usual answer, and it requires storing a fingerprint of the request body, not just the key.
- What is replayed. The stored response, or a pointer to the created resource? Replaying a stored body means storing bodies; returning a pointer means the second caller gets a different shape from the first.
Why it is worth doing before there are clients
Retrofitting idempotency after clients exist is a breaking change to their retry behaviour, or a silent one, which is worse. Deciding it while the only consumers are ours is the cheap moment. That argument is the reason this is filed rather than dropped.
Done when
A design note exists answering all four questions above; then: a repeated request carrying the same key returns the first outcome without creating a second resource, a reused key with a different body is refused rather than silently replayed, and keys expire on a stated schedule with a test that proves expiry.
What is missing
No write endpoint accepts a client-supplied idempotency key. A client that retries a
POSTafter a timeout has no way to say "this is the same request", and no way to learn whether the first attempt landed.The internal outbox makes our side of event delivery idempotent. That does nothing for the caller: a passport create that times out at the network layer may or may not have created a passport, and the only recovery is to list and compare.
Why this is filed rather than built
It is a design pass, not a feature, and it was mis-sized when first raised. Four things need deciding before any code:
PUTand lifecycle transitions are already idempotent by shape;POST /dppand evidence generation are not. A key on a route that does not need one is noise that still costs a storage row.422is the usual answer, and it requires storing a fingerprint of the request body, not just the key.Why it is worth doing before there are clients
Retrofitting idempotency after clients exist is a breaking change to their retry behaviour, or a silent one, which is worse. Deciding it while the only consumers are ours is the cheap moment. That argument is the reason this is filed rather than dropped.
Done when
A design note exists answering all four questions above; then: a repeated request carrying the same key returns the first outcome without creating a second resource, a reused key with a different body is refused rather than silently replayed, and keys expire on a stated schedule with a test that proves expiry.