Skip to content

database: Complete stabilization migration - 7 audit fixes - #22

Merged
TechHypeXP merged 34 commits into
mainfrom
database/stabilization
May 20, 2026
Merged

database: Complete stabilization migration - 7 audit fixes#22
TechHypeXP merged 34 commits into
mainfrom
database/stabilization

Conversation

@TechHypeXP

@TechHypeXP TechHypeXP commented May 19, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR applies all critical schema fixes identified in the database audit report as a single initial stabilization migration.

Fixes Applied (7/7)

  1. Auth Linkage - users.id references auth.users(id) with ON DELETE CASCADE
  2. Cascading Deletes - analyses, usage_logs cascade on delete; stripe_events set null
  3. Missing Columns - Added model_attempted, validation_report, validation_passed to match codebase expectations
  4. Timezone Safety - Converted all timestamps to timestamptz for UTC consistency
  5. Concurrent Safety - Added UNIQUE(user_id, video_id) constraint to prevent double-billing
  6. Quota Underflow Prevention - Added CHECK (analyses_used >= 0) constraint
  7. Performance Indexes - Added cache lookup, vector search, and foreign key indexes

Additional Updates

  • Created increment_user_quota() RPC for atomic quota enforcement
  • Created reset_user_quota() RPC for monthly quota resets
  • Enabled vector extension for semantic search support (Chunk 13+)

Verification

✅ All columns in insert statement match migration schema (route.ts:469-483)

  • model_attempted ✅
  • model_used ✅
  • validation_report ✅
  • validation_passed ✅

Test Plan

  • Run supabase db push to apply migration to remote
  • Verify no type errors with npx supabase gen types typescript
  • Confirm route.ts can insert analyses without schema errors
  • Test rate-limit endpoints with new RPCs
  • Verify RLS policies still function correctly

🤖 Generated with Claude Code

Summary by Sourcery

Apply a stabilization database migration that hardens core tables, constraints, and performance characteristics based on the audit findings.

Enhancements:

  • Normalize timestamp handling across core tables by converting to timestamptz for UTC-safe storage.
  • Tighten relational integrity and delete behavior via updated foreign keys, cascading deletes, and SET NULL semantics where appropriate.
  • Augment the analyses, users, usage_logs, and stripe_events tables with missing columns and constraints to match application expectations and prevent invalid states (e.g., non-negative quota, valid status/action values).
  • Add a UNIQUE constraint on analyses to prevent duplicate user/video records and introduce performance indexes for cache lookups, foreign keys, and vector-based search.
  • Enable the vector extension and add an embeddings column on analyses to support future semantic search capabilities.
  • Introduce increment_user_quota and reset_user_quota RPC functions for atomic quota accounting and periodic quota resets.

Summary by cubic

Completes the stabilization migration and security hardening: applies all 7 audit fixes with strict RLS and request‑scoped auth, safer timeouts, and exact hostname allowlists. Standardizes CI on Node 24 + pnpm@11.1.3, finalizes Sentry/QStash, upgrades to next@16.2.6, anchors Turbopack, and injects Supabase/OpenRouter env vars at build with safe CI fallbacks.

  • New Features

    • RPCs: increment_user_quota() and reset_user_quota() for atomic quota checks and monthly resets.
    • Vector + sharing: vector enabled; analyses.embedding, analyses.shared_token, analyses.shared_expires_at.
  • Refactors

    • Database/RLS: One stabilization migration (auth linkage, cascades, timestamptz, UNIQUE(user_id, video_id), non‑negative checks, perf indexes) plus strict RLS on users/analyses/usage_logs.
    • Runtime: Request‑scoped Supabase via @supabase/ssr; background tasks with after(); exact‑hostname SSRF allowlist; dynamic max_tokens; 5s timeouts for embeddings and QStash verify; Redis backoff/circuit breaker; require NEXT_PUBLIC_APP_URL for QStash.
    • Auth/Middleware/Sentry: Public‑route allowlist; 204 CORS preflight; edge‑safe constant‑time compare; split Sentry configs (client/server/edge) with DSN guard.
    • Tooling/CI: Composite action “Hex Intel Environment”; Node 24 + pnpm@11.1.3; root package.json + monorepo pnpm-lock.yaml + .npmrc; secret scan excludes node_modules; Vercel deploy via amondnet/vercel-action@master; build injects Supabase + OpenRouter env with dummy fallbacks.
    • Build: Turbopack root and .next distDir; web on next@16.2.6.

Written for commit 03a88ba. Summary will update on new commits. Review in cubic

Summary by CodeRabbit

  • New Features

    • User quota management with reset capability
    • Sharing support for analyses
    • Vector embedding support
  • Improvements

    • More robust YouTube URL parsing and metadata validation
    • Safer background processing for analysis requests
    • Request-scoped DB clients and stricter RLS enforcement
    • Timeout/retry improvements for embeddings, Redis, and OpenRouter
    • Improved Sentry init and middleware/CORS/auth handling
  • Chores

    • CI/CD, packaging, and developer docs updated (pnpm/workflows)

Review Change Stack

This migration applies all critical schema fixes in a single transaction:
1. Auth linkage (users.id references auth.users)
2. Cascading deletes (ON DELETE CASCADE/SET NULL)
3. Missing columns (model_attempted, validation_report, validation_passed)
4. Timezone safety (timestamptz for all timestamps)
5. Concurrent safety (UNIQUE constraint on user_video)
6. Quota underflow prevention (CHECK constraint)
7. Performance indexes (cache lookup, vector, foreign keys)

Additionally creates two RPCs:
- increment_user_quota: Atomic quota enforcement
- reset_user_quota: Monthly quota reset helper

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented May 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

Single stabilization migration that hardens core public tables (users, analyses, usage_logs, stripe_events), normalizes timestamps, enforces auth linkages and constraints, adds performance indexes, and introduces two quota-related RPC functions.

Sequence diagram for quota enforcement via increment_user_quota RPC

sequenceDiagram
  actor User
  participant ApiServer
  participant Supabase

  User->>ApiServer: POST /api/analyses
  ApiServer->>Supabase: RPC increment_user_quota(p_user_id, p_increment)
  Supabase-->>ApiServer: new_quota, tier
  ApiServer-->>User: 200 OK with analysis result
Loading

Entity relationship diagram for hardened core tables and constraints

