Skip to content

architecture(api): define end-to-end idempotency for create commands #1968

Description

@hmans

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:

  1. the EVT append commits;
  2. projection catch-up, response assembly, or the client connection fails;
  3. the client retries the same logical command;
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    draftNeeds refinement before startingenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions