Part of the audit remediation umbrella #67. Milestone 3. Severity: HIGH. Requirements: RECOV-32, RETRY-38.
Current SDK behavior
The @public TSDoc of idempotencyKeyStep
(packages/core/src/recovery/idempotency-key.ts:33-35) says: "Runs ONCE per call, upstream of
retry — not per attempt … so the server sees one stable key across every retry of the same
logical request."
The retry composition does the opposite. retry-dispatch.ts:24 calls dispatchWithRecovery
for each attempt. orchestrator.ts:59 runs requestChain.apply() inside it. engine.ts:351
re-sends the captured template, which never carries the header. generate() runs once per
attempt. Three attempts of one request produce three different keys.
retry-dispatch.test.ts:67 asserts this by name: "re-runs the request recovery chain on every
attempt".
dispatchWithRetry is @internal, so no consumer can build this composition today. Commit
1f48926 promoted idempotencyKeyStep and IdempotencyKeyOptions to @public, which
publishes the claim.
Expected behavior
The SDK sends the same idempotency key on every attempt of one logical request. The
idempotencyKeyStep TSDoc describes the layering the code has. The retry engine's test names
the behavior it proves.
Notes and leads
- Two layerings satisfy the contract. (a) Run the request recovery chain once, above the retry
loop, and let each attempt copy the prepared request; attempt-stamp then writes the ordinal
on the copy (RETRY-38 already preserves the key). (b) Memoize the key per logical request,
keyed on the template, so re-application is a no-op. (a) matches the TSDoc; (b) is smaller.
- Check
recovery/orchestrator.ts for other request steps that must not re-run per attempt
(client identity, auth stamps). The same question applies to them.
- Tests: N attempts, one
generate() call, identical header on every wire send. Rename
retry-dispatch.test.ts:67.
- Behavior change on an internal path plus a public doc: patch changeset for
@dexpace/core.
Part of the audit remediation umbrella #67. Milestone 3. Severity: HIGH. Requirements: RECOV-32, RETRY-38.
Current SDK behavior
The
@publicTSDoc ofidempotencyKeyStep(
packages/core/src/recovery/idempotency-key.ts:33-35) says: "Runs ONCE per call, upstream ofretry — not per attempt … so the server sees one stable key across every retry of the same
logical request."
The retry composition does the opposite.
retry-dispatch.ts:24callsdispatchWithRecoveryfor each attempt.
orchestrator.ts:59runsrequestChain.apply()inside it.engine.ts:351re-sends the captured template, which never carries the header.
generate()runs once perattempt. Three attempts of one request produce three different keys.
retry-dispatch.test.ts:67asserts this by name: "re-runs the request recovery chain on everyattempt".
dispatchWithRetryis@internal, so no consumer can build this composition today. Commit1f48926promotedidempotencyKeyStepandIdempotencyKeyOptionsto@public, whichpublishes the claim.
Expected behavior
The SDK sends the same idempotency key on every attempt of one logical request. The
idempotencyKeyStepTSDoc describes the layering the code has. The retry engine's test namesthe behavior it proves.
Notes and leads
loop, and let each attempt copy the prepared request;
attempt-stampthen writes the ordinalon the copy (RETRY-38 already preserves the key). (b) Memoize the key per logical request,
keyed on the template, so re-application is a no-op. (a) matches the TSDoc; (b) is smaller.
recovery/orchestrator.tsfor other request steps that must not re-run per attempt(client identity, auth stamps). The same question applies to them.
generate()call, identical header on every wire send. Renameretry-dispatch.test.ts:67.@dexpace/core.