erDiagram
  users {
    uuid id PK
    integer analyses_used
    timestamptz last_reset_date
    text tier
  }

  analyses {
    uuid id PK
    uuid user_id FK
    text video_id
    text model_attempted
    text model_used
    boolean validation_passed
    vector embedding
    varchar shared_token
    timestamptz created_at
  }

  usage_logs {
    uuid id PK
    uuid user_id FK
    text action
    integer tokens_used
    numeric cost_usd
    timestamptz created_at
  }

  stripe_events {
    uuid id PK
    uuid user_id FK
    text event_type
    integer amount_cents
    text status
    timestamptz created_at
  }

  users ||--o{ analyses : user_id_cascade
  users ||--o{ usage_logs : user_id_cascade
  users ||--o{ stripe_events : user_id_set_null

  analyses }o--o{ analyses : unique_user_video
  users ||--|| users : check_analyses_used_non_negative
Loading

File-Level Changes

Change Details Files
Hardened users table with additional profile/billing fields, timestamptz normalization, and quota safety constraint.
  • Added optional profile and Stripe identifiers columns with a unique constraint on stripe_customer_id.
  • Converted created_at, updated_at, and last_reset_date to timestamptz using UTC.
  • Added a CHECK constraint to prevent analyses_used from going negative.
supabase/migrations/20260519_complete_stabilization.sql
Extended analyses table for validation, sharing, semantic search, and enforced better relational and concurrency guarantees.
  • Added model_attempted, validation_report, validation_passed, and ensured model_used with sensible defaults.
  • Enabled vector extension and added an embedding vector column plus shared_token and shared_expires_at for sharing flows.
  • Converted timestamp columns to timestamptz and redefined user_id FK with ON DELETE CASCADE.
  • Added UNIQUE(user_id, video_id) constraint and cache-friendly defaults for model fields.
supabase/migrations/20260519_complete_stabilization.sql
Strengthened usage_logs table with richer tracking, timestamptz, and cascading delete behavior.
  • Added action column with constrained enum-like values and a metadata jsonb column.
  • Added tokens_used and cost_usd tracking columns with defaults.
  • Converted created_at to timestamptz and redefined user_id FK with ON DELETE CASCADE.
supabase/migrations/20260519_complete_stabilization.sql
Normalized stripe_events schema and adjusted user linkage to preserve billing history on user deletion.
  • Added event_type, amount_cents, status with constrained values, and payload jsonb.
  • Converted created_at to timestamptz.
  • Recreated user_id FK with ON DELETE SET NULL to avoid hard-deleting billing records.
supabase/migrations/20260519_complete_stabilization.sql
Added performance indexes across core tables, including semantic search support.
  • Created cache lookup index on analyses(user_id, video_id, created_at DESC).
  • Added foreign-key helper indexes on user_id for analyses, usage_logs, and stripe_events.
  • Created HNSW vector index on analyses.embedding for future semantic search.
supabase/migrations/20260519_complete_stabilization.sql
Introduced quota management RPCs for atomic increments and monthly resets.
  • Created SECURITY DEFINER increment_user_quota(p_user_id, p_increment) that atomically increments analyses_used and returns new_quota and tier.
  • Created SECURITY DEFINER reset_user_quota(p_user_id) that zeroes analyses_used, updates last_reset_date to current UTC timestamp, and returns reset metadata.
supabase/migrations/20260519_complete_stabilization.sql

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@vercel

vercel Bot commented May 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hex-yt-intel Ready Ready Preview, Comment May 20, 2026 9:18pm

@coderabbitai

coderabbitai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR hardens the database schema and enforces RLS, refactors the analyses API to run background work with next.after(), tightens QStash/webhook and SSRF checks, adds Edge-safe middleware and multi-runtime Sentry init, improves Supabase client auth handling, adds embedding/Redis resiliency, tunes OpenRouter token budgeting, and updates CI/CD pnpm/Vercel tooling and docs.

Changes

Database Schema Stabilization and RLS Security

Layer / File(s) Summary
Complete stabilization migration
supabase/migrations/20260519220000_complete_stabilization.sql
Single DO-block conditionally hardens users, analyses, usage_logs, and stripe_events: adds missing columns, timestamptz conversions, embedding vector support, cascade/soft-delete FKs, UNIQUE constraints, performance indexes, and two SECURITY DEFINER quota functions (increment_user_quota, reset_user_quota).
RLS policy lockdown and simplification
supabase/migrations/20260520_rls_lockdown_enforcement.sql, supabase/migrations/20260519_rls_lockdown.sql
Enable RLS (guarded) and install per-table ownership policies; reorganize guarded ALTER/DROP into DO-blocks and simplify UPDATE policy WITH CHECK clauses to auth.uid() = ....
Removed trigger and earlier permissive migrations
supabase/migrations/20260515_*.sql, supabase/migrations/20260516_*.sql
Removed prior public.handle_new_user() trigger and earlier migrations that enabled permissive RLS policies.

Web Application Hardening and Instrumentation

Layer / File(s) Summary
Background processing refactor with after()
web/app/api/analyses/route.ts
Import after() and schedule background stream collection and DB updates inside after(...); add dedicated try/catch around initial Supabase insert and the background collection/update/publish phases with Sentry breadcrumbs and error reporting; tighten SSRF worker hostname check.
QStash webhook and signature handling
web/lib/qstash-client.ts
Require NEXT_PUBLIC_APP_URL to build webhook URLs for validation/embedding tasks (throw when absent); initialize Receiver with `nextSigningKey: nextKey
Middleware Edge compatibility and auth hardening
web/middleware.ts
Add Edge-compatible constant-time timingSafeStringEqual helper, short-circuit OPTIONS preflight with CORS headers, add public route allowlist bypassing auth, and pass `process.env.NEXTAUTH_SECRET
Sentry multi-runtime instrumentation
web/instrumentation.ts, web/sentry.client.config.ts, web/sentry.server.config.ts, web/sentry.edge.config.ts
Conditionally initialize Sentry for Node runtime only when DSN exists via dynamic import; preserve Edge init via dynamic import; add client/server/edge Sentry config modules with tracing enabled.
Supabase request-scoped client and token extraction
web/lib/supabase.ts
Provide getSupabaseClient() anon-key factory, getSupabaseClientWithAuth(userToken?) async factory that uses cookie or supplied token for auth, and extractUserToken() to safely read sb-auth-token from cookies.
Skill package input validation hardening
skill/src/index.ts
Normalize and validate YouTube URLs across formats; validate worker JSON response shape and required fields; parse numeric metadata permissively.

Resiliency, Services, and Tests

Layer / File(s) Summary
Embedding fetch timeout
web/lib/embeddings.ts
Add per-attempt AbortController with 5s timeout for OpenRouter fetch attempts and ensure timeout cleared in finally.
Redis retry and circuit-breaker
web/lib/redis.ts
Replace single-shot eval with retry loop (max 3) for transient errors with exponential backoff; classify errors transient vs permanent and set redisAvailable = false on permanent failures.
OpenRouter token budgeting
web/lib/services/openrouter.ts
Compute dynamic maxTokens based on transcript length with a cap and pass it as max_tokens to OpenRouter requests.
Test constant update
web/tests/chunk-13-phase-b.spec.ts
Change INVALID_KEY test constant string value.

CI/CD Workflows and Tooling

Layer / File(s) Summary
pnpm action upgrade and workflow consistency
.github/workflows/ci-cd.yml
Bump pnpm/action-setup to v3 across CI jobs and set PNPM_VERSION to 11.1.3; adjust build step to run web-only build and run Supabase CLI from workflow root.
Vercel deployment action replacement
.github/workflows/ci-cd.yml, .github/workflows/staging-deploy.yml
Replace vercel/action@v5.1.0 with amondnet/vercel-action@master and update inputs to use vercel-args: '--prod'.
Package.json workspace and composite action inputs
package.json, action.yml
Add top-level package.json with workspace scripts and engine constraints; refactor action.yml to accept node-version and pnpm-version inputs and simplify setup steps.
Secrets documentation and TypeScript configuration
docs/ops/GITHUB_SECRETS_SETUP.md, web/tsconfig.json
Add GITHUB_SECRETS_SETUP.md documenting required CI/CD secrets and steps; add compilerOptions.ignoreDeprecations: "5.0" to web/tsconfig.json.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the primary change: a database migration that applies seven audit-identified schema fixes for stabilization.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch database/stabilization
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch database/stabilization

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@supabase

supabase Bot commented May 19, 2026

Copy link
Copy Markdown

Updates to Preview Branch (database/stabilization) ↗︎

Deployments Status Updated
Database Wed, 20 May 2026 21:18:09 UTC
Services Wed, 20 May 2026 21:18:09 UTC
APIs Wed, 20 May 2026 21:18:09 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Wed, 20 May 2026 21:18:12 UTC
Migrations Wed, 20 May 2026 21:18:13 UTC
Seeding ⏸️ Wed, 20 May 2026 21:18:02 UTC
Edge Functions ⏸️ Wed, 20 May 2026 21:18:02 UTC

❌ Branch Error • Wed, 20 May 2026 21:18:14 UTC

ERROR: relation "public.analyses" does not exist (SQLSTATE 42P01)
At statement: 0
-- Add model_used column to analyses table to track which Claude model generated each analysis
ALTER TABLE public.analyses
ADD COLUMN IF NOT EXISTS model_used VARCHAR(255) DEFAULT 'anthropic/claude-3.5-haiku'

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@github-actions

Copy link
Copy Markdown

❌ Linting failed. Please fix linting errors before merging.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues, and left some high level feedback:

  • The ALTER ... TYPE timestamp with time zone USING ... AT TIME ZONE 'UTC' conversions (e.g. for created_at, updated_at, published_at) can silently change existing data semantics and, in the case of published_at, fill nulls with NOW(); consider guarding these with explicit checks or a two-step migration that preserves existing nulls and respects whether the columns are already timestamptz.
  • Both increment_user_quota and reset_user_quota assume that a users row exists for p_user_id and report success even if the UPDATE affects zero rows; it would be safer to check FOUND (or use RETURNING and handle no-row cases) and either raise an error or return a clear failure indicator when the user does not exist.
  • The CREATE EXTENSION IF NOT EXISTS vector inside the DO block couples extension management with table migration and may fail in restricted environments; consider moving extension creation to a separate top-level migration statement so that superuser/owner privileges and failure modes are easier to control.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `ALTER ... TYPE timestamp with time zone USING ... AT TIME ZONE 'UTC'` conversions (e.g. for `created_at`, `updated_at`, `published_at`) can silently change existing data semantics and, in the case of `published_at`, fill nulls with `NOW()`; consider guarding these with explicit checks or a two-step migration that preserves existing nulls and respects whether the columns are already `timestamptz`.
- Both `increment_user_quota` and `reset_user_quota` assume that a `users` row exists for `p_user_id` and report success even if the `UPDATE` affects zero rows; it would be safer to check `FOUND` (or use `RETURNING` and handle no-row cases) and either raise an error or return a clear failure indicator when the user does not exist.
- The `CREATE EXTENSION IF NOT EXISTS vector` inside the `DO` block couples extension management with table migration and may fail in restricted environments; consider moving extension creation to a separate top-level migration statement so that superuser/owner privileges and failure modes are easier to control.

## Individual Comments

### Comment 1
<location path="supabase/migrations/20260519_complete_stabilization.sql" line_range="48-57" />
<code_context>
+
+    -- Add vector column for semantic search (future embedding support)
+    CREATE EXTENSION IF NOT EXISTS vector;
+    ALTER TABLE public.analyses
+    ADD COLUMN IF NOT EXISTS embedding vector(1536);
+
+    -- Add shared token support for read-only sharing
+    ALTER TABLE public.analyses
+    ADD COLUMN IF NOT EXISTS shared_token character varying UNIQUE,
+    ADD COLUMN IF NOT EXISTS shared_expires_at timestamp with time zone;
+
+    -- Convert all timestamps to timestamptz
+    ALTER TABLE public.analyses
+    ALTER COLUMN created_at TYPE timestamp with time zone USING created_at AT TIME ZONE 'UTC',
+    ALTER COLUMN updated_at TYPE timestamp with time zone USING updated_at AT TIME ZONE 'UTC',
+    ALTER COLUMN published_at TYPE timestamp with time zone USING COALESCE(published_at, NOW()) AT TIME ZONE 'UTC';
+
+    -- Drop old foreign key if it exists without cascading delete
</code_context>
<issue_to_address>
**issue (bug_risk):** Converting `published_at` while coercing NULLs to `NOW()` changes semantics for existing rows.

`COALESCE(published_at, NOW())` will retroactively assign a timestamp to all rows where `published_at` is currently NULL, erasing the distinction between NULL and “published”. If NULL is meaningful, keep it as NULL in the migration, e.g. `USING CASE WHEN published_at IS NULL THEN NULL ELSE published_at AT TIME ZONE 'UTC' END` (adjusted for the current column type as needed).
</issue_to_address>

### Comment 2
<location path="supabase/migrations/20260519_complete_stabilization.sql" line_range="23-32" />
<code_context>
+  IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'users' AND table_schema = 'public') THEN
+
+    -- Add missing columns to users table
+    ALTER TABLE public.users
+    ADD COLUMN IF NOT EXISTS name text,
+    ADD COLUMN IF NOT EXISTS avatar_url text,
+    ADD COLUMN IF NOT EXISTS stripe_customer_id text UNIQUE,
+    ADD COLUMN IF NOT EXISTS stripe_subscription_id text;
+
+    -- Convert timestamps to timestamptz for timezone safety
+    ALTER TABLE public.users
+    ALTER COLUMN created_at TYPE timestamp with time zone USING created_at AT TIME ZONE 'UTC',
+    ALTER COLUMN updated_at TYPE timestamp with time zone USING updated_at AT TIME ZONE 'UTC',
+    ALTER COLUMN last_reset_date TYPE timestamp with time zone USING last_reset_date AT TIME ZONE 'UTC';
+
+    -- Add quota underflow prevention check constraint
</code_context>
<issue_to_address>
**issue (bug_risk):** Blindly applying `AT TIME ZONE 'UTC'` assumes current columns are `timestamp without time zone`.

If any of these columns (`created_at`, `updated_at`, `last_reset_date`) are already `timestamptz` in some environments, `... USING col AT TIME ZONE 'UTC'` will incorrectly shift stored times. Consider either conditioning the migration on the existing data type (e.g. only when `data_type = 'timestamp without time zone'`) or using a `USING` expression that behaves correctly for both `timestamp` and `timestamptz`.
</issue_to_address>

### Comment 3
<location path="supabase/migrations/20260519_complete_stabilization.sql" line_range="169-170" />
<code_context>
+    CREATE INDEX IF NOT EXISTS idx_analyses_user_id
+    ON public.analyses(user_id);
+
+    CREATE INDEX IF NOT EXISTS idx_usage_logs_user_id
+    ON public.usage_logs(user_id);
+
+    CREATE INDEX IF NOT EXISTS idx_stripe_events_user_id
</code_context>
<issue_to_address>
**issue:** Index creation for `usage_logs` and `stripe_events` is conditioned only on `analyses` existing.

The outer `IF EXISTS` only checks for `public.analyses`, but this block also creates indexes on `usage_logs` and `stripe_events`. In an environment where `analyses` exists but one of those tables does not, the migration will fail. Please add per-table existence checks or move each index creation into its respective table-specific section above.
</issue_to_address>

### Comment 4
<location path="supabase/migrations/20260519_complete_stabilization.sql" line_range="219-228" />
<code_context>
+  -- FIX #7: CREATE reset_user_quota RPC (Monthly reset helper)
+  -- ============================================================================
+
+  CREATE OR REPLACE FUNCTION public.reset_user_quota(p_user_id uuid)
+  RETURNS TABLE (
+    reset_success boolean,
+    new_quota integer,
+    reset_date timestamp with time zone
+  )
+  LANGUAGE plpgsql
+  SECURITY DEFINER
+  SET search_path = public
+  AS $$
+  DECLARE
+    v_now timestamp with time zone;
+  BEGIN
+    v_now := CURRENT_TIMESTAMP AT TIME ZONE 'UTC';
+
+    UPDATE public.users
</code_context>
<issue_to_address>
**nitpick (bug_risk):** `CURRENT_TIMESTAMP AT TIME ZONE 'UTC'` introduces a redundant cast and can subtly shift values.

`CURRENT_TIMESTAMP` is already `timestamptz`, so `AT TIME ZONE 'UTC'` converts it to `timestamp` and then it’s implicitly cast back to `timestamptz` for `v_now`, which can be misleading. Use plain `CURRENT_TIMESTAMP`/`now()` for a UTC `timestamptz`, or only use `AT TIME ZONE 'UTC'` when storing a `timestamp without time zone`.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment on lines +23 to +32
ALTER TABLE public.users
ADD COLUMN IF NOT EXISTS name text,
ADD COLUMN IF NOT EXISTS avatar_url text,
ADD COLUMN IF NOT EXISTS stripe_customer_id text UNIQUE,
ADD COLUMN IF NOT EXISTS stripe_subscription_id text;

-- Convert timestamps to timestamptz for timezone safety
ALTER TABLE public.users
ALTER COLUMN created_at TYPE timestamp with time zone USING created_at AT TIME ZONE 'UTC',
ALTER COLUMN updated_at TYPE timestamp with time zone USING updated_at AT TIME ZONE 'UTC',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Blindly applying AT TIME ZONE 'UTC' assumes current columns are timestamp without time zone.

If any of these columns (created_at, updated_at, last_reset_date) are already timestamptz in some environments, ... USING col AT TIME ZONE 'UTC' will incorrectly shift stored times. Consider either conditioning the migration on the existing data type (e.g. only when data_type = 'timestamp without time zone') or using a USING expression that behaves correctly for both timestamp and timestamptz.

Comment on lines +169 to +170
CREATE INDEX IF NOT EXISTS idx_usage_logs_user_id
ON public.usage_logs(user_id);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Index creation for usage_logs and stripe_events is conditioned only on analyses existing.

The outer IF EXISTS only checks for public.analyses, but this block also creates indexes on usage_logs and stripe_events. In an environment where analyses exists but one of those tables does not, the migration will fail. Please add per-table existence checks or move each index creation into its respective table-specific section above.

Comment on lines +219 to +228
CREATE OR REPLACE FUNCTION public.reset_user_quota(p_user_id uuid)
RETURNS TABLE (
reset_success boolean,
new_quota integer,
reset_date timestamp with time zone
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (bug_risk): CURRENT_TIMESTAMP AT TIME ZONE 'UTC' introduces a redundant cast and can subtly shift values.

CURRENT_TIMESTAMP is already timestamptz, so AT TIME ZONE 'UTC' converts it to timestamp and then it’s implicitly cast back to timestamptz for v_now, which can be misleading. Use plain CURRENT_TIMESTAMP/now() for a UTC timestamptz, or only use AT TIME ZONE 'UTC' when storing a timestamp without time zone.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/migrations/20260519_complete_stabilization.sql`:
- Around line 54-55: The migration adds model_used to table public.analyses with
a DEFAULT of 'anthropic/claude-3.5-haiku' but then immediately changes the
default to 'anthropic/claude-haiku-4.5'; update the migration so the initial ADD
COLUMN for model_used does not set a conflicting DEFAULT (remove the DEFAULT
clause from the ALTER TABLE ... ADD COLUMN IF NOT EXISTS model_used VARCHAR(255)
statement) or else set the ADD COLUMN default to the intended final value
('anthropic/claude-haiku-4.5') so only one default is applied; ensure the later
ALTER COLUMN/default change for model_used is consistent with this choice.
- Around line 82-84: The migration adds a UNIQUE constraint with ALTER TABLE ...
ADD CONSTRAINT unique_user_video which will fail if that constraint already
exists; wrap the ADD CONSTRAINT in a guard that checks pg_constraint (e.g.
SELECT 1 FROM pg_constraint WHERE conname = 'unique_user_video') and only runs
the ALTER TABLE when it does not exist, so the migration becomes idempotent
while still creating the unique_user_video constraint on public.analyses when
absent.
- Around line 35-37: The migration adds a non-idempotent constraint
(check_analyses_used_non_negative on table public.users) which will fail if
re-run; update the migration to guard the ALTER TABLE ... ADD CONSTRAINT by
first checking existence (e.g., query pg_constraint/pg_class/pg_namespace for
conname = 'check_analyses_used_non_negative') and only run ADD CONSTRAINT when
it does not exist, or alternatively run ALTER TABLE public.users DROP CONSTRAINT
IF EXISTS check_analyses_used_non_negative; then run ADD CONSTRAINT
check_analyses_used_non_negative CHECK (analyses_used >= 0) so the migration
becomes idempotent.
- Line 71: The ALTER statement currently uses COALESCE(published_at, NOW())
which turns intentional NULLs into the current timestamp; update the USING
expression for ALTER COLUMN published_at to preserve NULLs instead of
substituting NOW(). Replace COALESCE(published_at, NOW()) AT TIME ZONE 'UTC'
with an expression that returns NULL when published_at IS NULL (e.g., USING
(CASE WHEN published_at IS NULL THEN NULL ELSE published_at AT TIME ZONE 'UTC'
END) or simply USING (published_at AT TIME ZONE 'UTC') ) so unpublished rows
remain NULL.
- Around line 159-173: The index creation for idx_usage_logs_user_id and
idx_stripe_events_user_id is incorrectly placed inside the IF block that only
checks for the analyses table; remove those two CREATE INDEX statements from the
analyses block and add them into their respective table-existence blocks: add
CREATE INDEX IF NOT EXISTS idx_usage_logs_user_id ON public.usage_logs(user_id)
inside the usage_logs IF block, and CREATE INDEX IF NOT EXISTS
idx_stripe_events_user_id ON public.stripe_events(user_id) inside the
stripe_events IF block, ensuring each index is only created when its table
exists.
- Around line 234-244: The UPDATE against public.users using p_user_id can
affect zero rows but the function always returns true; modify the logic after
the UPDATE to check whether any rows were updated (use FOUND or GET DIAGNOSTICS
ROW_COUNT) and return a failure result when none were affected. Concretely,
after the UPDATE on public.users (and before the RETURN QUERY that currently
emits reset_success = true), test IF NOT FOUND (or if row_count = 0) then RETURN
QUERY SELECT false AS reset_success, NULL::integer AS new_quota, v_now AS
reset_date (or appropriate values), else return the existing success tuple;
reference p_user_id, the UPDATE on public.users, and the current RETURN QUERY
when implementing the conditional.
- Around line 203-210: The UPDATE currently does nothing if p_user_id doesn't
match any row and the function silently returns no rows; modify the function
containing the UPDATE (the block using p_user_id, p_increment, v_new_quota,
v_tier) to detect when no row was updated by checking SQL%ROWCOUNT (or
equivalent) immediately after the UPDATE and, if it is 0, raise an exception
(RAISE EXCEPTION 'user not found: %', p_user_id) or otherwise return a clearly
distinguishable result instead of returning an empty result set; ensure the
subsequent RETURN QUERY SELECT v_new_quota, v_tier only runs when a row was
updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f736af3a-e021-4903-b49f-3a40d97a8897

📥 Commits

Reviewing files that changed from the base of the PR and between 0b6536e and c8db7d5.

📒 Files selected for processing (1)
  • supabase/migrations/20260519_complete_stabilization.sql

Comment on lines +35 to +37
-- Add quota underflow prevention check constraint
ALTER TABLE public.users
ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-idempotent constraint addition will fail on re-run.

ADD CONSTRAINT does not support IF NOT EXISTS in PostgreSQL. If this migration is re-applied or the constraint already exists, it will error. Wrap in an existence check or drop first.

Proposed fix: Guard with existence check
     -- Add quota underflow prevention check constraint
-    ALTER TABLE public.users
-    ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
+    IF NOT EXISTS (
+      SELECT 1 FROM information_schema.table_constraints
+      WHERE table_schema = 'public' AND table_name = 'users'
+        AND constraint_name = 'check_analyses_used_non_negative'
+    ) THEN
+      ALTER TABLE public.users
+      ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
+    END IF;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Add quota underflow prevention check constraint
ALTER TABLE public.users
ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
-- Add quota underflow prevention check constraint
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE table_schema = 'public' AND table_name = 'users'
AND constraint_name = 'check_analyses_used_non_negative'
) THEN
ALTER TABLE public.users
ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
END IF;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260519_complete_stabilization.sql` around lines 35 -
37, The migration adds a non-idempotent constraint
(check_analyses_used_non_negative on table public.users) which will fail if
re-run; update the migration to guard the ALTER TABLE ... ADD CONSTRAINT by
first checking existence (e.g., query pg_constraint/pg_class/pg_namespace for
conname = 'check_analyses_used_non_negative') and only run ADD CONSTRAINT when
it does not exist, or alternatively run ALTER TABLE public.users DROP CONSTRAINT
IF EXISTS check_analyses_used_non_negative; then run ADD CONSTRAINT
check_analyses_used_non_negative CHECK (analyses_used >= 0) so the migration
becomes idempotent.

Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql Outdated
Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql Outdated
Comment thread supabase/migrations/20260519_complete_stabilization.sql Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="supabase/migrations/20260519_complete_stabilization.sql">

<violation number="1" location="supabase/migrations/20260519_complete_stabilization.sql:16">
P0: The DO block and nested function bodies reuse `$$` delimiters, which breaks SQL parsing and can make the migration fail before applying changes.</violation>

<violation number="2" location="supabase/migrations/20260519_complete_stabilization.sql:37">
P2: Guard this constraint creation with an existence check; this migration will fail if `check_analyses_used_non_negative` already exists.</violation>

<violation number="3" location="supabase/migrations/20260519_complete_stabilization.sql:71">
P2: Converting `published_at` with `COALESCE(..., NOW())` mutates NULL values and can incorrectly stamp unpublished records with a publish time.</violation>

<violation number="4" location="supabase/migrations/20260519_complete_stabilization.sql:84">
P2: Make this UNIQUE constraint addition idempotent; adding `unique_user_video` unconditionally can fail and stop the migration when it already exists.</violation>

<violation number="5" location="supabase/migrations/20260519_complete_stabilization.sql:169">
P1: Index creation for `usage_logs`/`stripe_events` is not existence-guarded, so this migration can fail on partial schemas.</violation>

<violation number="6" location="supabase/migrations/20260519_complete_stabilization.sql:197">
P1: `SECURITY DEFINER` quota RPCs update rows by arbitrary `p_user_id` without ownership checks, creating a privilege-escalation path.</violation>

<violation number="7" location="supabase/migrations/20260519_complete_stabilization.sql:242">
P2: Do not unconditionally return `reset_success = true`; when no user row matches `p_user_id`, this reports a successful reset that never happened.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql Outdated
Comment thread supabase/migrations/20260519_complete_stabilization.sql Outdated
Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql
Comment thread supabase/migrations/20260519_complete_stabilization.sql Outdated
web-flow and others added 2 commits May 19, 2026 23:25
- Creates root-level package.json with workspace scripts (type-check, lint, test, build, dev)
- Routes all verification gates through pnpm -r to run across all workspaces
- Resolves 'ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND' error in CI/CD pipeline
- Enables GitHub Actions to discover and run tests correctly

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
…ility)

- Regenerate lockfile to match Next.js 16.2.6 and all dependencies
- Remove unstable_after import (not available in Next.js 16)
- Replace after() callback with void promise pattern for background tasks
- Replace crypto.timingSafeEqual with custom Edge Runtime-compatible version
- Move database insert to synchronous path before streaming response

All pre-commit gates pass:
✅ pnpm build
✅ pnpm type-check
✅ pnpm lint

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/app/api/analyses/route.ts">

<violation number="1" location="web/app/api/analyses/route.ts:466">
P2: The new synchronous insert treats unique-key conflicts as a 500. With `UNIQUE(user_id, video_id)`, concurrent requests for the same video can now fail as internal errors instead of a deterministic "already in progress" response.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread web/app/api/analyses/route.ts Outdated
Comment on lines +466 to +469
return NextResponse.json(
{ error: 'Database error: Could not create analysis record' },
{ status: 500 }
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The new synchronous insert treats unique-key conflicts as a 500. With UNIQUE(user_id, video_id), concurrent requests for the same video can now fail as internal errors instead of a deterministic "already in progress" response.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/api/analyses/route.ts, line 466:

<comment>The new synchronous insert treats unique-key conflicts as a 500. With `UNIQUE(user_id, video_id)`, concurrent requests for the same video can now fail as internal errors instead of a deterministic "already in progress" response.</comment>

<file context>
@@ -435,6 +435,40 @@ export async function POST(request: NextRequest) {
+      );
+    } catch (dbErr) {
+      console.error('[analyses] Failed to create initial record', { analysisId, error: String(dbErr) });
+      return NextResponse.json(
+        { error: 'Database error: Could not create analysis record' },
+        { status: 500 }
</file context>
Suggested change
return NextResponse.json(
{ error: 'Database error: Could not create analysis record' },
{ status: 500 }
);
const isUniqueConflict = typeof dbErr === 'object' && dbErr !== null && 'code' in dbErr && (dbErr as { code?: string }).code === '23505';
return NextResponse.json(
{ error: isUniqueConflict ? 'Analysis already in progress for this video' : 'Database error: Could not create analysis record' },
{ status: isUniqueConflict ? 409 : 500 }
);

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/app/api/analyses/route.ts (1)

495-555: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add quota increment after successful analysis creation.

The analyses_used counter is never incremented after an analysis completes. While the increment_user_quota() RPC exists in migrations, it's never called in the flow. Users with free tier limits (3 analyses/month) can run unlimited analyses because the quota counter never increases.

After the markdown update succeeds in the background task (around line 530), call the increment_user_quota() RPC to increment the user's analyses_used counter. This prevents quota bypass and ensures fair billing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/api/analyses/route.ts` around lines 495 - 555, The background task
that collects markdown and updates the analyses row (inside the async void IIFE
that reads from processorStream and uses parseSSELine and trackDatabaseQuery)
must call the Postgres RPC increment_user_quota after the markdown DB update
succeeds and before publishing the validation task; add a
supabase.rpc('increment_user_quota', { user_id: userId || 'anonymous' })
invocation (or similar RPC call) right after the successful trackDatabaseQuery
update, await it, and handle/log any RPC errors
(Sentry.captureException/addBreadcrumb) so quota increments won't silently fail;
ensure this runs only on successful update and does not block the overall
background flow if it errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@supabase/migrations/20260519_rls_lockdown.sql`:
- Around line 105-108: The users UPDATE RLS policy incorrectly references NEW.id
in the WITH CHECK expression (NEW is invalid in RLS expressions); change that
reference to the column name directly (use id) so the condition reads
auth.role() = 'authenticated' AND auth.uid() = id (i.e., remove the NEW. prefix
in the WITH CHECK of the users UPDATE policy).
- Around line 47-50: The RLS policies use the invalid pseudo-record NEW; update
the WITH CHECK expressions to reference columns directly (remove the NEW.
prefix). Specifically, in the analyses_update_own policy replace auth.uid() =
NEW.user_id with auth.uid() = user_id, and in the users_update policy replace
auth.uid() = NEW.id with auth.uid() = id so the policy expressions reference the
proposed new column values correctly.

In `@supabase/migrations/20260519220000_complete_stabilization.sql`:
- Around line 226-253: The function reset_user_quota currently returns
reset_success = true unconditionally; modify it to check the UPDATE row count
(use GET DIAGNOSTICS v_affected = ROW_COUNT; or check FOUND) after the UPDATE
and set reset_success accordingly. Declare an integer variable (e.g.,
v_affected) alongside v_now, and if v_affected = 0 RETURN QUERY a row with
reset_success = false (and new_quota/reset_date as NULL or sensible defaults);
otherwise RETURN QUERY the existing success row (true, 0, v_now). Ensure you
update the RETURN QUERY logic in reset_user_quota to use v_affected to decide
the returned values.
- Around line 35-37: The CHECK constraint addition is not idempotent: update the
ALTER TABLE block that adds check_analyses_used_non_negative on public.users to
guard against duplicates by first dropping the constraint if it exists (use DROP
CONSTRAINT IF EXISTS check_analyses_used_non_negative on table public.users) and
then re-create it with ADD CONSTRAINT check_analyses_used_non_negative CHECK
(analyses_used >= 0); this mirrors the pattern used for other constraints in the
migration and ensures rerunning the migration won’t fail.

In `@web/middleware.ts`:
- Around line 6-13: The function timingSafeStringEqual leaks length via an early
return; remove the early-length check in timingSafeStringEqual and make the
comparison run a fixed number of operations by iterating up to the maximum of
a.length and b.length, using charCodeAt with a default 0 for out-of-range
indices, accumulate mismatches with bitwise ops (and also incorporate the length
difference into the accumulator, e.g., by OR-ing a.length ^ b.length) and
finally return whether the accumulator is zero so the function executes the same
steps regardless of input lengths.

---

Outside diff comments:
In `@web/app/api/analyses/route.ts`:
- Around line 495-555: The background task that collects markdown and updates
the analyses row (inside the async void IIFE that reads from processorStream and
uses parseSSELine and trackDatabaseQuery) must call the Postgres RPC
increment_user_quota after the markdown DB update succeeds and before publishing
the validation task; add a supabase.rpc('increment_user_quota', { user_id:
userId || 'anonymous' }) invocation (or similar RPC call) right after the
successful trackDatabaseQuery update, await it, and handle/log any RPC errors
(Sentry.captureException/addBreadcrumb) so quota increments won't silently fail;
ensure this runs only on successful update and does not block the overall
background flow if it errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 28eab1f9-4218-463a-92e0-9f90454e491c

📥 Commits

Reviewing files that changed from the base of the PR and between c8db7d5 and beb94b9.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • package.json
  • supabase/migrations/20260519220000_complete_stabilization.sql
  • supabase/migrations/20260519_rls_lockdown.sql
  • web/app/api/analyses/route.ts
  • web/lib/qstash-client.ts
  • web/middleware.ts

Comment thread supabase/migrations/20260519_rls_lockdown.sql Outdated
Comment thread supabase/migrations/20260519_rls_lockdown.sql Outdated
Comment on lines +35 to +37
-- Add quota underflow prevention check constraint
ALTER TABLE public.users
ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CHECK constraint addition is not idempotent.

ADD CONSTRAINT fails if the constraint already exists. Unlike foreign keys elsewhere in this migration (which use DROP CONSTRAINT IF EXISTS first), this CHECK constraint has no guard. Re-running the migration will error.

Proposed fix: add conditional guard
     -- Add quota underflow prevention check constraint
-    ALTER TABLE public.users
-    ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
+    IF NOT EXISTS (
+      SELECT 1 FROM information_schema.table_constraints 
+      WHERE constraint_name = 'check_analyses_used_non_negative' 
+        AND table_schema = 'public' AND table_name = 'users'
+    ) THEN
+      ALTER TABLE public.users
+      ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
+    END IF;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Add quota underflow prevention check constraint
ALTER TABLE public.users
ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
-- Add quota underflow prevention check constraint
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'check_analyses_used_non_negative'
AND table_schema = 'public' AND table_name = 'users'
) THEN
ALTER TABLE public.users
ADD CONSTRAINT check_analyses_used_non_negative CHECK (analyses_used >= 0);
END IF;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260519220000_complete_stabilization.sql` around lines
35 - 37, The CHECK constraint addition is not idempotent: update the ALTER TABLE
block that adds check_analyses_used_non_negative on public.users to guard
against duplicates by first dropping the constraint if it exists (use DROP
CONSTRAINT IF EXISTS check_analyses_used_non_negative on table public.users) and
then re-create it with ADD CONSTRAINT check_analyses_used_non_negative CHECK
(analyses_used >= 0); this mirrors the pattern used for other constraints in the
migration and ensures rerunning the migration won’t fail.

Comment on lines +226 to +253
CREATE OR REPLACE FUNCTION public.reset_user_quota(p_user_id uuid)
RETURNS TABLE (
reset_success boolean,
new_quota integer,
reset_date timestamp with time zone
)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
v_now timestamp with time zone;
BEGIN
v_now := CURRENT_TIMESTAMP AT TIME ZONE 'UTC';

UPDATE public.users
SET
analyses_used = 0,
last_reset_date = v_now
WHERE id = p_user_id;

RETURN QUERY
SELECT
true as reset_success,
0 as new_quota,
v_now as reset_date;
END;
$$;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

reset_user_quota returns success even when no user is updated.

If p_user_id doesn't exist, the UPDATE affects zero rows, but the function still returns reset_success = true. This masks errors and could lead to silent failures in quota reset workflows.

Proposed fix: check affected row count
 CREATE OR REPLACE FUNCTION public.reset_user_quota(p_user_id uuid)
 RETURNS TABLE (
   reset_success boolean,
   new_quota integer,
   reset_date timestamp with time zone
 )
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path = public
 AS $$
 DECLARE
   v_now timestamp with time zone;
+  v_rows_affected integer;
 BEGIN
   v_now := CURRENT_TIMESTAMP AT TIME ZONE 'UTC';

   UPDATE public.users
   SET
     analyses_used = 0,
     last_reset_date = v_now
   WHERE id = p_user_id;

+  GET DIAGNOSTICS v_rows_affected = ROW_COUNT;
+
   RETURN QUERY
   SELECT
-    true as reset_success,
+    (v_rows_affected > 0) as reset_success,
     0 as new_quota,
     v_now as reset_date;
 END;
 $$;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260519220000_complete_stabilization.sql` around lines
226 - 253, The function reset_user_quota currently returns reset_success = true
unconditionally; modify it to check the UPDATE row count (use GET DIAGNOSTICS
v_affected = ROW_COUNT; or check FOUND) after the UPDATE and set reset_success
accordingly. Declare an integer variable (e.g., v_affected) alongside v_now, and
if v_affected = 0 RETURN QUERY a row with reset_success = false (and
new_quota/reset_date as NULL or sensible defaults); otherwise RETURN QUERY the
existing success row (true, 0, v_now). Ensure you update the RETURN QUERY logic
in reset_user_quota to use v_affected to decide the returned values.

Comment thread web/middleware.ts
Comment on lines +6 to +13
function timingSafeStringEqual(a: string, b: string): boolean {
if (a.length !== b.length) return false;
let mismatch = 0;
for (let i = 0; i < a.length; i++) {
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return mismatch === 0;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚖️ Poor tradeoff

Timing leak in length comparison defeats constant-time security.

The early return on line 7 when lengths differ leaks timing information about the secret's length. An attacker can probe different input lengths and measure response times to determine the expected token length, which significantly reduces the search space for brute-force attacks.

A proper constant-time comparison must always perform the same number of operations regardless of input. Even though this is dev-only, it degrades the security guarantee that timing-safe comparison should provide.

🔒 Proposed fix for constant-time comparison
-function timingSafeStringEqual(a: string, b: string): boolean {
-  if (a.length !== b.length) return false;
-  let mismatch = 0;
-  for (let i = 0; i < a.length; i++) {
-    mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
-  }
-  return mismatch === 0;
-}
+function timingSafeStringEqual(a: string, b: string): boolean {
+  // Include length difference in mismatch to avoid timing leak
+  const maxLen = Math.max(a.length, b.length);
+  let mismatch = a.length ^ b.length;
+  
+  for (let i = 0; i < maxLen; i++) {
+    const aCode = i < a.length ? a.charCodeAt(i) : 0;
+    const bCode = i < b.length ? b.charCodeAt(i) : 0;
+    mismatch |= aCode ^ bCode;
+  }
+  return mismatch === 0;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function timingSafeStringEqual(a: string, b: string): boolean {
if (a.length !== b.length) return false;
let mismatch = 0;
for (let i = 0; i < a.length; i++) {
mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return mismatch === 0;
}
function timingSafeStringEqual(a: string, b: string): boolean {
// Include length difference in mismatch to avoid timing leak
const maxLen = Math.max(a.length, b.length);
let mismatch = a.length ^ b.length;
for (let i = 0; i < maxLen; i++) {
const aCode = i < a.length ? a.charCodeAt(i) : 0;
const bCode = i < b.length ? b.charCodeAt(i) : 0;
mismatch |= aCode ^ bCode;
}
return mismatch === 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/middleware.ts` around lines 6 - 13, The function timingSafeStringEqual
leaks length via an early return; remove the early-length check in
timingSafeStringEqual and make the comparison run a fixed number of operations
by iterating up to the maximum of a.length and b.length, using charCodeAt with a
default 0 for out-of-range indices, accumulate mismatches with bitwise ops (and
also incorporate the length difference into the accumulator, e.g., by OR-ing
a.length ^ b.length) and finally return whether the accumulator is zero so the
function executes the same steps regardless of input lengths.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="supabase/migrations/20260519_rls_lockdown.sql">

<violation number="1" location="supabase/migrations/20260519_rls_lockdown.sql:38">
P2: Policy creation was moved outside table-existence guards, so this migration can fail hard when any target table is absent.</violation>
</file>

<file name="supabase/migrations/20260519220000_complete_stabilization.sql">

<violation number="1" location="supabase/migrations/20260519220000_complete_stabilization.sql:197">
P1: These SECURITY DEFINER RPCs lack caller authorization and execute-privilege hardening, so they can be used to update arbitrary users’ quota rows if callable by non-admin roles.</violation>

<violation number="2" location="supabase/migrations/20260519220000_complete_stabilization.sql:214">
P2: Validate `p_increment` as positive; currently negative values can decrease `analyses_used` and undermine quota enforcement.</violation>

<violation number="3" location="supabase/migrations/20260519220000_complete_stabilization.sql:239">
P2: This UTC conversion is incorrect for `timestamptz`; it can introduce timezone shifts when storing `last_reset_date`.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

-- This RPC atomically increments the user quota counter
-- Used by rate-limit enforcement to prevent race conditions

CREATE OR REPLACE FUNCTION public.increment_user_quota(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: These SECURITY DEFINER RPCs lack caller authorization and execute-privilege hardening, so they can be used to update arbitrary users’ quota rows if callable by non-admin roles.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260519220000_complete_stabilization.sql, line 197:

<comment>These SECURITY DEFINER RPCs lack caller authorization and execute-privilege hardening, so they can be used to update arbitrary users’ quota rows if callable by non-admin roles.</comment>

<file context>
@@ -173,81 +176,81 @@ DO $$ BEGIN
+-- This RPC atomically increments the user quota counter
+-- Used by rate-limit enforcement to prevent race conditions
+
+CREATE OR REPLACE FUNCTION public.increment_user_quota(
+  p_user_id uuid,
+  p_increment integer DEFAULT 1
</file context>

-- ============================================================================
-- PUBLIC.ANALYSES TABLE: Restrict to authenticated users, enforce user_id match
-- ============================================================================
CREATE POLICY "analyses_insert_own"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Policy creation was moved outside table-existence guards, so this migration can fail hard when any target table is absent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260519_rls_lockdown.sql, line 38:

<comment>Policy creation was moved outside table-existence guards, so this migration can fail hard when any target table is absent.</comment>

<file context>
@@ -2,154 +2,129 @@
+-- ============================================================================
+-- PUBLIC.ANALYSES TABLE: Restrict to authenticated users, enforce user_id match
+-- ============================================================================
+CREATE POLICY "analyses_insert_own"
+  ON public.analyses
+  FOR INSERT
</file context>

DECLARE
v_now timestamp with time zone;
BEGIN
v_now := CURRENT_TIMESTAMP AT TIME ZONE 'UTC';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This UTC conversion is incorrect for timestamptz; it can introduce timezone shifts when storing last_reset_date.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260519220000_complete_stabilization.sql, line 239:

<comment>This UTC conversion is incorrect for `timestamptz`; it can introduce timezone shifts when storing `last_reset_date`.</comment>

<file context>
@@ -173,81 +176,81 @@ DO $$ BEGIN
+DECLARE
+  v_now timestamp with time zone;
+BEGIN
+  v_now := CURRENT_TIMESTAMP AT TIME ZONE 'UTC';
+
+  UPDATE public.users
</file context>
Suggested change
v_now := CURRENT_TIMESTAMP AT TIME ZONE 'UTC';
v_now := CURRENT_TIMESTAMP;

v_tier text;
BEGIN
UPDATE public.users
SET analyses_used = analyses_used + p_increment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Validate p_increment as positive; currently negative values can decrease analyses_used and undermine quota enforcement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At supabase/migrations/20260519220000_complete_stabilization.sql, line 214:

<comment>Validate `p_increment` as positive; currently negative values can decrease `analyses_used` and undermine quota enforcement.</comment>

<file context>
@@ -173,81 +176,81 @@ DO $$ BEGIN
+  v_tier text;
+BEGIN
+  UPDATE public.users
+  SET analyses_used = analyses_used + p_increment
+  WHERE id = p_user_id
+  RETURNING analyses_used, "tier" INTO v_new_quota, v_tier;
</file context>
Suggested change
SET analyses_used = analyses_used + p_increment
SET analyses_used = analyses_used + GREATEST(p_increment, 0)

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/staging-deploy.yml">

<violation number="1" location=".github/workflows/staging-deploy.yml:70">
P1: Avoid using a floating GitHub Action ref (`@master`) for deployments; pin to an immutable version (or commit SHA) to keep CI reproducible and reduce supply-chain risk.</violation>
</file>

<file name=".github/workflows/ci-cd.yml">

<violation number="1" location=".github/workflows/ci-cd.yml:324">
P1: Avoid using a floating `@master` ref for GitHub Actions in deployment. Pin to a fixed version (or commit SHA) to prevent supply-chain drift and secret exposure risk.</violation>
</file>

<file name="docs/ops/GITHUB_SECRETS_SETUP.md">

<violation number="1" location="docs/ops/GITHUB_SECRETS_SETUP.md:44">
P2: `vercel whoami` returns the username, not the Organization ID (`orgId`). Using it for `VERCEL_ORG_ID` will cause Vercel CLI authentication to fail in GitHub Actions.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic


- name: Deploy to Vercel (Staging)
uses: vercel/action@v5.1.0
uses: amondnet/vercel-action@master

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Avoid using a floating GitHub Action ref (@master) for deployments; pin to an immutable version (or commit SHA) to keep CI reproducible and reduce supply-chain risk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/staging-deploy.yml, line 70:

<comment>Avoid using a floating GitHub Action ref (`@master`) for deployments; pin to an immutable version (or commit SHA) to keep CI reproducible and reduce supply-chain risk.</comment>

<file context>
@@ -67,12 +67,11 @@ jobs:
 
       - name: Deploy to Vercel (Staging)
-        uses: vercel/action@v5.1.0
+        uses: amondnet/vercel-action@master
         with:
           vercel-token: ${{ secrets.VERCEL_TOKEN }}
</file context>


- name: Deploy to Vercel
uses: vercel/action@v5.1.0
uses: amondnet/vercel-action@master

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Avoid using a floating @master ref for GitHub Actions in deployment. Pin to a fixed version (or commit SHA) to prevent supply-chain drift and secret exposure risk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/ci-cd.yml, line 324:

<comment>Avoid using a floating `@master` ref for GitHub Actions in deployment. Pin to a fixed version (or commit SHA) to prevent supply-chain drift and secret exposure risk.</comment>

<file context>
@@ -321,12 +321,12 @@ jobs:
 
       - name: Deploy to Vercel
-        uses: vercel/action@v5.1.0
+        uses: amondnet/vercel-action@master
         with:
           vercel-token: ${{ secrets.VERCEL_TOKEN }}
</file context>


**Get VERCEL_ORG_ID:**
```bash
vercel whoami

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: vercel whoami returns the username, not the Organization ID (orgId). Using it for VERCEL_ORG_ID will cause Vercel CLI authentication to fail in GitHub Actions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/ops/GITHUB_SECRETS_SETUP.md, line 44:

<comment>`vercel whoami` returns the username, not the Organization ID (`orgId`). Using it for `VERCEL_ORG_ID` will cause Vercel CLI authentication to fail in GitHub Actions.</comment>

<file context>
@@ -0,0 +1,116 @@
+
+**Get VERCEL_ORG_ID:**
+```bash
+vercel whoami
+# Returns org ID
+```
</file context>

@github-actions

Copy link
Copy Markdown

❌ Linting failed. Please fix linting errors before merging.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@github-actions

Copy link
Copy Markdown

Pipeline Status: All checks passed. Ready to merge.

@TechHypeXP
TechHypeXP merged commit 872f92e into main May 20, 2026
31 of 32 checks passed
@TechHypeXP
TechHypeXP deleted the database/stabilization branch May 20, 2026 21:28
TechHypeXP added a commit that referenced this pull request Jul 31, 2026
* database: Add complete stabilization migration with all 7 audit fixes

This migration applies all critical schema fixes in a single transaction:
1. Auth linkage (users.id references auth.users)
2. Cascading deletes (ON DELETE CASCADE/SET NULL)
3. Missing columns (model_attempted, validation_report, validation_passed)
4. Timezone safety (timestamptz for all timestamps)
5. Concurrent safety (UNIQUE constraint on user_video)
6. Quota underflow prevention (CHECK constraint)
7. Performance indexes (cache lookup, vector, foreign keys)

Additionally creates two RPCs:
- increment_user_quota: Atomic quota enforcement
- reset_user_quota: Monthly quota reset helper

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* build: Add root package.json for pnpm monorepo configuration

- Creates root-level package.json with workspace scripts (type-check, lint, test, build, dev)
- Routes all verification gates through pnpm -r to run across all workspaces
- Resolves 'ERR_PNPM_NO_IMPORTER_MANIFEST_FOUND' error in CI/CD pipeline
- Enables GitHub Actions to discover and run tests correctly

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* build: sync pnpm-lock.yaml with package.json (Next.js 16.2.6 compatibility)

- Regenerate lockfile to match Next.js 16.2.6 and all dependencies
- Remove unstable_after import (not available in Next.js 16)
- Replace after() callback with void promise pattern for background tasks
- Replace crypto.timingSafeEqual with custom Edge Runtime-compatible version
- Move database insert to synchronous path before streaming response

All pre-commit gates pass:
✅ pnpm build
✅ pnpm type-check
✅ pnpm lint

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix(db): resolve migration version collision by timestamping to 20260519220000

* fix(rls): resolve RLS policy syntax - use direct column references instead of NEW

* chore(db): remove duplicate migration files from version control

* ci: fix pipeline issues - update Vercel action, add TypeScript deprecation ignore

* docs(ops): add GitHub Actions secrets configuration guide

* fix(build): reconcile lockfile with Next.js 16.2.6 manifest

* fix(security): implement sentry fail-silent guard and enhance middleware public route exclusions

* fix(sentry+cors): finalize robust instrumentation and inject preflight headers

* fix(env): resolve composite action context bug and strict TS env null checks

* fix(ci): unify monorepo root install and scoped build filter

* fix(ci): standardize monorepo workflows, bump pnpm to 11.1.3, and remove legacy pathing

* docs(history): add session handover report for 2026-05-20

* fix(web): migrate to proxy convention and optimize turbopack root

* crit(security): restore middleware auth gate and fix CI secret false positive

* feat(stabilization): Complete Tier 1-3 security and resilience hardening

TIER 1: FOUNDATION - RLS & Request-Scoped Auth
- Database: Apply RLS lockdown migration (20260520)
  • Enforce auth.uid() = user_id on analyses table
  • Enforce auth.uid() = id on users table
  • Immutable audit trail on usage_logs

- Supabase Client: Implement request-scoped factory
  • Add getSupabaseClientWithAuth() for JWT-bound clients
  • Extract user tokens from cookies for RLS enforcement
  • Maintain backward compatibility with getSupabaseClient()
  • Ensure multi-tenant data isolation per request

TIER 2: EXTERNAL RESILIENCE - Timeout & SSRF Hardening
- SSRF Validation (analyses/route.ts):
  • Replace endsWith() with exact hostname matching
  • Use Set-based allowlist against worker domain spoofing

- Embedding Timeout (embeddings.ts):
  • Add 5s AbortController timeout to OpenRouter calls
  • Clear timeout in finally block to prevent memory leaks
  • Retry with exponential backoff on timeout

- Webhook Verification Timeout (qstash-client.ts):
  • Wrap receiver.verify() in Promise.race with 5s timeout
  • Prevent signature verification from hanging webhook handlers

TIER 3: FUNCTIONAL LOGIC - Token Budget & Redis Resilience
- Token Math (services/openrouter.ts):
  • Implement dynamic max_tokens scaling per Law #2
  • Base: 4000 tokens + (transcript_length / 10)
  • Cap: 10000 tokens maximum
  • Prevents truncation on long transcripts, limits budget overruns

- Redis Circuit Breaker (redis.ts):
  • Add exponential backoff retry to executeRedisScript()
  • 3 retries at 100ms, 200ms, 400ms delays
  • Distinguish transient (timeout, connection) vs permanent (auth) errors
  • Mark Redis unavailable after retries exhausted, fallback to in-memory cache

All changes verified via:
  ✓ pnpm build (13.9s)
  ✓ pnpm type-check (0 errors)
  ✓ pnpm lint (0 violations)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix(ci): update Node.js version to 22 for pnpm 11.1.3 compatibility

* fix(ci): resolve tsconfig deprecation and fix monorepo pathing

* fix(ci): remove invalid tsconfig option and move to CLI flag

* fix(ci): enable recursive pnpm install for monorepo workspace linking

* fix(ci): add .npmrc to unify peer dependency resolution

* fix(ci): hard-code auto-install-peers to resolve lockfile mismatch

* fix(ci): use env var for pnpm peer settings and force cache refresh

* ci(config): remove branch restrictions to enable stabilization validation

* fix(ci): perform clean lockfile regeneration to clear config metadata

* fix(ci): remove invalid ignoreDeprecations flag to resolve TS5103

* fix(build): replace hardcoded turbopack root path and anchor distDir

* fix(ci): exclude node_modules from security scan

* fix(ci): inject supabase environment variables into build step

* fix(ci): inject dummy env fallbacks to satisfy static build validation

* fix(build): add missing openrouter key fallback for static build validation

---------

Co-authored-by: Kelly Bakri <noreply@github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
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.

2 participants