Skip to content

Normalize ALL timestamps to epoch-ms + consolidate baseline (closes #226) - #40

Merged
important-new merged 7 commits into
mainfrom
feat/schema-timestamp-ms
Jul 13, 2026
Merged

Normalize ALL timestamps to epoch-ms + consolidate baseline (closes #226)#40
important-new merged 7 commits into
mainfrom
feat/schema-timestamp-ms

Conversation

@important-new

@important-new important-new commented Jul 13, 2026

Copy link
Copy Markdown
Owner

Completes InspectorHub#226: every time-semantic Drizzle column now stores integer epoch-milliseconds with explicit { mode: 'timestamp_ms' }; the only TEXT time columns left are documented calendar/clock exceptions (due_date, closing_date, start_time, end_time, date).

Because OpenInspection is pre-launch, this follows the repo policy (drizzle.config.ts) of regenerating the single migrations/0000_baseline.sql rather than stacking forward migrations — so the prior incremental timestamp work is collapsed into one clean baseline with no dead columns.

What changed

  • mode:'timestamp' (seconds) → timestamp_ms with an idempotent ×1000 conversion; bare integer('*_at') → explicit timestamp_ms; text datetime('now') and text-ISO '*_at' columns → integer timestamp_ms.
  • 11 text-ISO _at columns converted (automation_logs, inspections, inspection_requests, discount_codes, marketplace_templates/libraries, tenant_marketplace/library_imports).
  • lint:timestamps gate (scripts/check-timestamps.mjs) with 4 rules incl. Rule D: text('*_at') is a violation; calendar/clock text columns documented as intentional exceptions.
  • Single consolidated 0000_baseline.sql.

Verification

  • lint clean (timestamps gate 0 violations), db:check no drift (81 tables), type-check clean, suite green (unit 3085 / web 1188 / workers 87).
  • Data preserved on the deployed DB via export → ISO→ms transform → reimport: 2841 rows, zero loss, all timestamps land in epoch-ms.

🤖 Generated with Claude Code

important-new and others added 7 commits July 13, 2026 16:05
Annotates all 35 bare integer('*_at') columns (18 always-ms + 17
formerly-seconds columns migrated to ms by the prior task) with
{ mode: 'timestamp_ms' }, and exempts the one dead frozen column
(users.expires_at, guest removal) with a ts-lint-ok comment so the
gate skips it.

Fixes every consumer broken by the resulting number->Date type change:
insert/update sites across server/services and server/portal switch
from Date.now()/Math.floor(.../1000) to new Date(...), and read sites
that expose a public number contract (unix-ms or unix-seconds, e.g.
observer-link exp, QBO connection status, report verification
publishedAt) convert back via .getTime()/1000 at the boundary so
external consumers (settings UI, public verify page) are unaffected.

Also fixes two call sites that only broke at runtime because they
route through untyped `any` seams (ScopedDB.insert, the dual-driver
erasure orchestrator) that tsc cannot see through, and updates all
affected test fixtures (unit + workers) accordingly.

lint:timestamps now reports only the 4 remaining text datetime('now')
columns (Task 5 scope). db:check shows no drift (mode-only change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hkbz8jKCeiQigeqLuBNvhH
…stamp_ms

Numbered the drizzle-generated retype 0026 (not 0025) to avoid colliding with
the hand-authored 0025 seconds->ms data migration, which is not tracked in the
drizzle journal (mirrors the portal convention: pure data migrations occupy a
file number without a journal entry, leaving an idx gap).
Adds scripts/check-ts-range.mjs (npm run check:ts-range) — samples
MIN/MAX per column across a representative set of migrated tables
and fails if any non-null value falls outside epoch-ms ~2015-2035.

Refreshes the tenant-scoping baseline (line-shift only, no new
findings) — prior timestamp-migration commits on this branch moved
lines in observer-link.service.ts and qbo/api-base.ts past two
pre-existing, verified-safe update-by-id queries.
…to timestamp_ms via baseline consolidation

Converts the remaining 11 text('*_at') columns (automation_logs.send_at/
delivered_at, inspections.confirmed_at, inspection_requests.scheduled_at,
discount_codes.expires_at, marketplace_templates/libraries.created_at/updated_at,
tenant_marketplace/library_imports.imported_at) from ISO-string TEXT to
integer { mode: 'timestamp_ms' }. Documents the calendar/clock exceptions
(due_date, closing_date, start_time, end_time, date) that intentionally stay TEXT.

Per the repo pre-launch policy (drizzle.config.ts), schema changes regenerate the
single migrations/0000_baseline.sql — so this collapses the prior forward
migrations (incl. the B1 timestamp work) into one clean baseline with no dead
columns. Existing data is preserved by an export -> ISO->ms transform -> reimport
of the deployed DB (validated: 2841 rows, zero loss).

Widens lint:timestamps with Rule D (text('*_at') is a violation). All time-semantic
Drizzle columns now use integer timestamp_ms; closes the acceptance criteria of InspectorHub#226.
@important-new important-new changed the title Normalize all timestamp columns to epoch-milliseconds (timestamp_ms) Normalize ALL timestamps to epoch-ms + consolidate baseline (closes #226) Jul 13, 2026
@important-new
important-new merged commit 5f56679 into main Jul 13, 2026
5 checks passed
@important-new
important-new deleted the feat/schema-timestamp-ms branch July 13, 2026 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant