Skip to content

feat(act): idempotent actions via correlation-based deduplication#574

Closed
Rotorsoft wants to merge 1 commit intomasterfrom
feat/idempotency-correlation
Closed

feat(act): idempotent actions via correlation-based deduplication#574
Rotorsoft wants to merge 1 commit intomasterfrom
feat/idempotency-correlation

Conversation

@Rotorsoft
Copy link
Copy Markdown
Owner

Summary

  • Adds optional correlation field to Target for safe client retries
  • When provided, action() checks for existing events with that correlation on the stream before executing — if found, returns original events without re-execution
  • Reuses the existing correlation field and index in event metadata — no new store methods, tables, TTL, or cleanup

Other frameworks (Axon, Eventuous, Marten) use dedicated deduplication stores with TTL and background cleanup. Act reuses what's already there — the correlation ID in the immutable event log.

Test plan

  • pnpm test — 754 tests pass (7 new idempotency tests)
  • pnpm typecheck — clean
  • Tests cover: dedup on retry, different streams, no-correlation passthrough, multi-event actions, correlation in metadata

Closes #567

🤖 Generated with Claude Code

Add optional correlation field to Target. When provided, action()
checks for existing events with that correlation on the stream before
executing — if found, returns original events without re-execution.

Reuses the existing correlation field and index in event metadata.
No new store methods, tables, TTL, or cleanup — the correlation
lives in the immutable event log.

- Target type: optional correlation?: string
- event-sourcing.ts: dedup check before action execution
- 7 unit tests: dedup, different streams, no-correlation, multi-event
- README: design decision documenting approach vs other frameworks
- CLAUDE.md: idempotent actions section

Closes #567

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Rotorsoft
Copy link
Copy Markdown
Owner Author

Reverting after deeper analysis. Correlation-based deduplication has critical holes:

  1. TOCTOU race — two concurrent retries both pass the check before either commits, causing double-write. Optimistic concurrency throws ConcurrencyError, not a clean idempotent return.
  2. Semantic overloading — correlation is a trace ID that propagates through reactions. Reusing it as an idempotency key conflates two concerns with different lifecycles.
  3. Cross-action dedup — different actions with the same correlation on the same stream silently swallow the second action.
  4. State drift — dedup returns current state, not state-as-of-original-commit.
  5. No TTL — stale correlations block reuse forever.

Act relies on optimistic concurrency for conflict detection. Request-level idempotency belongs in the API middleware layer (tRPC, Express) with a dedicated dedup cache — the established pattern used by production systems.

@Rotorsoft Rotorsoft closed this Apr 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Idempotency keys — safe client retries over unreliable networks

1 participant