Summary
A rejected transaction's error code is not persisted for dedup replay, so a retried txId replays the reason message but drops the code. Pre-existing (predates the validation work in #20); surfaced during the #20 review.
Detail
rejectTx records only the message:
// src/server/sync-do.ts
private rejectTx(ws, txId, message, code?) {
recordTx(this.sql, txId, false, null, message, null) // <- code not stored
this.send(ws, { t: "rejected", txId, error: code ? { code, message } : { message } })
}
private replayReceipt(ws, txId, seen) {
// ...
this.send(ws, { t: "rejected", txId, error: { message: seen.error ?? "unknown" } }) // <- no code
}
So the first rejection of a txId sends { code, message }, but a later retry of the same txId replays { message } only. This affects every code (EXECUTE_FAILED, NON_SERIALIZABLE, and now VALIDATION).
Impact
Low. The human-readable reason still replays; only the machine-readable code is lost, and only on a duplicate-txId replay of a rejected tx. A client keying off error.code sees the code on the first response and undefined on replay.
Options
- Persist the code alongside the message in the dedup record (
recordTx/SeenTx) and replay it — fixes it generally for all codes.
- Don't record rejections for replay (re-run authorize/validation on retry so the code is re-derived) — changes dedup semantics.
Notes
Not a regression from #20 — that PR's VALIDATION code is simply a new code subject to the same pre-existing behavior. Its ADR/CHANGELOG wording describes the first-response behavior, which is accurate.
Summary
A rejected transaction's error
codeis not persisted for dedup replay, so a retriedtxIdreplays the reason message but drops thecode. Pre-existing (predates the validation work in #20); surfaced during the #20 review.Detail
rejectTxrecords only the message:So the first rejection of a
txIdsends{ code, message }, but a later retry of the sametxIdreplays{ message }only. This affects every code (EXECUTE_FAILED,NON_SERIALIZABLE, and nowVALIDATION).Impact
Low. The human-readable reason still replays; only the machine-readable
codeis lost, and only on a duplicate-txIdreplay of a rejected tx. A client keying offerror.codesees the code on the first response andundefinedon replay.Options
recordTx/SeenTx) and replay it — fixes it generally for all codes.Notes
Not a regression from #20 — that PR's
VALIDATIONcode is simply a new code subject to the same pre-existing behavior. Its ADR/CHANGELOG wording describes the first-response behavior, which is accurate.