Skip to content

FM-041 — Strategy Goals Bucketed Into Wrong Quarter

Neon HTTP fetch bypasses pg type parser. timestamp-without-tz returned Invalid Date → JSON null → fallback to current quarter.

FieldValue
SeveritySEV-2 (Strategy app data displayed wrong + PATCH 500s)
CategoryDatabase / Date Handling
Date2026-05-04
StatusArchived

Adding a goal to any non-current quarter column (“Q3 2026”, “Q4 2026”) rendered the goal in current quarter (“Q2 2026”). Dragging between lanes returned HTTP 500 with value.toISOString is not a function.

Neon’s HTTP fetch transport (neonConfig.poolQueryViaFetch = true) bypasses the binary wire protocol — the OID 1114 type parser for timestamp without time zone is never invoked. targetDate came back as Invalid Date, JSON-serialized to null, and the client’s getQuarterFromDate(null) fell back to current quarter.

PATCH /api/goals/:id accepted the body as-is. Clients sent targetDate as an ISO string (because JSON.stringify(Date) produces a string); Drizzle’s PgTimestamp.mapToDriverValue then called value.toISOString() on the string and threw.

  • Write side: Strict coerced PATCH body schema using z.coerce.date() for date fields. Route .safeParses and returns 400 on failure.
  • Read side: Repository derives quarter and targetDateIso in Postgres via EXTRACT(QUARTER FROM target_date)::int and to_char(target_date, ...). Client reads goal.quarter first (server source of truth).
  • getQuarterFromDate returns number | null — silent fallbacks were the bug.
  • Derive in SQL, not JS, when reading timestamp without time zone — Neon HTTP path is a known gap
  • Every PATCH route accepting dates must use z.coerce.date() — Drizzle’s mode:"date" requires Date objects
  • Don’t silently fall back to “current” — null should surface as null, not as a default