fix(ingestion): Horizon replay safety and idempotency (#1261) - #1303
Open
Nwoyemartha wants to merge 1 commit into
Open
Nwoyemartha wants to merge 1 commit into
Nwoyemartha wants to merge 1 commit into
Conversation
Bind every chain operation to its durable (stream_name, operation_id) ledger key inside the same transaction as the business effect and cursor checkpoint. Duplicate, reordered, concurrent, and timed-out deliveries of the same operation now deterministically produce at most one committed business effect; conflicting reuse of an operation id with a different payload and stale (behind-checkpoint) operations are rejected without any state write. The cursor checkpoint is forward-only so replay storms converge on the furthest checkpoint instead of regressing it. Closes CredenceOrg#1261 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@Nwoyemartha Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
(stream_name, operation_id)as a row of thehorizon_eventsledger, written in the same transaction as the business effect (identity/bond mutation) and the cursor checkpoint.EVENT_ID_CONFLICT) and stale/behind-checkpoint operations (STALE_INGESTION_EVENT), both without any state write.upsertCursorMonotonic) so replay storms and reordered deliveries converge on the furthest checkpoint instead of regressing it.onEventnow fires only for newly applied events, so a duplicate delivery can never surface a second business effect to subscribers.Correctness / Invariants
(stream_name, event_id)index is the concurrency gate).horizon-events-parity.md) remains unchanged.Tests
New integration-boundary tests (
src/listeners/__tests__/horizonBondIngestion.test.ts, 10 tests) at the actual business-effect boundary:EVENT_ID_CONFLICT); committed state intact.STALE_INGESTION_EVENT); no writes.Repository-level tests (
src/db/repositories/horizonEventRepository.test.ts) coverclaim()insert/duplicate/conflict semantics, canonical payload fingerprinting (key-order invariance), per-stream scoping, and the client-scoped idempotent INSERT.Validation
npx vitest run src/listeners/__tests__/horizonBondIngestion.test.ts src/db/repositories/horizonEventRepository.test.ts src/listeners/__tests__/horizonBondEvents.test.ts— PASS (3 files, 47 tests)npx tsc --noEmit— no new errors vsorigin/main(identical 40-error pre-existing baseline; zero errors in changed files)no-consoleinhorizonBondEvents.tsunchanged; test files are eslint-ignored)origin/mainvia worktree (horizonBondEvents.atomic.test.ts,horizonBondEvents.validation.test.ts,src/__tests__/horizonBondEvents.test.ts— all fail the same way on main due to aworkerPoolmetrics-mock issue unrelated to this change)Security / Correctness
appliedpath exactly once; committed-but-lost responses take thereplayedpath — both safe by construction.Compatibility / Migration
subscribeBondCreationEvents(dlqRouter, onEvent?, pool?)signature unchanged.onEventfires only for newly applied events (previously it fired for every delivery). Strictly safer; covered by updated listener tests.HorizonEventConflictError(EVENT_ID_CONFLICT) andHorizonEventStaleError(STALE_INGESTION_EVENT) abort the transaction and flow through the existing DLQ routing.horizon_events(033) andhorizon_cursors(007) tables. No rollback steps required.Documentation
Added
docs/horizon-replay-idempotency.mdcovering the invariant, durable request identity, deterministic outcomes, failure-mode handling, compatibility, and security assumptions.Issue
Closes #1261