consume: every dead-letter write sanitizes; remote claims are quoted and bounded master
Chunk-3 finding 1 from the second-opinion re-review (5 streams). The NUL-byte DLQ fix sanitized AddDeadLetter but not its two siblings writing the same last_error column: MarkRedriveAttempt and RetireDeadLetter passed the string straight through, so a NUL riding in a handler error (reachable: resolver.go interpolated up to 1 KiB of an attacker-controlled well-known response body into a transient error) made the post-redrive UPDATE fail — attempts never incremented, the row could never retire, it was fully re-executed on every pass forever, and the forward-progress guard stalled the entire DLQ drain behind it. All three writers now go through one funnel, execDeadLetterWrite, which sanitizes the error argument immediately before ExecContext — placed inside the store so a fourth writer can't skip it, with a loud typed guard on the convention. sanitizeErrorText gains a head-preserving 4096-byte cap applied before UTF-8 repair so a mid-rune cut becomes a replacement rune. At the source, the three sites echoing a remote-supplied DID claim (both well-known messages and the DNS-TXT message — the TXT value is remote-controlled too) now quote and cap it at 128 bytes. Repro tests pin the store writes with NUL-bearing errors, the bound, the redriver burning exactly MaxRedriveAttempts on a poison row and the drain progressing past it, and the quoted claims on all three resolver paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>