Problem
The in-package MemoryReplayStore is single-instance only. Multi-pod AdCP verifiers need a shared replay store, and the spec is explicit that the step-13 insert MUST be atomic with a cap check in distributed deployments to prevent cap drift. A naive implementer will write Seen → Insert as two round trips and ship a race condition.
Proposal
Ship adcp/signing/pgreplay/ mirroring the shape of adcp/idempotency/postgres.go (which already handles dedup under atomic INSERT semantics):
package pgreplay
import (
"database/sql"
"github.com/adcontextprotocol/adcp-go/adcp/signing"
)
const Schema = \`
CREATE TABLE IF NOT EXISTS adcp_signing_replay (
keyid TEXT NOT NULL,
nonce BYTEA NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
PRIMARY KEY (keyid, nonce)
);
CREATE INDEX IF NOT EXISTS adcp_signing_replay_expires_at_idx
ON adcp_signing_replay (expires_at);
\`
type PgBackend struct { ... }
func New(db *sql.DB, opts Options) *PgBackend
\`\`\`
Implementation uses a single `INSERT ... ON CONFLICT DO NOTHING` (or `INSERT ... RETURNING` with a rate-limit check) so the step-13 atomic cap + insert is one round trip. Driver-agnostic via `database/sql`, matching `idempotency.PgBackend`.
## Why Postgres instead of Redis
adcp-go already ships `adcp/idempotency/postgres.go`. Postgres is the house persistence; adding Redis just for replay dedup introduces a second system for no gain. Deployments that already run Postgres (governance agents, media buy sellers with idempotency_key dedup) get replay dedup on the same infra.
## Notes
- Also: consider changing `ReplayStore.Insert` signature to `(ok bool, err error)` so a distributed store can distinguish "cap rejected" from "couldn't reach the DB." Current `bool` collapses both into `request_signature_rate_abuse`.
- Separate `go.mod` sub-module to keep the zero-third-party-deps guarantee of the main signing package — matches how `adcp/idempotency` is structured.
- Redis adapter remains an option if a deployment specifically needs it, but is not the reference.
## Context
Raised in the DX expert review pass on the initial signing implementation for #43. The interface (`ReplayStore`) is in place; this is the reference adapter.
Problem
The in-package
MemoryReplayStoreis single-instance only. Multi-pod AdCP verifiers need a shared replay store, and the spec is explicit that the step-13 insert MUST be atomic with a cap check in distributed deployments to prevent cap drift. A naive implementer will writeSeen→Insertas two round trips and ship a race condition.Proposal
Ship
adcp/signing/pgreplay/mirroring the shape ofadcp/idempotency/postgres.go(which already handles dedup under atomic INSERT semantics):