You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
service-queue: the publish idempotency window never expires on Postgres/MySQL — String(row.created_at) >= windowStart compares a Date.toString() against ISO text #13993
Found by the driver-materialisation consumer census on #13973 (class (c) — genuinely wrong on one side). That sweep is not addressed by this card and does not close it.
The site
packages/services/service-queue/src/db-queue-adapter.ts:250-263, in DbQueueAdapter#publish:
windowStart is canonical ISO-8601 text: "2026-08-30T10:19:25.947Z".
Why it is wrong on the production default driver
created_at is a builtin audit column. It is not in datetimeFields, so no declared-field coercion reaches it, and SqlDriver#formatOutput repairs it only inside its if (this.isSqlite) arm. Pinned in packages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts: the live dialects hand updated_at/created_at out of the record read door as a JS Date; SQLite hands back canonical ISO-Z text.
So on Postgres and MySQL the left side of that comparison is
String(new Date()) === "Sun Aug 30 2026 18:19:25 GMT+0800 (China Standard Time)"
and the comparison is a lexicographic string compare of a value beginning with an ASCII letter against a value beginning with an ASCII digit. 'S' (0x53) >= '2' (0x32) — and the same holds for every weekday name and every ISO year this century. The predicate is unconditionally true.
Effect: on Postgres/MySQL every terminal (completed / dlq) row matching the idempotency key blocks a re-publish forever, instead of only while it is inside idempotencyWindowMs. publish() returns the old message id and silently enqueues nothing. The window the ADR-0057 retention comment at the top of this file is built around (the declared retention ... is measured on the very same created_at axis and is >= this window) does not exist on those dialects.
On SQLite both sides are canonical ISO-Z text, lexicographic order equals chronological order, and the check is correct. Every test this seam has drives SQLite or memory, which is why it has stayed green.
Why this is not a ?? fallback
Per #13973's standing prohibition, the open question is which side owes the canonical spelling, not how to make the consumer tolerant. Two candidate shapes:
B — normalise at the producer, i.e. teach the read door to present the builtin audit columns in one shape on every dialect. Fixes this site and every sibling in one place, but it reverses a deliberate driver decision (withPostgresCalendarDayAsText: "timestamptz / timestamp are deliberately untouched: those are instants, a Date is the right materialisation for them") and is therefore a maintainer call, not an implementer's.
A regression pin for either needs a Date on the read side; that discriminating input exists in CI only inside @objectstack/driver-sql today (see #13973's point-4 measurement), so the pin most likely belongs in this package driving a hand-made Date, the same split sql-driver-13567-audit-stamp-materialisation.test.ts documents.
Found by the driver-materialisation consumer census on #13973 (class (c) — genuinely wrong on one side). That sweep is not addressed by this card and does not close it.
The site
packages/services/service-queue/src/db-queue-adapter.ts:250-263, inDbQueueAdapter#publish:windowStartis canonical ISO-8601 text:"2026-08-30T10:19:25.947Z".Why it is wrong on the production default driver
created_atis a builtin audit column. It is not indatetimeFields, so no declared-field coercion reaches it, andSqlDriver#formatOutputrepairs it only inside itsif (this.isSqlite)arm. Pinned inpackages/drivers/driver-sql/src/sql-driver-13567-audit-stamp-materialisation.test.ts: the live dialects handupdated_at/created_atout of the record read door as a JSDate; SQLite hands back canonical ISO-Z text.So on Postgres and MySQL the left side of that comparison is
and the comparison is a lexicographic string compare of a value beginning with an ASCII letter against a value beginning with an ASCII digit.
'S' (0x53) >= '2' (0x32)— and the same holds for every weekday name and every ISO year this century. The predicate is unconditionally true.Effect: on Postgres/MySQL every terminal (
completed/dlq) row matching the idempotency key blocks a re-publish forever, instead of only while it is insideidempotencyWindowMs.publish()returns the old message id and silently enqueues nothing. The window the ADR-0057 retention comment at the top of this file is built around (the declared retention ... is measured on the very same created_at axis and is >= this window) does not exist on those dialects.On SQLite both sides are canonical ISO-Z text, lexicographic order equals chronological order, and the check is correct. Every test this seam has drives SQLite or memory, which is why it has stayed green.
Why this is not a
??fallbackPer #13973's standing prohibition, the open question is which side owes the canonical spelling, not how to make the consumer tolerant. Two candidate shapes:
canonicalVersionInstant): compare instants, not strings. Local, no contract change.withPostgresCalendarDayAsText: "timestamptz/timestampare deliberately untouched: those are instants, aDateis the right materialisation for them") and is therefore a maintainer call, not an implementer's.A regression pin for either needs a
Dateon the read side; that discriminating input exists in CI only inside@objectstack/driver-sqltoday (see #13973's point-4 measurement), so the pin most likely belongs in this package driving a hand-madeDate, the same splitsql-driver-13567-audit-stamp-materialisation.test.tsdocuments.Re-run
Backlink: #13973 (census), #13382 (the OCC seam, the same shape). Neither is addressed here.