Skip to content

Latest commit

 

History

History
64 lines (49 loc) · 5.18 KB

ADR-002-command-idempotency.md

File metadata and controls

64 lines (49 loc) · 5.18 KB

ADR-002: Command identity and persisted idempotency

Status: Accepted; implementation and GitHub qualification pending. Source contract: CommandRequest, CommandOutcome, and CommitReceipt in src/KeyLoad.Abstractions/Contracts.cs; product source sections 5, 38, and 41.

Context and decision

Network retries can repeat a request after its outcome committed but before the client received the response. Every mutating command therefore has a stable caller-generated CommandId, scoped to its authenticated principal and resolved atomic partition. The server fingerprints canonical command content and persists the outcome in the same ordered transaction as its effects. Matching retries return the stored receipt/error; reuse with a different fingerprint is a conflict. Event and message identities provide resource-level deduplication with their own declared retention horizons; they do not replace command identity.

Rationale and consequences

Persisting command identity with effects resolves lost responses across process restart and failover. An in-memory response cache cannot do so. Fingerprinting raw noncanonical JSON or omitting principal/scope could alias distinct requests, so canonicalization and authorization precede replay. Deduplication is bounded by persisted retention/format rules; this decision does not promise eternal retry safety after dedup metadata expires or exactly-once external side effects.

Related requirements

REQ-DSTORE-004/AC-DSTORE-004, REQ-MSG-002/AC-MSG-002, REQ-MSG-005/AC-MSG-005, plus EventStreams REQ-EVENT-004/AC-EVENT-004 (append OCC/idempotency extension). See DocumentStorage and Messaging.

Implementation contract

  1. Freeze command fingerprint inputs, principal/scope binding, error replay, and retention horizon before changing envelopes.
  2. Add TUnit tests for same-ID/same-content replay, same-ID/changed-content conflict, lost response after commit, precondition failure replay, and unrelated principal/partition scope.
  3. Keep shared command/request/outcome contracts in the existing src/KeyLoad.Abstractions/Contracts.cs building block and dispatch/persisted outcome handling in src/KeyLoad.Core/DatabaseEngine.cs; feature-specific mutation logic remains in its canonical owning Features/<SliceName>/ directory. Do not create a separate CommandExecution slice or project.
  4. The current outcome format has one canonical reader and writer. Unknown, malformed, or unsupported outcomes fail closed without rewrite or alternate-key lookup.
  5. GitHub qualification is TUnit, real process recovery, and replicated-cluster SDK outcomes after failover.

Dependencies: ADR-001, ADR-003, ADR-004, ADR-005. Stop if canonical fingerprint or dedup expiry behavior is not specified. Do not infer exactly-once handler execution or include an external API call in the database transaction.

flowchart LR
    Request[Authenticated command ID and payload] --> Canonical[Resolve scope and fingerprint]
    Canonical --> Lookup{Persisted outcome?}
    Lookup -->|same fingerprint| Return[Return stored outcome]
    Lookup -->|different fingerprint| Conflict[Reject conflict]
    Lookup -->|new command| Effects[Apply effects and outcome atomically]
    Effects --> Commit[Ordered commit]
Loading

Scoped command-outcome contract

REQ/AC-DSTORE-009 in DocumentStorage freezes the complete atomic partition identity in persisted command-outcome lookup. Current command writes use the accepted outcome representation; every lookup is bound to its normalized operation, authenticated principal, and complete partition scope. Public principal/ID-only result or key access is not an accepted path.

The current contract rejects a missing or contradictory locator, malformed record, duplicate scoped identity, or unsupported outcome without rewriting persisted bytes, searching an alternate key, or deriving identity from a hash. The current Unknown scope represents a normalized operation that failed before partition resolution. Its matching, freshly authorized retry replays only that same failed operation; it never invents partition authority or permits effects. Unknown scope values and contradictory identities fail closed. New commands use the canonical current identity and outcome representation. The real unit, scalar, process-recovery, restart and replicated-cluster cases are mapped in the feature contract. This section records the behavior contract and does not claim gate completion.

Command outcomes have no automatic TTL or purge. A matching authorized retry resolves its retained canonical outcome in the same store incarnation. This does not promise a finite minimum retention period, eternal replay after explicit store or record removal, or replay across a changed incarnation; an outcome from another incarnation is TokenInvalidated. Expiry, purge and a minimum temporal horizon need a separately accepted contract. Process-kill tests establish process-restart recovery only, never power-loss durability.