WebhookStore.List shipped, worked in every test, worked in the dogfood instance's ad-hoc checks, and would have hard-failed on the very first call against any database built from its own documented schema. The gap wasn't in the code path everyone reads. It was in a word.
The column type that isn't on the list
modernc.org/sqlite's driver auto-converts three column type names to time.Time when you scan into one: DATE, DATETIME, TIMESTAMP. Exact string match, no substring check. TIMESTAMPTZ, the type this codebase actually documents and creates for every timestamp column that needs to carry a timezone, isn't one of them. Scan a TIMESTAMPTZ value straight into *time.Time and you get a hard error, every time:
sql: Scan error on column index 0, name "created_at": unsupported Scan,
storing driver.Value type string into type *time.Time
storage.go already has the fix for this: timeScanner, built for exactly this problem back in A200, tolerant of four different string shapes SQLite might hand back. webhook.go and outbound.go just never used it. Six scan calls across both files read created_at, next_retry_at, expires_at, and attempted_at, every one of them TIMESTAMPTZ by documented DDL, straight into time.Time.
Why the tests never caught it
Every test table across webhook_test.go, outbound_test.go, and the G26–G29 integration groups declared these same columns as DATETIME. Not a typo, not one file drifting from another, a consistent, repo-wide substitution. And DATETIME *is* on the driver's auto-convert list. The tests were internally consistent and green. They just weren't testing the schema the codebase tells you to build.
The worst-affected function was fetchDueJobs, the query the worker pool's background loop runs every cycle to find webhook deliveries that are due. Not an admin-listing edge case. The actual delivery path. Fixed the same way as everything else: wrap the scan destination in scanDest(), and correct the test tables to TIMESTAMPTZ so they'd have caught this the first time.
The second bug hiding behind the first
Fixing the column types didn't make the tests pass. It surfaced a different error: cannot parse "2026-07-28 19:47:42.385619 +0200 CEST m=+0.028187501" as time.Time. That m=+0.028187501 is Go's monotonic clock reading, present on any time.Time built from a bare time.Now() call. realClock.Now(), the production clock implementation the worker pool uses everywhere, was returning exactly that: local time, monotonic reading attached. Every other timestamp-generation site in this codebase calls .UTC(), which strips the monotonic component as a side effect of changing location. realClock.Now() was the one place that didn't. One-line fix, one source of truth, rather than patching every call site that reads the clock.
A plan that turned out to be wrong, and how that got found
The fix for one nullable field, DeliveryStats's "most recent delivery attempt", was planned as a straight copy of TokenStore.List's existing pattern: scan into a nullable string, parse with time.RFC3339. Reasonable, consistent, minimal. It didn't work, and a new round-trip test (one that enqueues a real job, delivers it, and asks DeliveryStats for the result instead of asserting against a hand-built string) caught it immediately.
TokenStore gets away with a single-format parse because it writes its own timestamps pre-formatted to RFC3339 before insert, a closed loop, self-consistent by construction. outbound.go inserts a raw time.Time and lets the driver decide the string. That string turned out to be Go's own default time.Time.String() shape, no T separator, so RFC3339 parsing rejects it outright. The layout timeScanner already carries covers it. The fix ended up reusing that one call directly, rather than adding a second, narrower parser next to it.
The lesson isn't "TokenStore's pattern is wrong", it's correct for what TokenStore does. It's that a pattern's correctness depends on the write path behind it, and that's worth checking with a real round trip instead of assuming symmetry.