Skip to content

Rejection code is dropped on tx-dedup replay #21

Description

@grrowl

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

  1. Persist the code alongside the message in the dedup record (recordTx/SeenTx) and replay it — fixes it generally for all codes.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions