Problem
Public create commands do not carry a stable client operation ID. Chatto generates durable event and resource IDs inside each request, so an ambiguous outcome can become a duplicate create:
- the EVT append commits;
- projection catch-up, response assembly, or the client connection fails;
- the client retries the same logical command;
- the server generates new IDs and commits a second resource or message.
CreateMessageRequest and CreateRoomRequest are concrete examples. The event publisher already documents that a non-zero sequence plus a wait error means the fact committed even though local read-your-writes did not complete, but normal public callers cannot safely distinguish or recover that outcome.
Goal
Define a reusable end-to-end command-idempotency contract for public create and composite mutations, starting with message and room creation.
This is application behavior, not merely JetStream message-ID deduplication: a retry must recover the original durable result even after process restart or on another replica.
Design questions
- Where does the client operation ID live, and which public mutations require it?
- Is the key scoped by authenticated actor, RPC method, and target resource?
- What durable record maps an operation ID to its result without duplicating domain truth?
- How are concurrent same-key requests with identical payloads handled?
- How is reuse of one key with a different payload rejected?
- What retention period bounds idempotency state, and what happens after expiry?
- How do older clients without operation IDs behave during a rolling deployment?
- How should the API represent “committed, but the response could not be assembled” when no idempotency key is available?
Acceptance criteria
- Record the public behavior and storage boundary in the appropriate ADR/FDR before implementation.
- Retrying the same operation ID after commit but before response returns the original logical result and does not append duplicate domain facts.
- Concurrent replicas receiving the same operation ID converge on one committed result.
- Reusing a key with a materially different request is rejected deterministically.
- Idempotency state has an explicit retention, backup/restore, privacy, and cleanup contract.
- The public API compatibility impact is classified, including older-client/newer-server and newer-client/older-server behavior.
- Focused failure-injection tests cover errors after EVT commit, projection-wait timeout, response interruption, restart, and cross-replica retry.
- Message and room creation are either migrated in the first implementation or tracked as explicit child issues.
Non-goals
- Treating every update/delete RPC as a create-style idempotent command when its resource semantics already make retries safe.
- Exposing JetStream subjects, sequences, or message IDs through the public API.
Problem
Public create commands do not carry a stable client operation ID. Chatto generates durable event and resource IDs inside each request, so an ambiguous outcome can become a duplicate create:
CreateMessageRequestandCreateRoomRequestare concrete examples. The event publisher already documents that a non-zero sequence plus a wait error means the fact committed even though local read-your-writes did not complete, but normal public callers cannot safely distinguish or recover that outcome.Goal
Define a reusable end-to-end command-idempotency contract for public create and composite mutations, starting with message and room creation.
This is application behavior, not merely JetStream message-ID deduplication: a retry must recover the original durable result even after process restart or on another replica.
Design questions
Acceptance criteria
Non-goals