Skip to content

V1 retry fidelity (snapshot replay) #9977

Description

@kevin9foong

Parent

PRD #9972 — MRF V1 backward-compatible webhooks for single-step forms.

What to build

A generic V1 delivery that fails transiently is retried, and the retry delivers the same bytes the initial send would have — reconstructed from the frozen snapshot, never re-derived from the live row.

The V1 retry path is currently rejected outright: a retry naming the V1 wire shape raises a "format not recorded" error and never delivers. This ticket makes it work.

Pinned implementation decisions (Do not deviate)

  • Both sends flow through the same reconstruction. The initial send and every retry reconstruct the payload from the snapshot object by the same pure function. That is what makes them identical; do not build a separate retry-shaped path.
  • The wire shape travels on the queue message. The retry delivers the format recorded at enqueue time. Do not re-derive the shape at send time from the row, the form, or a flag — the retry worker cannot evaluate feature flags, and what an attempt delivers must be fixed when it is queued.
  • A live-row fallback is forbidden on the V1 branch. A retry whose snapshot is missing or malformed fails loud as an observable, alarmable data-integrity error. This asymmetry with V4 is deliberate and must be preserved: for V4 the live row is the wire payload, so a missing snapshot has a byte-correct fallback; for V1 the row is never a valid payload, because it holds V4 answer objects under the submission key. This is the single most likely thing in the design to be broken by a future refactor that unifies the two paths.
  • "Byte-identical" means modulo attachment presigned URLs. A fresh presigned URL is minted per send, each with its own signature and expiry, so byte-equality can never hold on a fixture with attachments. Compare the URL keys and their storage targets instead.
  • Legacy queue messages must still parse and deliver. Messages enqueued before this change carry no recorded shape and fall back to the pre-existing mechanism, so in-flight retries survive the deploy. An unknown message version fails loud.

Acceptance criteria

  • A V1 delivery that fails is queued for retry and delivered on the retry.
  • The retried payload is identical to the initial send's, modulo attachment presigned URLs.
  • A retry whose V1 snapshot is missing or malformed fails loud and never falls back to the live row.
  • A retry message enqueued before this change still parses and delivers.
  • Plumber's V4 retry behaviour is unchanged.

Test gates

Seam: the outbound webhook POST body, driven from the retry consumer. Prior art: the existing retry-fidelity spec.

Permanent gates (keep in the suite long-term)

  • Initial send and retry produce identical payloads for the same submission, modulo attachment presigned URLs.
  • A V1 retry with a missing snapshot fails loud; assert it does not deliver a live-row payload.
  • A legacy queue message (no recorded shape) parses and delivers by the pre-existing mechanism; an unknown message version fails loud.
  • Plumber V4 retry regression: unchanged from today.

Steering gates (removed once verified)

  • None — the fidelity comparison is the permanent contract.

Blocked by

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ready-for-agentTriaged and ready for an agent to pick up

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions