Skip to content

Commit 2288c34

Browse files
voidstackloopclaude
andcommitted
fix(server): SECURITY DEFINER maintenance functions wrote non-ISO timestamps into JSONB documents
sweep_stale_compute_nodes() and reclaim_expired_compute_leases() wrote their updatedAt field via to_jsonb(now()::text) / to_jsonb(p_now::text) -- casting a timestamptz to ::text first captures Postgres's own default text output ('2026-08-31 13:45:00.123456+00': space-separated, no colon in the offset), not ISO-8601. Every subsequent read re-parses that same document through computeNodeSchema/computeResourceRequestSchema, whose updatedAt field is z.string().datetime({offset:true}) -- strict ISO-8601 -- so it threw 'Invalid ISO datetime' the moment either function's output was read back. This is what postgres-compute-control-store.test.ts's 'the SECURITY DEFINER maintenance functions actually run' test was hitting in CI -- the one server-job failure left after the last two fixes (missing contracts build, deadlocking parallel test files). Confirmed by reading the actual SQL (no local Postgres available to run it against, per the standing dev-environment constraint) and cross-referencing Postgres's own documented JSON behavior: to_jsonb() applied directly to a timestamptz value formats it as real ISO-8601; to_jsonb() applied to its ::text cast does not. Fixed by dropping the ::text cast at all three call sites -- now() and p_now are already timestamptz, so to_jsonb() alone is correct and sufficient. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 3c1cbe3 commit 2288c34

1 file changed

Lines changed: 15 additions & 3 deletions

File tree

server/migrations/021_compute_control_plane.sql

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,15 @@ BEGIN
133133
WITH changed AS (
134134
UPDATE public.compute_nodes
135135
SET state='offline', updated_at=now(),
136-
document=jsonb_set(jsonb_set(document,'{state}','"offline"'::jsonb),'{updatedAt}',to_jsonb(now()::text))
136+
-- to_jsonb() applied directly to a timestamptz produces a real
137+
-- ISO-8601 string ("2026-08-31T13:45:00.123456+00:00"); casting
138+
-- to ::text first (the bug this replaced) instead captures
139+
-- Postgres's own default text format ("2026-08-31
140+
-- 13:45:00.123456+00" -- space-separated, no colon in the
141+
-- offset), which fails the strict z.string().datetime({offset:
142+
-- true}) this document's own updatedAt field is re-validated
143+
-- against on every subsequent read.
144+
document=jsonb_set(jsonb_set(document,'{state}','"offline"'::jsonb),'{updatedAt}',to_jsonb(now()))
137145
WHERE state='online' AND last_heartbeat_at < p_cutoff
138146
RETURNING id
139147
) SELECT COALESCE(array_agg(id),'{}'::uuid[]) INTO affected FROM changed;
@@ -152,14 +160,18 @@ BEGIN
152160
WITH expired AS (
153161
UPDATE public.compute_resource_leases
154162
SET state='expired', updated_at=p_now,
155-
document=jsonb_set(jsonb_set(document,'{state}','"expired"'::jsonb),'{updatedAt}',to_jsonb(p_now::text))
163+
-- See sweep_stale_compute_nodes() above for why this must not
164+
-- cast p_now to ::text before to_jsonb().
165+
document=jsonb_set(jsonb_set(document,'{state}','"expired"'::jsonb),'{updatedAt}',to_jsonb(p_now))
156166
WHERE state IN ('offered','acknowledged','running')
157167
AND (expires_at <= p_now OR (state='offered' AND acknowledgment_deadline_at <= p_now))
158168
RETURNING id,request_id
159169
), requeued AS (
160170
UPDATE public.compute_resource_requests request
161171
SET state='queued',updated_at=p_now,
162-
document=jsonb_set(jsonb_set(request.document - 'assignedAt','{state}','"queued"'::jsonb),'{updatedAt}',to_jsonb(p_now::text))
172+
-- See sweep_stale_compute_nodes() above for why this must not
173+
-- cast p_now to ::text before to_jsonb().
174+
document=jsonb_set(jsonb_set(request.document - 'assignedAt','{state}','"queued"'::jsonb),'{updatedAt}',to_jsonb(p_now))
163175
FROM expired WHERE request.id=expired.request_id AND request.state <> 'cancelled'
164176
RETURNING request.id
165177
) SELECT COALESCE(array_agg(id),'{}'::uuid[]) INTO affected FROM expired;

0 commit comments

Comments
 (0)