You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reframed 2026-07-24 after a multi-lens architecture study. The original premise, that a duplicate-submit window can be closed purely in the SDK, does not hold: a reserve marker is ambiguous between "accepted, commit write faulted" and "crashed or cancelled mid-submit, outcome unknown", so a local-only resolution is always wrong in one of the two. See #572 (held) for the failed local-only attempt.
What
Consolidate the hand-rolled submit-dedup into Journal::{reserve, commit, release, observed} on the existing LocalStoreHost verbs (get/set/delete), in crates/nexum-sdk/src/keeper.rs. No WIT change, no host change, no new interface. The study verdict was decisive: redb is single-process and dispatch is single-actor serialized, so there is no concurrent or cross-process race and atomicity buys nothing the fix needs. The defect is durability ordering, and a plain set() is already fsync-durable per operation.
reserve writes a distinct pre-submit marker durably before the submit await. commit overwrites it with the committed marker on acceptance. release deletes it on a retryable fault. Store distinct RESERVED and COMMITTED marker values and read the value on recovery, so an in-flight reserve is distinguishable from a done commit; treat any legacy empty presence marker as committed for forward compatibility.
Why
This is at-most-once within one process, honestly framed. It closes the window where the venue accepted and a separate record write then faulted while the process lived.
Not guaranteed
Not exactly-once across the HTTP effect. A crash, OutOfFuel trap, or deadline-cancel during the submit await leaves an ambiguous reserve that no local primitive can resolve. Recovery is the sweep reconcile in #573, against the venue idempotency key in #574. This issue is not complete on its own; it is the SDK-shape half.
Scope
nexum-sdkJournal: record/contains become reserve/commit/release, plus observed for the witness path and pending over the reserve prefix for reconcile and operator enumerate.
Do not fold in Gates, the Retrier refused: marker, or the in-memory watch eviction: they are already safe under single-actor serialization and want crash-durability only, which set() already gives.
Acceptance criteria
Journal exposes reserve/commit/release/observed/pending over the existing verbs, the dedup sites use it, the value distinguishes reserved from committed, and the doc states plainly that this is at-most-once and that the reconcile and venue-key issues complete the story.
What
Consolidate the hand-rolled submit-dedup into
Journal::{reserve, commit, release, observed}on the existingLocalStoreHostverbs (get/set/delete), incrates/nexum-sdk/src/keeper.rs. No WIT change, no host change, no new interface. The study verdict was decisive: redb is single-process and dispatch is single-actor serialized, so there is no concurrent or cross-process race and atomicity buys nothing the fix needs. The defect is durability ordering, and a plainset()is already fsync-durable per operation.reservewrites a distinct pre-submit marker durably before the submit await.commitoverwrites it with the committed marker on acceptance.releasedeletes it on a retryable fault. Store distinctRESERVEDandCOMMITTEDmarker values and read the value on recovery, so an in-flight reserve is distinguishable from a done commit; treat any legacy empty presence marker as committed for forward compatibility.Why
This is at-most-once within one process, honestly framed. It closes the window where the venue accepted and a separate record write then faulted while the process lived.
Not guaranteed
Not exactly-once across the HTTP effect. A crash,
OutOfFueltrap, or deadline-cancel during the submit await leaves an ambiguous reserve that no local primitive can resolve. Recovery is the sweep reconcile in #573, against the venue idempotency key in #574. This issue is not complete on its own; it is the SDK-shape half.Scope
nexum-sdkJournal:record/containsbecomereserve/commit/release, plusobservedfor the witness path andpendingover the reserve prefix for reconcile and operator enumerate.videre-sdk::Keeper::sweep,composable-cowsweep,ethflow-watcherobserved path. Thestop-lossmodule is being deleted (modules: delete the stop-loss example, subsumed by the generic ComposableCoW monitor #568).refused:marker, or the in-memory watch eviction: they are already safe under single-actor serialization and want crash-durability only, whichset()already gives.Acceptance criteria
Journalexposes reserve/commit/release/observed/pending over the existing verbs, the dedup sites use it, the value distinguishes reserved from committed, and the doc states plainly that this is at-most-once and that the reconcile and venue-key issues complete the story.