From 2f49284d73a447af4094fbe262b82f015b839669 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 11:23:21 -0500 Subject: [PATCH 01/11] feat: add gated free-tier cost pilot telemetry --- lib/costPilot.ts | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 lib/costPilot.ts diff --git a/lib/costPilot.ts b/lib/costPilot.ts new file mode 100644 index 0000000..a6cd738 --- /dev/null +++ b/lib/costPilot.ts @@ -0,0 +1,43 @@ +export type CostPilotSession = { + elapsedMs: number; + npmInstallMs: number | null; + npmInstallExitCode: number | null; + outcome: "success" | "failure"; +}; + +function createEventId(): string { + if (typeof globalThis.crypto?.randomUUID === "function") { + return globalThis.crypto.randomUUID(); + } + return Date.now().toString(36) + "-" + Math.random().toString(36).slice(2); +} + +/** + * Optional, fire-and-forget pilot telemetry. It sends durations and an outcome + * only; no source code, project identifiers, file paths, or error text. + */ +export function recordCostPilotSession(sample: CostPilotSession): void { + if ( + typeof window === "undefined" || + process.env.NEXT_PUBLIC_COST_PILOT_ENABLED !== "true" + ) { + return; + } + + const payload = { + eventId: createEventId(), + eventType: "editor_session", + elapsedMs: sample.elapsedMs, + npmInstallMs: sample.npmInstallMs, + npmInstallExitCode: sample.npmInstallExitCode, + outcome: sample.outcome, + }; + + void fetch("/api/cost-pilot/events", { + method: "POST", + credentials: "same-origin", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload), + keepalive: true, + }).catch(() => undefined); +} From 0e1bdd79e723134689669e272da0e6771f9ca7a6 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 11:23:28 -0500 Subject: [PATCH 02/11] feat: add authenticated cost pilot event sink --- app/api/cost-pilot/events/route.ts | 104 +++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 app/api/cost-pilot/events/route.ts diff --git a/app/api/cost-pilot/events/route.ts b/app/api/cost-pilot/events/route.ts new file mode 100644 index 0000000..17ac392 --- /dev/null +++ b/app/api/cost-pilot/events/route.ts @@ -0,0 +1,104 @@ +import { createHmac } from "node:crypto"; +import { NextResponse } from "next/server"; +import { eq } from "drizzle-orm"; +import { requireAuth } from "@/lib/auth"; +import { db } from "@/lib/db"; +import { users } from "@/drizzle/schema"; + +export const runtime = "nodejs"; + +const MAX_DURATION_MS = 60 * 60 * 1000; +const EVENT_ID_PATTERN = /^[a-zA-Z0-9-]{16,64}$/; + +function isDuration(value: unknown): value is number | null { + return value === null || ( + typeof value === "number" && + Number.isInteger(value) && + value >= 0 && + value <= MAX_DURATION_MS + ); +} + +export async function POST(request: Request) { + if (process.env.COST_PILOT_ENABLED !== "true") { + return new NextResponse(null, { status: 404 }); + } + + let userId: string; + try { + userId = await requireAuth(); + } catch { + return NextResponse.json({ error: "unauthorized" }, { status: 401 }); + } + + let body: unknown; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: "invalid_json" }, { status: 400 }); + } + + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "invalid_event" }, { status: 400 }); + } + + const event = body as Record; + const installExitCode = event.npmInstallExitCode; + if ( + typeof event.eventId !== "string" || + !EVENT_ID_PATTERN.test(event.eventId) || + event.eventType !== "editor_session" || + !isDuration(event.elapsedMs) || + event.elapsedMs === null || + !isDuration(event.npmInstallMs) || + !(installExitCode === null || ( + typeof installExitCode === "number" && + Number.isInteger(installExitCode) && + installExitCode >= 0 && + installExitCode <= 255 + )) || + (event.outcome !== "success" && event.outcome !== "failure") + ) { + return NextResponse.json({ error: "invalid_event" }, { status: 400 }); + } + + const clerkSecret = process.env.CLERK_SECRET_KEY; + if (!clerkSecret) { + return NextResponse.json({ error: "pilot_unavailable" }, { status: 503 }); + } + + try { + const [account] = await db + .select({ tier: users.tier }) + .from(users) + .where(eq(users.id, userId)) + .limit(1); + + if (!account || (account.tier ?? "free") !== "free") { + return new NextResponse(null, { status: 204 }); + } + + const occurredAt = new Date(); + const month = occurredAt.toISOString().slice(0, 7); + const actorMonthKey = createHmac("sha256", clerkSecret) + .update("peregrine-cost-pilot-v1:" + month + ":" + userId) + .digest("hex"); + + console.info("PEREGRINE_COST_PILOT_V1", JSON.stringify({ + eventId: event.eventId, + eventType: "editor_session", + actorMonthKey, + month, + occurredAt: occurredAt.toISOString(), + elapsedMs: event.elapsedMs, + npmInstallMs: event.npmInstallMs, + npmInstallExitCode: installExitCode, + outcome: event.outcome, + appRevision: process.env.VERCEL_GIT_COMMIT_SHA ?? null, + })); + + return NextResponse.json({ accepted: true }, { status: 202 }); + } catch { + return NextResponse.json({ error: "pilot_unavailable" }, { status: 503 }); + } +} From 3babc67afe8f37cba7aa1059f4b96a442a904974 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 11:23:34 -0500 Subject: [PATCH 03/11] docs: define free-tier cost pilot gate --- .../PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md diff --git a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md new file mode 100644 index 0000000..73f8aec --- /dev/null +++ b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md @@ -0,0 +1,83 @@ +# Peregrine Free-Tier Cost Pilot v0.1.0 + +**Status:** Draft protocol; not measured and not approved for production use +**Target:** less than USD 0.020 fully loaded cost per monthly active free IDE user +**Repository:** Sovereign-Development +**As of:** 2026-09-25 + +## Decision boundary + +The gate is the fully loaded monthly cost per free monthly active user (MAU). Also report marginal cost per free MAU so the scale curve is visible; marginal cost does not replace the gate. + +A free MAU is a distinct authenticated account whose current account tier is free and that starts at least one editor session during the UTC calendar month. The event endpoint derives a month-scoped HMAC of the Clerk user ID on the server. Raw user IDs, emails, project IDs, source files, paths, and error text are not emitted. + +## Calculation + +Fully loaded free-tier cost per MAU: + + (direct free-cohort variable spend + + allocated shared product operating cost + + WebContainer API license and usage fees) + / distinct free MAU + +Report the marginal variable-cost-per-MAU separately. Exclude internal labor from product COGS, but show it separately if useful. Include Vercel, Supabase, Cloudflare, AI provider usage, storage, network egress, email, monitoring, Stripe fees caused by free-tier activity, and every WebContainer charge. Use actual invoices or provider usage exports for the measurement period. + +Allocate shared costs using a documented usage driver. If no defensible driver exists, report a conservative upper bound that assigns the full shared product cost to the free cohort; do not silently omit it. Split one-time setup fees from recurring run-rate. + +## Pilot protocol + +- Run one 30-day UTC measurement window in an isolated, invite-only pilot environment. +- Aim for at least 30 distinct free MAU and 100 valid editor-session events. Below either minimum, label results preliminary; do not claim the target is verified. +- Include low, typical, and heavy usage patterns within the published free-tier limits. Record those limits with the result. +- Require complete cost inputs for every provider in scope and reconcile each reported amount to an invoice, usage export, or signed license quote. +- Report the mean fully loaded cost per free MAU, marginal cost per free MAU, p50/p90 editor-session duration, npm install success rate, and percentage of sessions with accepted telemetry. +- The gate passes only if the complete fully loaded average is below USD 0.020, no provider or license cost is missing, and the telemetry capture rate is at least 95%. Report tail usage and any outliers; the average does not replace limits against abuse or exceptional consumption. + +This pilot is an initial operating signal, not proof that costs remain below target at scale. Repeat at a materially larger cohort or validate provider pricing at the intended scale before widening the free tier. + +## Instrumentation + +The implementation in this branch emits one server log event per editor initialization when both pilot flags are enabled. It records elapsed time, npm install duration and exit code, outcome, app revision, UTC month, and a month-scoped HMAC actor key. The browser submits no code, file path, project identifier, email, or raw user ID. Only accounts currently marked free are logged. Event IDs support duplicate removal in analysis. + +The event sink is Vercel runtime logs. The event route is disabled unless both COST_PILOT_ENABLED and NEXT_PUBLIC_COST_PILOT_ENABLED are set to true. The code change does not enable either flag or deploy anything. Count accepted event IDs from the server logs and reconcile against pilot sessions; the capture-rate threshold is 95%. Export only the minimum fields for analysis and delete the exported row-level pilot data after 30 days, subject to the provider's available retention controls. + +This instrumentation does not estimate provider bills. Reconcile actual Vercel, Supabase, Cloudflare, AI, storage, egress, email, monitoring, Stripe, and WebContainer costs from provider-side billing evidence. Include the telemetry route and log retention in the measured cost. + +## License gate + +StackBlitz's current Commercial Usage page says a license is required for production use of the WebContainer API in a commercial, for-profit setting; prototypes and POCs are exempt. Its FAQ also mentions a nominal charge beyond 10,000 API requests per month, but does not provide a public price or make that threshold an explicit license exemption. Treat the commercial page as controlling until StackBlitz confirms otherwise in writing. + +Production and customer-facing pilot use remains blocked until written terms answer: the license fee and billing unit; definition of an API request and the 10,000/month threshold; whether this use case and free-tier users are covered; trial/POC boundaries; rate limits; key issuance and rotation; and support/SLA. Configure the licensed API key before WebContainer.boot in production. Do not treat browser-side execution as eliminating the vendor license cost. + +Official references: + +- https://webcontainers.io/enterprise +- https://developer.stackblitz.com/guides/user-guide/general-faqs +- https://developer.stackblitz.com/platform/api/webcontainer-api + +## Current evidence and blockers + +- No cost result is recorded. The pilot has not run. +- The connected Vercel account returned no teams, so the project, deployment environment, and runtime logs cannot be inspected or configured here. +- The Peregrine Supabase project returned INACTIVE. The database cannot currently serve as a live pilot dependency without restoration and verification. +- No Cloudflare account controls were exposed in this workspace. +- The production migration remains open: key rotation, Vercel repointing, and auth, Stripe webhook, database, and collaboration smoke tests remain unverified. +- No WebContainer API price or signed license terms are publicly available in the sources above. The commercial-use requirement is clear; the price and negotiated scope remain unresolved pending written vendor terms. + +## Evidence record + +Fill this table from provider receipts after the pilot. Do not estimate missing invoice amounts as zero. + +| Provider / cost bucket | Period | Amount (USD) | Evidence URL or receipt | Allocation method | Verified | +|---|---|---:|---|---|---| +| WebContainer license and usage | | | | Direct | | +| Vercel | | | | | | +| Supabase | | | | | | +| Cloudflare | | | | | | +| AI providers | | | | | | +| Storage, egress, email, monitoring | | | | | | +| Other direct product costs | | | | | | + +## Release decision + +Record the final sample size, event count, capture rate, free MAU, total cost, fully loaded cost per free MAU, marginal cost per free MAU, p50/p90 use, license state, missing evidence, reviewer, and decision. The status stays NOT VERIFIED until the cost and licensing gates pass with receipts. From 837a44cbcdaed99c95b8c25407e5b991d387bded Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 11:24:05 -0500 Subject: [PATCH 04/11] feat: require licensed WebContainers in production and measure sessions --- lib/webcontainer.ts | 64 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/lib/webcontainer.ts b/lib/webcontainer.ts index a650c8d..b6e0ca7 100644 --- a/lib/webcontainer.ts +++ b/lib/webcontainer.ts @@ -1,27 +1,65 @@ -import { WebContainer } from '@webcontainer/api'; +import { configureAPIKey, WebContainer } from "@webcontainer/api"; +import { recordCostPilotSession } from "./costPilot"; let containerPromise: Promise | null = null; +let apiKeyConfigured = false; + +function configureCommercialLicense(): void { + if (apiKeyConfigured) return; + + const apiKey = process.env.NEXT_PUBLIC_WEB_CONTAINER_API_KEY; + if (apiKey) { + configureAPIKey(apiKey); + } else if (process.env.NODE_ENV === "production") { + throw new Error( + "A licensed WebContainer API key is required for production. Configure NEXT_PUBLIC_WEB_CONTAINER_API_KEY." + ); + } + + apiKeyConfigured = true; +} export async function getWebContainer(): Promise { if (!containerPromise) { + configureCommercialLicense(); containerPromise = WebContainer.boot({ - coep: 'credentialless', - workdirName: 'project', + coep: "credentialless", + workdirName: "project", }); } return containerPromise; } export async function bootProject(files: Record): Promise { - const container = await getWebContainer(); - await container.mount( - Object.fromEntries( - Object.entries(files).map(([path, content]) => [path, { file: { contents: content } }]) - ) - ); - if (files['package.json']) { - const process = await container.spawn('npm', ['install']); - await process.exit; + const startedAt = performance.now(); + let npmInstallMs: number | null = null; + let npmInstallExitCode: number | null = null; + let outcome: "success" | "failure" = "failure"; + + try { + const container = await getWebContainer(); + await container.mount( + Object.fromEntries( + Object.entries(files).map(([path, content]) => [path, { file: { contents: content } }]) + ) + ); + if (files["package.json"]) { + const installStartedAt = performance.now(); + try { + const process = await container.spawn("npm", ["install"]); + npmInstallExitCode = await process.exit; + } finally { + npmInstallMs = Math.round(performance.now() - installStartedAt); + } + } + outcome = "success"; + return container; + } finally { + recordCostPilotSession({ + elapsedMs: Math.round(performance.now() - startedAt), + npmInstallMs, + npmInstallExitCode, + outcome, + }); } - return container; } From 64c9cdc41a2f1506a52fe33bf4c32536e3f8b363 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 11:24:12 -0500 Subject: [PATCH 05/11] chore: document licensed WebContainers and pilot flags --- .env.example | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index c795c4e..8bd8792 100644 --- a/.env.example +++ b/.env.example @@ -15,8 +15,12 @@ STRIPE_WEBHOOK_SECRET=whsec_... NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_... CLERK_SECRET_KEY=sk_test_... -# WebContainers Commercial License -WEB_CONTAINER_API_KEY= +# WebContainers commercial API key; required for production builds +NEXT_PUBLIC_WEB_CONTAINER_API_KEY= + +# Optional, disabled-by-default pilot telemetry +COST_PILOT_ENABLED=false +NEXT_PUBLIC_COST_PILOT_ENABLED=false # Optional fallback AI (Codeium) CODEIUM_API_KEY= From 576915d4c85f8c4b0a9c87569beb316c4e8e25f9 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 11:26:12 -0500 Subject: [PATCH 06/11] docs: document database prerequisite for cost pilot --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 8bd8792..0ea9515 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,6 @@ +# Supabase Postgres (Drizzle) +DATABASE_URL= + # Turso (libSQL) TURSO_DB_URL=libsql://peregrine-prod-org.turso.io TURSO_AUTH_TOKEN= From 686b925c729e68368e167a43f31dc840f668ff89 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 12:14:12 -0500 Subject: [PATCH 07/11] docs: record Cloudflare pilot access blocker --- docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md index 73f8aec..6bc1a1a 100644 --- a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md +++ b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md @@ -60,7 +60,7 @@ Official references: - No cost result is recorded. The pilot has not run. - The connected Vercel account returned no teams, so the project, deployment environment, and runtime logs cannot be inspected or configured here. - The Peregrine Supabase project returned INACTIVE. The database cannot currently serve as a live pilot dependency without restoration and verification. -- No Cloudflare account controls were exposed in this workspace. +- The Cloudflare connector is available, but read-only Workers, Pages, and billing inventory calls fail with API authentication error 10000; no Cloudflare usage or billing evidence was obtained. - The production migration remains open: key rotation, Vercel repointing, and auth, Stripe webhook, database, and collaboration smoke tests remain unverified. - No WebContainer API price or signed license terms are publicly available in the sources above. The commercial-use requirement is clear; the price and negotiated scope remain unresolved pending written vendor terms. From bb173093e4b3b7525ba586ed46922ea0ac84e79c Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 12:28:54 -0500 Subject: [PATCH 08/11] fix: report npm install failures in pilot telemetry --- lib/webcontainer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/webcontainer.ts b/lib/webcontainer.ts index b6e0ca7..fa25aad 100644 --- a/lib/webcontainer.ts +++ b/lib/webcontainer.ts @@ -52,7 +52,7 @@ export async function bootProject(files: Record): Promise Date: Fri, 25 Sep 2026 12:30:38 -0500 Subject: [PATCH 09/11] docs: tighten pilot capture measurement gate --- docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md index 6bc1a1a..2de7e81 100644 --- a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md +++ b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md @@ -39,7 +39,7 @@ This pilot is an initial operating signal, not proof that costs remain below tar The implementation in this branch emits one server log event per editor initialization when both pilot flags are enabled. It records elapsed time, npm install duration and exit code, outcome, app revision, UTC month, and a month-scoped HMAC actor key. The browser submits no code, file path, project identifier, email, or raw user ID. Only accounts currently marked free are logged. Event IDs support duplicate removal in analysis. -The event sink is Vercel runtime logs. The event route is disabled unless both COST_PILOT_ENABLED and NEXT_PUBLIC_COST_PILOT_ENABLED are set to true. The code change does not enable either flag or deploy anything. Count accepted event IDs from the server logs and reconcile against pilot sessions; the capture-rate threshold is 95%. Export only the minimum fields for analysis and delete the exported row-level pilot data after 30 days, subject to the provider's available retention controls. +The event sink is Vercel runtime logs. The event route is disabled unless both COST_PILOT_ENABLED and NEXT_PUBLIC_COST_PILOT_ENABLED are set to true. The code change does not enable either flag or deploy anything. The client currently sends a single fire-and-forget request with no retry and no independent attempted-session denominator. Accepted server log events alone cannot establish the 95% telemetry capture threshold. Before the cohort starts, select and verify an independent denominator source, inject a dropped-request case, and demonstrate that the capture-rate calculation detects it. Export only the minimum fields for analysis and delete the exported row-level pilot data after 30 days, subject to the provider's available retention controls. This instrumentation does not estimate provider bills. Reconcile actual Vercel, Supabase, Cloudflare, AI, storage, egress, email, monitoring, Stripe, and WebContainer costs from provider-side billing evidence. Include the telemetry route and log retention in the measured cost. @@ -62,6 +62,8 @@ Official references: - The Peregrine Supabase project returned INACTIVE. The database cannot currently serve as a live pilot dependency without restoration and verification. - The Cloudflare connector is available, but read-only Workers, Pages, and billing inventory calls fail with API authentication error 10000; no Cloudflare usage or billing evidence was obtained. - The production migration remains open: key rotation, Vercel repointing, and auth, Stripe webhook, database, and collaboration smoke tests remain unverified. +- The one-shot telemetry path has no independent editor-session attempt denominator; the 95% capture criterion is not currently measurable. +- Netlify returned no matching Peregrine site, and Linear returned no dedicated Peregrine project or gate item. The Sentry API could not be queried because its required auth token, organization, and project settings are absent; no deployment, incident, or monitoring-cost evidence was obtained from those sources. - No WebContainer API price or signed license terms are publicly available in the sources above. The commercial-use requirement is clear; the price and negotiated scope remain unresolved pending written vendor terms. ## Evidence record From f7a891bc06c29f92f16762b25309592c9ef1a608 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 12:35:39 -0500 Subject: [PATCH 10/11] docs: track Supabase restore readiness --- docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md index 2de7e81..b712f8f 100644 --- a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md +++ b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md @@ -59,7 +59,7 @@ Official references: - No cost result is recorded. The pilot has not run. - The connected Vercel account returned no teams, so the project, deployment environment, and runtime logs cannot be inspected or configured here. -- The Peregrine Supabase project returned INACTIVE. The database cannot currently serve as a live pilot dependency without restoration and verification. +- Supabase restore was accepted, but the project remains COMING_UP on repeated checks. Migration/table queries returned empty results before readiness; they are not treated as verified schema state. Recheck migrations and tables when status is ACTIVE. - The Cloudflare connector is available, but read-only Workers, Pages, and billing inventory calls fail with API authentication error 10000; no Cloudflare usage or billing evidence was obtained. - The production migration remains open: key rotation, Vercel repointing, and auth, Stripe webhook, database, and collaboration smoke tests remain unverified. - The one-shot telemetry path has no independent editor-session attempt denominator; the 95% capture criterion is not currently measurable. From 7d4a4ff160330aadc7ab9d2f62b4b96bfa464d33 Mon Sep 17 00:00:00 2001 From: "JaeTierce@" Date: Fri, 25 Sep 2026 12:38:50 -0500 Subject: [PATCH 11/11] docs: record Supabase RLS security blocker --- docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md index b712f8f..9372dde 100644 --- a/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md +++ b/docs/operations/PEREGRINE_FREE_TIER_COST_PILOT_v0.1.0.md @@ -59,7 +59,9 @@ Official references: - No cost result is recorded. The pilot has not run. - The connected Vercel account returned no teams, so the project, deployment environment, and runtime logs cannot be inspected or configured here. -- Supabase restore was accepted, but the project remains COMING_UP on repeated checks. Migration/table queries returned empty results before readiness; they are not treated as verified schema state. Recheck migrations and tables when status is ACTIVE. +- The Supabase project is now ACTIVE_HEALTHY. Live inspection verified migration `20260513032045` (`create_tables`) and the `public.users`, `public.projects`, and `public.files` tables; each currently reports 0 rows. +- The Supabase security advisor reports RLS disabled on all three public tables at ERROR / EXTERNAL severity. The enabled key inventory includes both a legacy `anon` key and a modern publishable key; key values are not recorded. Do not enable RLS without defining the access policy, because doing so without policies blocks data access. Review the Clerk-authenticated server path and database role before selecting policies. +- Legacy `anon` key rotation remains unverified; the connector exposes key metadata but no key-rotation operation. - The Cloudflare connector is available, but read-only Workers, Pages, and billing inventory calls fail with API authentication error 10000; no Cloudflare usage or billing evidence was obtained. - The production migration remains open: key rotation, Vercel repointing, and auth, Stripe webhook, database, and collaboration smoke tests remain unverified. - The one-shot telemetry path has no independent editor-session attempt denominator; the 95% capture criterion is not currently measurable.