Pre-launch sweep: AI rate limiter, error allowlist, jsonb column registry, missing workflow column
PR #8 worked through code-review findings accumulated before the public launch. Seven commits touch rate limiting, safe error sanitization, tabular access optimization, streamed chat error handling, and a jsonb serialization bug that was silently dropping chat history. The last one is the most consequential.
The chat_messages.workflow column was missing from the schema but the routes were already trying to write to it. Without the column, the insert failed, and because the insert was fire-and-forget, the failure was swallowed. Migration 010 adds the column; chat.ts and projectChat.ts were updated to check the insert error and return a 500 to the user rather than silently dropping their message. The same fix applies to both the single-document and project chat paths.
documents.ts got a safeRequestErrorDetail() function -- an explicit allowlist of error strings safe to pass through to clients ("filename is required", "Document not found", "Version not found", file-type mismatch patterns). Everything else collapses to a generic "Failed to ..." with a stable code field. A matching documentLoadError.ts helper on the frontend handles the codes. This is the defensive posture you'd want on any public API that wraps third-party storage.
The AI-route rate limiter adds MIKE_AI_RATE_LIMIT_WINDOW_MS and MIKE_AI_RATE_LIMIT_MAX (default 60 per 15 minutes) gating POST /chat, POST /projects/:id/chat, and the generate/regenerate-cell/chat paths on /tabular-review. Download links shifted from no-expiry to HMAC-signed tokens with a configurable DOWNLOAD_TOKEN_TTL_SECONDS (default 7 days).
postgresCompat.ts gained a jsonbColumnsByTable map listing every jsonb column across 10 tables. Insert, update, and upsert paths now call columnParam() which emits $N::jsonb casts for listed columns, fixing silent write failures on chat_messages.workflow, documents.structure_tree, projects.case_matter_metadata, and others. Finally, the streamed chat error handler now recognizes data.type === "error" events mid-stream and converts demo_budget_exceeded and insufficient_quota codes into specific user-facing messages.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?