Repository navigation
Normalize ALL timestamps to epoch-ms + consolidate baseline (closes #226) - #40
Merged
Merged
Conversation
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
…t (match siblings)
…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
added a commit
that referenced
this pull request
Aug 23, 2026
…ges (PDF TOC degrade + Word-export memory) (InspectorHub#242) * fix(pca): degrade TOC pass-2 render gracefully on Browser Rendering 429 The two-pass real-TOC-page-number renderer (generatePdfWithTocPages, InspectorHub#240) fires pass 2 immediately after pass 1. On the Workers Free plan, Browser Rendering allows only 1 Quick Action every 10 seconds, so pass 2 is rejected with HTTP 429 (code 2001) before it loads — which threw and 500'd EVERY full-tier PCA PDF download on free-tier BR. Pass 2 is an enhancement, not a requirement: on 429, back off 11s (clears the free-tier 10s window; wall-time only, no browser-hours) and retry once, then degrade to the un-numbered pass-1 PDF instead of throwing. Paid plans (10 Quick Actions/second) never hit this path. Mirrors the existing extractAnchorPages graceful-degrade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YErf73G9tqjUmMSB2y1RUB * feat(pca): consistent Browser Rendering rate-limit UX across all PDF surfaces Every surface that triggers a live Browser Rendering render on click now shares usePdfExport(): it fetches the PDF, and on a rate-limit / hard failure shows the reason and counts down a cooldown ('Retry in Ns') so repeated clicks don't compound the free-tier limit (1 render / 10s). Copy is centralized in the hook so the reminder reads identically everywhere. - ReportView 'Download PDF' FAB (owner + token) — refactored onto the hook - Editor 'Preview PDF' — was window.open (raw JSON 500 on failure) -> hook (view) - Verify page 'Download signed vN PDF' — was <a download> -> hook (download) - Repair-request 'View as PDF' — was <a target=_blank> -> hook (view) view-mode pre-opens the tab inside the click gesture to dodge the popup blocker. DS-conformant (ih-* tokens only). buttons source-scan test follows the logic into the hook. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YErf73G9tqjUmMSB2y1RUB * fix(pca): bound Word-export photo memory + declare optional IMAGES binding The Word (.docx) export embeds Appendix B photos. When the IMAGES binding is present each is downscaled to a ~960px JPEG (~200KB) and 50-100 photos fit under the isolate memory limit. When IMAGES is unset (an account without Images Transformations enabled), photos embed at full resolution (2-4MB) and a large report would OOM — docx's Packer.toBuffer materializes the whole document (all media) in memory, so peak is ~2x the embedded bytes. - buildAppendixPhotoInputs now embeds photos in order until a 32MiB byte budget is reached, then omits the rest (logged), keeping >=1 photo. Peak stays well under 128MiB regardless of the IMAGES binding or a transform-failure fallback. - wrangler.jsonc declares the optional IMAGES binding (account-scoped name-only, like STREAM): accounts with Images Transformations enabled — Free plan included, 5,000/mo — get downscaled exports; accounts without still deploy fine with env.IMAGES unset and fall back to the byte budget. - Deploy doc notes the binding is optional and how the fallback behaves. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YErf73G9tqjUmMSB2y1RUB * test(pca): real-isolate memory profile — 40x1MiB no-IMAGES export stays bounded Adds a workerd stress case exercising the dangerous path the byte budget guards: env.IMAGES unset (full-resolution embed), 40 genuinely-distinct 1 MiB photos. Proves in real workerd that the export completes (no OOM/retry) and APPENDIX_PHOTO_TOTAL_BYTE_BUDGET caps embedding (imageRelCount < 40; the tail photo #40 is omitted). Parameterizes seedLargeCommercialInspection with a photo-bytes fn so the existing 60-photo (tiny-PNG) case is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YErf73G9tqjUmMSB2y1RUB --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 singlemigrations/0000_baseline.sqlrather than stacking forward migrations — so the prior incremental timestamp work is collapsed into one clean baseline with no dead columns.What changed
integer('*_at')→ explicit timestamp_ms; textdatetime('now')and text-ISO'*_at'columns → integer timestamp_ms._atcolumns converted (automation_logs, inspections, inspection_requests, discount_codes, marketplace_templates/libraries, tenant_marketplace/library_imports).lint:timestampsgate (scripts/check-timestamps.mjs) with 4 rules incl. Rule D:text('*_at')is a violation; calendar/clock text columns documented as intentional exceptions.0000_baseline.sql.Verification
lintclean (timestamps gate 0 violations),db:checkno drift (81 tables), type-check clean, suite green (unit 3085 / web 1188 / workers 87).🤖 Generated with Claude Code