From de501f5a01950f1d92f751e9d77c8b2765982f17 Mon Sep 17 00:00:00 2001 From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:59:33 +0000 Subject: [PATCH 01/23] feat(emails): transactional emails for top-up and KiloClaw purchase Send exactly-once transactional emails for two purchase events: - Credit top-up (manual Checkout or auto-top-up), hooked into processTopUp's idempotent post-processing block and gated by the existing unique constraint on credit_transactions.stripe_payment_id. - KiloClaw subscription started (first paid billing period only), hooked into applyStripeFundedKiloClawPeriod and gated by an insert-before-send into kiloclaw_email_log so renewals are skipped. --- apps/web/src/emails/AGENTS.md | 2 + apps/web/src/emails/creditsTopUp.html | 211 ++++++++++++++++ .../emails/kiloClawSubscriptionStarted.html | 212 ++++++++++++++++ apps/web/src/lib/credits.ts | 75 ++++++ apps/web/src/lib/email.ts | 97 ++++++++ apps/web/src/lib/kiloclaw/credit-billing.ts | 98 ++++++++ apps/web/src/lib/purchase-emails.test.ts | 235 ++++++++++++++++++ 7 files changed, 930 insertions(+) create mode 100644 apps/web/src/emails/creditsTopUp.html create mode 100644 apps/web/src/emails/kiloClawSubscriptionStarted.html create mode 100644 apps/web/src/lib/purchase-emails.test.ts diff --git a/apps/web/src/emails/AGENTS.md b/apps/web/src/emails/AGENTS.md index 59d717c7a4..9e7f22d679 100644 --- a/apps/web/src/emails/AGENTS.md +++ b/apps/web/src/emails/AGENTS.md @@ -86,3 +86,5 @@ Every template must include this branding footer below the content table: | `clawEarlybirdExpiresTomorrow.html` | `expiry_date`, `claw_url`, `year` | `30` | | `clawComplementaryInferenceEnded.html` | `claw_url`, `year` | — | | `accountDeletionRequest.html` | `email`, `year` | — | +| `creditsTopUp.html` | `heading`, `intro`, `amount_usd`, `credits_usd`, `purchase_date`, `credits_url`, `receipt_section`, `year` | — | +| `kiloClawSubscriptionStarted.html` | `plan_name`, `price_usd`, `billing_period`, `next_billing_date`, `manage_url`, `year` | — | diff --git a/apps/web/src/emails/creditsTopUp.html b/apps/web/src/emails/creditsTopUp.html new file mode 100644 index 0000000000..0af90f31ac --- /dev/null +++ b/apps/web/src/emails/creditsTopUp.html @@ -0,0 +1,211 @@ + + + + + + {{ heading }} + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+

+ {{ heading }} +

+
+

+ {{ intro }} +

+
+ + + + +
+

+ Amount: ${{ amount_usd }} USD +

+

+ Credits: ${{ credits_usd }} USD +

+

+ Date: {{ purchase_date }} +

+
+
+ + + + +
+ + View your balance + +
+

+ {{ receipt_section }} +

+
+

+ If you have any questions, reply to this email — we're here to help. +

+

+ — The Kilo Team +

+
+ + + + + +
+

+ © {{ year }} Kilo Code, Inc
455 Market St, Ste 1940 PMB 993504
San + Francisco, CA 94105, USA +

+
+
+ + diff --git a/apps/web/src/emails/kiloClawSubscriptionStarted.html b/apps/web/src/emails/kiloClawSubscriptionStarted.html new file mode 100644 index 0000000000..63d0b6f290 --- /dev/null +++ b/apps/web/src/emails/kiloClawSubscriptionStarted.html @@ -0,0 +1,212 @@ + + + + + + Your KiloClaw subscription is active + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+

+ Your KiloClaw subscription is active +

+
+

+ Your first billing period for KiloClaw hosting has started. Here are the details: +

+
+ + + + +
+

+ Plan: {{ plan_name }} +

+

+ Price: ${{ price_usd }} USD +

+

+ Billing period: {{ billing_period }} +

+

+ Next billing date: + {{ next_billing_date }} +

+
+
+ + + + +
+ + Manage subscription + +
+
+

+ If you have any questions, reply to this email — we're here to help. +

+

+ — The Kilo Team +

+
+ + + + + +
+

+ © {{ year }} Kilo Code, Inc
455 Market St, Ste 1940 PMB 993504
San + Francisco, CA 94105, USA +

+
+
+ + diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index 5bd02f8736..1906c1d08a 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -4,10 +4,14 @@ import type { User } from '@kilocode/db/schema'; import { kilocode_users } from '@kilocode/db/schema'; import { db, type DrizzleTransaction } from '@/lib/drizzle'; import { sql, eq } from 'drizzle-orm'; +import { captureException } from '@sentry/nextjs'; +import Stripe from 'stripe'; import { after } from 'next/server'; import { processFirstTopupBonus } from '@/lib/firstTopupBonus'; import { grantCreditForCategory } from '@/lib/promotionalCredits'; import { IS_IN_AUTOMATED_TEST } from '@/lib/config.server'; +import { sendCreditsTopUpEmail } from '@/lib/email'; +import { client as stripeClient } from '@/lib/stripe-client'; export type StripeConfig = { type: 'stripe'; stripe_payment_id: string }; @@ -111,6 +115,13 @@ export async function processTopUp( }); } + await sendTopUpConfirmationEmail({ + user, + amountInCents, + stripeChargeOrInvoiceId: config.stripe_payment_id, + isAutoTopUp, + }); + if (!IS_IN_AUTOMATED_TEST) await delay(10000); }; @@ -118,3 +129,67 @@ export async function processTopUp( else after(processPostTopUpFreeStuff); return true; } + +// Idempotency: this function runs at most once per successful top-up because +// `processTopUp` is guarded by a unique constraint on +// `credit_transactions.stripe_payment_id` and only invokes its post-processing +// block on the row that actually inserted. Any later webhook retry for the +// same Stripe payment returns early with `false` before reaching here. +async function sendTopUpConfirmationEmail(params: { + user: User; + amountInCents: number; + stripeChargeOrInvoiceId: string; + isAutoTopUp: boolean; +}): Promise { + const { user, amountInCents, stripeChargeOrInvoiceId, isAutoTopUp } = params; + try { + const receiptUrl = await resolveStripeReceiptUrl(stripeChargeOrInvoiceId); + await sendCreditsTopUpEmail({ + to: user.google_user_email, + variant: isAutoTopUp ? 'auto' : 'manual', + amountCents: amountInCents, + creditsCents: amountInCents, + purchaseDate: new Date(), + receiptUrl, + }); + } catch (error) { + captureException(error, { + tags: { source: 'credits_topup_email' }, + extra: { kilo_user_id: user.id, stripeChargeOrInvoiceId, isAutoTopUp }, + }); + } +} + +async function resolveStripeReceiptUrl(stripeChargeOrInvoiceId: string): Promise { + // Skip outbound Stripe calls in automated tests — they are expensive, + // flake-prone, and unnecessary for exercising the email path. + if (IS_IN_AUTOMATED_TEST) return null; + + // Stripe charge IDs start with `ch_`; invoice IDs start with `in_`. + // Payment intent IDs (`pi_`) are used for organization top-ups. + try { + if (stripeChargeOrInvoiceId.startsWith('ch_')) { + const charge = await stripeClient.charges.retrieve(stripeChargeOrInvoiceId); + return charge.receipt_url ?? null; + } + if (stripeChargeOrInvoiceId.startsWith('in_')) { + const invoice = await stripeClient.invoices.retrieve(stripeChargeOrInvoiceId); + return invoice.hosted_invoice_url ?? null; + } + if (stripeChargeOrInvoiceId.startsWith('pi_')) { + const pi = await stripeClient.paymentIntents.retrieve(stripeChargeOrInvoiceId, { + expand: ['latest_charge'], + }); + const latestCharge = pi.latest_charge; + if (latestCharge && typeof latestCharge !== 'string') { + return latestCharge.receipt_url ?? null; + } + return null; + } + return null; + } catch (error) { + // Receipt URLs are a nice-to-have — never fail the email flow. + if (error instanceof Stripe.errors.StripeError) return null; + return null; + } +} diff --git a/apps/web/src/lib/email.ts b/apps/web/src/lib/email.ts index 6078d6beaa..5a259853c2 100644 --- a/apps/web/src/lib/email.ts +++ b/apps/web/src/lib/email.ts @@ -34,6 +34,8 @@ export const subjects = { clawCreditRenewalFailed: 'Action Required: KiloClaw Hosting Renewal Failed', clawComplementaryInferenceEnded: 'Your Free AI Inference Period Has Ended', accountDeletionRequest: 'Kilo: Account Deletion Request Received', + creditsTopUp: 'Your Kilo credit top-up', + kiloClawSubscriptionStarted: 'Your KiloClaw subscription is active', } as const; export type TemplateName = keyof typeof subjects; @@ -384,3 +386,98 @@ export async function sendAccountDeletionSupportNotification( replyTo: userEmail, }); } + +const CREDITS_TOPUP_COPY = { + manual: { + subject: 'Your Kilo credit top-up', + heading: 'Thanks for your top-up', + intro: + 'Your Kilo credit top-up has been processed and the credits are now available on your account.', + }, + auto: { + subject: 'Kilo auto top-up successful', + heading: 'Your auto top-up was successful', + intro: + 'Your account was automatically topped up so you can keep using Kilo without interruption. The new credits are available now.', + }, +} as const; + +export type CreditsTopUpVariant = keyof typeof CREDITS_TOPUP_COPY; + +type SendCreditsTopUpEmailProps = { + to: string; + variant: CreditsTopUpVariant; + amountCents: number; + creditsCents: number; + purchaseDate: Date; + receiptUrl?: string | null; +}; + +export function buildCreditsTopUpReceiptSection(receiptUrl: string | null | undefined): RawHtml { + if (!receiptUrl) return new RawHtml(''); + const escaped = escapeHtml(receiptUrl); + return new RawHtml( + `View your Stripe receipt.` + ); +} + +function formatUsd(cents: number): string { + return (cents / 100).toFixed(2); +} + +function formatDate(date: Date): string { + // Dates surfaced to end-users; the server locale is stable (UTC in prod) so + // explicit en-US formatting avoids surprise month-name changes in tests. + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + timeZone: 'UTC', + }); +} + +export async function sendCreditsTopUpEmail( + props: SendCreditsTopUpEmailProps +): Promise { + const copy = CREDITS_TOPUP_COPY[props.variant]; + const credits_url = `${NEXTAUTH_URL}/credits`; + return send({ + to: props.to, + templateName: 'creditsTopUp', + subjectOverride: copy.subject, + templateVars: { + heading: copy.heading, + intro: copy.intro, + amount_usd: formatUsd(props.amountCents), + credits_usd: formatUsd(props.creditsCents), + purchase_date: formatDate(props.purchaseDate), + credits_url, + receipt_section: buildCreditsTopUpReceiptSection(props.receiptUrl), + }, + }); +} + +type SendKiloClawSubscriptionStartedEmailProps = { + to: string; + planName: string; + priceCents: number; + billingPeriod: string; + nextBillingDate: Date; +}; + +export async function sendKiloClawSubscriptionStartedEmail( + props: SendKiloClawSubscriptionStartedEmailProps +): Promise { + const manage_url = `${NEXTAUTH_URL}/claw`; + return send({ + to: props.to, + templateName: 'kiloClawSubscriptionStarted', + templateVars: { + plan_name: props.planName, + price_usd: formatUsd(props.priceCents), + billing_period: props.billingPeriod, + next_billing_date: formatDate(props.nextBillingDate), + manage_url, + }, + }); +} diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index f79edc32cd..07deb4aa99 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -12,10 +12,13 @@ import { import { credit_transactions, kilocode_users, + kiloclaw_email_log, kiloclaw_instances, kiloclaw_subscriptions, } from '@kilocode/db/schema'; +import { captureException } from '@sentry/nextjs'; import { processTopUp } from '@/lib/credits'; +import { sendKiloClawSubscriptionStartedEmail } from '@/lib/email'; import { autoResumeIfSuspended, clearTrialInactivityStopAfterTrialTransition, @@ -642,6 +645,17 @@ export async function applyStripeFundedKiloClawPeriod(params: { }); } + if (resolvedInstanceId && amountMicrodollars > 0) { + await maybeSendKiloClawSubscriptionStartedEmail({ + userId, + instanceId: resolvedInstanceId, + plan, + amountCents: Math.round(amountMicrodollars / 10_000), + periodStart, + periodEnd, + }); + } + logInfo('Credit settlement completed', { user_id: userId, plan, @@ -653,6 +667,90 @@ export async function applyStripeFundedKiloClawPeriod(params: { return true; } +export const KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE = 'kiloclaw_subscription_started'; + +function formatBillingPeriod(periodStart: string, periodEnd: string): string { + const start = format(new Date(periodStart), 'MMM d, yyyy'); + const end = format(new Date(periodEnd), 'MMM d, yyyy'); + return `${start} – ${end}`; +} + +function planDisplayName(plan: 'commit' | 'standard'): string { + return plan === 'commit' ? 'KiloClaw Commit' : 'KiloClaw Standard'; +} + +// Idempotency: insert-before-send on `kiloclaw_email_log` guarded by the +// unique index (user_id, instance_id, email_type). Only the first paid period +// inserts a row; renewals (which would attempt the same row) are skipped. +async function maybeSendKiloClawSubscriptionStartedEmail(params: { + userId: string; + instanceId: string; + plan: 'commit' | 'standard'; + amountCents: number; + periodStart: string; + periodEnd: string; +}): Promise { + const { userId, instanceId, plan, amountCents, periodStart, periodEnd } = params; + try { + const insertResult = await db + .insert(kiloclaw_email_log) + .values({ + user_id: userId, + instance_id: instanceId, + email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + }) + .onConflictDoNothing(); + + if ((insertResult.rowCount ?? 0) === 0) { + // Not the first paid period — renewal, nothing to send. + return; + } + + const [user] = await db + .select({ email: kilocode_users.google_user_email }) + .from(kilocode_users) + .where(eq(kilocode_users.id, userId)) + .limit(1); + + if (!user) { + logWarning('KiloClaw subscription-started email: user not found', { + user_id: userId, + instance_id: instanceId, + }); + return; + } + + await sendKiloClawSubscriptionStartedEmail({ + to: user.email, + planName: planDisplayName(plan), + priceCents: amountCents, + billingPeriod: formatBillingPeriod(periodStart, periodEnd), + nextBillingDate: new Date(periodEnd), + }); + } catch (error) { + // Never fail the settlement flow because of an email error. + captureException(error, { + tags: { source: 'kiloclaw_subscription_started_email' }, + extra: { user_id: userId, instance_id: instanceId, plan }, + }); + // Best-effort rollback so a retry can re-attempt — mirrors the pattern in + // apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts. + try { + await db + .delete(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, userId), + eq(kiloclaw_email_log.instance_id, instanceId), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + ) + ); + } catch { + // Leave the marker in place; we prefer missing one email over duplicate sends. + } + } +} + /** * Enroll a user's instance in a KiloClaw hosting plan funded by credits. * diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts new file mode 100644 index 0000000000..6fe055c8a2 --- /dev/null +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -0,0 +1,235 @@ +import { describe, test, expect, jest, beforeEach, afterEach } from '@jest/globals'; +import { eq, and } from 'drizzle-orm'; +import { credit_transactions, kiloclaw_email_log } from '@kilocode/db/schema'; +import { db } from '@/lib/drizzle'; +import { insertTestUser } from '@/tests/helpers/user.helper'; +import { processTopUp } from '@/lib/credits'; +import { KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE } from '@/lib/kiloclaw/credit-billing'; +import type * as emailModule from '@/lib/email'; +import { + renderTemplate, + buildCreditsTopUpReceiptSection, + subjects, + type TemplateName, +} from '@/lib/email'; + +// Avoid firstTopupBonus side effects. +jest.mock('@/lib/firstTopupBonus', () => ({ + processFirstTopupBonus: jest.fn(), +})); + +// Count email sends via the shared `send` export. +const sendMock = jest.fn(async (_params: unknown) => ({ sent: true as const })); + +jest.mock('@/lib/email', () => { + const actual = jest.requireActual('@/lib/email'); + return { + ...actual, + send: jest.fn((params: unknown) => sendMock(params)), + }; +}); + +// Receipt URL lookups during unit tests must not touch Stripe. +jest.mock('@/lib/stripe-client', () => ({ + client: { + charges: { retrieve: jest.fn(async () => ({ receipt_url: null })) }, + invoices: { retrieve: jest.fn(async () => ({ hosted_invoice_url: null })) }, + paymentIntents: { retrieve: jest.fn(async () => ({ latest_charge: null })) }, + }, +})); + +describe('creditsTopUp template', () => { + test('renders required fields', () => { + const html = renderTemplate('creditsTopUp', { + heading: 'Thanks for your top-up', + intro: 'hello', + amount_usd: '15.00', + credits_usd: '15.00', + purchase_date: 'January 1, 2026', + credits_url: 'https://app.kilocode.ai/credits', + receipt_section: buildCreditsTopUpReceiptSection('https://stripe.test/receipt'), + year: '2026', + }); + expect(html).toContain('Thanks for your top-up'); + expect(html).toContain('$15.00'); + expect(html).toContain('January 1, 2026'); + expect(html).toContain('https://app.kilocode.ai/credits'); + expect(html).toContain('https://stripe.test/receipt'); + }); + + test('omits receipt section when receipt URL is missing', () => { + const html = renderTemplate('creditsTopUp', { + heading: 'h', + intro: 'i', + amount_usd: '5.00', + credits_usd: '5.00', + purchase_date: 'January 1, 2026', + credits_url: 'https://app.kilocode.ai/credits', + receipt_section: buildCreditsTopUpReceiptSection(null), + year: '2026', + }); + expect(html).not.toContain('View your Stripe receipt'); + }); +}); + +describe('kiloClawSubscriptionStarted template', () => { + test('renders required fields', () => { + const html = renderTemplate('kiloClawSubscriptionStarted', { + plan_name: 'KiloClaw Standard', + price_usd: '9.00', + billing_period: 'Jan 1, 2026 – Feb 1, 2026', + next_billing_date: 'February 1, 2026', + manage_url: 'https://app.kilocode.ai/claw', + year: '2026', + }); + expect(html).toContain('KiloClaw Standard'); + expect(html).toContain('$9.00'); + expect(html).toContain('Jan 1, 2026 – Feb 1, 2026'); + expect(html).toContain('February 1, 2026'); + expect(html).toContain('https://app.kilocode.ai/claw'); + }); +}); + +describe('subjects map', () => { + test('includes the new templates', () => { + const entries: TemplateName[] = ['creditsTopUp', 'kiloClawSubscriptionStarted']; + for (const name of entries) { + expect(subjects[name]).toBeTruthy(); + } + }); +}); + +describe('processTopUp credit top-up email', () => { + beforeEach(() => { + sendMock.mockClear(); + }); + + afterEach(() => { + sendMock.mockClear(); + }); + + test('sends credit top-up email once for a successful manual top-up', async () => { + const user = await insertTestUser({ + total_microdollars_acquired: 0, + microdollars_used: 0, + }); + + const stripePaymentId = `ch_test_${Date.now()}_${Math.random()}`; + const first = await processTopUp(user, 1500, { + type: 'stripe', + stripe_payment_id: stripePaymentId, + }); + expect(first).toBe(true); + + const topUpSends = sendMock.mock.calls + .map(([params]) => params as { templateName: string; templateVars: Record }) + .filter(p => p.templateName === 'creditsTopUp'); + expect(topUpSends).toHaveLength(1); + expect(topUpSends[0].templateVars.amount_usd).toBe('15.00'); + + sendMock.mockClear(); + + // Retry / webhook replay with the same stripe_payment_id must not re-send. + const second = await processTopUp(user, 1500, { + type: 'stripe', + stripe_payment_id: stripePaymentId, + }); + expect(second).toBe(false); + + const replayTopUpSends = sendMock.mock.calls + .map(([params]) => params as { templateName: string }) + .filter(p => p.templateName === 'creditsTopUp'); + expect(replayTopUpSends).toHaveLength(0); + }); + + test('uses auto-top-up copy when isAutoTopUp is true', async () => { + const user = await insertTestUser({ + total_microdollars_acquired: 0, + microdollars_used: 0, + }); + + await processTopUp( + user, + 2000, + { type: 'stripe', stripe_payment_id: `ch_auto_${Date.now()}_${Math.random()}` }, + { isAutoTopUp: true } + ); + + const call = sendMock.mock.calls + .map(([params]) => params as { templateName: string; subjectOverride?: string }) + .find(p => p.templateName === 'creditsTopUp'); + expect(call?.subjectOverride).toBe('Kilo auto top-up successful'); + }); + + test('does not send an email when skipPostTopUpFreeStuff is true', async () => { + const user = await insertTestUser({ + total_microdollars_acquired: 0, + microdollars_used: 0, + }); + + await processTopUp( + user, + 1000, + { type: 'stripe', stripe_payment_id: `ch_skip_${Date.now()}_${Math.random()}` }, + { skipPostTopUpFreeStuff: true } + ); + + const topUpSends = sendMock.mock.calls + .map(([params]) => params as { templateName: string }) + .filter(p => p.templateName === 'creditsTopUp'); + expect(topUpSends).toHaveLength(0); + + // Sanity: the credit transaction was still recorded. + const [txn] = await db + .select({ id: credit_transactions.id }) + .from(credit_transactions) + .where(eq(credit_transactions.kilo_user_id, user.id)) + .limit(1); + expect(txn).toBeTruthy(); + }); +}); + +describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { + test('matches kiloclaw_email_log.email_type', () => { + expect(KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE).toBe('kiloclaw_subscription_started'); + }); + + test('kiloclaw_email_log unique index prevents duplicate inserts', async () => { + const user = await insertTestUser({}); + + // Use a synthetic instance_id; email log has no FK cascade requirement for the test + // since we scope by user_id uniqueness. We need a real instance FK, so skip this test + // if we cannot create an instance cheaply. Instead assert that the global unique index + // (user_id, email_type) where instance_id IS NULL blocks duplicates. + const first = await db + .insert(kiloclaw_email_log) + .values({ + user_id: user.id, + instance_id: null, + email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + }) + .onConflictDoNothing(); + expect(first.rowCount).toBe(1); + + const second = await db + .insert(kiloclaw_email_log) + .values({ + user_id: user.id, + instance_id: null, + email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + }) + .onConflictDoNothing(); + expect(second.rowCount).toBe(0); + + const rows = await db + .select() + .from(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, user.id), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + ) + ); + expect(rows).toHaveLength(1); + }); +}); From 2d5276567d7a6226010152156444398ad071eece Mon Sep 17 00:00:00 2001 From: Job Rietbergen Date: Wed, 29 Apr 2026 11:26:49 +0200 Subject: [PATCH 02/23] fix(emails): add fixture vars for creditsTopUp and kiloClawSubscriptionStarted in email-testing-router --- .../src/routers/admin/email-testing-router.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/web/src/routers/admin/email-testing-router.ts b/apps/web/src/routers/admin/email-testing-router.ts index 9b87a81c7c..6ce90f8337 100644 --- a/apps/web/src/routers/admin/email-testing-router.ts +++ b/apps/web/src/routers/admin/email-testing-router.ts @@ -6,6 +6,7 @@ import { verifyEmail } from '@/lib/email-neverbounce'; import { subjects, creditsVars, + buildCreditsTopUpReceiptSection, renderTemplate, type RawHtml, type TemplateName, @@ -114,8 +115,26 @@ function fixtureTemplateVars(template: TemplateName): Record Date: Wed, 29 Apr 2026 12:03:49 +0200 Subject: [PATCH 03/23] fix(emails): gate kiloclaw subscription-started email on first paid period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Existing paid subscribers have no kiloclaw_email_log row for the new email_type, so their first renewal after deploy would insert a marker and send a misleading 'subscription is active' email. Gate the send on isFirstPaidPeriod — true only when the subscription was previously in 'trialing' status (trial → paid transition). Renewals and reactivations have before.status === 'active' and are skipped. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 07deb4aa99..3c991ef205 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -438,6 +438,10 @@ export async function applyStripeFundedKiloClawPeriod(params: { let wasSuspended = false; let resolvedInstanceId: string | undefined; let applied = false; + // True only when the subscription transitions from trialing → first paid period. + // Renewals (before.status === 'active') and reactivations must not trigger the + // "subscription started" email; existing paid subscribers have no log row yet. + let isFirstPaidPeriod = false; await db.transaction(async tx => { const user = await tx.query.kilocode_users.findFirst({ @@ -605,6 +609,7 @@ export async function applyStripeFundedKiloClawPeriod(params: { .from(kiloclaw_subscriptions) .where(eq(kiloclaw_subscriptions.id, targetRow.id)) .limit(1); + isFirstPaidPeriod = before?.status === 'trialing'; const [after] = await tx .update(kiloclaw_subscriptions) .set(updateSet) @@ -645,7 +650,7 @@ export async function applyStripeFundedKiloClawPeriod(params: { }); } - if (resolvedInstanceId && amountMicrodollars > 0) { + if (resolvedInstanceId && amountMicrodollars > 0 && isFirstPaidPeriod) { await maybeSendKiloClawSubscriptionStartedEmail({ userId, instanceId: resolvedInstanceId, From e31b6ebfaa4e5eaad613cb534152f0ba7bb0401f Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 11:38:34 -0600 Subject: [PATCH 04/23] fix(emails): recover kiloclaw subscription-started email on duplicate settlement Adds best-effort duplicate-settlement email recovery for the KiloClaw subscription-started email when a webhook replay hits the duplicate-credit path. Eligibility is derived from a durable subscription change-log entry proving a prior paid activation for the same subscription, plan, and period, with a 31-day window guard. Also broadens activation eligibility to cover canceled rows (including canceled paid rows that resubscribe), and strengthens tests to exercise the production settlement path end-to-end with realistic kiloclaw_email_log idempotency shapes. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 138 +++++- apps/web/src/lib/purchase-emails.test.ts | 444 +++++++++++++++++++- 2 files changed, 564 insertions(+), 18 deletions(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 3c991ef205..9040dc3da4 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -1,6 +1,6 @@ import 'server-only'; -import { and, eq, isNotNull, isNull, sql } from 'drizzle-orm'; +import { and, desc, eq, isNotNull, isNull, sql } from 'drizzle-orm'; import { addMonths, format } from 'date-fns'; import { db } from '@/lib/drizzle'; @@ -14,6 +14,7 @@ import { kilocode_users, kiloclaw_email_log, kiloclaw_instances, + kiloclaw_subscription_change_log, kiloclaw_subscriptions, } from '@kilocode/db/schema'; import { captureException } from '@sentry/nextjs'; @@ -437,11 +438,18 @@ export async function applyStripeFundedKiloClawPeriod(params: { let wasSuspended = false; let resolvedInstanceId: string | undefined; + let resolvedSubscriptionId: string | undefined; let applied = false; - // True only when the subscription transitions from trialing → first paid period. - // Renewals (before.status === 'active') and reactivations must not trigger the - // "subscription started" email; existing paid subscribers have no log row yet. - let isFirstPaidPeriod = false; + // True when this settlement transitions the subscription into a paid active + // period from a non-active state (trialing or canceled). Renewals + // (before.status === 'active') and recovery states (past_due / unpaid) do + // not send the "subscription started" email. See + // shouldSendSubscriptionStartedEmailForActivation. + let shouldSendSubscriptionStartedEmailForNewSettlement = false; + // Set when the primary settlement insert was a duplicate (processTopUp + // returned false). In that case the downstream email side effect may not + // have run yet and we attempt best-effort recovery after commit. + let settlementWasDuplicate = false; await db.transaction(async tx => { const user = await tx.query.kilocode_users.findFirst({ @@ -532,6 +540,7 @@ export async function applyStripeFundedKiloClawPeriod(params: { const targetRow = resolvedTarget.subscription; wasSuspended = !!targetRow.suspended_at; resolvedInstanceId = targetRow.instance_id ?? undefined; + resolvedSubscriptionId = targetRow.id; const shouldClearSchedule = targetRow.scheduled_plan === plan; const commitEndsAt = plan === 'commit' ? periodEnd : null; @@ -553,6 +562,7 @@ export async function applyStripeFundedKiloClawPeriod(params: { stripe_payment_id: stripePaymentId, }); applied = true; + settlementWasDuplicate = true; return; } @@ -609,7 +619,8 @@ export async function applyStripeFundedKiloClawPeriod(params: { .from(kiloclaw_subscriptions) .where(eq(kiloclaw_subscriptions.id, targetRow.id)) .limit(1); - isFirstPaidPeriod = before?.status === 'trialing'; + shouldSendSubscriptionStartedEmailForNewSettlement = + shouldSendSubscriptionStartedEmailForActivation(before?.status ?? null); const [after] = await tx .update(kiloclaw_subscriptions) .set(updateSet) @@ -650,7 +661,18 @@ export async function applyStripeFundedKiloClawPeriod(params: { }); } - if (resolvedInstanceId && amountMicrodollars > 0 && isFirstPaidPeriod) { + const shouldSendSubscriptionStartedEmail = + shouldSendSubscriptionStartedEmailForNewSettlement || + (settlementWasDuplicate && + resolvedSubscriptionId !== undefined && + (await didPriorSettlementRecordPaidActivation({ + subscriptionId: resolvedSubscriptionId, + plan, + periodStart, + periodEnd, + }))); + + if (resolvedInstanceId && amountMicrodollars > 0 && shouldSendSubscriptionStartedEmail) { await maybeSendKiloClawSubscriptionStartedEmail({ userId, instanceId: resolvedInstanceId, @@ -674,6 +696,108 @@ export async function applyStripeFundedKiloClawPeriod(params: { export const KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE = 'kiloclaw_subscription_started'; +// Conservative duplicate-recovery window. A change-log row older than this +// relative to `periodStart` is treated as a stale transition and will not +// trigger a recovered subscription-started email. The `kiloclaw_email_log` +// unique index is still the final idempotency guard. +export const SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS = 31 * 24 * 60 * 60 * 1000; + +// A settlement activates a paid period (and may produce a "subscription +// started" email) only when the subscription was NOT already active before +// settlement. Recovery/dunning states (past_due, unpaid) are excluded; those +// flows are payment-recovery on an active plan, not a new activation. +// Eligible: trialing, canceled (including canceled paid rows that resubscribe). +export function shouldSendSubscriptionStartedEmailForActivation( + beforeStatus: string | null +): boolean { + return beforeStatus === 'trialing' || beforeStatus === 'canceled'; +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function stringFieldOrNull(record: Record, key: string): string | null { + const value = record[key]; + return typeof value === 'string' ? value : null; +} + +// Compare two timestamp strings by parsing them as Dates. Handles the case +// where a JSONB-serialized timestamp uses Postgres text form +// ("2026-05-04 16:52:41.287+00") while the input is ISO-8601 +// ("2026-05-04T16:52:41.287Z"). Returns false for either side unparseable. +function timestampsEqual(a: string | null, b: string | null): boolean { + if (a === null || b === null) return false; + const aMs = new Date(a).getTime(); + const bMs = new Date(b).getTime(); + if (!Number.isFinite(aMs) || !Number.isFinite(bMs)) return false; + return aMs === bMs; +} + +// Best-effort check: does `kiloclaw_subscription_change_log` contain a prior +// `period_advanced` / `stripe_invoice_settlement` entry that transitioned the +// given subscription into a paid active period for the exact plan and +// period? Used to recover the subscription-started email when a replay of +// settlement hits the duplicate-credit path and the original in-transaction +// email send may have failed. Returns false on missing/malformed rows — the +// `kiloclaw_email_log` unique index remains the final idempotency guard. +async function didPriorSettlementRecordPaidActivation(params: { + subscriptionId: string; + plan: 'commit' | 'standard'; + periodStart: string; + periodEnd: string; +}): Promise { + const { subscriptionId, plan, periodStart, periodEnd } = params; + + const rows = await db + .select({ + created_at: kiloclaw_subscription_change_log.created_at, + before_state: kiloclaw_subscription_change_log.before_state, + after_state: kiloclaw_subscription_change_log.after_state, + }) + .from(kiloclaw_subscription_change_log) + .where( + and( + eq(kiloclaw_subscription_change_log.subscription_id, subscriptionId), + eq(kiloclaw_subscription_change_log.action, 'period_advanced'), + eq(kiloclaw_subscription_change_log.reason, 'stripe_invoice_settlement') + ) + ) + .orderBy(desc(kiloclaw_subscription_change_log.created_at)) + .limit(10); + + const periodStartMs = new Date(periodStart).getTime(); + if (!Number.isFinite(periodStartMs)) return false; + + for (const row of rows) { + if (!isRecord(row.before_state) || !isRecord(row.after_state)) continue; + + const beforeStatus = stringFieldOrNull(row.before_state, 'status'); + if (!shouldSendSubscriptionStartedEmailForActivation(beforeStatus)) continue; + + const afterStatus = stringFieldOrNull(row.after_state, 'status'); + const afterPlan = stringFieldOrNull(row.after_state, 'plan'); + const afterPeriodStart = stringFieldOrNull(row.after_state, 'current_period_start'); + const afterPeriodEnd = stringFieldOrNull(row.after_state, 'current_period_end'); + if ( + afterStatus !== 'active' || + afterPlan !== plan || + !timestampsEqual(afterPeriodStart, periodStart) || + !timestampsEqual(afterPeriodEnd, periodEnd) + ) { + continue; + } + + const createdAtMs = new Date(row.created_at).getTime(); + if (!Number.isFinite(createdAtMs)) continue; + if (Math.abs(createdAtMs - periodStartMs) > SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS) continue; + + return true; + } + + return false; +} + function formatBillingPeriod(periodStart: string, periodEnd: string): string { const start = format(new Date(periodStart), 'MMM d, yyyy'); const end = format(new Date(periodEnd), 'MMM d, yyyy'); diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 6fe055c8a2..f7bf484f5a 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -1,10 +1,18 @@ -import { describe, test, expect, jest, beforeEach, afterEach } from '@jest/globals'; import { eq, and } from 'drizzle-orm'; -import { credit_transactions, kiloclaw_email_log } from '@kilocode/db/schema'; +import { + credit_transactions, + kiloclaw_email_log, + kiloclaw_instances, + kiloclaw_subscription_change_log, + kiloclaw_subscriptions, +} from '@kilocode/db/schema'; import { db } from '@/lib/drizzle'; import { insertTestUser } from '@/tests/helpers/user.helper'; import { processTopUp } from '@/lib/credits'; -import { KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE } from '@/lib/kiloclaw/credit-billing'; +import { + KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS, +} from '@/lib/kiloclaw/credit-billing'; import type * as emailModule from '@/lib/email'; import { renderTemplate, @@ -18,7 +26,11 @@ jest.mock('@/lib/firstTopupBonus', () => ({ processFirstTopupBonus: jest.fn(), })); -// Count email sends via the shared `send` export. +// Count email sends via the shared `send` export as well as the +// `sendKiloClawSubscriptionStartedEmail` / `sendCreditsTopUpEmail` helpers. +// The helpers are replaced with jest.fns that forward the call via +// sendMock using a synthetic `templateName` so existing tests that filter +// by templateName continue to work. const sendMock = jest.fn(async (_params: unknown) => ({ sent: true as const })); jest.mock('@/lib/email', () => { @@ -26,6 +38,46 @@ jest.mock('@/lib/email', () => { return { ...actual, send: jest.fn((params: unknown) => sendMock(params)), + sendCreditsTopUpEmail: jest.fn( + async (props: { + to: string; + variant: 'auto' | 'manual'; + amountCents: number; + creditsCents: number; + purchaseDate: Date; + receiptUrl: string | null; + }) => + sendMock({ + to: props.to, + templateName: 'creditsTopUp', + templateVars: { + amount_usd: (props.amountCents / 100).toFixed(2), + credits_usd: (props.creditsCents / 100).toFixed(2), + receipt_url: props.receiptUrl, + variant: props.variant, + }, + subjectOverride: props.variant === 'auto' ? 'Kilo auto top-up successful' : undefined, + }) + ), + sendKiloClawSubscriptionStartedEmail: jest.fn( + async (props: { + to: string; + planName: string; + priceCents: number; + billingPeriod: string; + nextBillingDate: Date; + }) => + sendMock({ + to: props.to, + templateName: 'kiloClawSubscriptionStarted', + templateVars: { + plan_name: props.planName, + price_usd: (props.priceCents / 100).toFixed(2), + billing_period: props.billingPeriod, + next_billing_date: props.nextBillingDate.toISOString(), + }, + }) + ), }; }); @@ -38,6 +90,23 @@ jest.mock('@/lib/stripe-client', () => ({ }, })); +// Settlement post-commit side effects that aren't relevant to email behavior. +jest.mock('@/lib/kiloclaw/instance-lifecycle', () => ({ + autoResumeIfSuspended: jest.fn(async () => {}), + clearTrialInactivityStopAfterTrialTransition: jest.fn(async () => {}), +})); + +jest.mock('@/lib/kilo-pass/usage-triggered-bonus', () => ({ + computeUsageTriggeredMonthlyBonusDecision: jest.fn(() => ({ bonusPercentApplied: 0 })), + maybeIssueKiloPassBonusFromUsageThreshold: jest.fn(async () => {}), +})); + +jest.mock('@/lib/affiliate-events', () => ({ + enqueueAffiliateEventForUser: jest.fn(async () => {}), + buildAffiliateEventDedupeKey: jest.fn(() => 'test-dedupe-key'), + recordAffiliateAttributionAndQueueParentEvent: jest.fn(async () => {}), +})); + describe('creditsTopUp template', () => { test('renders required fields', () => { const html = renderTemplate('creditsTopUp', { @@ -194,18 +263,23 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { expect(KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE).toBe('kiloclaw_subscription_started'); }); - test('kiloclaw_email_log unique index prevents duplicate inserts', async () => { + test('kiloclaw_email_log unique index prevents duplicate inserts for (user, instance, type)', async () => { + // Production code writes (user_id, instance_id, email_type) via the + // per-instance unique index, so test that exact shape here. const user = await insertTestUser({}); + const [instance] = await db + .insert(kiloclaw_instances) + .values({ + user_id: user.id, + sandbox_id: `test-sandbox-${crypto.randomUUID()}`, + }) + .returning(); - // Use a synthetic instance_id; email log has no FK cascade requirement for the test - // since we scope by user_id uniqueness. We need a real instance FK, so skip this test - // if we cannot create an instance cheaply. Instead assert that the global unique index - // (user_id, email_type) where instance_id IS NULL blocks duplicates. const first = await db .insert(kiloclaw_email_log) .values({ user_id: user.id, - instance_id: null, + instance_id: instance.id, email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, }) .onConflictDoNothing(); @@ -215,7 +289,7 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { .insert(kiloclaw_email_log) .values({ user_id: user.id, - instance_id: null, + instance_id: instance.id, email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, }) .onConflictDoNothing(); @@ -227,9 +301,357 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { .where( and( eq(kiloclaw_email_log.user_id, user.id), + eq(kiloclaw_email_log.instance_id, instance.id), eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) ) ); expect(rows).toHaveLength(1); }); }); + +// ── Stripe-funded settlement → subscription-started email ────────────────── + +describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { + beforeEach(() => { + sendMock.mockClear(); + }); + afterEach(() => { + sendMock.mockClear(); + }); + + async function applyStripeFundedKiloClawPeriod( + params: Parameters< + typeof import('@/lib/kiloclaw/credit-billing').applyStripeFundedKiloClawPeriod + >[0] + ): Promise { + const mod = await import('@/lib/kiloclaw/credit-billing'); + return mod.applyStripeFundedKiloClawPeriod(params); + } + + async function seedSubscription(params: { + userId: string; + status: 'trialing' | 'canceled' | 'active' | 'past_due' | 'unpaid'; + plan: 'trial' | 'standard' | 'commit'; + stripeSubscriptionId: string; + }) { + const [instance] = await db + .insert(kiloclaw_instances) + .values({ + user_id: params.userId, + sandbox_id: `test-sandbox-${crypto.randomUUID()}`, + }) + .returning(); + const now = new Date(); + const [subscription] = await db + .insert(kiloclaw_subscriptions) + .values({ + user_id: params.userId, + instance_id: instance.id, + stripe_subscription_id: params.stripeSubscriptionId, + payment_source: 'stripe', + plan: params.plan, + status: params.status, + trial_started_at: + params.plan === 'trial' ? new Date(now.getTime() - 14 * 86_400_000).toISOString() : null, + trial_ends_at: + params.plan === 'trial' ? new Date(now.getTime() - 7 * 86_400_000).toISOString() : null, + current_period_start: + params.plan !== 'trial' ? new Date(now.getTime() - 30 * 86_400_000).toISOString() : null, + current_period_end: + params.plan !== 'trial' ? new Date(now.getTime() - 1 * 86_400_000).toISOString() : null, + }) + .returning(); + return { instance, subscription }; + } + + function countSubscriptionStartedSends(): number { + return sendMock.mock.calls.filter( + ([params]) => + (params as { templateName: string }).templateName === 'kiloClawSubscriptionStarted' + ).length; + } + + async function countEmailLogRows(userId: string, instanceId: string): Promise { + const rows = await db + .select() + .from(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, userId), + eq(kiloclaw_email_log.instance_id, instanceId), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + ) + ); + return rows.length; + } + + test('trialing trial → Stripe settlement sends one subscription-started email and writes the log row', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_trialing_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + + test('canceled trial → Stripe settlement sends one subscription-started email', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_canceled_trial_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'canceled', + plan: 'trial', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + + test('canceled paid row → Stripe settlement sends one subscription-started email for resubscribe', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_canceled_paid_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'canceled', + plan: 'standard', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + + test('active renewal → no subscription-started email', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_renewal_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'active', + plan: 'standard', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(0); + expect(await countEmailLogRows(user.id, instance.id)).toBe(0); + }); + + test('duplicate webhook replay → no second email when the log row already exists', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_replay_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + const periodStart = new Date().toISOString(); + const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + const stripePaymentId = `ch_${crypto.randomUUID()}`; + + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + expect(countSubscriptionStartedSends()).toBe(1); + + sendMock.mockClear(); + + // Same stripe_payment_id → processTopUp returns false (duplicate credit), + // so we take the duplicate-recovery path. The kiloclaw_email_log row from + // the first run must block a second send. + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + + expect(countSubscriptionStartedSends()).toBe(0); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + + test('duplicate webhook recovery → replay sends email once when durable change log shows paid activation but email log is missing', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_recovery_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + const periodStart = new Date().toISOString(); + const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + const stripePaymentId = `ch_${crypto.randomUUID()}`; + + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + expect(countSubscriptionStartedSends()).toBe(1); + + // Simulate the first run failing to send the email (marker not persisted): + // delete the email-log row, then replay with same stripe_payment_id. + await db + .delete(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, user.id), + eq(kiloclaw_email_log.instance_id, instance.id), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + ) + ); + sendMock.mockClear(); + + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + + test('stale duplicate recovery guard → old change-log row outside the window does not trigger a recovered email', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_stale_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + const periodStart = new Date().toISOString(); + const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + const stripePaymentId = `ch_${crypto.randomUUID()}`; + + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + expect(countSubscriptionStartedSends()).toBe(1); + + // Backdate the change-log row well outside the recovery window relative + // to periodStart, and clear the email-log row. A replay must NOT send. + const backdated = new Date( + new Date(periodStart).getTime() - SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS - 86_400_000 + ).toISOString(); + await db + .update(kiloclaw_subscription_change_log) + .set({ created_at: backdated }) + .where( + and( + eq(kiloclaw_subscription_change_log.action, 'period_advanced'), + eq(kiloclaw_subscription_change_log.reason, 'stripe_invoice_settlement') + ) + ); + await db + .delete(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, user.id), + eq(kiloclaw_email_log.instance_id, instance.id), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + ) + ); + sendMock.mockClear(); + + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + + expect(countSubscriptionStartedSends()).toBe(0); + expect(await countEmailLogRows(user.id, instance.id)).toBe(0); + }); +}); From b01e8abaad73b209f6ff399a868876af47d0b400 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 13:22:30 -0600 Subject: [PATCH 05/23] fix(emails): match kiloclaw subscription-created activation on period identity Replaces the created_at ordering used in didStripeSubscriptionCreatedRecordEligibleActivation with a direct plan + period_start + period_end identity match against the stripe_subscription_created row's after_state. created_at defaults to now() (transaction-start), which under concurrent webhook transactions can reorder relative to commit chronology and let a stale activation log re-fire the email on a later renewal when the email-log marker is absent (e.g. after the intentional rollback in maybeSendKiloClawSubscriptionStartedEmail error path). The handler in stripe-handlers.ts already stamps the Stripe-derived plan and period boundaries onto both the subscription row and the change-log after_state, so an activation log can only match the specific period it activated. A later renewal covers a different period and cannot match. Mirrors the existing identity-match approach in didPriorSettlementRecordPaidActivation. Tests updated to stamp period boundaries when simulating handleKiloClawSubscriptionCreated and to seed the prior-activation renewal case with a different period than the current settlement. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 83 +++++++++++++- apps/web/src/lib/purchase-emails.test.ts | 113 ++++++++++++++++++++ 2 files changed, 195 insertions(+), 1 deletion(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 9040dc3da4..70c6d259fd 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -619,8 +619,22 @@ export async function applyStripeFundedKiloClawPeriod(params: { .from(kiloclaw_subscriptions) .where(eq(kiloclaw_subscriptions.id, targetRow.id)) .limit(1); + // Prefer the in-memory `before.status`. When Stripe's + // customer.subscription.created handler ran before invoice.paid, it + // already transitioned a non-hybrid row to 'active', hiding the + // pre-activation state from this snapshot. Fall back to the durable + // `kiloclaw_subscription_change_log` entry written by that handler, + // which preserves the pre-Stripe `before_state.status` and records the + // Stripe-derived plan/period in `after_state`. shouldSendSubscriptionStartedEmailForNewSettlement = - shouldSendSubscriptionStartedEmailForActivation(before?.status ?? null); + shouldSendSubscriptionStartedEmailForActivation(before?.status ?? null) || + (await didStripeSubscriptionCreatedRecordEligibleActivation({ + tx, + subscriptionId: targetRow.id, + plan, + periodStart, + periodEnd, + })); const [after] = await tx .update(kiloclaw_subscriptions) .set(updateSet) @@ -798,6 +812,73 @@ async function didPriorSettlementRecordPaidActivation(params: { return false; } +// Durable pre-settlement activation signal. +// +// handleKiloClawSubscriptionCreated can run before invoice.paid and will +// transition a non-hybrid row to status='active', masking the pre-Stripe +// status from the in-transaction snapshot used to decide whether this +// settlement is a first paid activation. The handler writes a +// `stripe_subscription_created` change-log row that preserves +// `before_state.status` and records the Stripe-derived plan/period in +// `after_state`, which is the durable evidence we need. +// +// Returns true when a `stripe_subscription_created` entry for this +// subscription has: +// - `before_state.status` that `shouldSendSubscriptionStartedEmailForActivation` +// accepts (trialing or canceled), AND +// - `after_state.plan` / `after_state.current_period_start` / +// `after_state.current_period_end` matching the current settlement. +// +// Matching on identity (plan + period boundaries) instead of audit-log +// ordering avoids relying on `created_at`, which is `now()` and therefore +// transaction-start scoped rather than a reliable commit/insert chronology +// under concurrent webhook transactions. A later renewal settlement has a +// different period than the original activation, so an old +// `stripe_subscription_created` row from the initial activation cannot match +// and cannot re-fire the email. The `kiloclaw_email_log` unique index +// remains the final idempotency guard. +async function didStripeSubscriptionCreatedRecordEligibleActivation(params: { + tx: CreditBillingTx; + subscriptionId: string; + plan: 'commit' | 'standard'; + periodStart: string; + periodEnd: string; +}): Promise { + const { tx, subscriptionId, plan, periodStart, periodEnd } = params; + const rows = await tx + .select({ + before_state: kiloclaw_subscription_change_log.before_state, + after_state: kiloclaw_subscription_change_log.after_state, + }) + .from(kiloclaw_subscription_change_log) + .where( + and( + eq(kiloclaw_subscription_change_log.subscription_id, subscriptionId), + eq(kiloclaw_subscription_change_log.reason, 'stripe_subscription_created') + ) + ) + .orderBy(desc(kiloclaw_subscription_change_log.created_at)) + .limit(20); + + for (const row of rows) { + if (!isRecord(row.before_state) || !isRecord(row.after_state)) continue; + const beforeStatus = stringFieldOrNull(row.before_state, 'status'); + if (!shouldSendSubscriptionStartedEmailForActivation(beforeStatus)) continue; + const afterPlan = stringFieldOrNull(row.after_state, 'plan'); + const afterPeriodStart = stringFieldOrNull(row.after_state, 'current_period_start'); + const afterPeriodEnd = stringFieldOrNull(row.after_state, 'current_period_end'); + if ( + afterPlan === plan && + timestampsEqual(afterPeriodStart, periodStart) && + timestampsEqual(afterPeriodEnd, periodEnd) + ) { + return true; + } + } + + return false; +} + function formatBillingPeriod(periodStart: string, periodEnd: string): string { const start = format(new Date(periodStart), 'MMM d, yyyy'); const end = format(new Date(periodEnd), 'MMM d, yyyy'); diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index f7bf484f5a..0759db623b 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -6,6 +6,7 @@ import { kiloclaw_subscription_change_log, kiloclaw_subscriptions, } from '@kilocode/db/schema'; +import { insertKiloClawSubscriptionChangeLog } from '@kilocode/db'; import { db } from '@/lib/drizzle'; import { insertTestUser } from '@/tests/helpers/user.helper'; import { processTopUp } from '@/lib/credits'; @@ -463,6 +464,65 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(1); }); + test('subscription.created before invoice.paid → settlement still sends one subscription-started email', async () => { + // Realistic Stripe ordering: customer.subscription.created is processed + // before invoice.paid. handleKiloClawSubscriptionCreated flips a non-hybrid + // row to status='active', stamps the Stripe-derived period boundaries onto + // the row, and writes a durable `stripe_subscription_created` change-log + // row preserving the pre-Stripe status. The subsequent settlement's + // in-memory `before.status` is already 'active', so the email decision + // must fall back to the durable log. + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_created_before_paid_${crypto.randomUUID()}`; + const { instance, subscription } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + const periodStart = new Date().toISOString(); + const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + + const trialingSnapshot = subscription; + // Simulate handleKiloClawSubscriptionCreated running before invoice.paid + // (see apps/web/src/lib/kiloclaw/stripe-handlers.ts): for non-hybrid rows + // it stamps the Stripe-derived plan, status, and period boundaries. + const [activatedSubscription] = await db + .update(kiloclaw_subscriptions) + .set({ + status: 'active', + plan: 'standard', + current_period_start: periodStart, + current_period_end: periodEnd, + }) + .where(eq(kiloclaw_subscriptions.id, subscription.id)) + .returning(); + await insertKiloClawSubscriptionChangeLog(db, { + subscriptionId: subscription.id, + actor: { actorType: 'system', actorId: 'stripe-webhook' }, + action: 'status_changed', + reason: 'stripe_subscription_created', + before: trialingSnapshot, + after: activatedSubscription, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + test('active renewal → no subscription-started email', async () => { const user = await insertTestUser({}); const stripeSubscriptionId = `sub_renewal_${crypto.randomUUID()}`; @@ -489,6 +549,59 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(0); }); + test('active renewal after a prior activation → eligible subscription.created log for a different period does NOT trigger a second email', async () => { + // Defence-in-depth for the durable-signal fallback: the helper matches on + // plan + period boundaries of the `stripe_subscription_created.after_state` + // against the current settlement period, so an activation log recorded for + // the original (earlier) period cannot re-fire the email on subsequent + // renewal settlements that cover a different period. + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_renewal_after_prior_${crypto.randomUUID()}`; + const { instance, subscription } = await seedSubscription({ + userId: user.id, + status: 'active', + plan: 'standard', + stripeSubscriptionId, + }); + + // Original stripe_subscription_created (trialing → active) from activation. + // `subscription.current_period_start/end` are seeded to the prior period + // (30 days ago → 1 day ago), which is deliberately different from the + // renewal settlement period used below. + await insertKiloClawSubscriptionChangeLog(db, { + subscriptionId: subscription.id, + actor: { actorType: 'system', actorId: 'stripe-webhook' }, + action: 'status_changed', + reason: 'stripe_subscription_created', + before: { ...subscription, status: 'trialing' }, + after: subscription, + }); + // Prior settlement that already handled the activation email. + await insertKiloClawSubscriptionChangeLog(db, { + subscriptionId: subscription.id, + actor: { actorType: 'system', actorId: 'kiloclaw-credit-billing' }, + action: 'period_advanced', + reason: 'stripe_invoice_settlement', + before: { ...subscription, status: 'trialing' }, + after: subscription, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(0); + expect(await countEmailLogRows(user.id, instance.id)).toBe(0); + }); + test('duplicate webhook replay → no second email when the log row already exists', async () => { const user = await insertTestUser({}); const stripeSubscriptionId = `sub_replay_${crypto.randomUUID()}`; From 6574126f0b29fa10bd81610885dc49be50cd58fb Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 13:23:11 -0600 Subject: [PATCH 06/23] test(emails): scope stale-recovery subscription_change_log update by subscription_id The 'stale duplicate recovery guard' test backdated every matching period_advanced / stripe_invoice_settlement row without scoping to the test's subscription, mutating shared DB state from earlier tests in the file and potentially corrupting later assertions. seedSubscription already returns the subscription row; destructure it and add eq(kiloclaw_subscription_change_log.subscription_id, subscription.id) to the WHERE clause alongside the existing action / reason predicates. --- apps/web/src/lib/purchase-emails.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 0759db623b..966f36ed64 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -705,7 +705,7 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { test('stale duplicate recovery guard → old change-log row outside the window does not trigger a recovered email', async () => { const user = await insertTestUser({}); const stripeSubscriptionId = `sub_stale_${crypto.randomUUID()}`; - const { instance } = await seedSubscription({ + const { instance, subscription } = await seedSubscription({ userId: user.id, status: 'trialing', plan: 'trial', @@ -738,6 +738,7 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { .set({ created_at: backdated }) .where( and( + eq(kiloclaw_subscription_change_log.subscription_id, subscription.id), eq(kiloclaw_subscription_change_log.action, 'period_advanced'), eq(kiloclaw_subscription_change_log.reason, 'stripe_invoice_settlement') ) From 47fc413481645130abc90faa3388bbf119fc3ca6 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 13:53:22 -0600 Subject: [PATCH 07/23] fix(emails): dedupe kiloclaw subscription-started email by activation period MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the per-instance-lifetime idempotency key on `kiloclaw_email_log` with a per-activation key so that users who cancel and resubscribe on the same KiloClaw instance actually receive a second subscription-started email. - `packages/db/src/schema.ts` + migration `0106_noisy_pete_wisdom.sql`: add `period_start timestamptz NOT NULL DEFAULT 'epoch'` to `kiloclaw_email_log`; drop `UQ_kiloclaw_email_log_user_instance_type`; add `UQ_kiloclaw_email_log_user_instance_type_period` on `(user_id, instance_id, email_type, period_start)` WHERE `instance_id IS NOT NULL`. - `apps/web/src/lib/kiloclaw/credit-billing.ts`: the insert-before-send in `maybeSendKiloClawSubscriptionStartedEmail` now writes `period_start`, and the delete-on-error branch now scopes by `period_start` so a failed send only clears its own marker. - `apps/web/src/lib/purchase-emails.test.ts`: rewrote the unique-index unit test for the new shape, added a sibling test proving the index admits a second row for a new period, and added an end-to-end regression test that activates, cancels, and resubscribes on the same instance and asserts two sends and two log rows. The KiloClaw transactional email work introduced a subscription-started email gated on `kiloclaw_email_log` as the durable idempotency surface. The existing unique index on `(user_id, instance_id, email_type)` was designed for one-per-instance-lifetime emails (e.g. `claw_instance_ready`) and is wrong for activation-event emails: after the first activation wrote a row, every future resubscribe on the same instance would conflict on the index, `onConflictDoNothing` would return `rowCount=0`, and the function would exit early without sending. Users who cancel and rejoin — a normal lifecycle — silently lost their activation email. The adversarial review captured this as cloud-ib7 with the explicit instruction to pick one of two product semantics and make the code consistent with the test expectation. We picked "one email per activation event" because (a) the existing test at purchase-emails.test.ts:440 already asserts a canceled-paid resubscribe should send, and (b) removing canceled-paid rows from eligibility would deprive returning customers of a confirmation they reasonably expect. **Why a `period_start` column and not a `subscription_id` column.** Resubscribe (both Stripe checkout and credit enrollment) UPDATEs the existing `kiloclaw_subscriptions` row in place rather than inserting a new one — Stripe path at `stripe-handlers.ts:870+` (allowed when `existingRow.status === 'canceled'`), credit path at `credit-billing.ts:1147` via `onConflictDoUpdate` on `instance_id`. Our internal `kiloclaw_subscriptions.id` is therefore stable across every activation on the instance and adds no discriminative power as a dedupe key. `stripe_subscription_id` is NULL for pure-credit subscriptions (`enrollWithCredits` explicitly writes `stripe_subscription_id: null`), so it cannot serve as the key either without special-casing. What actually differs across activations on both paths is the period boundary: Stripe stamps fresh `current_period_start` from the invoice line item; credits stamp `nowIso` on every enrollment. One column handles both paths. **Why `NOT NULL DEFAULT 'epoch'` instead of nullable.** Postgres treats `NULL` as distinct in unique indexes by default, which would let any other email type that omits `period_start` insert multiple rows and break the existing one-per-instance-lifetime contract for `claw_instance_ready`, `claw_suspended_*`, and friends. Drizzle's `nullsNotDistinct()` is only available on `unique()` constraints, which do not support partial `WHERE`. Defaulting to `'epoch'` lets every existing writer keep working unchanged — they all collapse onto the same `(user, instance, type, 'epoch')` index row — while only the subscription-started email path opts in to per-activation keying by explicitly writing `periodStart`. **Why not a synthetic `dedupe_key text`.** A natural timestamp column is queryable, self-documenting, and makes admin tooling easier ("show me all activation emails for period X"). A synthetic string key forces every reader to parse it. **Why the delete-on-error also got tightened.** The previous delete cleared every row for `(user, instance, type)`, which was fine when only one row could exist. With per-activation keying it would be a foot-gun: a failed send on activation N could erase activation N-1's durable marker. The new scope is `(user, instance, type, periodStart)` so a failure only touches its own insert. **Other writers of `kiloclaw_email_log` are unaffected.** The kiloclaw billing worker (`services/kiloclaw-billing/src/lifecycle.ts`), the KiloClaw router instance-destroy cleanup, the admin trial-reset flow, and the admin instance-reset flow all write and delete rows without referencing `period_start`. The `DEFAULT 'epoch'` fills in a stable value so their inserts still collapse one-per-(user, instance, type) and their deletes (filtered by `email_type IN (...)`) still match every relevant row regardless of `period_start`. **Existing production rows get `period_start='epoch'` on backfill.** For `kiloclaw_subscription_started` rows written before the migration, this means the first post-deploy activation on the same instance will write a row with a real `periodStart` and succeed. For renewals that is correctly suppressed upstream by `shouldSendSubscriptionStartedEmailForActivation` (before we ever reach the email helper), so existing active subscribers do not get duplicate emails. For resubscribes — the exact cohort this fix exists for — a second email correctly fires. **Coordinates with adjacent beads.** - cloud-4lb (enrollWithCredits does not send subscription-started on credit activation) becomes trivial to land: pass the new period start to `maybeSendKiloClawSubscriptionStartedEmail` and per-activation dedupe already works for the credit path. - cloud-j1o (template copy hard-codes "first billing period") was previously moot because resubscribes never received the email. It is now a real product bug and should be addressed. **Does not touch.** Email rendering or templates, credit accounting, Stripe webhook parsing, subscription lifecycle state machine, top-up email flow (cloud-0zq), `softDeleteUser`/GDPR retention (the new column is a billing-period boundary, not PII; the retention test at `user.test.ts:1536` still passes). Manually verified: - `pnpm --filter web typecheck` passes - `pnpm --filter kiloclaw-billing typecheck` passes - `pnpm --filter web test -- purchase-emails` — 20/20 pass, including the new per-period admit-second-row test and the end-to-end activate→cancel→resubscribe test - `pnpm --filter web test -- user.test` — 59/59 pass, including the GDPR retention test - `pnpm --filter kiloclaw-billing test` — 60/60 pass - `pnpm format` clean Closes cloud-ib7. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 19 ++- apps/web/src/lib/purchase-emails.test.ts | 119 +++++++++++++++++- .../src/migrations/0106_noisy_pete_wisdom.sql | 3 + packages/db/src/schema.ts | 15 ++- 4 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 packages/db/src/migrations/0106_noisy_pete_wisdom.sql diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 70c6d259fd..5daa403da7 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -890,8 +890,13 @@ function planDisplayName(plan: 'commit' | 'standard'): string { } // Idempotency: insert-before-send on `kiloclaw_email_log` guarded by the -// unique index (user_id, instance_id, email_type). Only the first paid period -// inserts a row; renewals (which would attempt the same row) are skipped. +// unique index (user_id, instance_id, email_type, period_start). Each +// activation event (fresh `periodStart`) gets exactly one row; webhook +// replays of the same event collide on the index and return early. Because +// the KiloClaw subscription row is reused across cancel+resubscribe (both +// Stripe and credit paths UPDATE in place), period_start is what actually +// distinguishes a resubscribe's activation from the original — hence one +// email per activation, not one per instance lifetime. async function maybeSendKiloClawSubscriptionStartedEmail(params: { userId: string; instanceId: string; @@ -908,11 +913,12 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { user_id: userId, instance_id: instanceId, email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + period_start: periodStart, }) .onConflictDoNothing(); if ((insertResult.rowCount ?? 0) === 0) { - // Not the first paid period — renewal, nothing to send. + // This activation was already processed — webhook replay, nothing to send. return; } @@ -944,7 +950,9 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { extra: { user_id: userId, instance_id: instanceId, plan }, }); // Best-effort rollback so a retry can re-attempt — mirrors the pattern in - // apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts. + // apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts. Scope + // the delete to this activation's period so we only clear the marker we + // just inserted. try { await db .delete(kiloclaw_email_log) @@ -952,7 +960,8 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { and( eq(kiloclaw_email_log.user_id, userId), eq(kiloclaw_email_log.instance_id, instanceId), - eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE), + eq(kiloclaw_email_log.period_start, periodStart) ) ); } catch { diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 966f36ed64..eb940e5edc 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -264,9 +264,10 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { expect(KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE).toBe('kiloclaw_subscription_started'); }); - test('kiloclaw_email_log unique index prevents duplicate inserts for (user, instance, type)', async () => { - // Production code writes (user_id, instance_id, email_type) via the - // per-instance unique index, so test that exact shape here. + test('kiloclaw_email_log unique index dedupes webhook replays of the same activation', async () => { + // Production code writes (user_id, instance_id, email_type, period_start) + // via the per-instance/period unique index. A second insert with the same + // period_start collides (webhook replay). const user = await insertTestUser({}); const [instance] = await db .insert(kiloclaw_instances) @@ -276,12 +277,14 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { }) .returning(); + const periodStart = new Date().toISOString(); const first = await db .insert(kiloclaw_email_log) .values({ user_id: user.id, instance_id: instance.id, email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + period_start: periodStart, }) .onConflictDoNothing(); expect(first.rowCount).toBe(1); @@ -292,6 +295,7 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { user_id: user.id, instance_id: instance.id, email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + period_start: periodStart, }) .onConflictDoNothing(); expect(second.rowCount).toBe(0); @@ -308,6 +312,57 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { ); expect(rows).toHaveLength(1); }); + + test('kiloclaw_email_log unique index allows a second row for a new activation period', async () => { + // Cancel+resubscribe reuses the same kiloclaw_subscriptions row but + // stamps a fresh current_period_start, so the per-activation unique + // index admits a second row and a second email goes out. + const user = await insertTestUser({}); + const [instance] = await db + .insert(kiloclaw_instances) + .values({ + user_id: user.id, + sandbox_id: `test-sandbox-${crypto.randomUUID()}`, + }) + .returning(); + + const firstPeriodStart = new Date(Date.now() - 30 * 86_400_000).toISOString(); + const secondPeriodStart = new Date().toISOString(); + + const first = await db + .insert(kiloclaw_email_log) + .values({ + user_id: user.id, + instance_id: instance.id, + email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + period_start: firstPeriodStart, + }) + .onConflictDoNothing(); + expect(first.rowCount).toBe(1); + + const second = await db + .insert(kiloclaw_email_log) + .values({ + user_id: user.id, + instance_id: instance.id, + email_type: KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, + period_start: secondPeriodStart, + }) + .onConflictDoNothing(); + expect(second.rowCount).toBe(1); + + const rows = await db + .select() + .from(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, user.id), + eq(kiloclaw_email_log.instance_id, instance.id), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) + ) + ); + expect(rows).toHaveLength(2); + }); }); // ── Stripe-funded settlement → subscription-started email ────────────────── @@ -464,6 +519,64 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(1); }); + test('activate → cancel → resubscribe on same instance sends a second subscription-started email', async () => { + // Real-world flow: user activates, receives the email, cancels, then + // resubscribes later. Both paid activations on the same instance should + // each send a subscription-started email because each activation covers + // a different period. The per-instance lifetime dedupe (pre-fix) would + // suppress the second email. + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_resubscribe_${crypto.randomUUID()}`; + const { instance, subscription } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + // First activation: trial → paid. + const firstPeriodStart = new Date(Date.now() - 60 * 86_400_000).toISOString(); + const firstPeriodEnd = new Date(Date.now() - 30 * 86_400_000).toISOString(); + await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_first_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: firstPeriodStart, + periodEnd: firstPeriodEnd, + }); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + + sendMock.mockClear(); + + // Simulate cancellation: status=canceled on the same row. + await db + .update(kiloclaw_subscriptions) + .set({ status: 'canceled' }) + .where(eq(kiloclaw_subscriptions.id, subscription.id)); + + // Second activation (resubscribe): same row, fresh period boundaries. + const secondPeriodStart = new Date().toISOString(); + const secondPeriodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_second_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: secondPeriodStart, + periodEnd: secondPeriodEnd, + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(2); + }); + test('subscription.created before invoice.paid → settlement still sends one subscription-started email', async () => { // Realistic Stripe ordering: customer.subscription.created is processed // before invoice.paid. handleKiloClawSubscriptionCreated flips a non-hybrid diff --git a/packages/db/src/migrations/0106_noisy_pete_wisdom.sql b/packages/db/src/migrations/0106_noisy_pete_wisdom.sql new file mode 100644 index 0000000000..726432c56b --- /dev/null +++ b/packages/db/src/migrations/0106_noisy_pete_wisdom.sql @@ -0,0 +1,3 @@ +DROP INDEX "UQ_kiloclaw_email_log_user_instance_type";--> statement-breakpoint +ALTER TABLE "kiloclaw_email_log" ADD COLUMN "period_start" timestamp with time zone DEFAULT 'epoch' NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "UQ_kiloclaw_email_log_user_instance_type_period" ON "kiloclaw_email_log" USING btree ("user_id","instance_id","email_type","period_start") WHERE "kiloclaw_email_log"."instance_id" is not null; \ No newline at end of file diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 6075b0ce06..051e058c5a 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -4374,6 +4374,14 @@ export const kiloclaw_subscription_change_log = pgTable( export type KiloClawSubscriptionChangeLog = typeof kiloclaw_subscription_change_log.$inferSelect; export type NewKiloClawSubscriptionChangeLog = typeof kiloclaw_subscription_change_log.$inferInsert; +// Per-activation dedupe for instance-scoped emails (e.g. subscription-started) +// keys on the activation's `period_start`. The owning `kiloclaw_subscriptions` +// row is reused across cancel+resubscribe (both Stripe and credit paths +// UPDATE the existing row in place), so only `period_start` — which advances +// on every fresh activation — distinguishes activation events. `period_start` +// defaults to the Unix epoch so the unique-index math works for any future +// per-instance email type that has no natural per-activation boundary: those +// callers pass no value and collapse to one row per (user, instance, type). export const kiloclaw_email_log = pgTable( 'kiloclaw_email_log', { @@ -4386,14 +4394,17 @@ export const kiloclaw_email_log = pgTable( .references(() => kilocode_users.id), instance_id: uuid().references(() => kiloclaw_instances.id), email_type: text().notNull(), + period_start: timestamp({ withTimezone: true, mode: 'string' }) + .notNull() + .default(sql`'epoch'`), sent_at: timestamp({ withTimezone: true, mode: 'string' }).defaultNow().notNull(), }, table => [ uniqueIndex('UQ_kiloclaw_email_log_user_type_global') .on(table.user_id, table.email_type) .where(isNull(table.instance_id)), - uniqueIndex('UQ_kiloclaw_email_log_user_instance_type') - .on(table.user_id, table.instance_id, table.email_type) + uniqueIndex('UQ_kiloclaw_email_log_user_instance_type_period') + .on(table.user_id, table.instance_id, table.email_type, table.period_start) .where(isNotNull(table.instance_id)), index('IDX_kiloclaw_email_log_type_sent_instance') .on(table.email_type, table.sent_at, table.instance_id, table.user_id) From f6ee05801de5dd49bdcddec7101e0ea11f1129af Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 14:25:32 -0600 Subject: [PATCH 08/23] fix(emails): send kiloclaw subscription-started email on $0 settlements Per KiloClaw billing spec (Stripe-Funded Credit Settlement rule 10), $0 invoices must still run settlement and transition the row into the activated hybrid state. The subscription-started email is an activation notification, not a revenue side effect, so it must fire regardless of invoice amount. Revenue side effects (analytics, affiliate sale events) apply their own amount_paid > 0 guard in stripe-handlers.ts. Drops the amountMicrodollars > 0 gate on the email so users activated by a full coupon or promo still receive the activation notification. The existing '$0 Stripe settlement' test in purchase-emails.test.ts locks in this behavior. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 5daa403da7..f5a2c8d037 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -686,7 +686,14 @@ export async function applyStripeFundedKiloClawPeriod(params: { periodEnd, }))); - if (resolvedInstanceId && amountMicrodollars > 0 && shouldSendSubscriptionStartedEmail) { + // Per KiloClaw billing spec (Stripe-Funded Credit Settlement rule 10), + // $0 KiloClaw invoices must still run settlement and transition the row + // into the activated hybrid state. The subscription-started email is an + // activation notification, not a revenue side effect, so it must fire + // regardless of invoice amount. Revenue side effects (analytics, + // affiliate sale events) apply their own `amount_paid > 0` guard in + // stripe-handlers.ts. + if (resolvedInstanceId && shouldSendSubscriptionStartedEmail) { await maybeSendKiloClawSubscriptionStartedEmail({ userId, instanceId: resolvedInstanceId, From 0dfc67e097c589b2af774ebf7feb40da5546ca8d Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 14:25:43 -0600 Subject: [PATCH 09/23] fix(emails): clear kiloclaw subscription-started email log when provider not configured MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit maybeSendKiloClawSubscriptionStartedEmail inserted the kiloclaw_email_log marker before calling sendKiloClawSubscriptionStartedEmail and only deleted it if the send threw. When the provider returned {sent: false, reason: 'provider_not_configured'} without throwing (e.g. Mailgun env missing in a preview environment), the marker persisted and permanently suppressed the email on future webhook retries via the unique index guard. Inspect the SendResult and clear the marker on provider_not_configured so a retry can re-attempt. Mirrors the proven pattern in services/kiloclaw-billing/src/lifecycle.ts:879-884. neverbounce_rejected is deliberately left in place: the verdict is terminal for that address (invalid / disposable), so retrying would loop forever. Leaving the row keeps the outcome idempotent — we tried once, the address was rejected, we do not try again. Refactored the delete branch into deleteSubscriptionStartedEmailLog, reused by both the non-throwing failure path and the existing catch. Tests: one asserting the log row is cleared on provider_not_configured so a retry can re-send, one asserting the row persists on neverbounce_rejected so we do not retry a terminally invalid address. Widened sendMock's return type to SendResult so mockImplementationOnce can return {sent: false, reason: ...}. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 45 ++++++-- apps/web/src/lib/purchase-emails.test.ts | 120 +++++++++++++++++++- 2 files changed, 153 insertions(+), 12 deletions(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index f5a2c8d037..2e12bd49c8 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -943,13 +943,27 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { return; } - await sendKiloClawSubscriptionStartedEmail({ + const sendResult = await sendKiloClawSubscriptionStartedEmail({ to: user.email, planName: planDisplayName(plan), priceCents: amountCents, billingPeriod: formatBillingPeriod(periodStart, periodEnd), nextBillingDate: new Date(periodEnd), }); + + // If the provider is not configured (e.g. Mailgun env missing in a test or + // preview environment), the send never reached a real provider and a later + // webhook retry should be free to re-attempt. Clear the marker we just + // inserted so the unique-index guard does not permanently suppress the + // email. Mirrors services/kiloclaw-billing/src/lifecycle.ts:879-884. + // + // `neverbounce_rejected` is deliberately NOT cleared: NeverBounce's verdict + // is terminal for that address (invalid / disposable), so retrying would + // loop forever. Leaving the row keeps the behavior idempotent — we tried + // once, the address was rejected, we don't try again. + if (!sendResult.sent && sendResult.reason === 'provider_not_configured') { + await deleteSubscriptionStartedEmailLog({ userId, instanceId, periodStart }); + } } catch (error) { // Never fail the settlement flow because of an email error. captureException(error, { @@ -961,22 +975,31 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { // the delete to this activation's period so we only clear the marker we // just inserted. try { - await db - .delete(kiloclaw_email_log) - .where( - and( - eq(kiloclaw_email_log.user_id, userId), - eq(kiloclaw_email_log.instance_id, instanceId), - eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE), - eq(kiloclaw_email_log.period_start, periodStart) - ) - ); + await deleteSubscriptionStartedEmailLog({ userId, instanceId, periodStart }); } catch { // Leave the marker in place; we prefer missing one email over duplicate sends. } } } +async function deleteSubscriptionStartedEmailLog(params: { + userId: string; + instanceId: string; + periodStart: string; +}): Promise { + const { userId, instanceId, periodStart } = params; + await db + .delete(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, userId), + eq(kiloclaw_email_log.instance_id, instanceId), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE), + eq(kiloclaw_email_log.period_start, periodStart) + ) + ); +} + /** * Enroll a user's instance in a KiloClaw hosting plan funded by credits. * diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index eb940e5edc..084218a8ef 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -32,7 +32,9 @@ jest.mock('@/lib/firstTopupBonus', () => ({ // The helpers are replaced with jest.fns that forward the call via // sendMock using a synthetic `templateName` so existing tests that filter // by templateName continue to work. -const sendMock = jest.fn(async (_params: unknown) => ({ sent: true as const })); +const sendMock = jest.fn, [unknown]>(async () => ({ + sent: true, +})); jest.mock('@/lib/email', () => { const actual = jest.requireActual('@/lib/email'); @@ -519,6 +521,42 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(1); }); + test('$0 Stripe settlement (full coupon / promo) still sends one subscription-started email', async () => { + // Per KiloClaw billing spec Stripe-Funded Credit Settlement rule 10, + // `$0` KiloClaw invoices must still run settlement so Stripe-created + // subscriptions transition into the activated hybrid state. The + // subscription-started email is an activation notification, not a + // revenue side effect, so it must fire even when amount_paid is 0. + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_zero_amount_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `in_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 0, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + + const zeroAmountSend = sendMock.mock.calls + .map(([params]) => params as { templateName: string; templateVars: Record }) + .find(p => p.templateName === 'kiloClawSubscriptionStarted'); + expect(zeroAmountSend?.templateVars.price_usd).toBe('0.00'); + }); + test('activate → cancel → resubscribe on same instance sends a second subscription-started email', async () => { // Real-world flow: user activates, receives the email, cancels, then // resubscribes later. Both paid activations on the same instance should @@ -815,6 +853,86 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(1); }); + test('provider_not_configured → email log row is cleared so a retry can re-attempt', async () => { + // Regression: maybeSendKiloClawSubscriptionStartedEmail used to insert the + // kiloclaw_email_log marker before calling the provider, and only delete + // it if the send threw. When the provider returned {sent: false, + // reason: 'provider_not_configured'} without throwing, the marker + // remained and permanently suppressed the email on future retries. + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_provider_unconfigured_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + sendMock.mockImplementationOnce(async () => ({ + sent: false, + reason: 'provider_not_configured' as const, + })); + + const periodStart = new Date().toISOString(); + const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + + expect(applied).toBe(true); + // We did attempt to send exactly once, but the provider wasn't configured. + expect(countSubscriptionStartedSends()).toBe(1); + // The marker row must be gone so a later retry can re-attempt — the + // unique index would otherwise permanently suppress this activation. + expect(await countEmailLogRows(user.id, instance.id)).toBe(0); + }); + + test('neverbounce_rejected → email log row is retained so we do not retry a terminally invalid address', async () => { + // NeverBounce's "invalid" / "disposable" verdict is terminal for that + // address; retrying would loop forever. Leaving the kiloclaw_email_log + // row in place makes the outcome idempotent: we tried once, the address + // was rejected, we don't try again. + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_neverbounce_rejected_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'trialing', + plan: 'trial', + stripeSubscriptionId, + }); + + sendMock.mockImplementationOnce(async () => ({ + sent: false, + reason: 'neverbounce_rejected' as const, + })); + + const periodStart = new Date().toISOString(); + const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart, + periodEnd, + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(1); + expect(await countEmailLogRows(user.id, instance.id)).toBe(1); + }); + test('stale duplicate recovery guard → old change-log row outside the window does not trigger a recovered email', async () => { const user = await insertTestUser({}); const stripeSubscriptionId = `sub_stale_${crypto.randomUUID()}`; From 69e114bbd9cda8e6e676389dd19afa7c8cf53f01 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 14:28:11 -0600 Subject: [PATCH 10/23] chore(emails): replace import() type annotation with top-level type import in purchase-emails test oxlint's consistent-type-imports rule forbids inline import() type annotations. Convert to a top-level 'import type * as creditBillingModule' at the file header, matching the existing pattern used for emailModule. --- apps/web/src/lib/purchase-emails.test.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 084218a8ef..1306dc44cf 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -14,6 +14,7 @@ import { KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS, } from '@/lib/kiloclaw/credit-billing'; +import type * as creditBillingModule from '@/lib/kiloclaw/credit-billing'; import type * as emailModule from '@/lib/email'; import { renderTemplate, @@ -378,9 +379,7 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { }); async function applyStripeFundedKiloClawPeriod( - params: Parameters< - typeof import('@/lib/kiloclaw/credit-billing').applyStripeFundedKiloClawPeriod - >[0] + params: Parameters[0] ): Promise { const mod = await import('@/lib/kiloclaw/credit-billing'); return mod.applyStripeFundedKiloClawPeriod(params); From 373e0def50d717ff52b89cb791f0b25ae8dde085 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 15:26:21 -0600 Subject: [PATCH 11/23] test(emails): mock Mailgun transport instead of helper exports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactors purchase-emails.test.ts to mock sendViaMailgun and verifyEmail so every test exercises the real sendCreditsTopUpEmail and sendKiloClawSubscriptionStartedEmail code paths — including formatUsd rounding, formatDate formatting, subjectOverride selection, and credits_url / manage_url / receipt_section construction. Previously the helpers themselves were mocked with synthetic implementations, so a rename like receipt_url → receipt_section would ship green. Adds direct payload tests for both helpers covering the happy path, neverbounce rejection, provider_not_configured, null receipt URL, and the zero-cent price case. --- apps/web/src/lib/purchase-emails.test.ts | 329 ++++++++++++++++------- 1 file changed, 232 insertions(+), 97 deletions(-) diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 1306dc44cf..584f595d7e 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -15,11 +15,12 @@ import { SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS, } from '@/lib/kiloclaw/credit-billing'; import type * as creditBillingModule from '@/lib/kiloclaw/credit-billing'; -import type * as emailModule from '@/lib/email'; import { renderTemplate, buildCreditsTopUpReceiptSection, subjects, + sendCreditsTopUpEmail, + sendKiloClawSubscriptionStartedEmail, type TemplateName, } from '@/lib/email'; @@ -28,62 +29,23 @@ jest.mock('@/lib/firstTopupBonus', () => ({ processFirstTopupBonus: jest.fn(), })); -// Count email sends via the shared `send` export as well as the -// `sendKiloClawSubscriptionStartedEmail` / `sendCreditsTopUpEmail` helpers. -// The helpers are replaced with jest.fns that forward the call via -// sendMock using a synthetic `templateName` so existing tests that filter -// by templateName continue to work. -const sendMock = jest.fn, [unknown]>(async () => ({ - sent: true, +// Mock the outbound transport (Mailgun) and the upstream address-validity +// check (NeverBounce) rather than the helper exports. This way the real +// `send()`, `sendCreditsTopUpEmail`, and `sendKiloClawSubscriptionStartedEmail` +// wiring — formatUsd rounding, formatDate formatting, subjectOverride, +// credits_url / manage_url construction, receipt_section rendering — is +// exercised on every test, so a regression in any of that wiring fails here. +type SendViaMailgunParams = { to: string; subject: string; html: string; replyTo?: string }; +const sendViaMailgunMock = jest.fn, [SendViaMailgunParams]>(async () => true); +const verifyEmailMock = jest.fn, [string]>(async () => true); + +jest.mock('@/lib/email-mailgun', () => ({ + sendViaMailgun: (params: SendViaMailgunParams) => sendViaMailgunMock(params), })); -jest.mock('@/lib/email', () => { - const actual = jest.requireActual('@/lib/email'); - return { - ...actual, - send: jest.fn((params: unknown) => sendMock(params)), - sendCreditsTopUpEmail: jest.fn( - async (props: { - to: string; - variant: 'auto' | 'manual'; - amountCents: number; - creditsCents: number; - purchaseDate: Date; - receiptUrl: string | null; - }) => - sendMock({ - to: props.to, - templateName: 'creditsTopUp', - templateVars: { - amount_usd: (props.amountCents / 100).toFixed(2), - credits_usd: (props.creditsCents / 100).toFixed(2), - receipt_url: props.receiptUrl, - variant: props.variant, - }, - subjectOverride: props.variant === 'auto' ? 'Kilo auto top-up successful' : undefined, - }) - ), - sendKiloClawSubscriptionStartedEmail: jest.fn( - async (props: { - to: string; - planName: string; - priceCents: number; - billingPeriod: string; - nextBillingDate: Date; - }) => - sendMock({ - to: props.to, - templateName: 'kiloClawSubscriptionStarted', - templateVars: { - plan_name: props.planName, - price_usd: (props.priceCents / 100).toFixed(2), - billing_period: props.billingPeriod, - next_billing_date: props.nextBillingDate.toISOString(), - }, - }) - ), - }; -}); +jest.mock('@/lib/email-neverbounce', () => ({ + verifyEmail: (email: string) => verifyEmailMock(email), +})); // Receipt URL lookups during unit tests must not touch Stripe. jest.mock('@/lib/stripe-client', () => ({ @@ -172,13 +134,35 @@ describe('subjects map', () => { }); }); +// Each template has a unique subject line (or a documented subjectOverride), +// so we discriminate Mailgun calls by subject rather than by templateName. +const CREDITS_TOPUP_MANUAL_SUBJECT = subjects.creditsTopUp; +const CREDITS_TOPUP_AUTO_SUBJECT = 'Kilo auto top-up successful'; +const KILOCLAW_SUBSCRIPTION_STARTED_SUBJECT = subjects.kiloClawSubscriptionStarted; + +function creditsTopUpSends(): SendViaMailgunParams[] { + return sendViaMailgunMock.mock.calls + .map(([params]) => params) + .filter( + p => p.subject === CREDITS_TOPUP_MANUAL_SUBJECT || p.subject === CREDITS_TOPUP_AUTO_SUBJECT + ); +} + +function subscriptionStartedSends(): SendViaMailgunParams[] { + return sendViaMailgunMock.mock.calls + .map(([params]) => params) + .filter(p => p.subject === KILOCLAW_SUBSCRIPTION_STARTED_SUBJECT); +} + describe('processTopUp credit top-up email', () => { beforeEach(() => { - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); }); afterEach(() => { - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); }); test('sends credit top-up email once for a successful manual top-up', async () => { @@ -194,13 +178,14 @@ describe('processTopUp credit top-up email', () => { }); expect(first).toBe(true); - const topUpSends = sendMock.mock.calls - .map(([params]) => params as { templateName: string; templateVars: Record }) - .filter(p => p.templateName === 'creditsTopUp'); + const topUpSends = creditsTopUpSends(); expect(topUpSends).toHaveLength(1); - expect(topUpSends[0].templateVars.amount_usd).toBe('15.00'); + // $15.00 comes from formatUsd(1500) in the real helper — if formatUsd + // rounding regresses, this assertion fails. + expect(topUpSends[0].html).toContain('$15.00 USD'); + expect(topUpSends[0].to).toBe(user.google_user_email); - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); // Retry / webhook replay with the same stripe_payment_id must not re-send. const second = await processTopUp(user, 1500, { @@ -209,10 +194,7 @@ describe('processTopUp credit top-up email', () => { }); expect(second).toBe(false); - const replayTopUpSends = sendMock.mock.calls - .map(([params]) => params as { templateName: string }) - .filter(p => p.templateName === 'creditsTopUp'); - expect(replayTopUpSends).toHaveLength(0); + expect(creditsTopUpSends()).toHaveLength(0); }); test('uses auto-top-up copy when isAutoTopUp is true', async () => { @@ -228,10 +210,13 @@ describe('processTopUp credit top-up email', () => { { isAutoTopUp: true } ); - const call = sendMock.mock.calls - .map(([params]) => params as { templateName: string; subjectOverride?: string }) - .find(p => p.templateName === 'creditsTopUp'); - expect(call?.subjectOverride).toBe('Kilo auto top-up successful'); + const autoSend = sendViaMailgunMock.mock.calls + .map(([params]) => params) + .find(p => p.subject === CREDITS_TOPUP_AUTO_SUBJECT); + expect(autoSend).toBeTruthy(); + // The auto variant also swaps the heading/intro copy — prove both the + // subject override and the templateVars wiring land. + expect(autoSend?.html).toContain('Your auto top-up was successful'); }); test('does not send an email when skipPostTopUpFreeStuff is true', async () => { @@ -247,10 +232,7 @@ describe('processTopUp credit top-up email', () => { { skipPostTopUpFreeStuff: true } ); - const topUpSends = sendMock.mock.calls - .map(([params]) => params as { templateName: string }) - .filter(p => p.templateName === 'creditsTopUp'); - expect(topUpSends).toHaveLength(0); + expect(creditsTopUpSends()).toHaveLength(0); // Sanity: the credit transaction was still recorded. const [txn] = await db @@ -372,10 +354,12 @@ describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { beforeEach(() => { - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); }); afterEach(() => { - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); }); async function applyStripeFundedKiloClawPeriod( @@ -422,10 +406,7 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { } function countSubscriptionStartedSends(): number { - return sendMock.mock.calls.filter( - ([params]) => - (params as { templateName: string }).templateName === 'kiloClawSubscriptionStarted' - ).length; + return subscriptionStartedSends().length; } async function countEmailLogRows(userId: string, instanceId: string): Promise { @@ -550,10 +531,10 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(countSubscriptionStartedSends()).toBe(1); expect(await countEmailLogRows(user.id, instance.id)).toBe(1); - const zeroAmountSend = sendMock.mock.calls - .map(([params]) => params as { templateName: string; templateVars: Record }) - .find(p => p.templateName === 'kiloClawSubscriptionStarted'); - expect(zeroAmountSend?.templateVars.price_usd).toBe('0.00'); + const zeroAmountSend = subscriptionStartedSends()[0]; + expect(zeroAmountSend).toBeTruthy(); + // formatUsd(0) must render "0.00", not "0" or "NaN" — real helper wiring. + expect(zeroAmountSend.html).toContain('$0.00 USD'); }); test('activate → cancel → resubscribe on same instance sends a second subscription-started email', async () => { @@ -587,7 +568,8 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(countSubscriptionStartedSends()).toBe(1); expect(await countEmailLogRows(user.id, instance.id)).toBe(1); - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); // Simulate cancellation: status=canceled on the same row. await db @@ -778,7 +760,8 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { }); expect(countSubscriptionStartedSends()).toBe(1); - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); // Same stripe_payment_id → processTopUp returns false (duplicate credit), // so we take the duplicate-recovery path. The kiloclaw_email_log row from @@ -835,7 +818,8 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) ) ); - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); await applyStripeFundedKiloClawPeriod({ userId: user.id, @@ -867,10 +851,9 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { stripeSubscriptionId, }); - sendMock.mockImplementationOnce(async () => ({ - sent: false, - reason: 'provider_not_configured' as const, - })); + // Mailgun returns false when MAILGUN_API_KEY/MAILGUN_DOMAIN are missing, + // which `send()` translates into { sent: false, reason: 'provider_not_configured' }. + sendViaMailgunMock.mockImplementationOnce(async () => false); const periodStart = new Date().toISOString(); const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); @@ -908,10 +891,9 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { stripeSubscriptionId, }); - sendMock.mockImplementationOnce(async () => ({ - sent: false, - reason: 'neverbounce_rejected' as const, - })); + // NeverBounce returns false for invalid/disposable addresses, which + // `send()` translates into { sent: false, reason: 'neverbounce_rejected' }. + verifyEmailMock.mockImplementationOnce(async () => false); const periodStart = new Date().toISOString(); const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); @@ -928,7 +910,11 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { }); expect(applied).toBe(true); - expect(countSubscriptionStartedSends()).toBe(1); + // verifyEmail was called once for this user (the send attempt), + // so we did try. sendViaMailgun was not reached because verification + // rejected the address — which is the whole point of this branch. + expect(verifyEmailMock).toHaveBeenCalledWith(user.google_user_email); + expect(countSubscriptionStartedSends()).toBe(0); expect(await countEmailLogRows(user.id, instance.id)).toBe(1); }); @@ -982,7 +968,8 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) ) ); - sendMock.mockClear(); + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); await applyStripeFundedKiloClawPeriod({ userId: user.id, @@ -999,3 +986,151 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(0); }); }); + +// ── Direct helper payload tests ──────────────────────────────────────────── +// The surrounding describe blocks mock @/lib/email-mailgun and exercise the +// real helpers through production call sites. These tests assert the exact +// Mailgun payload the helpers emit, protecting: +// - formatUsd rounding +// - formatDate formatting +// - purchase_date / credits_url / receipt_section / manage_url mapping +// - subjectOverride selection + +describe('sendCreditsTopUpEmail payload', () => { + beforeEach(() => { + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); + }); + + test('manual variant emits the canonical subject, formatted amounts, and a receipt link', async () => { + const result = await sendCreditsTopUpEmail({ + to: 'recipient@example.com', + variant: 'manual', + amountCents: 1500, + creditsCents: 1500, + purchaseDate: new Date('2026-01-15T12:00:00Z'), + receiptUrl: 'https://pay.stripe.com/receipts/abc', + }); + + expect(result).toEqual({ sent: true }); + expect(sendViaMailgunMock).toHaveBeenCalledTimes(1); + const [params] = sendViaMailgunMock.mock.calls[0]; + expect(params.to).toBe('recipient@example.com'); + expect(params.subject).toBe(subjects.creditsTopUp); + // formatUsd(1500) rounding. + expect(params.html).toContain('$15.00 USD'); + // formatDate en-US UTC formatting. + expect(params.html).toContain('January 15, 2026'); + // credits_url construction (NEXTAUTH_URL + '/credits' — http://localhost:3000 in tests). + expect(params.html).toContain('/credits'); + // receipt_section rendering (RawHtml, escaped href). + expect(params.html).toContain('https://pay.stripe.com/receipts/abc'); + expect(params.html).toContain('View your Stripe receipt'); + }); + + test('auto variant overrides the subject and swaps the heading copy', async () => { + await sendCreditsTopUpEmail({ + to: 'recipient@example.com', + variant: 'auto', + amountCents: 2000, + creditsCents: 2000, + purchaseDate: new Date('2026-02-01T00:00:00Z'), + receiptUrl: null, + }); + + const [params] = sendViaMailgunMock.mock.calls[0]; + expect(params.subject).toBe('Kilo auto top-up successful'); + expect(params.html).toContain('Your auto top-up was successful'); + // No receipt URL → receipt section empty, "View your Stripe receipt" absent. + expect(params.html).not.toContain('View your Stripe receipt'); + }); + + test('null receipt URL renders an empty receipt section without breaking the template', async () => { + await sendCreditsTopUpEmail({ + to: 'recipient@example.com', + variant: 'manual', + amountCents: 500, + creditsCents: 500, + purchaseDate: new Date('2026-03-01T00:00:00Z'), + receiptUrl: null, + }); + + const [params] = sendViaMailgunMock.mock.calls[0]; + expect(params.html).toContain('$5.00 USD'); + expect(params.html).not.toContain('View your Stripe receipt'); + }); + + test('neverbounce rejection short-circuits before Mailgun is called', async () => { + verifyEmailMock.mockImplementationOnce(async () => false); + + const result = await sendCreditsTopUpEmail({ + to: 'bad@example.com', + variant: 'manual', + amountCents: 1000, + creditsCents: 1000, + purchaseDate: new Date(), + receiptUrl: null, + }); + + expect(result).toEqual({ sent: false, reason: 'neverbounce_rejected' }); + expect(sendViaMailgunMock).not.toHaveBeenCalled(); + }); + + test('mailgun misconfiguration surfaces as provider_not_configured', async () => { + sendViaMailgunMock.mockImplementationOnce(async () => false); + + const result = await sendCreditsTopUpEmail({ + to: 'recipient@example.com', + variant: 'manual', + amountCents: 1000, + creditsCents: 1000, + purchaseDate: new Date(), + receiptUrl: null, + }); + + expect(result).toEqual({ sent: false, reason: 'provider_not_configured' }); + }); +}); + +describe('sendKiloClawSubscriptionStartedEmail payload', () => { + beforeEach(() => { + sendViaMailgunMock.mockClear(); + verifyEmailMock.mockClear(); + }); + + test('emits the canonical subject, formatted price/next-billing-date, and the manage link', async () => { + const result = await sendKiloClawSubscriptionStartedEmail({ + to: 'recipient@example.com', + planName: 'KiloClaw Standard', + priceCents: 900, + billingPeriod: 'Jan 15, 2026 – Feb 15, 2026', + nextBillingDate: new Date('2026-02-15T00:00:00Z'), + }); + + expect(result).toEqual({ sent: true }); + const [params] = sendViaMailgunMock.mock.calls[0]; + expect(params.to).toBe('recipient@example.com'); + expect(params.subject).toBe(subjects.kiloClawSubscriptionStarted); + expect(params.html).toContain('KiloClaw Standard'); + // formatUsd(900). + expect(params.html).toContain('$9.00 USD'); + expect(params.html).toContain('Jan 15, 2026 – Feb 15, 2026'); + // formatDate(next billing). + expect(params.html).toContain('February 15, 2026'); + // manage_url construction (NEXTAUTH_URL + '/claw'). + expect(params.html).toContain('/claw'); + }); + + test('zero-cent price still renders "$0.00 USD" (formatUsd rounding)', async () => { + await sendKiloClawSubscriptionStartedEmail({ + to: 'recipient@example.com', + planName: 'KiloClaw Standard', + priceCents: 0, + billingPeriod: 'Jan 1, 2026 – Feb 1, 2026', + nextBillingDate: new Date('2026-02-01T00:00:00Z'), + }); + + const [params] = sendViaMailgunMock.mock.calls[0]; + expect(params.html).toContain('$0.00 USD'); + }); +}); From acf3d7a8f634d399c5673cdbd1aad05a644b044d Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 15:45:28 -0600 Subject: [PATCH 12/23] fix(emails): neutralize 'first billing period' copy in subscription-started email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The template hard-coded 'Your first billing period for KiloClaw hosting has started', but the subscription-started email is intentionally sent on every activation — including resubscribes after cancellation (per the per-activation period_start dedupe landed in cloud-ib7). For those resubscribers the 'first' language is factually wrong. Replace with neutral wording ('A KiloClaw hosting billing period has started') that is correct for both trial→paid and canceled→resubscribe activations and aligns with the transactional content guideline in apps/web/src/emails/AGENTS.md. Closes cloud-j1o. --- apps/web/src/emails/kiloClawSubscriptionStarted.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/emails/kiloClawSubscriptionStarted.html b/apps/web/src/emails/kiloClawSubscriptionStarted.html index 63d0b6f290..d9ee766ddf 100644 --- a/apps/web/src/emails/kiloClawSubscriptionStarted.html +++ b/apps/web/src/emails/kiloClawSubscriptionStarted.html @@ -51,7 +51,7 @@ sans-serif; " > - Your first billing period for KiloClaw hosting has started. Here are the details: + A KiloClaw hosting billing period has started. Here are the details:

From d6fe7c57c788d942915d9e7979d51affcd086bf3 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 15:45:42 -0600 Subject: [PATCH 13/23] fix(emails): recover top-up confirmation email on duplicate webhook retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit processTopUp commits the credit_transactions row and then fires the top-up confirmation email via after(). If the process exited between those two steps, the credit-transactions unique index deduped the credit on webhook retry but the email was lost — the retry bailed early on the duplicate insert before reaching the email step. Add a top_up_email_log outbox marker keyed by stripe_payment_id and run the same marker-gated send on both first attempts and duplicate-webhook retries. Mirrors the existing maybeSendKiloClawSubscriptionStartedEmail pattern in credit-billing.ts: - First-attempt send inserts the marker before sending. - Duplicate-webhook path observes the committed credit, attempts the same marker-gated send, and only fires if no prior send has been recorded. - skipPostTopUpFreeStuff is respected on the retry path so Kilo-Pass-style internal reuses of processTopUp cannot send user-facing top-up emails. - provider_not_configured clears the marker so future retries can re-attempt; neverbounce_rejected is intentionally kept as a terminal state. Retain top_up_email_log rows on softDeleteUser (financial outbox record, no PII beyond user_id which references the anonymized user row). Added GDPR retention test. Generated migration 0107_magical_rattler. --- apps/web/src/lib/credits.ts | 94 +++++++++++++-- apps/web/src/lib/purchase-emails.test.ts | 112 ++++++++++++++++++ apps/web/src/lib/user.test.ts | 21 ++++ apps/web/src/lib/user.ts | 1 + .../src/migrations/0107_magical_rattler.sql | 9 ++ packages/db/src/migrations/meta/_journal.json | 7 ++ packages/db/src/schema.ts | 27 +++++ 7 files changed, 260 insertions(+), 11 deletions(-) create mode 100644 packages/db/src/migrations/0107_magical_rattler.sql diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index 1906c1d08a..f62bda1bac 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -1,4 +1,4 @@ -import { credit_transactions } from '@kilocode/db/schema'; +import { credit_transactions, top_up_email_log } from '@kilocode/db/schema'; import type { User } from '@kilocode/db/schema'; import { kilocode_users } from '@kilocode/db/schema'; @@ -89,8 +89,24 @@ export async function processTopUp( .insert(credit_transactions) .values(creditTransactionOptions) .onConflictDoNothing(); - if (attemptToInsert.rowCount === 0) { - //violated one of the unique constraints, i.e. this credit is already in the queue. + const didInsertCreditTransaction = (attemptToInsert.rowCount ?? 0) > 0; + + if (!didInsertCreditTransaction) { + // A prior processTopUp call already committed the credit transaction for + // this stripe_payment_id (duplicate webhook / retry). The credit itself + // is idempotent, but the confirmation email is not guaranteed to have + // been sent — the original process could have exited between the credit + // commit and `after(processPostTopUpFreeStuff)`. Attempt to recover the + // email via the durable top_up_email_log marker. If a marker already + // exists the insert collides and no second email is sent. + if (!skipPostTopUpFreeStuff) { + await recoverTopUpConfirmationEmailIfMissing({ + user, + amountInCents, + stripeChargeOrInvoiceId: config.stripe_payment_id, + isAutoTopUp, + }); + } return false; } @@ -115,7 +131,7 @@ export async function processTopUp( }); } - await sendTopUpConfirmationEmail({ + await maybeSendTopUpConfirmationEmail({ user, amountInCents, stripeChargeOrInvoiceId: config.stripe_payment_id, @@ -130,12 +146,15 @@ export async function processTopUp( return true; } -// Idempotency: this function runs at most once per successful top-up because -// `processTopUp` is guarded by a unique constraint on -// `credit_transactions.stripe_payment_id` and only invokes its post-processing -// block on the row that actually inserted. Any later webhook retry for the -// same Stripe payment returns early with `false` before reaching here. -async function sendTopUpConfirmationEmail(params: { +// Idempotency is enforced by the unique index on +// `top_up_email_log.stripe_payment_id`. Every send attempt — first-attempt +// and webhook-retry recovery — first inserts a marker row with +// `onConflictDoNothing()`. A rowCount of 0 means an earlier attempt already +// sent the email, so we bail without sending again. If the provider was not +// configured (e.g. Mailgun env missing in preview/test), the marker is +// cleared so a future retry can re-attempt. Mirrors +// `maybeSendKiloClawSubscriptionStartedEmail` in credit-billing.ts. +async function maybeSendTopUpConfirmationEmail(params: { user: User; amountInCents: number; stripeChargeOrInvoiceId: string; @@ -143,8 +162,21 @@ async function sendTopUpConfirmationEmail(params: { }): Promise { const { user, amountInCents, stripeChargeOrInvoiceId, isAutoTopUp } = params; try { + const insertResult = await db + .insert(top_up_email_log) + .values({ + stripe_payment_id: stripeChargeOrInvoiceId, + user_id: user.id, + }) + .onConflictDoNothing(); + + if ((insertResult.rowCount ?? 0) === 0) { + // An earlier attempt already sent this top-up email. Don't re-send. + return; + } + const receiptUrl = await resolveStripeReceiptUrl(stripeChargeOrInvoiceId); - await sendCreditsTopUpEmail({ + const sendResult = await sendCreditsTopUpEmail({ to: user.google_user_email, variant: isAutoTopUp ? 'auto' : 'manual', amountCents: amountInCents, @@ -152,14 +184,54 @@ async function sendTopUpConfirmationEmail(params: { purchaseDate: new Date(), receiptUrl, }); + + // `neverbounce_rejected` is deliberately NOT cleared: NeverBounce's verdict + // is terminal for that address, so retrying would loop forever. Keep the + // marker so we never try again for this payment. + if (!sendResult.sent && sendResult.reason === 'provider_not_configured') { + await deleteTopUpEmailLog(stripeChargeOrInvoiceId); + } } catch (error) { captureException(error, { tags: { source: 'credits_topup_email' }, extra: { kilo_user_id: user.id, stripeChargeOrInvoiceId, isAutoTopUp }, }); + // Best-effort rollback so a retry can re-attempt — mirrors the pattern in + // `maybeSendKiloClawSubscriptionStartedEmail`. + try { + await deleteTopUpEmailLog(stripeChargeOrInvoiceId); + } catch { + // Leave the marker in place; we prefer missing one email over duplicate sends. + } } } +// Called from the duplicate-webhook path in `processTopUp`, where the credit +// transaction is already committed but the first attempt may have exited +// before sending the email. Runs the same marker-gated send so a successful +// prior send still dedupes on the unique index. +async function recoverTopUpConfirmationEmailIfMissing(params: { + user: User; + amountInCents: number; + stripeChargeOrInvoiceId: string; + isAutoTopUp: boolean; +}): Promise { + // Reuse the same gated-send path. The marker insert with + // onConflictDoNothing() naturally skips when the original attempt already + // sent, and fires the email when it didn't. + if (IS_IN_AUTOMATED_TEST) { + await maybeSendTopUpConfirmationEmail(params); + } else { + after(() => maybeSendTopUpConfirmationEmail(params)); + } +} + +async function deleteTopUpEmailLog(stripeChargeOrInvoiceId: string): Promise { + await db + .delete(top_up_email_log) + .where(eq(top_up_email_log.stripe_payment_id, stripeChargeOrInvoiceId)); +} + async function resolveStripeReceiptUrl(stripeChargeOrInvoiceId: string): Promise { // Skip outbound Stripe calls in automated tests — they are expensive, // flake-prone, and unnecessary for exercising the email path. diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 584f595d7e..01e69e39aa 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -5,6 +5,7 @@ import { kiloclaw_instances, kiloclaw_subscription_change_log, kiloclaw_subscriptions, + top_up_email_log, } from '@kilocode/db/schema'; import { insertKiloClawSubscriptionChangeLog } from '@kilocode/db'; import { db } from '@/lib/drizzle'; @@ -242,6 +243,117 @@ describe('processTopUp credit top-up email', () => { .limit(1); expect(txn).toBeTruthy(); }); + + test('recovers confirmation email on webhook retry when first attempt did not send', async () => { + // Simulate the failure mode: the first processTopUp committed the credit + // transaction but exited before firing the email (no top_up_email_log + // marker). A webhook retry must observe the missing marker and send. + const user = await insertTestUser({ + total_microdollars_acquired: 0, + microdollars_used: 0, + }); + + const stripePaymentId = `ch_recover_${Date.now()}_${Math.random()}`; + + await db.insert(credit_transactions).values({ + id: crypto.randomUUID(), + kilo_user_id: user.id, + is_free: false, + amount_microdollars: 1500 * 10_000, + description: 'Top-up via stripe', + original_baseline_microdollars_used: 0, + stripe_payment_id: stripePaymentId, + }); + + // Retry arrives. processTopUp sees the credit is already committed and + // should attempt marker-gated email recovery. + const retry = await processTopUp(user, 1500, { + type: 'stripe', + stripe_payment_id: stripePaymentId, + }); + expect(retry).toBe(false); + + expect(creditsTopUpSends()).toHaveLength(1); + + // Marker row exists to prevent a third attempt from re-sending. + const [marker] = await db + .select({ id: top_up_email_log.id }) + .from(top_up_email_log) + .where(eq(top_up_email_log.stripe_payment_id, stripePaymentId)) + .limit(1); + expect(marker).toBeTruthy(); + + sendViaMailgunMock.mockClear(); + + // A third retry must observe the marker and skip. + const thirdAttempt = await processTopUp(user, 1500, { + type: 'stripe', + stripe_payment_id: stripePaymentId, + }); + expect(thirdAttempt).toBe(false); + expect(creditsTopUpSends()).toHaveLength(0); + }); + + test('writes a top_up_email_log marker on first-attempt send', async () => { + const user = await insertTestUser({ + total_microdollars_acquired: 0, + microdollars_used: 0, + }); + + const stripePaymentId = `ch_marker_${Date.now()}_${Math.random()}`; + const first = await processTopUp(user, 1500, { + type: 'stripe', + stripe_payment_id: stripePaymentId, + }); + expect(first).toBe(true); + + const [marker] = await db + .select({ stripe_payment_id: top_up_email_log.stripe_payment_id }) + .from(top_up_email_log) + .where(eq(top_up_email_log.stripe_payment_id, stripePaymentId)) + .limit(1); + expect(marker).toEqual({ stripe_payment_id: stripePaymentId }); + }); + + test('recovery path skips email when skipPostTopUpFreeStuff is true on retry', async () => { + // Guards against the edge case where a retry caller passes + // skipPostTopUpFreeStuff: true (e.g. a Kilo Pass flow re-using + // processTopUp as a primitive) — we must not send a user-facing top-up + // confirmation email just because a marker was missing. + const user = await insertTestUser({ + total_microdollars_acquired: 0, + microdollars_used: 0, + }); + + const stripePaymentId = `ch_skip_retry_${Date.now()}_${Math.random()}`; + + await db.insert(credit_transactions).values({ + id: crypto.randomUUID(), + kilo_user_id: user.id, + is_free: false, + amount_microdollars: 1500 * 10_000, + description: 'Top-up via stripe', + original_baseline_microdollars_used: 0, + stripe_payment_id: stripePaymentId, + }); + + const retry = await processTopUp( + user, + 1500, + { type: 'stripe', stripe_payment_id: stripePaymentId }, + { skipPostTopUpFreeStuff: true } + ); + expect(retry).toBe(false); + + expect(creditsTopUpSends()).toHaveLength(0); + + const [marker] = await db + .select({ id: top_up_email_log.id }) + .from(top_up_email_log) + .where(eq(top_up_email_log.stripe_payment_id, stripePaymentId)) + .limit(1); + expect(marker).toBeUndefined(); + }); }); describe('KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE constant', () => { diff --git a/apps/web/src/lib/user.test.ts b/apps/web/src/lib/user.test.ts index c41f510a00..45fd9599f5 100644 --- a/apps/web/src/lib/user.test.ts +++ b/apps/web/src/lib/user.test.ts @@ -38,6 +38,7 @@ import { kiloclaw_earlybird_purchases, kiloclaw_subscriptions, kiloclaw_email_log, + top_up_email_log, kiloclaw_cli_runs, bot_requests, bot_request_cloud_agent_sessions, @@ -116,6 +117,7 @@ describe('User', () => { await db.delete(stytch_fingerprints); await db.delete(kiloclaw_cli_runs); await db.delete(kiloclaw_email_log); + await db.delete(top_up_email_log); await db.delete(kiloclaw_version_pins); await db.delete(kiloclaw_image_catalog); await db.delete(kiloclaw_subscriptions); @@ -1715,6 +1717,25 @@ describe('User', () => { ).toBe(1); }); + it('should retain top_up_email_log rows for the user', async () => { + const user = await insertTestUser(); + + await db.insert(top_up_email_log).values({ + user_id: user.id, + stripe_payment_id: `ch_retain_${randomUUID()}`, + }); + + await softDeleteUser(user.id); + + expect( + await db + .select({ count: count() }) + .from(top_up_email_log) + .where(eq(top_up_email_log.user_id, user.id)) + .then(r => r[0].count) + ).toBe(1); + }); + it('should throw SoftDeletePreconditionError for active KiloClaw subscription', async () => { const user = await insertTestUser(); await db.insert(kiloclaw_subscriptions).values({ diff --git a/apps/web/src/lib/user.ts b/apps/web/src/lib/user.ts index 4f86021f7b..d5a929ad9d 100644 --- a/apps/web/src/lib/user.ts +++ b/apps/web/src/lib/user.ts @@ -594,6 +594,7 @@ export class SoftDeletePreconditionError extends Error { * - referral_code_usages (financial, references anonymized user) * - kiloclaw_subscriptions, kiloclaw_earlybird_purchases, kiloclaw_email_log (retained records) * - kiloclaw_scheduled_action_targets (retained operational records; + * - top_up_email_log (retained outbox marker, financial record) * user_id FK references the anonymized kilocode_users row — no PII * stored directly on the target row) * diff --git a/packages/db/src/migrations/0107_magical_rattler.sql b/packages/db/src/migrations/0107_magical_rattler.sql new file mode 100644 index 0000000000..92833ff6a7 --- /dev/null +++ b/packages/db/src/migrations/0107_magical_rattler.sql @@ -0,0 +1,9 @@ +CREATE TABLE "top_up_email_log" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "stripe_payment_id" text NOT NULL, + "user_id" text NOT NULL, + "sent_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "top_up_email_log" ADD CONSTRAINT "top_up_email_log_user_id_kilocode_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."kilocode_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "UQ_top_up_email_log_stripe_payment_id" ON "top_up_email_log" USING btree ("stripe_payment_id"); \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 9882aae6ba..c7337f51de 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -771,6 +771,13 @@ "when": 1777664842970, "tag": "0109_conscious_kingpin", "breakpoints": true + }, + { + "idx": 107, + "version": "7", + "when": 1777926674979, + "tag": "0107_magical_rattler", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 051e058c5a..7eaf03cb1b 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -4414,6 +4414,33 @@ export const kiloclaw_email_log = pgTable( export type KiloClawEmailLog = typeof kiloclaw_email_log.$inferSelect; +// Outbox marker for top-up confirmation emails, keyed by the Stripe payment id +// (`ch_…` / `in_…` / `pi_…`). `processTopUp` commits the credit_transactions +// row before firing the email via `after()`. If the process exits between +// those two steps and Stripe retries the webhook, the credit-transactions +// unique index dedupes the credit but the email would otherwise be lost. +// Inserting a marker row on the first successful send — and attempting an +// insert on every webhook replay — lets a retry observe "marker missing, +// credit already committed" and recover the email exactly once. The unique +// index on `stripe_payment_id` is the whole dedupe mechanism. +export const top_up_email_log = pgTable( + 'top_up_email_log', + { + id: uuid() + .default(sql`gen_random_uuid()`) + .primaryKey() + .notNull(), + stripe_payment_id: text().notNull(), + user_id: text() + .notNull() + .references(() => kilocode_users.id), + sent_at: timestamp({ withTimezone: true, mode: 'string' }).defaultNow().notNull(), + }, + table => [uniqueIndex('UQ_top_up_email_log_stripe_payment_id').on(table.stripe_payment_id)] +); + +export type TopUpEmailLog = typeof top_up_email_log.$inferSelect; + // Bot Request Logs — tracks each message handled by the new bot (src/lib/bot.ts). // Rows are created as 'pending' on receipt and updated as processing progresses. export type BotRequestStatus = 'pending' | 'completed' | 'error'; From 9081b6434ff6a2dad8c28da65463f2410666b693 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 15:57:58 -0600 Subject: [PATCH 14/23] test(emails): cover past_due and unpaid dunning settlement branches Assert applyStripeFundedKiloClawPeriod does not send a subscription-started email (or write a kiloclaw_email_log row) when settling a successful renewal retry on a past_due or unpaid subscription. Pins the shouldSendSubscriptionStartedEmailForActivation contract: dunning recoveries are not new activations. Closes cloud-7gh. --- apps/web/src/lib/purchase-emails.test.ts | 59 ++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 01e69e39aa..ec318780d0 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -793,6 +793,65 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(await countEmailLogRows(user.id, instance.id)).toBe(0); }); + // past_due and unpaid are Stripe's dunning states — the subscription is + // already activated on a paid plan and Stripe is retrying a failed renewal + // charge. When that retry eventually succeeds, the settlement reaches this + // code path with `before.status` still set to the dunning value. Per + // shouldSendSubscriptionStartedEmailForActivation these MUST NOT send the + // subscription-started email — it would be a duplicate activation + // notification for a plan the user was already on. + test('past_due recovery settlement → no subscription-started email', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_past_due_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'past_due', + plan: 'standard', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(0); + expect(await countEmailLogRows(user.id, instance.id)).toBe(0); + }); + + test('unpaid recovery settlement → no subscription-started email', async () => { + const user = await insertTestUser({}); + const stripeSubscriptionId = `sub_unpaid_${crypto.randomUUID()}`; + const { instance } = await seedSubscription({ + userId: user.id, + status: 'unpaid', + plan: 'standard', + stripeSubscriptionId, + }); + + const applied = await applyStripeFundedKiloClawPeriod({ + userId: user.id, + metadataInstanceId: instance.id, + stripeSubscriptionId, + stripePaymentId: `ch_${crypto.randomUUID()}`, + plan: 'standard', + amountMicrodollars: 9_000_000, + periodStart: new Date().toISOString(), + periodEnd: new Date(Date.now() + 30 * 86_400_000).toISOString(), + }); + + expect(applied).toBe(true); + expect(countSubscriptionStartedSends()).toBe(0); + expect(await countEmailLogRows(user.id, instance.id)).toBe(0); + }); + test('active renewal after a prior activation → eligible subscription.created log for a different period does NOT trigger a second email', async () => { // Defence-in-depth for the durable-signal fallback: the helper matches on // plan + period boundaries of the `stripe_subscription_created.after_state` From 6e3692acf0eee44d85861b43639cbb83b717fff3 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 16:24:03 -0600 Subject: [PATCH 15/23] refactor(emails): drop 31-day recovery window in settlement activation check Remove SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS and the created_at window guard in didPriorSettlementRecordPaidActivation. The identity match (subscription_id + action/reason scope + exact plan + period boundaries on after_state) is already unique per activation: stripe_invoice_settlement rows are written only by applyStripeFundedKiloClawPeriod once per successful settlement, and KiloClaw never uses Stripe proration, so renewals move period boundaries forward and two settlements on the same subscription cannot share plan+period. Removing the window lets legitimately delayed webhook replays (long outage, manual Stripe-dashboard resend) still recover the subscription-started email. The kiloclaw_email_log unique index remains the final idempotency guard. Also drops the now-obsolete 'stale duplicate recovery guard' test. Refs: cloud-ymg --- apps/web/src/lib/kiloclaw/credit-billing.ts | 23 +++---- apps/web/src/lib/purchase-emails.test.ts | 74 +-------------------- 2 files changed, 10 insertions(+), 87 deletions(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 2e12bd49c8..4a0846e125 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -717,12 +717,6 @@ export async function applyStripeFundedKiloClawPeriod(params: { export const KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE = 'kiloclaw_subscription_started'; -// Conservative duplicate-recovery window. A change-log row older than this -// relative to `periodStart` is treated as a stale transition and will not -// trigger a recovered subscription-started email. The `kiloclaw_email_log` -// unique index is still the final idempotency guard. -export const SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS = 31 * 24 * 60 * 60 * 1000; - // A settlement activates a paid period (and may produce a "subscription // started" email) only when the subscription was NOT already active before // settlement. Recovery/dunning states (past_due, unpaid) are excluded; those @@ -762,6 +756,15 @@ function timestampsEqual(a: string | null, b: string | null): boolean { // settlement hits the duplicate-credit path and the original in-transaction // email send may have failed. Returns false on missing/malformed rows — the // `kiloclaw_email_log` unique index remains the final idempotency guard. +// +// Identity is established by subscription_id + action/reason scope + exact +// plan/period-boundary match on `after_state`. `stripe_invoice_settlement` +// rows are written only by `applyStripeFundedKiloClawPeriod` (once per +// successful settlement), and KiloClaw never uses Stripe proration, so +// renewals move period boundaries forward and two settlements on the same +// subscription cannot share plan+period. No time-window guard is needed: a +// legitimately delayed webhook replay (e.g., manual Stripe-dashboard +// resend well after the period started) should still recover the email. async function didPriorSettlementRecordPaidActivation(params: { subscriptionId: string; plan: 'commit' | 'standard'; @@ -772,7 +775,6 @@ async function didPriorSettlementRecordPaidActivation(params: { const rows = await db .select({ - created_at: kiloclaw_subscription_change_log.created_at, before_state: kiloclaw_subscription_change_log.before_state, after_state: kiloclaw_subscription_change_log.after_state, }) @@ -787,9 +789,6 @@ async function didPriorSettlementRecordPaidActivation(params: { .orderBy(desc(kiloclaw_subscription_change_log.created_at)) .limit(10); - const periodStartMs = new Date(periodStart).getTime(); - if (!Number.isFinite(periodStartMs)) return false; - for (const row of rows) { if (!isRecord(row.before_state) || !isRecord(row.after_state)) continue; @@ -809,10 +808,6 @@ async function didPriorSettlementRecordPaidActivation(params: { continue; } - const createdAtMs = new Date(row.created_at).getTime(); - if (!Number.isFinite(createdAtMs)) continue; - if (Math.abs(createdAtMs - periodStartMs) > SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS) continue; - return true; } diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index ec318780d0..256a8efcb3 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -3,7 +3,6 @@ import { credit_transactions, kiloclaw_email_log, kiloclaw_instances, - kiloclaw_subscription_change_log, kiloclaw_subscriptions, top_up_email_log, } from '@kilocode/db/schema'; @@ -11,10 +10,7 @@ import { insertKiloClawSubscriptionChangeLog } from '@kilocode/db'; import { db } from '@/lib/drizzle'; import { insertTestUser } from '@/tests/helpers/user.helper'; import { processTopUp } from '@/lib/credits'; -import { - KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE, - SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS, -} from '@/lib/kiloclaw/credit-billing'; +import { KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE } from '@/lib/kiloclaw/credit-billing'; import type * as creditBillingModule from '@/lib/kiloclaw/credit-billing'; import { renderTemplate, @@ -1088,74 +1084,6 @@ describe('applyStripeFundedKiloClawPeriod subscription-started email', () => { expect(countSubscriptionStartedSends()).toBe(0); expect(await countEmailLogRows(user.id, instance.id)).toBe(1); }); - - test('stale duplicate recovery guard → old change-log row outside the window does not trigger a recovered email', async () => { - const user = await insertTestUser({}); - const stripeSubscriptionId = `sub_stale_${crypto.randomUUID()}`; - const { instance, subscription } = await seedSubscription({ - userId: user.id, - status: 'trialing', - plan: 'trial', - stripeSubscriptionId, - }); - - const periodStart = new Date().toISOString(); - const periodEnd = new Date(Date.now() + 30 * 86_400_000).toISOString(); - const stripePaymentId = `ch_${crypto.randomUUID()}`; - - await applyStripeFundedKiloClawPeriod({ - userId: user.id, - metadataInstanceId: instance.id, - stripeSubscriptionId, - stripePaymentId, - plan: 'standard', - amountMicrodollars: 9_000_000, - periodStart, - periodEnd, - }); - expect(countSubscriptionStartedSends()).toBe(1); - - // Backdate the change-log row well outside the recovery window relative - // to periodStart, and clear the email-log row. A replay must NOT send. - const backdated = new Date( - new Date(periodStart).getTime() - SUBSCRIPTION_STARTED_RECOVERY_WINDOW_MS - 86_400_000 - ).toISOString(); - await db - .update(kiloclaw_subscription_change_log) - .set({ created_at: backdated }) - .where( - and( - eq(kiloclaw_subscription_change_log.subscription_id, subscription.id), - eq(kiloclaw_subscription_change_log.action, 'period_advanced'), - eq(kiloclaw_subscription_change_log.reason, 'stripe_invoice_settlement') - ) - ); - await db - .delete(kiloclaw_email_log) - .where( - and( - eq(kiloclaw_email_log.user_id, user.id), - eq(kiloclaw_email_log.instance_id, instance.id), - eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE) - ) - ); - sendViaMailgunMock.mockClear(); - verifyEmailMock.mockClear(); - - await applyStripeFundedKiloClawPeriod({ - userId: user.id, - metadataInstanceId: instance.id, - stripeSubscriptionId, - stripePaymentId, - plan: 'standard', - amountMicrodollars: 9_000_000, - periodStart, - periodEnd, - }); - - expect(countSubscriptionStartedSends()).toBe(0); - expect(await countEmailLogRows(user.id, instance.id)).toBe(0); - }); }); // ── Direct helper payload tests ──────────────────────────────────────────── From b8daf9af6249a617b9ee003f6ea6550c53c644e1 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 16:41:48 -0600 Subject: [PATCH 16/23] fix(emails): point subscription-started manage CTA at /claw/subscription /claw redirects active users to /claw/chat and inactive users to /claw/new, so the 'Manage subscription' CTA landed on the wrong page. Point it at /claw/subscription, the personal subscription management route. --- apps/web/src/lib/email.ts | 2 +- apps/web/src/lib/purchase-emails.test.ts | 8 ++++---- apps/web/src/routers/admin/email-testing-router.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/web/src/lib/email.ts b/apps/web/src/lib/email.ts index 5a259853c2..f96b9b0c21 100644 --- a/apps/web/src/lib/email.ts +++ b/apps/web/src/lib/email.ts @@ -468,7 +468,7 @@ type SendKiloClawSubscriptionStartedEmailProps = { export async function sendKiloClawSubscriptionStartedEmail( props: SendKiloClawSubscriptionStartedEmailProps ): Promise { - const manage_url = `${NEXTAUTH_URL}/claw`; + const manage_url = `${NEXTAUTH_URL}/claw/subscription`; return send({ to: props.to, templateName: 'kiloClawSubscriptionStarted', diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 256a8efcb3..4873b54b05 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -111,14 +111,14 @@ describe('kiloClawSubscriptionStarted template', () => { price_usd: '9.00', billing_period: 'Jan 1, 2026 – Feb 1, 2026', next_billing_date: 'February 1, 2026', - manage_url: 'https://app.kilocode.ai/claw', + manage_url: 'https://app.kilocode.ai/claw/subscription', year: '2026', }); expect(html).toContain('KiloClaw Standard'); expect(html).toContain('$9.00'); expect(html).toContain('Jan 1, 2026 – Feb 1, 2026'); expect(html).toContain('February 1, 2026'); - expect(html).toContain('https://app.kilocode.ai/claw'); + expect(html).toContain('https://app.kilocode.ai/claw/subscription'); }); }); @@ -1216,8 +1216,8 @@ describe('sendKiloClawSubscriptionStartedEmail payload', () => { expect(params.html).toContain('Jan 15, 2026 – Feb 15, 2026'); // formatDate(next billing). expect(params.html).toContain('February 15, 2026'); - // manage_url construction (NEXTAUTH_URL + '/claw'). - expect(params.html).toContain('/claw'); + // manage_url construction (NEXTAUTH_URL + '/claw/subscription'). + expect(params.html).toContain('/claw/subscription'); }); test('zero-cent price still renders "$0.00 USD" (formatUsd rounding)', async () => { diff --git a/apps/web/src/routers/admin/email-testing-router.ts b/apps/web/src/routers/admin/email-testing-router.ts index 6ce90f8337..a53c0a2e9b 100644 --- a/apps/web/src/routers/admin/email-testing-router.ts +++ b/apps/web/src/routers/admin/email-testing-router.ts @@ -132,7 +132,7 @@ function fixtureTemplateVars(template: TemplateName): Record Date: Mon, 4 May 2026 18:05:18 -0600 Subject: [PATCH 17/23] fix(emails): look up user before inserting subscription-started email log If kilocode_users returned empty after the marker was inserted, the marker persisted and permanently suppressed the subscription-started email on retry via the unique index. Move the user lookup before the insert so a missing user returns without writing a marker. Mirrors the ordering in apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts, which does the same user-lookup-before-marker check on a sibling kiloclaw_email_log path. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 32 ++++++++++++--------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 4a0846e125..1e3c327af0 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -909,6 +909,24 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { }): Promise { const { userId, instanceId, plan, amountCents, periodStart, periodEnd } = params; try { + // Look up the user BEFORE inserting the marker. Inserting first and + // then discovering a missing user would leak the marker and permanently + // suppress the email on retry. Mirrors + // apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts:100-151. + const [user] = await db + .select({ email: kilocode_users.google_user_email }) + .from(kilocode_users) + .where(eq(kilocode_users.id, userId)) + .limit(1); + + if (!user) { + logWarning('KiloClaw subscription-started email: user not found', { + user_id: userId, + instance_id: instanceId, + }); + return; + } + const insertResult = await db .insert(kiloclaw_email_log) .values({ @@ -924,20 +942,6 @@ async function maybeSendKiloClawSubscriptionStartedEmail(params: { return; } - const [user] = await db - .select({ email: kilocode_users.google_user_email }) - .from(kilocode_users) - .where(eq(kilocode_users.id, userId)) - .limit(1); - - if (!user) { - logWarning('KiloClaw subscription-started email: user not found', { - user_id: userId, - instance_id: instanceId, - }); - return; - } - const sendResult = await sendKiloClawSubscriptionStartedEmail({ to: user.email, planName: planDisplayName(plan), From 16d09983a7d48f5a2d5d67b0dae5aa365d3ed01c Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 18:47:18 -0600 Subject: [PATCH 18/23] docs(emails): acknowledge known gaps in insert-before-send email markers Soften the comments on the two new email dedupe paths to call out the shared at-most-once-marker gaps (crash between insert and send; catch-block rollback after ambiguous provider errors). References the sibling sites that share the same pattern so the fix is scoped to a shared outbox across all of them rather than a one-off here. --- apps/web/src/lib/credits.ts | 22 ++++++++++++++----- apps/web/src/lib/kiloclaw/credit-billing.ts | 24 ++++++++++++++++----- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index f62bda1bac..73cc7dca5d 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -146,14 +146,26 @@ export async function processTopUp( return true; } -// Idempotency is enforced by the unique index on +// Best-effort at-most-once dedupe via an insert-before-send marker on // `top_up_email_log.stripe_payment_id`. Every send attempt — first-attempt // and webhook-retry recovery — first inserts a marker row with // `onConflictDoNothing()`. A rowCount of 0 means an earlier attempt already -// sent the email, so we bail without sending again. If the provider was not -// configured (e.g. Mailgun env missing in preview/test), the marker is -// cleared so a future retry can re-attempt. Mirrors -// `maybeSendKiloClawSubscriptionStartedEmail` in credit-billing.ts. +// claimed this payment, so we bail without sending again. If the provider +// was not configured (e.g. Mailgun env missing in preview/test), the marker +// is cleared so a future retry can re-attempt. +// +// Known gaps shared with every other insert-before-send email path in this +// codebase (`maybeSendKiloClawSubscriptionStartedEmail` below, +// `services/kiloclaw-billing/src/lifecycle.ts` ~L850, and the +// `kiloclaw_email_log`-gated sends in `apps/web/src/app/api/internal/kiloclaw/`): +// 1. A crash between the marker insert and the provider send permanently +// suppresses the email on retry — the marker looks "already sent". +// 2. Rolling the marker back in the catch block after an ambiguous provider +// exception can duplicate the email if the provider actually accepted it. +// Fixing either properly requires a real outbox (pending/sent/terminal state +// + provider idempotency keys) applied uniformly across all of the above +// call sites. Tracked as follow-up tech debt; intentionally NOT fixed in +// isolation here so the new email paths stay uniform with the existing ones. async function maybeSendTopUpConfirmationEmail(params: { user: User; amountInCents: number; diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 1e3c327af0..54a1ec7b09 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -891,14 +891,28 @@ function planDisplayName(plan: 'commit' | 'standard'): string { return plan === 'commit' ? 'KiloClaw Commit' : 'KiloClaw Standard'; } -// Idempotency: insert-before-send on `kiloclaw_email_log` guarded by the -// unique index (user_id, instance_id, email_type, period_start). Each -// activation event (fresh `periodStart`) gets exactly one row; webhook -// replays of the same event collide on the index and return early. Because -// the KiloClaw subscription row is reused across cancel+resubscribe (both +// Best-effort at-most-once dedupe via insert-before-send on +// `kiloclaw_email_log`, guarded by the unique index +// (user_id, instance_id, email_type, period_start). Each activation event +// (fresh `periodStart`) gets exactly one row; webhook replays of the same +// event collide on the index and return early. Because the +// KiloClaw subscription row is reused across cancel+resubscribe (both // Stripe and credit paths UPDATE in place), period_start is what actually // distinguishes a resubscribe's activation from the original — hence one // email per activation, not one per instance lifetime. +// +// Known gaps shared with every other insert-before-send email path in this +// codebase (`maybeSendTopUpConfirmationEmail` in `apps/web/src/lib/credits.ts`, +// `services/kiloclaw-billing/src/lifecycle.ts` ~L850, and the +// `kiloclaw_email_log`-gated sends in `apps/web/src/app/api/internal/kiloclaw/`): +// 1. A crash between the marker insert and the provider send permanently +// suppresses the email on retry — the marker looks "already sent". +// 2. Rolling the marker back in the catch block after an ambiguous provider +// exception can duplicate the email if the provider actually accepted it. +// Fixing either properly requires a real outbox (pending/sent/terminal state +// + provider idempotency keys) applied uniformly across all of the above +// call sites. Tracked as follow-up tech debt; intentionally NOT fixed in +// isolation here so this new email path stays uniform with the existing ones. async function maybeSendKiloClawSubscriptionStartedEmail(params: { userId: string; instanceId: string; From c8a15040ffbc921a741bff0bd89147cdcedb86a8 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 19:39:14 -0600 Subject: [PATCH 19/23] refactor(emails): surface non-benign Stripe errors in receipt lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Narrow the catch in resolveStripeReceiptUrl to only silence StripeInvalidRequestError (the expected outcome when the payment was refunded/voided before the webhook arrived). Route every other error — rate-limit, API 5xx, authentication, non-Stripe programmer faults — through captureException so systemic failures become visible instead of being silently swallowed. Matches the autoTopUp.ts / admin-router.ts convention of swallowing a specific known-benign Stripe subclass and reporting the rest. The email flow still never fails on receipt-lookup errors. --- apps/web/src/lib/credits.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index 73cc7dca5d..08e10b1bee 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -272,8 +272,23 @@ async function resolveStripeReceiptUrl(stripeChargeOrInvoiceId: string): Promise } return null; } catch (error) { - // Receipt URLs are a nice-to-have — never fail the email flow. - if (error instanceof Stripe.errors.StripeError) return null; + // Receipt URLs are a nice-to-have — never fail the email flow. Narrow + // the silenced set to the one expected subclass and surface everything + // else, matching the autoTopUp.ts / admin-router.ts pattern of + // swallowing specific known-benign Stripe errors and reporting the rest. + // + // `StripeInvalidRequestError` is the expected outcome when the charge / + // invoice / payment-intent was refunded or voided between payment and + // this lookup, or when the ID is otherwise unrecognizable to Stripe. + // Everything else — rate-limit / API 5xx / auth failure after key + // rotation / non-Stripe programmer error — is engineer-actionable. + if (error instanceof Stripe.errors.StripeInvalidRequestError) { + return null; + } + captureException(error, { + tags: { source: 'credits_topup_receipt_lookup' }, + extra: { stripeChargeOrInvoiceId }, + }); return null; } } From ed4e9f8eff853c8c9e8d7b923e2ced91896cafbd Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Mon, 4 May 2026 19:39:21 -0600 Subject: [PATCH 20/23] perf(emails): short-circuit duplicate-settlement change-log scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On webhook replays against an already-emailed, already-settled period, applyStripeFundedKiloClawPeriod was running a kiloclaw_subscription_change_log scan plus application-side JSONB filtering on every retry, only for the subsequent marker-insert to no-op against the kiloclaw_email_log unique index. Gate the expensive scan on a fast existence check covered by the UQ_kiloclaw_email_log_user_instance_type_period index: if the activation already has an email-log row, we know the send is handled and can skip both the change-log recovery logic and the downstream send call. Correctness is unchanged — the unique index remains the authoritative idempotency guard. --- apps/web/src/lib/kiloclaw/credit-billing.ts | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index 54a1ec7b09..c31faad5ae 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -675,10 +675,22 @@ export async function applyStripeFundedKiloClawPeriod(params: { }); } + // Steady-state webhook replays against an already-emailed, already-settled + // period hit the duplicate-settlement branch on every retry. The real + // idempotency guard is the `kiloclaw_email_log` unique index inside + // `maybeSendKiloClawSubscriptionStartedEmail`, but we can skip the more + // expensive `kiloclaw_subscription_change_log` scan (and the subsequent + // no-op send call) when a matching email-log row already exists. const shouldSendSubscriptionStartedEmail = shouldSendSubscriptionStartedEmailForNewSettlement || (settlementWasDuplicate && resolvedSubscriptionId !== undefined && + resolvedInstanceId !== undefined && + !(await subscriptionStartedEmailAlreadyLoggedForActivation({ + userId, + instanceId: resolvedInstanceId, + periodStart, + })) && (await didPriorSettlementRecordPaidActivation({ subscriptionId: resolvedSubscriptionId, plan, @@ -1013,6 +1025,31 @@ async function deleteSubscriptionStartedEmailLog(params: { ); } +// Fast-path existence check covered by the +// `UQ_kiloclaw_email_log_user_instance_type_period` unique index. Used to +// short-circuit the duplicate-settlement activation recovery path before +// running the more expensive `kiloclaw_subscription_change_log` scan. +async function subscriptionStartedEmailAlreadyLoggedForActivation(params: { + userId: string; + instanceId: string; + periodStart: string; +}): Promise { + const { userId, instanceId, periodStart } = params; + const [existing] = await db + .select({ id: kiloclaw_email_log.id }) + .from(kiloclaw_email_log) + .where( + and( + eq(kiloclaw_email_log.user_id, userId), + eq(kiloclaw_email_log.instance_id, instanceId), + eq(kiloclaw_email_log.email_type, KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE), + eq(kiloclaw_email_log.period_start, periodStart) + ) + ) + .limit(1); + return existing !== undefined; +} + /** * Enroll a user's instance in a KiloClaw hosting plan funded by credits. * From b998bb37dc85a86cb5fb1d884ab159b6f1764cd1 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Tue, 5 May 2026 08:46:01 -0600 Subject: [PATCH 21/23] Renamed top_up_email_log to transactional_email_log --- apps/web/src/lib/credits.ts | 34 +- apps/web/src/lib/purchase-emails.test.ts | 26 +- apps/web/src/lib/user.test.ts | 15 +- apps/web/src/lib/user.ts | 2 +- .../src/migrations/0106_noisy_pete_wisdom.sql | 3 - .../src/migrations/0107_magical_rattler.sql | 9 - .../src/migrations/0110_giant_gabe_jones.sql | 14 + .../db/src/migrations/meta/0110_snapshot.json | 18697 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 6 +- packages/db/src/schema.ts | 31 +- 10 files changed, 18774 insertions(+), 63 deletions(-) delete mode 100644 packages/db/src/migrations/0106_noisy_pete_wisdom.sql delete mode 100644 packages/db/src/migrations/0107_magical_rattler.sql create mode 100644 packages/db/src/migrations/0110_giant_gabe_jones.sql create mode 100644 packages/db/src/migrations/meta/0110_snapshot.json diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index 08e10b1bee..e92eb72673 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -1,9 +1,9 @@ -import { credit_transactions, top_up_email_log } from '@kilocode/db/schema'; +import { credit_transactions, transactional_email_log } from '@kilocode/db/schema'; import type { User } from '@kilocode/db/schema'; import { kilocode_users } from '@kilocode/db/schema'; import { db, type DrizzleTransaction } from '@/lib/drizzle'; -import { sql, eq } from 'drizzle-orm'; +import { and, sql, eq } from 'drizzle-orm'; import { captureException } from '@sentry/nextjs'; import Stripe from 'stripe'; import { after } from 'next/server'; @@ -51,6 +51,7 @@ type ProcessTopUpOptions = { }; const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); +const CREDITS_TOP_UP_CONFIRMATION_EMAIL_TYPE = 'credits_top_up_confirmation'; export async function processTopUp( user: User, @@ -97,7 +98,7 @@ export async function processTopUp( // is idempotent, but the confirmation email is not guaranteed to have // been sent — the original process could have exited between the credit // commit and `after(processPostTopUpFreeStuff)`. Attempt to recover the - // email via the durable top_up_email_log marker. If a marker already + // email via the durable transactional_email_log marker. If a marker already // exists the insert collides and no second email is sent. if (!skipPostTopUpFreeStuff) { await recoverTopUpConfirmationEmailIfMissing({ @@ -146,9 +147,10 @@ export async function processTopUp( return true; } -// Best-effort at-most-once dedupe via an insert-before-send marker on -// `top_up_email_log.stripe_payment_id`. Every send attempt — first-attempt -// and webhook-retry recovery — first inserts a marker row with +// Best-effort at-most-once dedupe via an insert-before-send marker in +// `transactional_email_log`. Every send attempt — first-attempt +// and webhook-retry recovery — first inserts a marker row keyed by +// (email_type, idempotency_key) with // `onConflictDoNothing()`. A rowCount of 0 means an earlier attempt already // claimed this payment, so we bail without sending again. If the provider // was not configured (e.g. Mailgun env missing in preview/test), the marker @@ -175,10 +177,11 @@ async function maybeSendTopUpConfirmationEmail(params: { const { user, amountInCents, stripeChargeOrInvoiceId, isAutoTopUp } = params; try { const insertResult = await db - .insert(top_up_email_log) + .insert(transactional_email_log) .values({ - stripe_payment_id: stripeChargeOrInvoiceId, user_id: user.id, + email_type: CREDITS_TOP_UP_CONFIRMATION_EMAIL_TYPE, + idempotency_key: stripeChargeOrInvoiceId, }) .onConflictDoNothing(); @@ -201,7 +204,7 @@ async function maybeSendTopUpConfirmationEmail(params: { // is terminal for that address, so retrying would loop forever. Keep the // marker so we never try again for this payment. if (!sendResult.sent && sendResult.reason === 'provider_not_configured') { - await deleteTopUpEmailLog(stripeChargeOrInvoiceId); + await deleteTopUpEmailMarker(stripeChargeOrInvoiceId); } } catch (error) { captureException(error, { @@ -211,7 +214,7 @@ async function maybeSendTopUpConfirmationEmail(params: { // Best-effort rollback so a retry can re-attempt — mirrors the pattern in // `maybeSendKiloClawSubscriptionStartedEmail`. try { - await deleteTopUpEmailLog(stripeChargeOrInvoiceId); + await deleteTopUpEmailMarker(stripeChargeOrInvoiceId); } catch { // Leave the marker in place; we prefer missing one email over duplicate sends. } @@ -238,10 +241,15 @@ async function recoverTopUpConfirmationEmailIfMissing(params: { } } -async function deleteTopUpEmailLog(stripeChargeOrInvoiceId: string): Promise { +async function deleteTopUpEmailMarker(stripeChargeOrInvoiceId: string): Promise { await db - .delete(top_up_email_log) - .where(eq(top_up_email_log.stripe_payment_id, stripeChargeOrInvoiceId)); + .delete(transactional_email_log) + .where( + and( + eq(transactional_email_log.email_type, CREDITS_TOP_UP_CONFIRMATION_EMAIL_TYPE), + eq(transactional_email_log.idempotency_key, stripeChargeOrInvoiceId) + ) + ); } async function resolveStripeReceiptUrl(stripeChargeOrInvoiceId: string): Promise { diff --git a/apps/web/src/lib/purchase-emails.test.ts b/apps/web/src/lib/purchase-emails.test.ts index 4873b54b05..419d2dc69c 100644 --- a/apps/web/src/lib/purchase-emails.test.ts +++ b/apps/web/src/lib/purchase-emails.test.ts @@ -4,7 +4,7 @@ import { kiloclaw_email_log, kiloclaw_instances, kiloclaw_subscriptions, - top_up_email_log, + transactional_email_log, } from '@kilocode/db/schema'; import { insertKiloClawSubscriptionChangeLog } from '@kilocode/db'; import { db } from '@/lib/drizzle'; @@ -242,7 +242,7 @@ describe('processTopUp credit top-up email', () => { test('recovers confirmation email on webhook retry when first attempt did not send', async () => { // Simulate the failure mode: the first processTopUp committed the credit - // transaction but exited before firing the email (no top_up_email_log + // transaction but exited before firing the email (no transactional_email_log // marker). A webhook retry must observe the missing marker and send. const user = await insertTestUser({ total_microdollars_acquired: 0, @@ -273,9 +273,9 @@ describe('processTopUp credit top-up email', () => { // Marker row exists to prevent a third attempt from re-sending. const [marker] = await db - .select({ id: top_up_email_log.id }) - .from(top_up_email_log) - .where(eq(top_up_email_log.stripe_payment_id, stripePaymentId)) + .select({ id: transactional_email_log.id }) + .from(transactional_email_log) + .where(eq(transactional_email_log.idempotency_key, stripePaymentId)) .limit(1); expect(marker).toBeTruthy(); @@ -290,7 +290,7 @@ describe('processTopUp credit top-up email', () => { expect(creditsTopUpSends()).toHaveLength(0); }); - test('writes a top_up_email_log marker on first-attempt send', async () => { + test('writes a transactional_email_log marker on first-attempt send', async () => { const user = await insertTestUser({ total_microdollars_acquired: 0, microdollars_used: 0, @@ -304,11 +304,11 @@ describe('processTopUp credit top-up email', () => { expect(first).toBe(true); const [marker] = await db - .select({ stripe_payment_id: top_up_email_log.stripe_payment_id }) - .from(top_up_email_log) - .where(eq(top_up_email_log.stripe_payment_id, stripePaymentId)) + .select({ idempotency_key: transactional_email_log.idempotency_key }) + .from(transactional_email_log) + .where(eq(transactional_email_log.idempotency_key, stripePaymentId)) .limit(1); - expect(marker).toEqual({ stripe_payment_id: stripePaymentId }); + expect(marker).toEqual({ idempotency_key: stripePaymentId }); }); test('recovery path skips email when skipPostTopUpFreeStuff is true on retry', async () => { @@ -344,9 +344,9 @@ describe('processTopUp credit top-up email', () => { expect(creditsTopUpSends()).toHaveLength(0); const [marker] = await db - .select({ id: top_up_email_log.id }) - .from(top_up_email_log) - .where(eq(top_up_email_log.stripe_payment_id, stripePaymentId)) + .select({ id: transactional_email_log.id }) + .from(transactional_email_log) + .where(eq(transactional_email_log.idempotency_key, stripePaymentId)) .limit(1); expect(marker).toBeUndefined(); }); diff --git a/apps/web/src/lib/user.test.ts b/apps/web/src/lib/user.test.ts index 45fd9599f5..b7a4b39b2b 100644 --- a/apps/web/src/lib/user.test.ts +++ b/apps/web/src/lib/user.test.ts @@ -38,7 +38,7 @@ import { kiloclaw_earlybird_purchases, kiloclaw_subscriptions, kiloclaw_email_log, - top_up_email_log, + transactional_email_log, kiloclaw_cli_runs, bot_requests, bot_request_cloud_agent_sessions, @@ -117,7 +117,7 @@ describe('User', () => { await db.delete(stytch_fingerprints); await db.delete(kiloclaw_cli_runs); await db.delete(kiloclaw_email_log); - await db.delete(top_up_email_log); + await db.delete(transactional_email_log); await db.delete(kiloclaw_version_pins); await db.delete(kiloclaw_image_catalog); await db.delete(kiloclaw_subscriptions); @@ -1717,12 +1717,13 @@ describe('User', () => { ).toBe(1); }); - it('should retain top_up_email_log rows for the user', async () => { + it('should retain transactional_email_log rows for the user', async () => { const user = await insertTestUser(); - await db.insert(top_up_email_log).values({ + await db.insert(transactional_email_log).values({ user_id: user.id, - stripe_payment_id: `ch_retain_${randomUUID()}`, + email_type: 'credits_top_up_confirmation', + idempotency_key: `ch_retain_${randomUUID()}`, }); await softDeleteUser(user.id); @@ -1730,8 +1731,8 @@ describe('User', () => { expect( await db .select({ count: count() }) - .from(top_up_email_log) - .where(eq(top_up_email_log.user_id, user.id)) + .from(transactional_email_log) + .where(eq(transactional_email_log.user_id, user.id)) .then(r => r[0].count) ).toBe(1); }); diff --git a/apps/web/src/lib/user.ts b/apps/web/src/lib/user.ts index d5a929ad9d..27a701840d 100644 --- a/apps/web/src/lib/user.ts +++ b/apps/web/src/lib/user.ts @@ -594,7 +594,7 @@ export class SoftDeletePreconditionError extends Error { * - referral_code_usages (financial, references anonymized user) * - kiloclaw_subscriptions, kiloclaw_earlybird_purchases, kiloclaw_email_log (retained records) * - kiloclaw_scheduled_action_targets (retained operational records; - * - top_up_email_log (retained outbox marker, financial record) + * - transactional_email_log (retained outbox marker, financial record) * user_id FK references the anonymized kilocode_users row — no PII * stored directly on the target row) * diff --git a/packages/db/src/migrations/0106_noisy_pete_wisdom.sql b/packages/db/src/migrations/0106_noisy_pete_wisdom.sql deleted file mode 100644 index 726432c56b..0000000000 --- a/packages/db/src/migrations/0106_noisy_pete_wisdom.sql +++ /dev/null @@ -1,3 +0,0 @@ -DROP INDEX "UQ_kiloclaw_email_log_user_instance_type";--> statement-breakpoint -ALTER TABLE "kiloclaw_email_log" ADD COLUMN "period_start" timestamp with time zone DEFAULT 'epoch' NOT NULL;--> statement-breakpoint -CREATE UNIQUE INDEX "UQ_kiloclaw_email_log_user_instance_type_period" ON "kiloclaw_email_log" USING btree ("user_id","instance_id","email_type","period_start") WHERE "kiloclaw_email_log"."instance_id" is not null; \ No newline at end of file diff --git a/packages/db/src/migrations/0107_magical_rattler.sql b/packages/db/src/migrations/0107_magical_rattler.sql deleted file mode 100644 index 92833ff6a7..0000000000 --- a/packages/db/src/migrations/0107_magical_rattler.sql +++ /dev/null @@ -1,9 +0,0 @@ -CREATE TABLE "top_up_email_log" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "stripe_payment_id" text NOT NULL, - "user_id" text NOT NULL, - "sent_at" timestamp with time zone DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "top_up_email_log" ADD CONSTRAINT "top_up_email_log_user_id_kilocode_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."kilocode_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE UNIQUE INDEX "UQ_top_up_email_log_stripe_payment_id" ON "top_up_email_log" USING btree ("stripe_payment_id"); \ No newline at end of file diff --git a/packages/db/src/migrations/0110_giant_gabe_jones.sql b/packages/db/src/migrations/0110_giant_gabe_jones.sql new file mode 100644 index 0000000000..5076d7af4d --- /dev/null +++ b/packages/db/src/migrations/0110_giant_gabe_jones.sql @@ -0,0 +1,14 @@ +CREATE TABLE "transactional_email_log" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "user_id" text NOT NULL, + "email_type" text NOT NULL, + "idempotency_key" text NOT NULL, + "sent_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +DROP INDEX "UQ_kiloclaw_email_log_user_instance_type";--> statement-breakpoint +ALTER TABLE "kiloclaw_email_log" ADD COLUMN "period_start" timestamp with time zone DEFAULT 'epoch' NOT NULL;--> statement-breakpoint +ALTER TABLE "transactional_email_log" ADD CONSTRAINT "transactional_email_log_user_id_kilocode_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."kilocode_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "UQ_transactional_email_log_type_idempotency_key" ON "transactional_email_log" USING btree ("email_type","idempotency_key");--> statement-breakpoint +CREATE INDEX "IDX_transactional_email_log_user_id" ON "transactional_email_log" USING btree ("user_id");--> statement-breakpoint +CREATE UNIQUE INDEX "UQ_kiloclaw_email_log_user_instance_type_period" ON "kiloclaw_email_log" USING btree ("user_id","instance_id","email_type","period_start") WHERE "kiloclaw_email_log"."instance_id" is not null; \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0110_snapshot.json b/packages/db/src/migrations/meta/0110_snapshot.json new file mode 100644 index 0000000000..27e308f3aa --- /dev/null +++ b/packages/db/src/migrations/meta/0110_snapshot.json @@ -0,0 +1,18697 @@ +{ + "id": "2e8518ae-2f1d-4fe2-b392-5fc56d50c626", + "prevId": "4c1ec3c4-df75-4028-a0ab-68a0ad3c1c4d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_configs": { + "name": "agent_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_type": { + "name": "agent_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "runtime_state": { + "name": "runtime_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_configs_org_id": { + "name": "IDX_agent_configs_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_owned_by_user_id": { + "name": "IDX_agent_configs_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_agent_type": { + "name": "IDX_agent_configs_agent_type", + "columns": [ + { + "expression": "agent_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_platform": { + "name": "IDX_agent_configs_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_configs_owned_by_organization_id_organizations_id_fk": { + "name": "agent_configs_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_configs", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_configs_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_configs_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_configs_org_agent_platform": { + "name": "UQ_agent_configs_org_agent_platform", + "nullsNotDistinct": false, + "columns": [ + "owned_by_organization_id", + "agent_type", + "platform" + ] + }, + "UQ_agent_configs_user_agent_platform": { + "name": "UQ_agent_configs_user_agent_platform", + "nullsNotDistinct": false, + "columns": [ + "owned_by_user_id", + "agent_type", + "platform" + ] + } + }, + "policies": {}, + "checkConstraints": { + "agent_configs_owner_check": { + "name": "agent_configs_owner_check", + "value": "(\n (\"agent_configs\".\"owned_by_user_id\" IS NOT NULL AND \"agent_configs\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_configs\".\"owned_by_user_id\" IS NULL AND \"agent_configs\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "agent_configs_agent_type_check": { + "name": "agent_configs_agent_type_check", + "value": "\"agent_configs\".\"agent_type\" IN ('code_review', 'auto_triage', 'auto_fix', 'security_scan')" + } + }, + "isRLSEnabled": false + }, + "public.agent_environment_profile_commands": { + "name": "agent_environment_profile_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_commands_profile_id": { + "name": "IDX_agent_env_profile_commands_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_commands_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_commands_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_commands", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_commands_profile_sequence": { + "name": "UQ_agent_env_profile_commands_profile_sequence", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "sequence" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_repo_bindings": { + "name": "agent_environment_profile_repo_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_agent_env_profile_repo_bindings_user": { + "name": "UQ_agent_env_profile_repo_bindings_user", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profile_repo_bindings_org": { + "name": "UQ_agent_env_profile_repo_bindings_org", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_repo_bindings_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_repo_bindings_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profile_repo_bindings_owned_by_organization_id_organizations_id_fk": { + "name": "agent_environment_profile_repo_bindings_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profile_repo_bindings_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_environment_profile_repo_bindings_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_env_profile_repo_bindings_owner_check": { + "name": "agent_env_profile_repo_bindings_owner_check", + "value": "(\n (\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" IS NOT NULL AND \"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" IS NULL AND \"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.agent_environment_profile_vars": { + "name": "agent_environment_profile_vars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_secret": { + "name": "is_secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_vars_profile_id": { + "name": "IDX_agent_env_profile_vars_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_vars_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_vars_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_vars", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_vars_profile_key": { + "name": "UQ_agent_env_profile_vars_profile_key", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profiles": { + "name": "agent_environment_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_agent_env_profiles_org_name": { + "name": "UQ_agent_env_profiles_org_name", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_user_name": { + "name": "UQ_agent_env_profiles_user_name", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_org_default": { + "name": "UQ_agent_env_profiles_org_default", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"is_default\" = true AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_user_default": { + "name": "UQ_agent_env_profiles_user_default", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"is_default\" = true AND \"agent_environment_profiles\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_org_id": { + "name": "IDX_agent_env_profiles_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_user_id": { + "name": "IDX_agent_env_profiles_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profiles_owned_by_organization_id_organizations_id_fk": { + "name": "agent_environment_profiles_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_environment_profiles", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profiles_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_environment_profiles_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_environment_profiles", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_env_profiles_owner_check": { + "name": "agent_env_profiles_owner_check", + "value": "(\n (\"agent_environment_profiles\".\"owned_by_user_id\" IS NOT NULL AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_environment_profiles\".\"owned_by_user_id\" IS NULL AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.api_kind": { + "name": "api_kind", + "schema": "", + "columns": { + "api_kind_id": { + "name": "api_kind_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "api_kind": { + "name": "api_kind", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_api_kind": { + "name": "UQ_api_kind", + "columns": [ + { + "expression": "api_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_request_log": { + "name": "api_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_api_request_log_created_at": { + "name": "idx_api_request_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_feedback": { + "name": "app_builder_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_status": { + "name": "preview_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_streaming": { + "name": "is_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recent_messages": { + "name": "recent_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_app_builder_feedback_created_at": { + "name": "IDX_app_builder_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_feedback_kilo_user_id": { + "name": "IDX_app_builder_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_feedback_project_id": { + "name": "IDX_app_builder_feedback_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "app_builder_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "app_builder_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "app_builder_feedback_project_id_app_builder_projects_id_fk": { + "name": "app_builder_feedback_project_id_app_builder_projects_id_fk", + "tableFrom": "app_builder_feedback", + "tableTo": "app_builder_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_project_sessions": { + "name": "app_builder_project_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "worker_version": { + "name": "worker_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'v1'" + } + }, + "indexes": { + "IDX_app_builder_project_sessions_project_id": { + "name": "IDX_app_builder_project_sessions_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_project_sessions_project_id_app_builder_projects_id_fk": { + "name": "app_builder_project_sessions_project_id_app_builder_projects_id_fk", + "tableFrom": "app_builder_project_sessions", + "tableTo": "app_builder_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_app_builder_project_sessions_cloud_agent_session_id": { + "name": "UQ_app_builder_project_sessions_cloud_agent_session_id", + "nullsNotDistinct": false, + "columns": [ + "cloud_agent_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_projects": { + "name": "app_builder_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "git_repo_full_name": { + "name": "git_repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_platform_integration_id": { + "name": "git_platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "migrated_at": { + "name": "migrated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_app_builder_projects_created_by_user_id": { + "name": "IDX_app_builder_projects_created_by_user_id", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_owned_by_user_id": { + "name": "IDX_app_builder_projects_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_owned_by_organization_id": { + "name": "IDX_app_builder_projects_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_created_at": { + "name": "IDX_app_builder_projects_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_last_message_at": { + "name": "IDX_app_builder_projects_last_message_at", + "columns": [ + { + "expression": "last_message_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_projects_owned_by_user_id_kilocode_users_id_fk": { + "name": "app_builder_projects_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "app_builder_projects_owned_by_organization_id_organizations_id_fk": { + "name": "app_builder_projects_owned_by_organization_id_organizations_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "app_builder_projects_deployment_id_deployments_id_fk": { + "name": "app_builder_projects_deployment_id_deployments_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "app_builder_projects_git_platform_integration_id_platform_integrations_id_fk": { + "name": "app_builder_projects_git_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "platform_integrations", + "columnsFrom": [ + "git_platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "app_builder_projects_owner_check": { + "name": "app_builder_projects_owner_check", + "value": "(\n (\"app_builder_projects\".\"owned_by_user_id\" IS NOT NULL AND \"app_builder_projects\".\"owned_by_organization_id\" IS NULL) OR\n (\"app_builder_projects\".\"owned_by_user_id\" IS NULL AND \"app_builder_projects\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.app_min_versions": { + "name": "app_min_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ios_min_version": { + "name": "ios_min_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "android_min_version": { + "name": "android_min_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_reported_messages": { + "name": "app_reported_messages", + "schema": "", + "columns": { + "report_id": { + "name": "report_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signature": { + "name": "signature", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "app_reported_messages_cli_session_id_cli_sessions_session_id_fk": { + "name": "app_reported_messages_cli_session_id_cli_sessions_session_id_fk", + "tableFrom": "app_reported_messages", + "tableTo": "cli_sessions", + "columnsFrom": [ + "cli_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_fix_tickets": { + "name": "auto_fix_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "triage_ticket_id": { + "name": "triage_ticket_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_url": { + "name": "issue_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_body": { + "name": "issue_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_author": { + "name": "issue_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_labels": { + "name": "issue_labels", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "trigger_source": { + "name": "trigger_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'label'" + }, + "review_comment_id": { + "name": "review_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "review_comment_body": { + "name": "review_comment_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "diff_hunk": { + "name": "diff_hunk", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_head_ref": { + "name": "pr_head_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "intent_summary": { + "name": "intent_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_files": { + "name": "related_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_branch": { + "name": "pr_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_fix_tickets_repo_issue": { + "name": "UQ_auto_fix_tickets_repo_issue", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_fix_tickets\".\"trigger_source\" = 'label'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_auto_fix_tickets_repo_review_comment": { + "name": "UQ_auto_fix_tickets_repo_review_comment", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "review_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_fix_tickets\".\"review_comment_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_owned_by_org": { + "name": "IDX_auto_fix_tickets_owned_by_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_owned_by_user": { + "name": "IDX_auto_fix_tickets_owned_by_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_status": { + "name": "IDX_auto_fix_tickets_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_created_at": { + "name": "IDX_auto_fix_tickets_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_triage_ticket_id": { + "name": "IDX_auto_fix_tickets_triage_ticket_id", + "columns": [ + { + "expression": "triage_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_session_id": { + "name": "IDX_auto_fix_tickets_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_fix_tickets_owned_by_organization_id_organizations_id_fk": { + "name": "auto_fix_tickets_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_fix_tickets_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_fix_tickets_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_fix_tickets_platform_integration_id_platform_integrations_id_fk": { + "name": "auto_fix_tickets_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_fix_tickets_triage_ticket_id_auto_triage_tickets_id_fk": { + "name": "auto_fix_tickets_triage_ticket_id_auto_triage_tickets_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "auto_triage_tickets", + "columnsFrom": [ + "triage_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_fix_tickets_cli_session_id_cli_sessions_session_id_fk": { + "name": "auto_fix_tickets_cli_session_id_cli_sessions_session_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "cli_sessions", + "columnsFrom": [ + "cli_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_fix_tickets_owner_check": { + "name": "auto_fix_tickets_owner_check", + "value": "(\n (\"auto_fix_tickets\".\"owned_by_user_id\" IS NOT NULL AND \"auto_fix_tickets\".\"owned_by_organization_id\" IS NULL) OR\n (\"auto_fix_tickets\".\"owned_by_user_id\" IS NULL AND \"auto_fix_tickets\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "auto_fix_tickets_status_check": { + "name": "auto_fix_tickets_status_check", + "value": "\"auto_fix_tickets\".\"status\" IN ('pending', 'running', 'completed', 'failed', 'cancelled')" + }, + "auto_fix_tickets_classification_check": { + "name": "auto_fix_tickets_classification_check", + "value": "\"auto_fix_tickets\".\"classification\" IN ('bug', 'feature', 'question', 'unclear')" + }, + "auto_fix_tickets_confidence_check": { + "name": "auto_fix_tickets_confidence_check", + "value": "\"auto_fix_tickets\".\"confidence\" >= 0 AND \"auto_fix_tickets\".\"confidence\" <= 1" + }, + "auto_fix_tickets_trigger_source_check": { + "name": "auto_fix_tickets_trigger_source_check", + "value": "\"auto_fix_tickets\".\"trigger_source\" IN ('label', 'review_comment')" + } + }, + "isRLSEnabled": false + }, + "public.auto_model": { + "name": "auto_model", + "schema": "", + "columns": { + "auto_model_id": { + "name": "auto_model_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "auto_model": { + "name": "auto_model", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_auto_model": { + "name": "UQ_auto_model", + "columns": [ + { + "expression": "auto_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_top_up_configs": { + "name": "auto_top_up_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_method_id": { + "name": "stripe_payment_method_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5000 + }, + "last_auto_top_up_at": { + "name": "last_auto_top_up_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempt_started_at": { + "name": "attempt_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_top_up_configs_owned_by_user_id": { + "name": "UQ_auto_top_up_configs_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_top_up_configs\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_auto_top_up_configs_owned_by_organization_id": { + "name": "UQ_auto_top_up_configs_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_top_up_configs\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_top_up_configs_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_top_up_configs_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_top_up_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "auto_top_up_configs_owned_by_organization_id_organizations_id_fk": { + "name": "auto_top_up_configs_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_top_up_configs", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_top_up_configs_exactly_one_owner": { + "name": "auto_top_up_configs_exactly_one_owner", + "value": "(\"auto_top_up_configs\".\"owned_by_user_id\" IS NOT NULL AND \"auto_top_up_configs\".\"owned_by_organization_id\" IS NULL) OR (\"auto_top_up_configs\".\"owned_by_user_id\" IS NULL AND \"auto_top_up_configs\".\"owned_by_organization_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.auto_triage_tickets": { + "name": "auto_triage_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_url": { + "name": "issue_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_body": { + "name": "issue_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_author": { + "name": "issue_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_type": { + "name": "issue_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_labels": { + "name": "issue_labels", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "intent_summary": { + "name": "intent_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_files": { + "name": "related_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_duplicate": { + "name": "is_duplicate", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duplicate_of_ticket_id": { + "name": "duplicate_of_ticket_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "similarity_score": { + "name": "similarity_score", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "qdrant_point_id": { + "name": "qdrant_point_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "should_auto_fix": { + "name": "should_auto_fix", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "action_taken": { + "name": "action_taken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_metadata": { + "name": "action_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_triage_tickets_repo_issue": { + "name": "UQ_auto_triage_tickets_repo_issue", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owned_by_org": { + "name": "IDX_auto_triage_tickets_owned_by_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owned_by_user": { + "name": "IDX_auto_triage_tickets_owned_by_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_status": { + "name": "IDX_auto_triage_tickets_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_created_at": { + "name": "IDX_auto_triage_tickets_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_qdrant_point_id": { + "name": "IDX_auto_triage_tickets_qdrant_point_id", + "columns": [ + { + "expression": "qdrant_point_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owner_status_created": { + "name": "IDX_auto_triage_tickets_owner_status_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_user_status_created": { + "name": "IDX_auto_triage_tickets_user_status_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_repo_classification": { + "name": "IDX_auto_triage_tickets_repo_classification", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "classification", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_triage_tickets_owned_by_organization_id_organizations_id_fk": { + "name": "auto_triage_tickets_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_triage_tickets_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_triage_tickets_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_triage_tickets_platform_integration_id_platform_integrations_id_fk": { + "name": "auto_triage_tickets_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_triage_tickets_duplicate_of_ticket_id_auto_triage_tickets_id_fk": { + "name": "auto_triage_tickets_duplicate_of_ticket_id_auto_triage_tickets_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "auto_triage_tickets", + "columnsFrom": [ + "duplicate_of_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_triage_tickets_owner_check": { + "name": "auto_triage_tickets_owner_check", + "value": "(\n (\"auto_triage_tickets\".\"owned_by_user_id\" IS NOT NULL AND \"auto_triage_tickets\".\"owned_by_organization_id\" IS NULL) OR\n (\"auto_triage_tickets\".\"owned_by_user_id\" IS NULL AND \"auto_triage_tickets\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "auto_triage_tickets_issue_type_check": { + "name": "auto_triage_tickets_issue_type_check", + "value": "\"auto_triage_tickets\".\"issue_type\" IN ('issue', 'pull_request')" + }, + "auto_triage_tickets_classification_check": { + "name": "auto_triage_tickets_classification_check", + "value": "\"auto_triage_tickets\".\"classification\" IN ('bug', 'feature', 'question', 'duplicate', 'unclear')" + }, + "auto_triage_tickets_confidence_check": { + "name": "auto_triage_tickets_confidence_check", + "value": "\"auto_triage_tickets\".\"confidence\" >= 0 AND \"auto_triage_tickets\".\"confidence\" <= 1" + }, + "auto_triage_tickets_similarity_score_check": { + "name": "auto_triage_tickets_similarity_score_check", + "value": "\"auto_triage_tickets\".\"similarity_score\" >= 0 AND \"auto_triage_tickets\".\"similarity_score\" <= 1" + }, + "auto_triage_tickets_status_check": { + "name": "auto_triage_tickets_status_check", + "value": "\"auto_triage_tickets\".\"status\" IN ('pending', 'analyzing', 'actioned', 'failed', 'skipped')" + }, + "auto_triage_tickets_action_taken_check": { + "name": "auto_triage_tickets_action_taken_check", + "value": "\"auto_triage_tickets\".\"action_taken\" IN ('pr_created', 'comment_posted', 'closed_duplicate', 'needs_clarification')" + } + }, + "isRLSEnabled": false + }, + "public.bot_request_cloud_agent_sessions": { + "name": "bot_request_cloud_agent_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "bot_request_id": { + "name": "bot_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "spawn_group_id": { + "name": "spawn_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repo": { + "name": "github_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlab_project": { + "name": "gitlab_project", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "callback_step": { + "name": "callback_step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "final_message": { + "name": "final_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "final_message_fetched_at": { + "name": "final_message_fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "final_message_error": { + "name": "final_message_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "continuation_started_at": { + "name": "continuation_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_bot_request_cas_cloud_agent_session_id": { + "name": "UQ_bot_request_cas_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id": { + "name": "IDX_bot_request_cas_bot_request_id", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id_spawn_group_id": { + "name": "IDX_bot_request_cas_bot_request_id_spawn_group_id", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spawn_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id_spawn_group_id_status": { + "name": "IDX_bot_request_cas_bot_request_id_spawn_group_id_status", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spawn_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_request_cloud_agent_sessions_bot_request_id_bot_requests_id_fk": { + "name": "bot_request_cloud_agent_sessions_bot_request_id_bot_requests_id_fk", + "tableFrom": "bot_request_cloud_agent_sessions", + "tableTo": "bot_requests", + "columnsFrom": [ + "bot_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_requests": { + "name": "bot_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_thread_id": { + "name": "platform_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_message_id": { + "name": "platform_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_message": { + "name": "user_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steps": { + "name": "steps", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_bot_requests_created_at": { + "name": "IDX_bot_requests_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_created_by": { + "name": "IDX_bot_requests_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_organization_id": { + "name": "IDX_bot_requests_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_platform_integration_id": { + "name": "IDX_bot_requests_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_status": { + "name": "IDX_bot_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_requests_created_by_kilocode_users_id_fk": { + "name": "bot_requests_created_by_kilocode_users_id_fk", + "tableFrom": "bot_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_requests_organization_id_organizations_id_fk": { + "name": "bot_requests_organization_id_organizations_id_fk", + "tableFrom": "bot_requests", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_requests_platform_integration_id_platform_integrations_id_fk": { + "name": "bot_requests_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "bot_requests", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.byok_api_keys": { + "name": "byok_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_byok_api_keys_organization_id": { + "name": "IDX_byok_api_keys_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_byok_api_keys_kilo_user_id": { + "name": "IDX_byok_api_keys_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_byok_api_keys_provider_id": { + "name": "IDX_byok_api_keys_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "byok_api_keys_organization_id_organizations_id_fk": { + "name": "byok_api_keys_organization_id_organizations_id_fk", + "tableFrom": "byok_api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "byok_api_keys_kilo_user_id_kilocode_users_id_fk": { + "name": "byok_api_keys_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "byok_api_keys", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_byok_api_keys_org_provider": { + "name": "UQ_byok_api_keys_org_provider", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider_id" + ] + }, + "UQ_byok_api_keys_user_provider": { + "name": "UQ_byok_api_keys_user_provider", + "nullsNotDistinct": false, + "columns": [ + "kilo_user_id", + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "byok_api_keys_owner_check": { + "name": "byok_api_keys_owner_check", + "value": "(\n (\"byok_api_keys\".\"kilo_user_id\" IS NOT NULL AND \"byok_api_keys\".\"organization_id\" IS NULL) OR\n (\"byok_api_keys\".\"kilo_user_id\" IS NULL AND \"byok_api_keys\".\"organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.channel_badge_counts": { + "name": "channel_badge_counts", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "badge_count": { + "name": "badge_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "channel_badge_counts_user_id_kilocode_users_id_fk": { + "name": "channel_badge_counts_user_id_kilocode_users_id_fk", + "tableFrom": "channel_badge_counts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "channel_badge_counts_user_id_channel_id_pk": { + "name": "channel_badge_counts_user_id_channel_id_pk", + "columns": [ + "user_id", + "channel_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_sessions": { + "name": "cli_sessions", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_on_platform": { + "name": "created_on_platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "api_conversation_history_blob_url": { + "name": "api_conversation_history_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_metadata_blob_url": { + "name": "task_metadata_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ui_messages_blob_url": { + "name": "ui_messages_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_state_blob_url": { + "name": "git_state_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from": { + "name": "forked_from", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_mode": { + "name": "last_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cli_sessions_kilo_user_id": { + "name": "IDX_cli_sessions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_created_at": { + "name": "IDX_cli_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_updated_at": { + "name": "IDX_cli_sessions_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_organization_id": { + "name": "IDX_cli_sessions_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_user_updated": { + "name": "IDX_cli_sessions_user_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "cli_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cli_sessions_forked_from_cli_sessions_session_id_fk": { + "name": "cli_sessions_forked_from_cli_sessions_session_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "forked_from" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_parent_session_id_cli_sessions_session_id_fk": { + "name": "cli_sessions_parent_session_id_cli_sessions_session_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_organization_id_organizations_id_fk": { + "name": "cli_sessions_organization_id_organizations_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cli_sessions_cloud_agent_session_id_unique": { + "name": "cli_sessions_cloud_agent_session_id_unique", + "nullsNotDistinct": false, + "columns": [ + "cloud_agent_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_sessions_v2": { + "name": "cli_sessions_v2", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_on_platform": { + "name": "created_on_platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_updated_at": { + "name": "status_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cli_sessions_v2_parent_session_id_kilo_user_id": { + "name": "IDX_cli_sessions_v2_parent_session_id_kilo_user_id", + "columns": [ + { + "expression": "parent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cli_sessions_v2_public_id": { + "name": "UQ_cli_sessions_v2_public_id", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_sessions_v2\".\"public_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cli_sessions_v2_cloud_agent_session_id": { + "name": "UQ_cli_sessions_v2_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_sessions_v2\".\"cloud_agent_session_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_organization_id": { + "name": "IDX_cli_sessions_v2_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_kilo_user_id": { + "name": "IDX_cli_sessions_v2_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_created_at": { + "name": "IDX_cli_sessions_v2_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_user_updated": { + "name": "IDX_cli_sessions_v2_user_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_sessions_v2_kilo_user_id_kilocode_users_id_fk": { + "name": "cli_sessions_v2_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cli_sessions_v2_organization_id_organizations_id_fk": { + "name": "cli_sessions_v2_organization_id_organizations_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_v2_parent_session_id_kilo_user_id_fk": { + "name": "cli_sessions_v2_parent_session_id_kilo_user_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "cli_sessions_v2", + "columnsFrom": [ + "parent_session_id", + "kilo_user_id" + ], + "columnsTo": [ + "session_id", + "kilo_user_id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cli_sessions_v2_session_id_kilo_user_id_pk": { + "name": "cli_sessions_v2_session_id_kilo_user_id_pk", + "columns": [ + "session_id", + "kilo_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_agent_code_reviews": { + "name": "cloud_agent_code_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_title": { + "name": "pr_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author": { + "name": "pr_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author_github_id": { + "name": "pr_author_github_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_ref": { + "name": "head_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "platform_project_id": { + "name": "platform_project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_reason": { + "name": "terminal_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'v1'" + }, + "check_run_id": { + "name": "check_run_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_tokens_in": { + "name": "total_tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_tokens_out": { + "name": "total_tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_cost_musd": { + "name": "total_cost_musd", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_code_reviews_repo_pr_sha": { + "name": "UQ_cloud_agent_code_reviews_repo_pr_sha", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "head_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_owned_by_org_id": { + "name": "idx_cloud_agent_code_reviews_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_owned_by_user_id": { + "name": "idx_cloud_agent_code_reviews_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_session_id": { + "name": "idx_cloud_agent_code_reviews_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_cli_session_id": { + "name": "idx_cloud_agent_code_reviews_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_status": { + "name": "idx_cloud_agent_code_reviews_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_repo": { + "name": "idx_cloud_agent_code_reviews_repo", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_pr_number": { + "name": "idx_cloud_agent_code_reviews_pr_number", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_created_at": { + "name": "idx_cloud_agent_code_reviews_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_pr_author_github_id": { + "name": "idx_cloud_agent_code_reviews_pr_author_github_id", + "columns": [ + { + "expression": "pr_author_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_code_reviews_owned_by_organization_id_organizations_id_fk": { + "name": "cloud_agent_code_reviews_owned_by_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_reviews_owned_by_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_code_reviews_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_reviews_platform_integration_id_platform_integrations_id_fk": { + "name": "cloud_agent_code_reviews_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_code_reviews_owner_check": { + "name": "cloud_agent_code_reviews_owner_check", + "value": "(\n (\"cloud_agent_code_reviews\".\"owned_by_user_id\" IS NOT NULL AND \"cloud_agent_code_reviews\".\"owned_by_organization_id\" IS NULL) OR\n (\"cloud_agent_code_reviews\".\"owned_by_user_id\" IS NULL AND \"cloud_agent_code_reviews\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_feedback": { + "name": "cloud_agent_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_streaming": { + "name": "is_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recent_messages": { + "name": "recent_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cloud_agent_feedback_created_at": { + "name": "IDX_cloud_agent_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_feedback_kilo_user_id": { + "name": "IDX_cloud_agent_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_feedback_cloud_agent_session_id": { + "name": "IDX_cloud_agent_feedback_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "cloud_agent_feedback_organization_id_organizations_id_fk": { + "name": "cloud_agent_feedback_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_feedback", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_agent_webhook_triggers": { + "name": "cloud_agent_webhook_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "trigger_id": { + "name": "trigger_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cloud_agent'" + }, + "kiloclaw_instance_id": { + "name": "kiloclaw_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "activation_mode": { + "name": "activation_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'webhook'" + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_timezone": { + "name": "cron_timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'UTC'" + }, + "github_repo": { + "name": "github_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_webhook_triggers_user_trigger": { + "name": "UQ_cloud_agent_webhook_triggers_user_trigger", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_webhook_triggers\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cloud_agent_webhook_triggers_org_trigger": { + "name": "UQ_cloud_agent_webhook_triggers_org_trigger", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_webhook_triggers\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_user": { + "name": "IDX_cloud_agent_webhook_triggers_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_org": { + "name": "IDX_cloud_agent_webhook_triggers_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_active": { + "name": "IDX_cloud_agent_webhook_triggers_active", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_profile": { + "name": "IDX_cloud_agent_webhook_triggers_profile", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_webhook_triggers_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_webhook_triggers_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_organization_id_organizations_id_fk": { + "name": "cloud_agent_webhook_triggers_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_kiloclaw_instance_id_kiloclaw_instances_id_fk": { + "name": "cloud_agent_webhook_triggers_kiloclaw_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "kiloclaw_instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_profile_id_agent_environment_profiles_id_fk": { + "name": "cloud_agent_webhook_triggers_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_cloud_agent_webhook_triggers_owner": { + "name": "CHK_cloud_agent_webhook_triggers_owner", + "value": "(\n (\"cloud_agent_webhook_triggers\".\"user_id\" IS NOT NULL AND \"cloud_agent_webhook_triggers\".\"organization_id\" IS NULL) OR\n (\"cloud_agent_webhook_triggers\".\"user_id\" IS NULL AND \"cloud_agent_webhook_triggers\".\"organization_id\" IS NOT NULL)\n )" + }, + "CHK_cloud_agent_webhook_triggers_cloud_agent_fields": { + "name": "CHK_cloud_agent_webhook_triggers_cloud_agent_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"target_type\" != 'cloud_agent' OR\n (\"cloud_agent_webhook_triggers\".\"github_repo\" IS NOT NULL AND \"cloud_agent_webhook_triggers\".\"profile_id\" IS NOT NULL)\n )" + }, + "CHK_cloud_agent_webhook_triggers_kiloclaw_fields": { + "name": "CHK_cloud_agent_webhook_triggers_kiloclaw_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"target_type\" != 'kiloclaw_chat' OR\n \"cloud_agent_webhook_triggers\".\"kiloclaw_instance_id\" IS NOT NULL\n )" + }, + "CHK_cloud_agent_webhook_triggers_scheduled_fields": { + "name": "CHK_cloud_agent_webhook_triggers_scheduled_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"activation_mode\" != 'scheduled' OR\n \"cloud_agent_webhook_triggers\".\"cron_expression\" IS NOT NULL\n )" + } + }, + "isRLSEnabled": false + }, + "public.code_indexing_manifest": { + "name": "code_indexing_manifest", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_lines": { + "name": "total_lines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_ai_lines": { + "name": "total_ai_lines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_code_indexing_manifest_organization_id": { + "name": "IDX_code_indexing_manifest_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_kilo_user_id": { + "name": "IDX_code_indexing_manifest_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_project_id": { + "name": "IDX_code_indexing_manifest_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_file_hash": { + "name": "IDX_code_indexing_manifest_file_hash", + "columns": [ + { + "expression": "file_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_git_branch": { + "name": "IDX_code_indexing_manifest_git_branch", + "columns": [ + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_created_at": { + "name": "IDX_code_indexing_manifest_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_indexing_manifest_kilo_user_id_kilocode_users_id_fk": { + "name": "code_indexing_manifest_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "code_indexing_manifest", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_indexing_manifest_org_user_project_hash_branch": { + "name": "UQ_code_indexing_manifest_org_user_project_hash_branch", + "nullsNotDistinct": true, + "columns": [ + "organization_id", + "kilo_user_id", + "project_id", + "file_path", + "git_branch" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.code_indexing_search": { + "name": "code_indexing_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "query": { + "name": "query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_code_indexing_search_organization_id": { + "name": "IDX_code_indexing_search_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_kilo_user_id": { + "name": "IDX_code_indexing_search_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_project_id": { + "name": "IDX_code_indexing_search_project_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_created_at": { + "name": "IDX_code_indexing_search_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_indexing_search_kilo_user_id_kilocode_users_id_fk": { + "name": "code_indexing_search_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "code_indexing_search", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_contributors": { + "name": "contributor_champion_contributors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "first_contribution_at": { + "name": "first_contribution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_contribution_at": { + "name": "last_contribution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "all_time_contributions": { + "name": "all_time_contributions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "manual_email": { + "name": "manual_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_contributors_last_contribution_at": { + "name": "IDX_contributor_champion_contributors_last_contribution_at", + "columns": [ + { + "expression": "last_contribution_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_contributors_manual_email": { + "name": "IDX_contributor_champion_contributors_manual_email", + "columns": [ + { + "expression": "manual_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_contributors_github_login": { + "name": "UQ_contributor_champion_contributors_github_login", + "nullsNotDistinct": false, + "columns": [ + "github_login" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_events": { + "name": "contributor_champion_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "contributor_id": { + "name": "contributor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_pr_number": { + "name": "github_pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "github_pr_url": { + "name": "github_pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_pr_title": { + "name": "github_pr_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_author_login": { + "name": "github_author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_author_email": { + "name": "github_author_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_events_contributor_id": { + "name": "IDX_contributor_champion_events_contributor_id", + "columns": [ + { + "expression": "contributor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_events_merged_at": { + "name": "IDX_contributor_champion_events_merged_at", + "columns": [ + { + "expression": "merged_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_events_author_email": { + "name": "IDX_contributor_champion_events_author_email", + "columns": [ + { + "expression": "github_author_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contributor_champion_events_contributor_id_contributor_champion_contributors_id_fk": { + "name": "contributor_champion_events_contributor_id_contributor_champion_contributors_id_fk", + "tableFrom": "contributor_champion_events", + "tableTo": "contributor_champion_contributors", + "columnsFrom": [ + "contributor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_events_repo_pr": { + "name": "UQ_contributor_champion_events_repo_pr", + "nullsNotDistinct": false, + "columns": [ + "repo_full_name", + "github_pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_memberships": { + "name": "contributor_champion_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "contributor_id": { + "name": "contributor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selected_tier": { + "name": "selected_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrolled_tier": { + "name": "enrolled_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credit_amount_microdollars": { + "name": "credit_amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credits_last_granted_at": { + "name": "credits_last_granted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "linked_kilo_user_id": { + "name": "linked_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_memberships_credits_due": { + "name": "IDX_contributor_champion_memberships_credits_due", + "columns": [ + { + "expression": "credits_last_granted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"contributor_champion_memberships\".\"enrolled_tier\" IS NOT NULL AND \"contributor_champion_memberships\".\"credit_amount_microdollars\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_memberships_linked_kilo_user_id": { + "name": "IDX_contributor_champion_memberships_linked_kilo_user_id", + "columns": [ + { + "expression": "linked_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contributor_champion_memberships_contributor_id_contributor_champion_contributors_id_fk": { + "name": "contributor_champion_memberships_contributor_id_contributor_champion_contributors_id_fk", + "tableFrom": "contributor_champion_memberships", + "tableTo": "contributor_champion_contributors", + "columnsFrom": [ + "contributor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "contributor_champion_memberships_linked_kilo_user_id_kilocode_users_id_fk": { + "name": "contributor_champion_memberships_linked_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "contributor_champion_memberships", + "tableTo": "kilocode_users", + "columnsFrom": [ + "linked_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_memberships_contributor_id": { + "name": "UQ_contributor_champion_memberships_contributor_id", + "nullsNotDistinct": false, + "columns": [ + "contributor_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "contributor_champion_memberships_selected_tier_check": { + "name": "contributor_champion_memberships_selected_tier_check", + "value": "\"contributor_champion_memberships\".\"selected_tier\" IS NULL OR \"contributor_champion_memberships\".\"selected_tier\" IN ('contributor', 'ambassador', 'champion')" + }, + "contributor_champion_memberships_enrolled_tier_check": { + "name": "contributor_champion_memberships_enrolled_tier_check", + "value": "\"contributor_champion_memberships\".\"enrolled_tier\" IS NULL OR \"contributor_champion_memberships\".\"enrolled_tier\" IN ('contributor', 'ambassador', 'champion')" + } + }, + "isRLSEnabled": false + }, + "public.contributor_champion_sync_state": { + "name": "contributor_champion_sync_state", + "schema": "", + "columns": { + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "last_merged_at": { + "name": "last_merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credit_campaigns": { + "name": "credit_campaigns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit_category": { + "name": "credit_category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credit_expiry_hours": { + "name": "credit_expiry_hours", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "campaign_ends_at": { + "name": "campaign_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_redemptions_allowed": { + "name": "total_redemptions_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_credit_campaigns_slug": { + "name": "UQ_credit_campaigns_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_credit_campaigns_credit_category": { + "name": "UQ_credit_campaigns_credit_category", + "columns": [ + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credit_campaigns_slug_format_check": { + "name": "credit_campaigns_slug_format_check", + "value": "\"credit_campaigns\".\"slug\" ~ '^[a-z0-9-]{5,40}$'" + }, + "credit_campaigns_amount_positive_check": { + "name": "credit_campaigns_amount_positive_check", + "value": "\"credit_campaigns\".\"amount_microdollars\" > 0" + }, + "credit_campaigns_credit_expiry_hours_positive_check": { + "name": "credit_campaigns_credit_expiry_hours_positive_check", + "value": "\"credit_campaigns\".\"credit_expiry_hours\" IS NULL OR \"credit_campaigns\".\"credit_expiry_hours\" > 0" + }, + "credit_campaigns_total_redemptions_allowed_positive_check": { + "name": "credit_campaigns_total_redemptions_allowed_positive_check", + "value": "\"credit_campaigns\".\"total_redemptions_allowed\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.credit_transactions": { + "name": "credit_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expiration_baseline_microdollars_used": { + "name": "expiration_baseline_microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "original_baseline_microdollars_used": { + "name": "original_baseline_microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_transaction_id": { + "name": "original_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_id": { + "name": "stripe_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coinbase_credit_block_id": { + "name": "coinbase_credit_block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credit_category": { + "name": "credit_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_date": { + "name": "expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "check_category_uniqueness": { + "name": "check_category_uniqueness", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_credit_transactions_created_at": { + "name": "IDX_credit_transactions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_is_free": { + "name": "IDX_credit_transactions_is_free", + "columns": [ + { + "expression": "is_free", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_kilo_user_id": { + "name": "IDX_credit_transactions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_credit_category": { + "name": "IDX_credit_transactions_credit_category", + "columns": [ + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_stripe_payment_id": { + "name": "IDX_credit_transactions_stripe_payment_id", + "columns": [ + { + "expression": "stripe_payment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_original_transaction_id": { + "name": "IDX_credit_transactions_original_transaction_id", + "columns": [ + { + "expression": "original_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_coinbase_credit_block_id": { + "name": "IDX_credit_transactions_coinbase_credit_block_id", + "columns": [ + { + "expression": "coinbase_credit_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_organization_id": { + "name": "IDX_credit_transactions_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_unique_category": { + "name": "IDX_credit_transactions_unique_category", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credit_transactions\".\"check_category_uniqueness\" = TRUE", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_llm2": { + "name": "custom_llm2", + "schema": "", + "columns": { + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_builds": { + "name": "deployment_builds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_builds_deployment_id": { + "name": "idx_deployment_builds_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_builds_status": { + "name": "idx_deployment_builds_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_builds_deployment_id_deployments_id_fk": { + "name": "deployment_builds_deployment_id_deployments_id_fk", + "tableFrom": "deployment_builds", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_env_vars": { + "name": "deployment_env_vars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_secret": { + "name": "is_secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_env_vars_deployment_id": { + "name": "idx_deployment_env_vars_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_env_vars_deployment_id_deployments_id_fk": { + "name": "deployment_env_vars_deployment_id_deployments_id_fk", + "tableFrom": "deployment_env_vars", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployment_env_vars_deployment_key": { + "name": "UQ_deployment_env_vars_deployment_key", + "nullsNotDistinct": false, + "columns": [ + "deployment_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_events": { + "name": "deployment_events", + "schema": "", + "columns": { + "build_id": { + "name": "build_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'log'" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_deployment_events_build_id": { + "name": "idx_deployment_events_build_id", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_events_timestamp": { + "name": "idx_deployment_events_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_events_type": { + "name": "idx_deployment_events_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_events_build_id_deployment_builds_id_fk": { + "name": "deployment_events_build_id_deployment_builds_id_fk", + "tableFrom": "deployment_events", + "tableTo": "deployment_builds", + "columnsFrom": [ + "build_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "deployment_events_build_id_event_id_pk": { + "name": "deployment_events_build_id_event_id_pk", + "columns": [ + "build_id", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_threat_detections": { + "name": "deployment_threat_detections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "build_id": { + "name": "build_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "threat_type": { + "name": "threat_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_threat_detections_deployment_id": { + "name": "idx_deployment_threat_detections_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_threat_detections_created_at": { + "name": "idx_deployment_threat_detections_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_threat_detections_deployment_id_deployments_id_fk": { + "name": "deployment_threat_detections_deployment_id_deployments_id_fk", + "tableFrom": "deployment_threat_detections", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_threat_detections_build_id_deployment_builds_id_fk": { + "name": "deployment_threat_detections_build_id_deployment_builds_id_fk", + "tableFrom": "deployment_threat_detections", + "tableTo": "deployment_builds", + "columnsFrom": [ + "build_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployments": { + "name": "deployments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deployment_slug": { + "name": "deployment_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_worker_name": { + "name": "internal_worker_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_source": { + "name": "repository_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_url": { + "name": "deployment_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "git_auth_token": { + "name": "git_auth_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_deployed_at": { + "name": "last_deployed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_build_id": { + "name": "last_build_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "threat_status": { + "name": "threat_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_from": { + "name": "created_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_deployments_owned_by_user_id": { + "name": "idx_deployments_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_owned_by_organization_id": { + "name": "idx_deployments_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_platform_integration_id": { + "name": "idx_deployments_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_repository_source_branch": { + "name": "idx_deployments_repository_source_branch", + "columns": [ + { + "expression": "repository_source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_threat_status_pending": { + "name": "idx_deployments_threat_status_pending", + "columns": [ + { + "expression": "threat_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"deployments\".\"threat_status\" = 'pending_scan'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployments_owned_by_user_id_kilocode_users_id_fk": { + "name": "deployments_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "deployments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployments_owned_by_organization_id_organizations_id_fk": { + "name": "deployments_owned_by_organization_id_organizations_id_fk", + "tableFrom": "deployments", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployments_deployment_slug": { + "name": "UQ_deployments_deployment_slug", + "nullsNotDistinct": false, + "columns": [ + "deployment_slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "deployments_owner_check": { + "name": "deployments_owner_check", + "value": "(\n (\"deployments\".\"owned_by_user_id\" IS NOT NULL AND \"deployments\".\"owned_by_organization_id\" IS NULL) OR\n (\"deployments\".\"owned_by_user_id\" IS NULL AND \"deployments\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "deployments_source_type_check": { + "name": "deployments_source_type_check", + "value": "\"deployments\".\"source_type\" IN ('github', 'git', 'app-builder')" + } + }, + "isRLSEnabled": false + }, + "public.device_auth_requests": { + "name": "device_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_device_auth_requests_code": { + "name": "UQ_device_auth_requests_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_status": { + "name": "IDX_device_auth_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_expires_at": { + "name": "IDX_device_auth_requests_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_kilo_user_id": { + "name": "IDX_device_auth_requests_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_auth_requests_kilo_user_id_kilocode_users_id_fk": { + "name": "device_auth_requests_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "device_auth_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord_gateway_listener": { + "name": "discord_gateway_listener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "listener_id": { + "name": "listener_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.editor_name": { + "name": "editor_name", + "schema": "", + "columns": { + "editor_name_id": { + "name": "editor_name_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "editor_name": { + "name": "editor_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_editor_name": { + "name": "UQ_editor_name", + "columns": [ + { + "expression": "editor_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.enrichment_data": { + "name": "enrichment_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_enrichment_data": { + "name": "github_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linkedin_enrichment_data": { + "name": "linkedin_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "clay_enrichment_data": { + "name": "clay_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_enrichment_data_user_id": { + "name": "IDX_enrichment_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "enrichment_data_user_id_kilocode_users_id_fk": { + "name": "enrichment_data_user_id_kilocode_users_id_fk", + "tableFrom": "enrichment_data", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_enrichment_data_user_id": { + "name": "UQ_enrichment_data_user_id", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exa_monthly_usage": { + "name": "exa_monthly_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "month": { + "name": "month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_charged_microdollars": { + "name": "total_charged_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "free_allowance_microdollars": { + "name": "free_allowance_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 10000000 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_exa_monthly_usage_personal": { + "name": "idx_exa_monthly_usage_personal", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"exa_monthly_usage\".\"organization_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_exa_monthly_usage_org": { + "name": "idx_exa_monthly_usage_org", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"exa_monthly_usage\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exa_usage_log": { + "name": "exa_usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "charged_to_balance": { + "name": "charged_to_balance", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_exa_usage_log_user_created": { + "name": "idx_exa_usage_log_user_created", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "exa_usage_log_id_created_at_pk": { + "name": "exa_usage_log_id_created_at_pk", + "columns": [ + "id", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feature": { + "name": "feature", + "schema": "", + "columns": { + "feature_id": { + "name": "feature_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_feature": { + "name": "UQ_feature", + "columns": [ + { + "expression": "feature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finish_reason": { + "name": "finish_reason", + "schema": "", + "columns": { + "finish_reason_id": { + "name": "finish_reason_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_finish_reason": { + "name": "UQ_finish_reason", + "columns": [ + { + "expression": "finish_reason", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_model_usage": { + "name": "free_model_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_free_model_usage_ip_created_at": { + "name": "idx_free_model_usage_ip_created_at", + "columns": [ + { + "expression": "ip_address", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_free_model_usage_created_at": { + "name": "idx_free_model_usage_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_free_model_usage_user_created_at": { + "name": "idx_free_model_usage_user_created_at", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"free_model_usage\".\"kilo_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.http_ip": { + "name": "http_ip", + "schema": "", + "columns": { + "http_ip_id": { + "name": "http_ip_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "http_ip": { + "name": "http_ip", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_http_ip": { + "name": "UQ_http_ip", + "columns": [ + { + "expression": "http_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.http_user_agent": { + "name": "http_user_agent", + "schema": "", + "columns": { + "http_user_agent_id": { + "name": "http_user_agent_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_http_user_agent": { + "name": "UQ_http_user_agent", + "columns": [ + { + "expression": "http_user_agent", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ja4_digest": { + "name": "ja4_digest", + "schema": "", + "columns": { + "ja4_digest_id": { + "name": "ja4_digest_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "ja4_digest": { + "name": "ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_ja4_digest": { + "name": "UQ_ja4_digest", + "columns": [ + { + "expression": "ja4_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilo_pass_audit_log": { + "name": "kilo_pass_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_credit_transaction_id": { + "name": "related_credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_monthly_issuance_id": { + "name": "related_monthly_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "IDX_kilo_pass_audit_log_created_at": { + "name": "IDX_kilo_pass_audit_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_kilo_user_id": { + "name": "IDX_kilo_pass_audit_log_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_kilo_pass_subscription_id": { + "name": "IDX_kilo_pass_audit_log_kilo_pass_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_action": { + "name": "IDX_kilo_pass_audit_log_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_result": { + "name": "IDX_kilo_pass_audit_log_result", + "columns": [ + { + "expression": "result", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_idempotency_key": { + "name": "IDX_kilo_pass_audit_log_idempotency_key", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_event_id": { + "name": "IDX_kilo_pass_audit_log_stripe_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_invoice_id": { + "name": "IDX_kilo_pass_audit_log_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_subscription_id": { + "name": "IDX_kilo_pass_audit_log_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_related_credit_transaction_id": { + "name": "IDX_kilo_pass_audit_log_related_credit_transaction_id", + "columns": [ + { + "expression": "related_credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_related_monthly_issuance_id": { + "name": "IDX_kilo_pass_audit_log_related_monthly_issuance_id", + "columns": [ + { + "expression": "related_monthly_issuance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_audit_log_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_audit_log_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_audit_log_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_related_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_audit_log_related_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "credit_transactions", + "columnsFrom": [ + "related_credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_related_monthly_issuance_id_kilo_pass_issuances_id_fk": { + "name": "kilo_pass_audit_log_related_monthly_issuance_id_kilo_pass_issuances_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "related_monthly_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_audit_log_action_check": { + "name": "kilo_pass_audit_log_action_check", + "value": "\"kilo_pass_audit_log\".\"action\" IN ('stripe_webhook_received', 'kilo_pass_invoice_paid_handled', 'base_credits_issued', 'bonus_credits_issued', 'bonus_credits_skipped_idempotent', 'first_month_50pct_promo_issued', 'yearly_monthly_base_cron_started', 'yearly_monthly_base_cron_completed', 'issue_yearly_remaining_credits', 'yearly_monthly_bonus_cron_started', 'yearly_monthly_bonus_cron_completed')" + }, + "kilo_pass_audit_log_result_check": { + "name": "kilo_pass_audit_log_result_check", + "value": "\"kilo_pass_audit_log\".\"result\" IN ('success', 'skipped_idempotent', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_issuance_items": { + "name": "kilo_pass_issuance_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_issuance_id": { + "name": "kilo_pass_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit_transaction_id": { + "name": "credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "bonus_percent_applied": { + "name": "bonus_percent_applied", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_issuance_items_issuance_id": { + "name": "IDX_kilo_pass_issuance_items_issuance_id", + "columns": [ + { + "expression": "kilo_pass_issuance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuance_items_credit_transaction_id": { + "name": "IDX_kilo_pass_issuance_items_credit_transaction_id", + "columns": [ + { + "expression": "credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_issuance_items_kilo_pass_issuance_id_kilo_pass_issuances_id_fk": { + "name": "kilo_pass_issuance_items_kilo_pass_issuance_id_kilo_pass_issuances_id_fk", + "tableFrom": "kilo_pass_issuance_items", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "kilo_pass_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_issuance_items_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_issuance_items_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_issuance_items", + "tableTo": "credit_transactions", + "columnsFrom": [ + "credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilo_pass_issuance_items_credit_transaction_id_unique": { + "name": "kilo_pass_issuance_items_credit_transaction_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credit_transaction_id" + ] + }, + "UQ_kilo_pass_issuance_items_issuance_kind": { + "name": "UQ_kilo_pass_issuance_items_issuance_kind", + "nullsNotDistinct": false, + "columns": [ + "kilo_pass_issuance_id", + "kind" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_issuance_items_bonus_percent_applied_range_check": { + "name": "kilo_pass_issuance_items_bonus_percent_applied_range_check", + "value": "\"kilo_pass_issuance_items\".\"bonus_percent_applied\" IS NULL OR (\"kilo_pass_issuance_items\".\"bonus_percent_applied\" >= 0 AND \"kilo_pass_issuance_items\".\"bonus_percent_applied\" <= 1)" + }, + "kilo_pass_issuance_items_amount_usd_non_negative_check": { + "name": "kilo_pass_issuance_items_amount_usd_non_negative_check", + "value": "\"kilo_pass_issuance_items\".\"amount_usd\" >= 0" + }, + "kilo_pass_issuance_items_kind_check": { + "name": "kilo_pass_issuance_items_kind_check", + "value": "\"kilo_pass_issuance_items\".\"kind\" IN ('base', 'bonus', 'promo_first_month_50pct')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_issuances": { + "name": "kilo_pass_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_month": { + "name": "issue_month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_issuances_stripe_invoice_id": { + "name": "UQ_kilo_pass_issuances_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_issuances\".\"stripe_invoice_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuances_subscription_id": { + "name": "IDX_kilo_pass_issuances_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuances_issue_month": { + "name": "IDX_kilo_pass_issuances_issue_month", + "columns": [ + { + "expression": "issue_month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_issuances_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_issuances_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_issuances", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_issuances_subscription_issue_month": { + "name": "UQ_kilo_pass_issuances_subscription_issue_month", + "nullsNotDistinct": false, + "columns": [ + "kilo_pass_subscription_id", + "issue_month" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_issuances_issue_month_day_one_check": { + "name": "kilo_pass_issuances_issue_month_day_one_check", + "value": "EXTRACT(DAY FROM \"kilo_pass_issuances\".\"issue_month\") = 1" + }, + "kilo_pass_issuances_source_check": { + "name": "kilo_pass_issuances_source_check", + "value": "\"kilo_pass_issuances\".\"source\" IN ('stripe_invoice', 'cron')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_pause_events": { + "name": "kilo_pass_pause_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resumes_at": { + "name": "resumes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resumed_at": { + "name": "resumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_pause_events_subscription_id": { + "name": "IDX_kilo_pass_pause_events_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_pause_events_one_open_per_sub": { + "name": "UQ_kilo_pass_pause_events_one_open_per_sub", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_pause_events\".\"resumed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_pause_events_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_pause_events_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_pause_events", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_pause_events_resumed_at_after_paused_at_check": { + "name": "kilo_pass_pause_events_resumed_at_after_paused_at_check", + "value": "\"kilo_pass_pause_events\".\"resumed_at\" IS NULL OR \"kilo_pass_pause_events\".\"resumed_at\" >= \"kilo_pass_pause_events\".\"paused_at\"" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_scheduled_changes": { + "name": "kilo_pass_scheduled_changes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_tier": { + "name": "from_tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_cadence": { + "name": "from_cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_tier": { + "name": "to_tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_cadence": { + "name": "to_cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_scheduled_changes_kilo_user_id": { + "name": "IDX_kilo_pass_scheduled_changes_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_status": { + "name": "IDX_kilo_pass_scheduled_changes_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_stripe_subscription_id": { + "name": "IDX_kilo_pass_scheduled_changes_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_scheduled_changes_active_stripe_subscription_id": { + "name": "UQ_kilo_pass_scheduled_changes_active_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_scheduled_changes\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_effective_at": { + "name": "IDX_kilo_pass_scheduled_changes_effective_at", + "columns": [ + { + "expression": "effective_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_deleted_at": { + "name": "IDX_kilo_pass_scheduled_changes_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_scheduled_changes_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_scheduled_changes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_scheduled_changes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_scheduled_changes_stripe_subscription_id_kilo_pass_subscriptions_stripe_subscription_id_fk": { + "name": "kilo_pass_scheduled_changes_stripe_subscription_id_kilo_pass_subscriptions_stripe_subscription_id_fk", + "tableFrom": "kilo_pass_scheduled_changes", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "stripe_subscription_id" + ], + "columnsTo": [ + "stripe_subscription_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_scheduled_changes_from_tier_check": { + "name": "kilo_pass_scheduled_changes_from_tier_check", + "value": "\"kilo_pass_scheduled_changes\".\"from_tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_scheduled_changes_from_cadence_check": { + "name": "kilo_pass_scheduled_changes_from_cadence_check", + "value": "\"kilo_pass_scheduled_changes\".\"from_cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_scheduled_changes_to_tier_check": { + "name": "kilo_pass_scheduled_changes_to_tier_check", + "value": "\"kilo_pass_scheduled_changes\".\"to_tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_scheduled_changes_to_cadence_check": { + "name": "kilo_pass_scheduled_changes_to_cadence_check", + "value": "\"kilo_pass_scheduled_changes\".\"to_cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_scheduled_changes_status_check": { + "name": "kilo_pass_scheduled_changes_status_check", + "value": "\"kilo_pass_scheduled_changes\".\"status\" IN ('not_started', 'active', 'completed', 'released', 'canceled')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_subscriptions": { + "name": "kilo_pass_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_streak_months": { + "name": "current_streak_months", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_yearly_issue_at": { + "name": "next_yearly_issue_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_subscriptions_kilo_user_id": { + "name": "IDX_kilo_pass_subscriptions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_status": { + "name": "IDX_kilo_pass_subscriptions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_cadence": { + "name": "IDX_kilo_pass_subscriptions_cadence", + "columns": [ + { + "expression": "cadence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_subscriptions_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_subscriptions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilo_pass_subscriptions_stripe_subscription_id_unique": { + "name": "kilo_pass_subscriptions_stripe_subscription_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_subscriptions_current_streak_months_non_negative_check": { + "name": "kilo_pass_subscriptions_current_streak_months_non_negative_check", + "value": "\"kilo_pass_subscriptions\".\"current_streak_months\" >= 0" + }, + "kilo_pass_subscriptions_tier_check": { + "name": "kilo_pass_subscriptions_tier_check", + "value": "\"kilo_pass_subscriptions\".\"tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_subscriptions_cadence_check": { + "name": "kilo_pass_subscriptions_cadence_check", + "value": "\"kilo_pass_subscriptions\".\"cadence\" IN ('monthly', 'yearly')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_access_codes": { + "name": "kiloclaw_access_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_access_codes_code": { + "name": "UQ_kiloclaw_access_codes_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_access_codes_user_status": { + "name": "IDX_kiloclaw_access_codes_user_status", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_access_codes_one_active_per_user": { + "name": "UQ_kiloclaw_access_codes_one_active_per_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_access_codes_kilo_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_access_codes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_access_codes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_admin_audit_logs": { + "name": "kiloclaw_admin_audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_admin_audit_logs_target_user_id": { + "name": "IDX_kiloclaw_admin_audit_logs_target_user_id", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_admin_audit_logs_action": { + "name": "IDX_kiloclaw_admin_audit_logs_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_admin_audit_logs_created_at": { + "name": "IDX_kiloclaw_admin_audit_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_cli_runs": { + "name": "kiloclaw_cli_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "initiated_by_admin_id": { + "name": "initiated_by_admin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_cli_runs_user_id": { + "name": "IDX_kiloclaw_cli_runs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_cli_runs_started_at": { + "name": "IDX_kiloclaw_cli_runs_started_at", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_cli_runs_instance_id": { + "name": "IDX_kiloclaw_cli_runs_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_cli_runs_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_cli_runs_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_cli_runs_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_cli_runs_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_cli_runs_initiated_by_admin_id_kilocode_users_id_fk": { + "name": "kiloclaw_cli_runs_initiated_by_admin_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "initiated_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_earlybird_purchases": { + "name": "kiloclaw_earlybird_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manual_payment_id": { + "name": "manual_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kiloclaw_earlybird_purchases_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_earlybird_purchases_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_earlybird_purchases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_earlybird_purchases_user_id_unique": { + "name": "kiloclaw_earlybird_purchases_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "kiloclaw_earlybird_purchases_stripe_charge_id_unique": { + "name": "kiloclaw_earlybird_purchases_stripe_charge_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_charge_id" + ] + }, + "kiloclaw_earlybird_purchases_manual_payment_id_unique": { + "name": "kiloclaw_earlybird_purchases_manual_payment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "manual_payment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_email_log": { + "name": "kiloclaw_email_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email_type": { + "name": "email_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_start": { + "name": "period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "'epoch'" + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_email_log_user_type_global": { + "name": "UQ_kiloclaw_email_log_user_type_global", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_email_log\".\"instance_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_email_log_user_instance_type_period": { + "name": "UQ_kiloclaw_email_log_user_instance_type_period", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_email_log\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_email_log_type_sent_instance": { + "name": "IDX_kiloclaw_email_log_type_sent_instance", + "columns": [ + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_email_log\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_email_log_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_email_log_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_email_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_email_log_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_email_log_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_email_log", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_google_oauth_connections": { + "name": "kiloclaw_google_oauth_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'google'" + }, + "account_email": { + "name": "account_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_subject": { + "name": "account_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_secret_encrypted": { + "name": "oauth_client_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_profile": { + "name": "credential_profile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kilo_owned'" + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "grants_by_source": { + "name": "grants_by_source", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_google_oauth_connections_instance": { + "name": "UQ_kiloclaw_google_oauth_connections_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_google_oauth_connections_status": { + "name": "IDX_kiloclaw_google_oauth_connections_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_google_oauth_connections_provider": { + "name": "IDX_kiloclaw_google_oauth_connections_provider", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_google_oauth_connections_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_google_oauth_connections_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_google_oauth_connections", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_google_oauth_connections_status_check": { + "name": "kiloclaw_google_oauth_connections_status_check", + "value": "\"kiloclaw_google_oauth_connections\".\"status\" IN ('active', 'action_required', 'disconnected')" + }, + "kiloclaw_google_oauth_connections_credential_profile_check": { + "name": "kiloclaw_google_oauth_connections_credential_profile_check", + "value": "\"kiloclaw_google_oauth_connections\".\"credential_profile\" IN ('legacy', 'kilo_owned')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_image_catalog": { + "name": "kiloclaw_image_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "openclaw_version": { + "name": "openclaw_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_digest": { + "name": "image_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "rollout_percent": { + "name": "rollout_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_latest": { + "name": "is_latest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_kiloclaw_image_catalog_status": { + "name": "IDX_kiloclaw_image_catalog_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_image_catalog_variant": { + "name": "IDX_kiloclaw_image_catalog_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_image_catalog_one_latest_per_variant": { + "name": "UQ_kiloclaw_image_catalog_one_latest_per_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_image_catalog\".\"is_latest\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_image_catalog_one_candidate_per_variant": { + "name": "UQ_kiloclaw_image_catalog_one_candidate_per_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_image_catalog\".\"is_latest\" = false AND \"kiloclaw_image_catalog\".\"rollout_percent\" > 0 AND \"kiloclaw_image_catalog\".\"status\" = 'available'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_image_catalog_image_tag_unique": { + "name": "kiloclaw_image_catalog_image_tag_unique", + "nullsNotDistinct": false, + "columns": [ + "image_tag" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_inbound_email_aliases": { + "name": "kiloclaw_inbound_email_aliases", + "schema": "", + "columns": { + "alias": { + "name": "alias", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_inbound_email_aliases_instance_id": { + "name": "IDX_kiloclaw_inbound_email_aliases_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_inbound_email_aliases_active_instance": { + "name": "UQ_kiloclaw_inbound_email_aliases_active_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_inbound_email_aliases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_inbound_email_aliases_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_inbound_email_aliases_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_inbound_email_aliases", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_inbound_email_reserved_aliases": { + "name": "kiloclaw_inbound_email_reserved_aliases", + "schema": "", + "columns": { + "alias": { + "name": "alias", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_instances": { + "name": "kiloclaw_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_id": { + "name": "sandbox_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'fly'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbound_email_enabled": { + "name": "inbound_email_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inactive_trial_stopped_at": { + "name": "inactive_trial_stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tracked_image_tag": { + "name": "tracked_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_instances_active": { + "name": "UQ_kiloclaw_instances_active", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sandbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_personal_by_user": { + "name": "IDX_kiloclaw_instances_active_personal_by_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_org_by_user_org": { + "name": "IDX_kiloclaw_instances_active_org_by_user_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_tracked_image_tag": { + "name": "IDX_kiloclaw_instances_tracked_image_tag", + "columns": [ + { + "expression": "tracked_image_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_instances_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_instances_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_instances", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_instances_organization_id_organizations_id_fk": { + "name": "kiloclaw_instances_organization_id_organizations_id_fk", + "tableFrom": "kiloclaw_instances", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_stages": { + "name": "kiloclaw_scheduled_action_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scheduled_action_id": { + "name": "scheduled_action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_index": { + "name": "stage_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "notice_sent_at": { + "name": "notice_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "applied_count": { + "name": "applied_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_stages_parent_index": { + "name": "UQ_kiloclaw_scheduled_action_stages_parent_index", + "columns": [ + { + "expression": "scheduled_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_stages_notice_due": { + "name": "IDX_kiloclaw_scheduled_action_stages_notice_due", + "columns": [ + { + "expression": "scheduled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_stages\".\"notice_sent_at\" IS NULL AND \"kiloclaw_scheduled_action_stages\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_stages_scheduled_action_id_kiloclaw_scheduled_actions_id_fk": { + "name": "kiloclaw_scheduled_action_stages_scheduled_action_id_kiloclaw_scheduled_actions_id_fk", + "tableFrom": "kiloclaw_scheduled_action_stages", + "tableTo": "kiloclaw_scheduled_actions", + "columnsFrom": [ + "scheduled_action_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_targets": { + "name": "kiloclaw_scheduled_action_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scheduled_action_id": { + "name": "scheduled_action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_image_tag": { + "name": "source_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_image_tag": { + "name": "target_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_targets_parent_instance": { + "name": "UQ_kiloclaw_scheduled_action_targets_parent_instance", + "columns": [ + { + "expression": "scheduled_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_targets_stage": { + "name": "IDX_kiloclaw_scheduled_action_targets_stage", + "columns": [ + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_targets_pending_by_instance": { + "name": "IDX_kiloclaw_scheduled_action_targets_pending_by_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_targets\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_targets_scheduled_action_id_kiloclaw_scheduled_actions_id_fk": { + "name": "kiloclaw_scheduled_action_targets_scheduled_action_id_kiloclaw_scheduled_actions_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_scheduled_actions", + "columnsFrom": [ + "scheduled_action_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_stage_id_kiloclaw_scheduled_action_stages_id_fk": { + "name": "kiloclaw_scheduled_action_targets_stage_id_kiloclaw_scheduled_action_stages_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_scheduled_action_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_scheduled_action_targets_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_scheduled_action_targets_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_actions": { + "name": "kiloclaw_scheduled_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_image_tag": { + "name": "target_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_pins": { + "name": "override_pins", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notice_lead_hours": { + "name": "notice_lead_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "notice_subject": { + "name": "notice_subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notice_body": { + "name": "notice_body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_count": { + "name": "total_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "applied_count": { + "name": "applied_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "IDX_kiloclaw_scheduled_actions_status": { + "name": "IDX_kiloclaw_scheduled_actions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_actions_action_type": { + "name": "IDX_kiloclaw_scheduled_actions_action_type", + "columns": [ + { + "expression": "action_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_actions_created_by": { + "name": "IDX_kiloclaw_scheduled_actions_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_actions_target_image_tag_kiloclaw_image_catalog_image_tag_fk": { + "name": "kiloclaw_scheduled_actions_target_image_tag_kiloclaw_image_catalog_image_tag_fk", + "tableFrom": "kiloclaw_scheduled_actions", + "tableTo": "kiloclaw_image_catalog", + "columnsFrom": [ + "target_image_tag" + ], + "columnsTo": [ + "image_tag" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_actions_created_by_kilocode_users_id_fk": { + "name": "kiloclaw_scheduled_actions_created_by_kilocode_users_id_fk", + "tableFrom": "kiloclaw_scheduled_actions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_subscription_change_log": { + "name": "kiloclaw_subscription_change_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "before_state": { + "name": "before_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_state": { + "name": "after_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_subscription_change_log_subscription_created_at": { + "name": "IDX_kiloclaw_subscription_change_log_subscription_created_at", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscription_change_log_created_at": { + "name": "IDX_kiloclaw_subscription_change_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_subscription_change_log_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_subscription_change_log_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_subscription_change_log", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_subscription_change_log_actor_type_check": { + "name": "kiloclaw_subscription_change_log_actor_type_check", + "value": "\"kiloclaw_subscription_change_log\".\"actor_type\" IN ('user', 'system')" + }, + "kiloclaw_subscription_change_log_action_check": { + "name": "kiloclaw_subscription_change_log_action_check", + "value": "\"kiloclaw_subscription_change_log\".\"action\" IN ('created', 'status_changed', 'plan_switched', 'period_advanced', 'canceled', 'reactivated', 'suspended', 'destruction_scheduled', 'reassigned', 'backfilled', 'payment_source_changed', 'schedule_changed', 'admin_override')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_subscriptions": { + "name": "kiloclaw_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transferred_to_subscription_id": { + "name": "transferred_to_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "access_origin": { + "name": "access_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_source": { + "name": "payment_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_plan": { + "name": "scheduled_plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduled_by": { + "name": "scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_conversion": { + "name": "pending_conversion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trial_started_at": { + "name": "trial_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credit_renewal_at": { + "name": "credit_renewal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "commit_ends_at": { + "name": "commit_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "past_due_since": { + "name": "past_due_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "destruction_deadline": { + "name": "destruction_deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_requested_at": { + "name": "auto_resume_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_retry_after": { + "name": "auto_resume_retry_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_attempt_count": { + "name": "auto_resume_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "auto_top_up_triggered_for_period": { + "name": "auto_top_up_triggered_for_period", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_subscriptions_status": { + "name": "IDX_kiloclaw_subscriptions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_user_id": { + "name": "IDX_kiloclaw_subscriptions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_user_status": { + "name": "IDX_kiloclaw_subscriptions_user_status", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_transferred_to": { + "name": "IDX_kiloclaw_subscriptions_transferred_to", + "columns": [ + { + "expression": "transferred_to_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_stripe_schedule_id": { + "name": "IDX_kiloclaw_subscriptions_stripe_schedule_id", + "columns": [ + { + "expression": "stripe_schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_auto_resume_retry_after": { + "name": "IDX_kiloclaw_subscriptions_auto_resume_retry_after", + "columns": [ + { + "expression": "auto_resume_retry_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_subscriptions_instance": { + "name": "UQ_kiloclaw_subscriptions_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_subscriptions\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_subscriptions_transferred_to": { + "name": "UQ_kiloclaw_subscriptions_transferred_to", + "columns": [ + { + "expression": "transferred_to_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_subscriptions\".\"transferred_to_subscription_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_earlybird_origin": { + "name": "IDX_kiloclaw_subscriptions_earlybird_origin", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "access_origin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_subscriptions\".\"access_origin\" = 'earlybird'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_subscriptions_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_subscriptions_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_subscriptions_transferred_to_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_subscriptions_transferred_to_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "transferred_to_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_subscriptions_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_subscriptions_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_subscriptions_stripe_subscription_id_unique": { + "name": "kiloclaw_subscriptions_stripe_subscription_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kiloclaw_subscriptions_plan_check": { + "name": "kiloclaw_subscriptions_plan_check", + "value": "\"kiloclaw_subscriptions\".\"plan\" IN ('trial', 'commit', 'standard')" + }, + "kiloclaw_subscriptions_scheduled_plan_check": { + "name": "kiloclaw_subscriptions_scheduled_plan_check", + "value": "\"kiloclaw_subscriptions\".\"scheduled_plan\" IN ('commit', 'standard')" + }, + "kiloclaw_subscriptions_scheduled_by_check": { + "name": "kiloclaw_subscriptions_scheduled_by_check", + "value": "\"kiloclaw_subscriptions\".\"scheduled_by\" IN ('auto', 'user')" + }, + "kiloclaw_subscriptions_status_check": { + "name": "kiloclaw_subscriptions_status_check", + "value": "\"kiloclaw_subscriptions\".\"status\" IN ('trialing', 'active', 'past_due', 'canceled', 'unpaid')" + }, + "kiloclaw_subscriptions_access_origin_check": { + "name": "kiloclaw_subscriptions_access_origin_check", + "value": "\"kiloclaw_subscriptions\".\"access_origin\" IN ('earlybird')" + }, + "kiloclaw_subscriptions_payment_source_check": { + "name": "kiloclaw_subscriptions_payment_source_check", + "value": "\"kiloclaw_subscriptions\".\"payment_source\" IN ('stripe', 'credits')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_version_pins": { + "name": "kiloclaw_version_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_by": { + "name": "pinned_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kiloclaw_version_pins_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_version_pins_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_version_pins_image_tag_kiloclaw_image_catalog_image_tag_fk": { + "name": "kiloclaw_version_pins_image_tag_kiloclaw_image_catalog_image_tag_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kiloclaw_image_catalog", + "columnsFrom": [ + "image_tag" + ], + "columnsTo": [ + "image_tag" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "kiloclaw_version_pins_pinned_by_kilocode_users_id_fk": { + "name": "kiloclaw_version_pins_pinned_by_kilocode_users_id_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kilocode_users", + "columnsFrom": [ + "pinned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_version_pins_instance_id_unique": { + "name": "kiloclaw_version_pins_instance_id_unique", + "nullsNotDistinct": false, + "columns": [ + "instance_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilocode_users": { + "name": "kilocode_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "google_user_email": { + "name": "google_user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_user_name": { + "name": "google_user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_user_image_url": { + "name": "google_user_image_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "hosted_domain": { + "name": "hosted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "kilo_pass_threshold": { + "name": "kilo_pass_threshold", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "total_microdollars_acquired": { + "name": "total_microdollars_acquired", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "next_credit_expiration_at": { + "name": "next_credit_expiration_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "has_validation_stytch": { + "name": "has_validation_stytch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_validation_novel_card_with_hold": { + "name": "has_validation_novel_card_with_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_at": { + "name": "blocked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_by_kilo_user_id": { + "name": "blocked_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_token_pepper": { + "name": "api_token_pepper", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "web_session_pepper": { + "name": "web_session_pepper", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_enabled": { + "name": "auto_top_up_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "kiloclaw_early_access": { + "name": "kiloclaw_early_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cohorts": { + "name": "cohorts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "completed_welcome_form": { + "name": "completed_welcome_form", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "linkedin_url": { + "name": "linkedin_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_url": { + "name": "github_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_server_membership_verified_at": { + "name": "discord_server_membership_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "openrouter_upstream_safety_identifier": { + "name": "openrouter_upstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vercel_downstream_safety_identifier": { + "name": "vercel_downstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_source": { + "name": "customer_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signup_ip": { + "name": "signup_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_deletion_requested_at": { + "name": "account_deletion_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_domain": { + "name": "email_domain", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kilocode_users_signup_ip_created_at": { + "name": "IDX_kilocode_users_signup_ip_created_at", + "columns": [ + { + "expression": "signup_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_blocked_at": { + "name": "IDX_kilocode_users_blocked_at", + "columns": [ + { + "expression": "blocked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_blocked_by_kilo_user_id": { + "name": "IDX_kilocode_users_blocked_by_kilo_user_id", + "columns": [ + { + "expression": "blocked_by_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_openrouter_upstream_safety_identifier": { + "name": "UQ_kilocode_users_openrouter_upstream_safety_identifier", + "columns": [ + { + "expression": "openrouter_upstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"openrouter_upstream_safety_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_vercel_downstream_safety_identifier": { + "name": "UQ_kilocode_users_vercel_downstream_safety_identifier", + "columns": [ + { + "expression": "vercel_downstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"vercel_downstream_safety_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_normalized_email": { + "name": "IDX_kilocode_users_normalized_email", + "columns": [ + { + "expression": "normalized_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_email_domain": { + "name": "IDX_kilocode_users_email_domain", + "columns": [ + { + "expression": "email_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_b1afacbcf43f2c7c4cb9f7e7faa": { + "name": "UQ_b1afacbcf43f2c7c4cb9f7e7faa", + "nullsNotDistinct": false, + "columns": [ + "google_user_email" + ] + } + }, + "policies": {}, + "checkConstraints": { + "blocked_reason_not_empty": { + "name": "blocked_reason_not_empty", + "value": "length(blocked_reason) > 0" + } + }, + "isRLSEnabled": false + }, + "public.magic_link_tokens": { + "name": "magic_link_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_magic_link_tokens_email": { + "name": "idx_magic_link_tokens_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_magic_link_tokens_expires_at": { + "name": "idx_magic_link_tokens_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_expires_at_future": { + "name": "check_expires_at_future", + "value": "\"magic_link_tokens\".\"expires_at\" > \"magic_link_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.microdollar_usage": { + "name": "microdollar_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_hit_tokens": { + "name": "cache_hit_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_model": { + "name": "requested_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_discount": { + "name": "cache_discount", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_error": { + "name": "has_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "abuse_classification": { + "name": "abuse_classification", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "inference_provider": { + "name": "inference_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_created_at": { + "name": "idx_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_abuse_classification": { + "name": "idx_abuse_classification", + "columns": [ + { + "expression": "abuse_classification", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_kilo_user_id_created_at2": { + "name": "idx_kilo_user_id_created_at2", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_organization_id": { + "name": "idx_microdollar_usage_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"microdollar_usage\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.microdollar_usage_metadata": { + "name": "microdollar_usage_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "http_user_agent_id": { + "name": "http_user_agent_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "http_ip_id": { + "name": "http_ip_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_city_id": { + "name": "vercel_ip_city_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_country_id": { + "name": "vercel_ip_country_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_latitude": { + "name": "vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_longitude": { + "name": "vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "ja4_digest_id": { + "name": "ja4_digest_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_prompt_prefix": { + "name": "user_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_prefix_id": { + "name": "system_prompt_prefix_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "system_prompt_length": { + "name": "system_prompt_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_middle_out_transform": { + "name": "has_middle_out_transform", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "upstream_id": { + "name": "upstream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finish_reason_id": { + "name": "finish_reason_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency": { + "name": "latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moderation_latency": { + "name": "moderation_latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "generation_time": { + "name": "generation_time", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "is_byok": { + "name": "is_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_user_byok": { + "name": "is_user_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "streamed": { + "name": "streamed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "editor_name_id": { + "name": "editor_name_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_kind_id": { + "name": "api_kind_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_tools": { + "name": "has_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode_id": { + "name": "mode_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auto_model_id": { + "name": "auto_model_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "market_cost": { + "name": "market_cost", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_microdollar_usage_metadata_created_at": { + "name": "idx_microdollar_usage_metadata_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "microdollar_usage_metadata_http_user_agent_id_http_user_agent_http_user_agent_id_fk": { + "name": "microdollar_usage_metadata_http_user_agent_id_http_user_agent_http_user_agent_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "http_user_agent", + "columnsFrom": [ + "http_user_agent_id" + ], + "columnsTo": [ + "http_user_agent_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_http_ip_id_http_ip_http_ip_id_fk": { + "name": "microdollar_usage_metadata_http_ip_id_http_ip_http_ip_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "http_ip", + "columnsFrom": [ + "http_ip_id" + ], + "columnsTo": [ + "http_ip_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_vercel_ip_city_id_vercel_ip_city_vercel_ip_city_id_fk": { + "name": "microdollar_usage_metadata_vercel_ip_city_id_vercel_ip_city_vercel_ip_city_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "vercel_ip_city", + "columnsFrom": [ + "vercel_ip_city_id" + ], + "columnsTo": [ + "vercel_ip_city_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_vercel_ip_country_id_vercel_ip_country_vercel_ip_country_id_fk": { + "name": "microdollar_usage_metadata_vercel_ip_country_id_vercel_ip_country_vercel_ip_country_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "vercel_ip_country", + "columnsFrom": [ + "vercel_ip_country_id" + ], + "columnsTo": [ + "vercel_ip_country_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_ja4_digest_id_ja4_digest_ja4_digest_id_fk": { + "name": "microdollar_usage_metadata_ja4_digest_id_ja4_digest_ja4_digest_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "ja4_digest", + "columnsFrom": [ + "ja4_digest_id" + ], + "columnsTo": [ + "ja4_digest_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_system_prompt_prefix_id_system_prompt_prefix_system_prompt_prefix_id_fk": { + "name": "microdollar_usage_metadata_system_prompt_prefix_id_system_prompt_prefix_system_prompt_prefix_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "system_prompt_prefix", + "columnsFrom": [ + "system_prompt_prefix_id" + ], + "columnsTo": [ + "system_prompt_prefix_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mode": { + "name": "mode", + "schema": "", + "columns": { + "mode_id": { + "name": "mode_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_mode": { + "name": "UQ_mode", + "columns": [ + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_stats": { + "name": "model_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "is_featured": { + "name": "is_featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_stealth": { + "name": "is_stealth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_recommended": { + "name": "is_recommended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "openrouter_id": { + "name": "openrouter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aa_slug": { + "name": "aa_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_creator": { + "name": "model_creator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_slug": { + "name": "creator_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_date": { + "name": "release_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "price_input": { + "name": "price_input", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "price_output": { + "name": "price_output", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "coding_index": { + "name": "coding_index", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false + }, + "speed_tokens_per_sec": { + "name": "speed_tokens_per_sec", + "type": "numeric(8, 2)", + "primaryKey": false, + "notNull": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_output_tokens": { + "name": "max_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_modalities": { + "name": "input_modalities", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "openrouter_data": { + "name": "openrouter_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "benchmarks": { + "name": "benchmarks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chart_data": { + "name": "chart_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_stats_openrouter_id": { + "name": "IDX_model_stats_openrouter_id", + "columns": [ + { + "expression": "openrouter_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_slug": { + "name": "IDX_model_stats_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_is_active": { + "name": "IDX_model_stats_is_active", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_creator_slug": { + "name": "IDX_model_stats_creator_slug", + "columns": [ + { + "expression": "creator_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_price_input": { + "name": "IDX_model_stats_price_input", + "columns": [ + { + "expression": "price_input", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_coding_index": { + "name": "IDX_model_stats_coding_index", + "columns": [ + { + "expression": "coding_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_context_length": { + "name": "IDX_model_stats_context_length", + "columns": [ + { + "expression": "context_length", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_stats_openrouter_id_unique": { + "name": "model_stats_openrouter_id_unique", + "nullsNotDistinct": false, + "columns": [ + "openrouter_id" + ] + }, + "model_stats_slug_unique": { + "name": "model_stats_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.models_by_provider": { + "name": "models_by_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "openrouter": { + "name": "openrouter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "vercel": { + "name": "vercel", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_audit_logs": { + "name": "organization_audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_audit_logs_organization_id": { + "name": "IDX_organization_audit_logs_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_action": { + "name": "IDX_organization_audit_logs_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_actor_id": { + "name": "IDX_organization_audit_logs_actor_id", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_created_at": { + "name": "IDX_organization_audit_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_invitations": { + "name": "organization_invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_organization_invitations_token": { + "name": "UQ_organization_invitations_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_org_id": { + "name": "IDX_organization_invitations_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_email": { + "name": "IDX_organization_invitations_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_expires_at": { + "name": "IDX_organization_invitations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_membership_removals": { + "name": "organization_membership_removals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_by": { + "name": "removed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_role": { + "name": "previous_role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_org_membership_removals_org_id": { + "name": "IDX_org_membership_removals_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_org_membership_removals_user_id": { + "name": "IDX_org_membership_removals_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_org_membership_removals_org_user": { + "name": "UQ_org_membership_removals_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_memberships": { + "name": "organization_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_memberships_org_id": { + "name": "IDX_organization_memberships_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_memberships_user_id": { + "name": "IDX_organization_memberships_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_memberships_org_user": { + "name": "UQ_organization_memberships_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_seats_purchases": { + "name": "organization_seats_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_stripe_id": { + "name": "subscription_stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seat_count": { + "name": "seat_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "billing_cycle": { + "name": "billing_cycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'monthly'" + } + }, + "indexes": { + "IDX_organization_seats_org_id": { + "name": "IDX_organization_seats_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_expires_at": { + "name": "IDX_organization_seats_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_created_at": { + "name": "IDX_organization_seats_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_updated_at": { + "name": "IDX_organization_seats_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_starts_at": { + "name": "IDX_organization_seats_starts_at", + "columns": [ + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_seats_idempotency_key": { + "name": "UQ_organization_seats_idempotency_key", + "nullsNotDistinct": false, + "columns": [ + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_user_limits": { + "name": "organization_user_limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microdollar_limit": { + "name": "microdollar_limit", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_user_limits_org_id": { + "name": "IDX_organization_user_limits_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_user_limits_user_id": { + "name": "IDX_organization_user_limits_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_user_limits_org_user": { + "name": "UQ_organization_user_limits_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id", + "limit_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_user_usage": { + "name": "organization_user_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microdollar_usage": { + "name": "microdollar_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_user_daily_usage_org_id": { + "name": "IDX_organization_user_daily_usage_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_user_daily_usage_user_id": { + "name": "IDX_organization_user_daily_usage_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_user_daily_usage_org_user_date": { + "name": "UQ_organization_user_daily_usage_org_user_date", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id", + "limit_type", + "usage_date" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "microdollars_balance": { + "name": "microdollars_balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_microdollars_acquired": { + "name": "total_microdollars_acquired", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "next_credit_expiration_at": { + "name": "next_credit_expiration_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_enabled": { + "name": "auto_top_up_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "seat_count": { + "name": "seat_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_seats": { + "name": "require_seats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sso_domain": { + "name": "sso_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'teams'" + }, + "free_trial_end_at": { + "name": "free_trial_end_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "company_domain": { + "name": "company_domain", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_organizations_sso_domain": { + "name": "IDX_organizations_sso_domain", + "columns": [ + { + "expression": "sso_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organizations_name_not_empty_check": { + "name": "organizations_name_not_empty_check", + "value": "length(trim(\"organizations\".\"name\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.organization_modes": { + "name": "organization_modes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "IDX_organization_modes_organization_id": { + "name": "IDX_organization_modes_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_modes_org_id_slug": { + "name": "UQ_organization_modes_org_id_slug", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payment_methods": { + "name": "payment_methods", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_fingerprint": { + "name": "stripe_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last4": { + "name": "last4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line1": { + "name": "address_line1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line2": { + "name": "address_line2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "three_d_secure_supported": { + "name": "three_d_secure_supported", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "funding": { + "name": "funding", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "regulated_status": { + "name": "regulated_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line1_check_status": { + "name": "address_line1_check_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code_check_status": { + "name": "postal_code_check_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eligible_for_free_credits": { + "name": "eligible_for_free_credits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_data": { + "name": "stripe_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_d7d7fb15569674aaadcfbc0428": { + "name": "IDX_d7d7fb15569674aaadcfbc0428", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_e1feb919d0ab8a36381d5d5138": { + "name": "IDX_e1feb919d0ab8a36381d5d5138", + "columns": [ + { + "expression": "stripe_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_payment_methods_organization_id": { + "name": "IDX_payment_methods_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_29df1b0403df5792c96bbbfdbe6": { + "name": "UQ_29df1b0403df5792c96bbbfdbe6", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_impact_sale_reversals": { + "name": "pending_impact_sale_reversals", + "schema": "", + "columns": { + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "dispute_id": { + "name": "dispute_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_impact_sale_reversals_attempt_count_non_negative_check": { + "name": "pending_impact_sale_reversals_attempt_count_non_negative_check", + "value": "\"pending_impact_sale_reversals\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.platform_integrations": { + "name": "platform_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "integration_type": { + "name": "integration_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_installation_id": { + "name": "platform_installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_account_id": { + "name": "platform_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_account_login": { + "name": "platform_account_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "repository_access": { + "name": "repository_access", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repositories": { + "name": "repositories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "repositories_synced_at": { + "name": "repositories_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "kilo_requester_user_id": { + "name": "kilo_requester_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_requester_account_id": { + "name": "platform_requester_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "integration_status": { + "name": "integration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "suspended_by": { + "name": "suspended_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'standard'" + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_platform_integrations_owned_by_org_platform_inst": { + "name": "UQ_platform_integrations_owned_by_org_platform_inst", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_owned_by_user_platform_inst": { + "name": "UQ_platform_integrations_owned_by_user_platform_inst", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_slack_platform_inst": { + "name": "UQ_platform_integrations_slack_platform_inst", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'slack' AND \"platform_integrations\".\"platform_installation_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_org_id": { + "name": "IDX_platform_integrations_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_user_id": { + "name": "IDX_platform_integrations_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform_inst_id": { + "name": "IDX_platform_integrations_platform_inst_id", + "columns": [ + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform": { + "name": "IDX_platform_integrations_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_org_platform": { + "name": "IDX_platform_integrations_owned_by_org_platform", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_user_platform": { + "name": "IDX_platform_integrations_owned_by_user_platform", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_integration_status": { + "name": "IDX_platform_integrations_integration_status", + "columns": [ + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_kilo_requester": { + "name": "IDX_platform_integrations_kilo_requester", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_requester_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform_requester": { + "name": "IDX_platform_integrations_platform_requester", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_requester_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "platform_integrations_owned_by_organization_id_organizations_id_fk": { + "name": "platform_integrations_owned_by_organization_id_organizations_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "platform_integrations_owned_by_user_id_kilocode_users_id_fk": { + "name": "platform_integrations_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "platform_integrations_owner_check": { + "name": "platform_integrations_owner_check", + "value": "(\n (\"platform_integrations\".\"owned_by_user_id\" IS NOT NULL AND \"platform_integrations\".\"owned_by_organization_id\" IS NULL) OR\n (\"platform_integrations\".\"owned_by_user_id\" IS NULL AND \"platform_integrations\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.referral_code_usages": { + "name": "referral_code_usages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "referring_kilo_user_id": { + "name": "referring_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redeeming_kilo_user_id": { + "name": "redeeming_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_referral_code_usages_redeeming_kilo_user_id": { + "name": "IDX_referral_code_usages_redeeming_kilo_user_id", + "columns": [ + { + "expression": "redeeming_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_referral_code_usages_redeeming_user_id_code": { + "name": "UQ_referral_code_usages_redeeming_user_id_code", + "nullsNotDistinct": false, + "columns": [ + "redeeming_kilo_user_id", + "referring_kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_referral_codes_kilo_user_id": { + "name": "UQ_referral_codes_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_referral_codes_code": { + "name": "IDX_referral_codes_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_check_catalog": { + "name": "security_advisor_check_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "explanation": { + "name": "explanation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_check_catalog_check_id_unique": { + "name": "security_advisor_check_catalog_check_id_unique", + "nullsNotDistinct": false, + "columns": [ + "check_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "security_advisor_check_catalog_severity_check": { + "name": "security_advisor_check_catalog_severity_check", + "value": "\"security_advisor_check_catalog\".\"severity\" in ('critical', 'warn', 'info')" + } + }, + "isRLSEnabled": false + }, + "public.security_advisor_content": { + "name": "security_advisor_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_content_key_unique": { + "name": "security_advisor_content_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_kiloclaw_coverage": { + "name": "security_advisor_kiloclaw_coverage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "area": { + "name": "area", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_check_ids": { + "name": "match_check_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_kiloclaw_coverage_area_unique": { + "name": "security_advisor_kiloclaw_coverage_area_unique", + "nullsNotDistinct": false, + "columns": [ + "area" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_scans": { + "name": "security_advisor_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_platform": { + "name": "source_platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_method": { + "name": "source_method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "openclaw_version": { + "name": "openclaw_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_ip": { + "name": "public_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "findings_critical": { + "name": "findings_critical", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "findings_warn": { + "name": "findings_warn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "findings_info": { + "name": "findings_info", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_security_advisor_scans_user_created_at": { + "name": "idx_security_advisor_scans_user_created_at", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_advisor_scans_created_at": { + "name": "idx_security_advisor_scans_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_advisor_scans_platform": { + "name": "idx_security_advisor_scans_platform", + "columns": [ + { + "expression": "source_platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_analysis_owner_state": { + "name": "security_analysis_owner_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_analysis_enabled_at": { + "name": "auto_analysis_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "block_reason": { + "name": "block_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consecutive_actor_resolution_failures": { + "name": "consecutive_actor_resolution_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_actor_resolution_failure_at": { + "name": "last_actor_resolution_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_analysis_owner_state_org_owner": { + "name": "UQ_security_analysis_owner_state_org_owner", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_analysis_owner_state\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_analysis_owner_state_user_owner": { + "name": "UQ_security_analysis_owner_state_user_owner", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_analysis_owner_state\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_analysis_owner_state_owned_by_organization_id_organizations_id_fk": { + "name": "security_analysis_owner_state_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_analysis_owner_state", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_owner_state_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_analysis_owner_state_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_analysis_owner_state", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_analysis_owner_state_owner_check": { + "name": "security_analysis_owner_state_owner_check", + "value": "(\n (\"security_analysis_owner_state\".\"owned_by_user_id\" IS NOT NULL AND \"security_analysis_owner_state\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_analysis_owner_state\".\"owned_by_user_id\" IS NULL AND \"security_analysis_owner_state\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_analysis_owner_state_block_reason_check": { + "name": "security_analysis_owner_state_block_reason_check", + "value": "\"security_analysis_owner_state\".\"block_reason\" IS NULL OR \"security_analysis_owner_state\".\"block_reason\" IN ('INSUFFICIENT_CREDITS', 'ACTOR_RESOLUTION_FAILED', 'OPERATOR_PAUSE')" + } + }, + "isRLSEnabled": false + }, + "public.security_analysis_queue": { + "name": "security_analysis_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "queue_status": { + "name": "queue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity_rank": { + "name": "severity_rank", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_by_job_id": { + "name": "claimed_by_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reopen_requeue_count": { + "name": "reopen_requeue_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_analysis_queue_finding_id": { + "name": "UQ_security_analysis_queue_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_claim_path_org": { + "name": "idx_security_analysis_queue_claim_path_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "severity_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_claim_path_user": { + "name": "idx_security_analysis_queue_claim_path_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "severity_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_in_flight_org": { + "name": "idx_security_analysis_queue_in_flight_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queue_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_in_flight_user": { + "name": "idx_security_analysis_queue_in_flight_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queue_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_lag_dashboards": { + "name": "idx_security_analysis_queue_lag_dashboards", + "columns": [ + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_pending_reconciliation": { + "name": "idx_security_analysis_queue_pending_reconciliation", + "columns": [ + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_running_reconciliation": { + "name": "idx_security_analysis_queue_running_reconciliation", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_failure_trend": { + "name": "idx_security_analysis_queue_failure_trend", + "columns": [ + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"failure_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_analysis_queue_finding_id_security_findings_id_fk": { + "name": "security_analysis_queue_finding_id_security_findings_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_queue_owned_by_organization_id_organizations_id_fk": { + "name": "security_analysis_queue_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_queue_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_analysis_queue_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_analysis_queue_owner_check": { + "name": "security_analysis_queue_owner_check", + "value": "(\n (\"security_analysis_queue\".\"owned_by_user_id\" IS NOT NULL AND \"security_analysis_queue\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_analysis_queue\".\"owned_by_user_id\" IS NULL AND \"security_analysis_queue\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_analysis_queue_status_check": { + "name": "security_analysis_queue_status_check", + "value": "\"security_analysis_queue\".\"queue_status\" IN ('queued', 'pending', 'running', 'failed', 'completed')" + }, + "security_analysis_queue_claim_token_required_check": { + "name": "security_analysis_queue_claim_token_required_check", + "value": "\"security_analysis_queue\".\"queue_status\" NOT IN ('pending', 'running') OR \"security_analysis_queue\".\"claim_token\" IS NOT NULL" + }, + "security_analysis_queue_attempt_count_non_negative_check": { + "name": "security_analysis_queue_attempt_count_non_negative_check", + "value": "\"security_analysis_queue\".\"attempt_count\" >= 0" + }, + "security_analysis_queue_reopen_requeue_count_non_negative_check": { + "name": "security_analysis_queue_reopen_requeue_count_non_negative_check", + "value": "\"security_analysis_queue\".\"reopen_requeue_count\" >= 0" + }, + "security_analysis_queue_severity_rank_check": { + "name": "security_analysis_queue_severity_rank_check", + "value": "\"security_analysis_queue\".\"severity_rank\" IN (0, 1, 2, 3)" + }, + "security_analysis_queue_failure_code_check": { + "name": "security_analysis_queue_failure_code_check", + "value": "\"security_analysis_queue\".\"failure_code\" IS NULL OR \"security_analysis_queue\".\"failure_code\" IN (\n 'NETWORK_TIMEOUT',\n 'UPSTREAM_5XX',\n 'TEMP_TOKEN_FAILURE',\n 'START_CALL_AMBIGUOUS',\n 'REQUEUE_TEMPORARY_PRECONDITION',\n 'ACTOR_RESOLUTION_FAILED',\n 'GITHUB_TOKEN_UNAVAILABLE',\n 'INVALID_CONFIG',\n 'MISSING_OWNERSHIP',\n 'PERMISSION_DENIED_PERMANENT',\n 'UNSUPPORTED_SEVERITY',\n 'INSUFFICIENT_CREDITS',\n 'STATE_GUARD_REJECTED',\n 'SKIPPED_ALREADY_IN_PROGRESS',\n 'SKIPPED_NO_LONGER_ELIGIBLE',\n 'REOPEN_LOOP_GUARD',\n 'RUN_LOST'\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_audit_log": { + "name": "security_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "before_state": { + "name": "before_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_state": { + "name": "after_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_security_audit_log_org_created": { + "name": "IDX_security_audit_log_org_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_user_created": { + "name": "IDX_security_audit_log_user_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_resource": { + "name": "IDX_security_audit_log_resource", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_actor": { + "name": "IDX_security_audit_log_actor", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_action": { + "name": "IDX_security_audit_log_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_audit_log_owned_by_organization_id_organizations_id_fk": { + "name": "security_audit_log_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_audit_log", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_audit_log_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_audit_log_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_audit_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_audit_log_owner_check": { + "name": "security_audit_log_owner_check", + "value": "(\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"owned_by_organization_id\" IS NULL) OR (\"security_audit_log\".\"owned_by_user_id\" IS NULL AND \"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL)" + }, + "security_audit_log_action_check": { + "name": "security_audit_log_action_check", + "value": "\"security_audit_log\".\"action\" IN ('security.finding.created', 'security.finding.status_change', 'security.finding.dismissed', 'security.finding.auto_dismissed', 'security.finding.analysis_started', 'security.finding.analysis_completed', 'security.finding.deleted', 'security.config.enabled', 'security.config.disabled', 'security.config.updated', 'security.sync.triggered', 'security.sync.completed', 'security.audit_log.exported')" + } + }, + "isRLSEnabled": false + }, + "public.security_findings": { + "name": "security_findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ghsa_id": { + "name": "ghsa_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_ecosystem": { + "name": "package_ecosystem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vulnerable_version_range": { + "name": "vulnerable_version_range", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "patched_version": { + "name": "patched_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest_path": { + "name": "manifest_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "ignored_reason": { + "name": "ignored_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ignored_by": { + "name": "ignored_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fixed_at": { + "name": "fixed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sla_due_at": { + "name": "sla_due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dependabot_html_url": { + "name": "dependabot_html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwe_ids": { + "name": "cwe_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cvss_score": { + "name": "cvss_score", + "type": "numeric(3, 1)", + "primaryKey": false, + "notNull": false + }, + "dependency_scope": { + "name": "dependency_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_status": { + "name": "analysis_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_started_at": { + "name": "analysis_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "analysis_completed_at": { + "name": "analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "analysis_error": { + "name": "analysis_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis": { + "name": "analysis", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "raw_data": { + "name": "raw_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_detected_at": { + "name": "first_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_security_findings_org_id": { + "name": "idx_security_findings_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_user_id": { + "name": "idx_security_findings_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_repo": { + "name": "idx_security_findings_repo", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_severity": { + "name": "idx_security_findings_severity", + "columns": [ + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_status": { + "name": "idx_security_findings_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_package": { + "name": "idx_security_findings_package", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_sla_due_at": { + "name": "idx_security_findings_sla_due_at", + "columns": [ + { + "expression": "sla_due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_session_id": { + "name": "idx_security_findings_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_cli_session_id": { + "name": "idx_security_findings_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_analysis_status": { + "name": "idx_security_findings_analysis_status", + "columns": [ + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_org_analysis_in_flight": { + "name": "idx_security_findings_org_analysis_in_flight", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_findings\".\"analysis_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_user_analysis_in_flight": { + "name": "idx_security_findings_user_analysis_in_flight", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_findings\".\"analysis_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_findings_owned_by_organization_id_organizations_id_fk": { + "name": "security_findings_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_findings", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_findings_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_findings_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_findings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_findings_platform_integration_id_platform_integrations_id_fk": { + "name": "security_findings_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "security_findings", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "uq_security_findings_source": { + "name": "uq_security_findings_source", + "nullsNotDistinct": false, + "columns": [ + "repo_full_name", + "source", + "source_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "security_findings_owner_check": { + "name": "security_findings_owner_check", + "value": "(\n (\"security_findings\".\"owned_by_user_id\" IS NOT NULL AND \"security_findings\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_findings\".\"owned_by_user_id\" IS NULL AND \"security_findings\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.shared_cli_sessions": { + "name": "shared_cli_sessions", + "schema": "", + "columns": { + "share_id": { + "name": "share_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shared_state": { + "name": "shared_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "api_conversation_history_blob_url": { + "name": "api_conversation_history_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_metadata_blob_url": { + "name": "task_metadata_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ui_messages_blob_url": { + "name": "ui_messages_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_state_blob_url": { + "name": "git_state_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_shared_cli_sessions_session_id": { + "name": "IDX_shared_cli_sessions_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_shared_cli_sessions_created_at": { + "name": "IDX_shared_cli_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_cli_sessions_session_id_cli_sessions_session_id_fk": { + "name": "shared_cli_sessions_session_id_cli_sessions_session_id_fk", + "tableFrom": "shared_cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shared_cli_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "shared_cli_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "shared_cli_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_cli_sessions_shared_state_check": { + "name": "shared_cli_sessions_shared_state_check", + "value": "\"shared_cli_sessions\".\"shared_state\" IN ('public', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.slack_bot_requests": { + "name": "slack_bot_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_team_name": { + "name": "slack_team_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message": { + "name": "user_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message_truncated": { + "name": "user_message_truncated", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_calls_made": { + "name": "tool_calls_made", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_slack_bot_requests_created_at": { + "name": "idx_slack_bot_requests_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_slack_team_id": { + "name": "idx_slack_bot_requests_slack_team_id", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_owned_by_org_id": { + "name": "idx_slack_bot_requests_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_owned_by_user_id": { + "name": "idx_slack_bot_requests_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_status": { + "name": "idx_slack_bot_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_event_type": { + "name": "idx_slack_bot_requests_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_team_created": { + "name": "idx_slack_bot_requests_team_created", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_bot_requests_owned_by_organization_id_organizations_id_fk": { + "name": "slack_bot_requests_owned_by_organization_id_organizations_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_bot_requests_owned_by_user_id_kilocode_users_id_fk": { + "name": "slack_bot_requests_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_bot_requests_platform_integration_id_platform_integrations_id_fk": { + "name": "slack_bot_requests_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_bot_requests_owner_check": { + "name": "slack_bot_requests_owner_check", + "value": "(\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NOT NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NULL) OR\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NOT NULL) OR\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.source_embeddings": { + "name": "source_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_line": { + "name": "start_line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_line": { + "name": "end_line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_base_branch": { + "name": "is_base_branch", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_source_embeddings_organization_id": { + "name": "IDX_source_embeddings_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_kilo_user_id": { + "name": "IDX_source_embeddings_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_project_id": { + "name": "IDX_source_embeddings_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_created_at": { + "name": "IDX_source_embeddings_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_updated_at": { + "name": "IDX_source_embeddings_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_file_path_lower": { + "name": "IDX_source_embeddings_file_path_lower", + "columns": [ + { + "expression": "LOWER(\"file_path\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_git_branch": { + "name": "IDX_source_embeddings_git_branch", + "columns": [ + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_org_project_branch": { + "name": "IDX_source_embeddings_org_project_branch", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "source_embeddings_organization_id_organizations_id_fk": { + "name": "source_embeddings_organization_id_organizations_id_fk", + "tableFrom": "source_embeddings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "source_embeddings_kilo_user_id_kilocode_users_id_fk": { + "name": "source_embeddings_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "source_embeddings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_source_embeddings_org_project_branch_file_lines": { + "name": "UQ_source_embeddings_org_project_branch_file_lines", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "project_id", + "git_branch", + "file_path", + "start_line", + "end_line" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stytch_fingerprints": { + "name": "stytch_fingerprints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitor_fingerprint": { + "name": "visitor_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "browser_fingerprint": { + "name": "browser_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "browser_id": { + "name": "browser_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hardware_fingerprint": { + "name": "hardware_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "network_fingerprint": { + "name": "network_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitor_id": { + "name": "visitor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verdict_action": { + "name": "verdict_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detected_device_type": { + "name": "detected_device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_authentic_device": { + "name": "is_authentic_device", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{\"\"}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fingerprint_data": { + "name": "fingerprint_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_free_tier_allowed": { + "name": "kilo_free_tier_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_fingerprint_data": { + "name": "idx_fingerprint_data", + "columns": [ + { + "expression": "fingerprint_data", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_hardware_fingerprint": { + "name": "idx_hardware_fingerprint", + "columns": [ + { + "expression": "hardware_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_kilo_user_id": { + "name": "idx_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_reasons": { + "name": "idx_reasons", + "columns": [ + { + "expression": "reasons", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_verdict_action": { + "name": "idx_verdict_action", + "columns": [ + { + "expression": "verdict_action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_visitor_fingerprint": { + "name": "idx_visitor_fingerprint", + "columns": [ + { + "expression": "visitor_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_prompt_prefix": { + "name": "system_prompt_prefix", + "schema": "", + "columns": { + "system_prompt_prefix_id": { + "name": "system_prompt_prefix_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "system_prompt_prefix": { + "name": "system_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_system_prompt_prefix": { + "name": "UQ_system_prompt_prefix", + "columns": [ + { + "expression": "system_prompt_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactional_email_log": { + "name": "transactional_email_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_type": { + "name": "email_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_transactional_email_log_type_idempotency_key": { + "name": "UQ_transactional_email_log_type_idempotency_key", + "columns": [ + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_transactional_email_log_user_id": { + "name": "IDX_transactional_email_log_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactional_email_log_user_id_kilocode_users_id_fk": { + "name": "transactional_email_log_user_id_kilocode_users_id_fk", + "tableFrom": "transactional_email_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_admin_notes": { + "name": "user_admin_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note_content": { + "name": "note_content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "admin_kilo_user_id": { + "name": "admin_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_34517df0b385234babc38fe81b": { + "name": "IDX_34517df0b385234babc38fe81b", + "columns": [ + { + "expression": "admin_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_ccbde98c4c14046daa5682ec4f": { + "name": "IDX_ccbde98c4c14046daa5682ec4f", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_d0270eb24ef6442d65a0b7853c": { + "name": "IDX_d0270eb24ef6442d65a0b7853c", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_affiliate_attributions": { + "name": "user_affiliate_attributions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tracking_id": { + "name": "tracking_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_affiliate_attributions_user_id": { + "name": "IDX_user_affiliate_attributions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_affiliate_attributions_user_id_kilocode_users_id_fk": { + "name": "user_affiliate_attributions_user_id_kilocode_users_id_fk", + "tableFrom": "user_affiliate_attributions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_affiliate_attributions_user_provider": { + "name": "UQ_user_affiliate_attributions_user_provider", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_affiliate_attributions_provider_check": { + "name": "user_affiliate_attributions_provider_check", + "value": "\"user_affiliate_attributions\".\"provider\" IN ('impact')" + } + }, + "isRLSEnabled": false + }, + "public.user_affiliate_events": { + "name": "user_affiliate_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_event_id": { + "name": "parent_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_action_id": { + "name": "impact_action_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_submission_uri": { + "name": "impact_submission_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_affiliate_events_claim_path": { + "name": "IDX_user_affiliate_events_claim_path", + "columns": [ + { + "expression": "delivery_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_affiliate_events_parent_event_id": { + "name": "IDX_user_affiliate_events_parent_event_id", + "columns": [ + { + "expression": "parent_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_affiliate_events_provider_event_type_charge": { + "name": "IDX_user_affiliate_events_provider_event_type_charge", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_affiliate_events_user_id_kilocode_users_id_fk": { + "name": "user_affiliate_events_user_id_kilocode_users_id_fk", + "tableFrom": "user_affiliate_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "user_affiliate_events_parent_event_id_fk": { + "name": "user_affiliate_events_parent_event_id_fk", + "tableFrom": "user_affiliate_events", + "tableTo": "user_affiliate_events", + "columnsFrom": [ + "parent_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_affiliate_events_dedupe_key": { + "name": "UQ_user_affiliate_events_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_affiliate_events_provider_check": { + "name": "user_affiliate_events_provider_check", + "value": "\"user_affiliate_events\".\"provider\" IN ('impact')" + }, + "user_affiliate_events_event_type_check": { + "name": "user_affiliate_events_event_type_check", + "value": "\"user_affiliate_events\".\"event_type\" IN ('signup', 'trial_start', 'trial_end', 'sale', 'sale_reversal')" + }, + "user_affiliate_events_delivery_state_check": { + "name": "user_affiliate_events_delivery_state_check", + "value": "\"user_affiliate_events\".\"delivery_state\" IN ('queued', 'blocked', 'sending', 'delivered', 'failed')" + }, + "user_affiliate_events_attempt_count_non_negative_check": { + "name": "user_affiliate_events_attempt_count_non_negative_check", + "value": "\"user_affiliate_events\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.user_auth_provider": { + "name": "user_auth_provider", + "schema": "", + "columns": { + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hosted_domain": { + "name": "hosted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_auth_provider_kilo_user_id": { + "name": "IDX_user_auth_provider_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_auth_provider_hosted_domain": { + "name": "IDX_user_auth_provider_hosted_domain", + "columns": [ + { + "expression": "hosted_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_auth_provider_provider_provider_account_id_pk": { + "name": "user_auth_provider_provider_provider_account_id_pk", + "columns": [ + "provider", + "provider_account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_feedback": { + "name": "user_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feedback_for": { + "name": "feedback_for", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "feedback_batch": { + "name": "feedback_batch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "context_json": { + "name": "context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_feedback_created_at": { + "name": "IDX_user_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_kilo_user_id": { + "name": "IDX_user_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_feedback_for": { + "name": "IDX_user_feedback_feedback_for", + "columns": [ + { + "expression": "feedback_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_feedback_batch": { + "name": "IDX_user_feedback_feedback_batch", + "columns": [ + { + "expression": "feedback_batch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_source": { + "name": "IDX_user_feedback_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "user_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_period_cache": { + "name": "user_period_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cache_type": { + "name": "cache_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_type": { + "name": "period_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "shared_url_token": { + "name": "shared_url_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_user_period_cache_kilo_user_id": { + "name": "IDX_user_period_cache_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_period_cache": { + "name": "UQ_user_period_cache", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cache_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_period_cache_lookup": { + "name": "IDX_user_period_cache_lookup", + "columns": [ + { + "expression": "cache_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_period_cache_share_token": { + "name": "UQ_user_period_cache_share_token", + "columns": [ + { + "expression": "shared_url_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_period_cache\".\"shared_url_token\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_period_cache_kilo_user_id_kilocode_users_id_fk": { + "name": "user_period_cache_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_period_cache", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_period_cache_period_type_check": { + "name": "user_period_cache_period_type_check", + "value": "\"user_period_cache\".\"period_type\" IN ('year', 'quarter', 'month', 'week', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.user_push_tokens": { + "name": "user_push_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_push_tokens_token": { + "name": "UQ_user_push_tokens_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_push_tokens_user_id": { + "name": "IDX_user_push_tokens_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_push_tokens_user_id_kilocode_users_id_fk": { + "name": "user_push_tokens_user_id_kilocode_users_id_fk", + "tableFrom": "user_push_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_ip_city": { + "name": "vercel_ip_city", + "schema": "", + "columns": { + "vercel_ip_city_id": { + "name": "vercel_ip_city_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vercel_ip_city": { + "name": "vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_vercel_ip_city": { + "name": "UQ_vercel_ip_city", + "columns": [ + { + "expression": "vercel_ip_city", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_ip_country": { + "name": "vercel_ip_country", + "schema": "", + "columns": { + "vercel_ip_country_id": { + "name": "vercel_ip_country_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vercel_ip_country": { + "name": "vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_vercel_ip_country": { + "name": "UQ_vercel_ip_country", + "columns": [ + { + "expression": "vercel_ip_country", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_events": { + "name": "webhook_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_action": { + "name": "event_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "processed": { + "name": "processed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "handlers_triggered": { + "name": "handlers_triggered", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "errors": { + "name": "errors", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "event_signature": { + "name": "event_signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_webhook_events_owned_by_org_id": { + "name": "IDX_webhook_events_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_owned_by_user_id": { + "name": "IDX_webhook_events_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_platform": { + "name": "IDX_webhook_events_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_event_type": { + "name": "IDX_webhook_events_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_created_at": { + "name": "IDX_webhook_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_events_owned_by_organization_id_organizations_id_fk": { + "name": "webhook_events_owned_by_organization_id_organizations_id_fk", + "tableFrom": "webhook_events", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_events_owned_by_user_id_kilocode_users_id_fk": { + "name": "webhook_events_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "webhook_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_webhook_events_signature": { + "name": "UQ_webhook_events_signature", + "nullsNotDistinct": false, + "columns": [ + "event_signature" + ] + } + }, + "policies": {}, + "checkConstraints": { + "webhook_events_owner_check": { + "name": "webhook_events_owner_check", + "value": "(\n (\"webhook_events\".\"owned_by_user_id\" IS NOT NULL AND \"webhook_events\".\"owned_by_organization_id\" IS NULL) OR\n (\"webhook_events\".\"owned_by_user_id\" IS NULL AND \"webhook_events\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.microdollar_usage_view": { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_hit_tokens": { + "name": "cache_hit_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_model": { + "name": "requested_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_prompt_prefix": { + "name": "user_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_prefix": { + "name": "system_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_length": { + "name": "system_prompt_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_discount": { + "name": "cache_discount", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_middle_out_transform": { + "name": "has_middle_out_transform", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_error": { + "name": "has_error", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "abuse_classification": { + "name": "abuse_classification", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "inference_provider": { + "name": "inference_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "upstream_id": { + "name": "upstream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency": { + "name": "latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moderation_latency": { + "name": "moderation_latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "generation_time": { + "name": "generation_time", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "is_byok": { + "name": "is_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_user_byok": { + "name": "is_user_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "streamed": { + "name": "streamed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "editor_name": { + "name": "editor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_kind": { + "name": "api_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_tools": { + "name": "has_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_model": { + "name": "auto_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "market_cost": { + "name": "market_cost", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n mu.id,\n mu.kilo_user_id,\n meta.message_id,\n mu.cost,\n mu.input_tokens,\n mu.output_tokens,\n mu.cache_write_tokens,\n mu.cache_hit_tokens,\n mu.created_at,\n ip.http_ip AS http_x_forwarded_for,\n city.vercel_ip_city AS http_x_vercel_ip_city,\n country.vercel_ip_country AS http_x_vercel_ip_country,\n meta.vercel_ip_latitude AS http_x_vercel_ip_latitude,\n meta.vercel_ip_longitude AS http_x_vercel_ip_longitude,\n ja4.ja4_digest AS http_x_vercel_ja4_digest,\n mu.provider,\n mu.model,\n mu.requested_model,\n meta.user_prompt_prefix,\n spp.system_prompt_prefix,\n meta.system_prompt_length,\n ua.http_user_agent,\n mu.cache_discount,\n meta.max_tokens,\n meta.has_middle_out_transform,\n mu.has_error,\n mu.abuse_classification,\n mu.organization_id,\n mu.inference_provider,\n mu.project_id,\n meta.status_code,\n meta.upstream_id,\n frfr.finish_reason,\n meta.latency,\n meta.moderation_latency,\n meta.generation_time,\n meta.is_byok,\n meta.is_user_byok,\n meta.streamed,\n meta.cancelled,\n edit.editor_name,\n ak.api_kind,\n meta.has_tools,\n meta.machine_id,\n feat.feature,\n meta.session_id,\n md.mode,\n am.auto_model,\n meta.market_cost,\n meta.is_free\n FROM \"microdollar_usage\" mu\n LEFT JOIN \"microdollar_usage_metadata\" meta ON mu.id = meta.id\n LEFT JOIN \"http_ip\" ip ON meta.http_ip_id = ip.http_ip_id\n LEFT JOIN \"vercel_ip_city\" city ON meta.vercel_ip_city_id = city.vercel_ip_city_id\n LEFT JOIN \"vercel_ip_country\" country ON meta.vercel_ip_country_id = country.vercel_ip_country_id\n LEFT JOIN \"ja4_digest\" ja4 ON meta.ja4_digest_id = ja4.ja4_digest_id\n LEFT JOIN \"system_prompt_prefix\" spp ON meta.system_prompt_prefix_id = spp.system_prompt_prefix_id\n LEFT JOIN \"http_user_agent\" ua ON meta.http_user_agent_id = ua.http_user_agent_id\n LEFT JOIN \"finish_reason\" frfr ON meta.finish_reason_id = frfr.finish_reason_id\n LEFT JOIN \"editor_name\" edit ON meta.editor_name_id = edit.editor_name_id\n LEFT JOIN \"api_kind\" ak ON meta.api_kind_id = ak.api_kind_id\n LEFT JOIN \"feature\" feat ON meta.feature_id = feat.feature_id\n LEFT JOIN \"mode\" md ON meta.mode_id = md.mode_id\n LEFT JOIN \"auto_model\" am ON meta.auto_model_id = am.auto_model_id\n", + "name": "microdollar_usage_view", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index c7337f51de..c6aba3fe3a 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -773,10 +773,10 @@ "breakpoints": true }, { - "idx": 107, + "idx": 110, "version": "7", - "when": 1777926674979, - "tag": "0107_magical_rattler", + "when": 1777992652327, + "tag": "0110_giant_gabe_jones", "breakpoints": true } ] diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 7eaf03cb1b..b07166ed0b 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -4414,32 +4414,35 @@ export const kiloclaw_email_log = pgTable( export type KiloClawEmailLog = typeof kiloclaw_email_log.$inferSelect; -// Outbox marker for top-up confirmation emails, keyed by the Stripe payment id -// (`ch_…` / `in_…` / `pi_…`). `processTopUp` commits the credit_transactions -// row before firing the email via `after()`. If the process exits between -// those two steps and Stripe retries the webhook, the credit-transactions -// unique index dedupes the credit but the email would otherwise be lost. -// Inserting a marker row on the first successful send — and attempting an -// insert on every webhook replay — lets a retry observe "marker missing, -// credit already committed" and recover the email exactly once. The unique -// index on `stripe_payment_id` is the whole dedupe mechanism. -export const top_up_email_log = pgTable( - 'top_up_email_log', +// Outbox marker for transactional emails that need durable idempotency beyond +// their triggering side effect. For top-up confirmations, `processTopUp` +// commits the credit_transactions row before firing the email via `after()`; +// if the process exits between those steps, a webhook retry can observe that +// the transactional email marker is missing and recover the email exactly once. +export const transactional_email_log = pgTable( + 'transactional_email_log', { id: uuid() .default(sql`gen_random_uuid()`) .primaryKey() .notNull(), - stripe_payment_id: text().notNull(), user_id: text() .notNull() .references(() => kilocode_users.id), + email_type: text().notNull(), + idempotency_key: text().notNull(), sent_at: timestamp({ withTimezone: true, mode: 'string' }).defaultNow().notNull(), }, - table => [uniqueIndex('UQ_top_up_email_log_stripe_payment_id').on(table.stripe_payment_id)] + table => [ + uniqueIndex('UQ_transactional_email_log_type_idempotency_key').on( + table.email_type, + table.idempotency_key + ), + index('IDX_transactional_email_log_user_id').on(table.user_id), + ] ); -export type TopUpEmailLog = typeof top_up_email_log.$inferSelect; +export type TransactionalEmailLog = typeof transactional_email_log.$inferSelect; // Bot Request Logs — tracks each message handled by the new bot (src/lib/bot.ts). // Rows are created as 'pending' on receipt and updated as processing progresses. From 2b9afc4fae556728906a10849e186468117ab764 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Tue, 5 May 2026 10:30:08 -0600 Subject: [PATCH 22/23] Use JSDoc comments inm credits.ts and credit-billing.ts --- apps/web/src/lib/credits.ts | 60 +++--- apps/web/src/lib/kiloclaw/credit-billing.ts | 202 ++++++++++++-------- 2 files changed, 152 insertions(+), 110 deletions(-) diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index e92eb72673..15c7be88a8 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -147,27 +147,32 @@ export async function processTopUp( return true; } -// Best-effort at-most-once dedupe via an insert-before-send marker in -// `transactional_email_log`. Every send attempt — first-attempt -// and webhook-retry recovery — first inserts a marker row keyed by -// (email_type, idempotency_key) with -// `onConflictDoNothing()`. A rowCount of 0 means an earlier attempt already -// claimed this payment, so we bail without sending again. If the provider -// was not configured (e.g. Mailgun env missing in preview/test), the marker -// is cleared so a future retry can re-attempt. -// -// Known gaps shared with every other insert-before-send email path in this -// codebase (`maybeSendKiloClawSubscriptionStartedEmail` below, -// `services/kiloclaw-billing/src/lifecycle.ts` ~L850, and the -// `kiloclaw_email_log`-gated sends in `apps/web/src/app/api/internal/kiloclaw/`): -// 1. A crash between the marker insert and the provider send permanently -// suppresses the email on retry — the marker looks "already sent". -// 2. Rolling the marker back in the catch block after an ambiguous provider -// exception can duplicate the email if the provider actually accepted it. -// Fixing either properly requires a real outbox (pending/sent/terminal state -// + provider idempotency keys) applied uniformly across all of the above -// call sites. Tracked as follow-up tech debt; intentionally NOT fixed in -// isolation here so the new email paths stay uniform with the existing ones. +/** + * Best-effort at-most-once dedupe via an insert-before-send marker in + * `transactional_email_log`. Every send attempt, first-attempt and + * webhook-retry recovery, first inserts a marker row keyed by + * (email_type, idempotency_key) with `onConflictDoNothing()`. A rowCount of 0 + * means an earlier attempt already claimed this payment, so we bail without + * sending again. If the provider was not configured (e.g. Mailgun env missing + * in preview/test), the marker is cleared so a future retry can re-attempt. + * + * Known gaps shared with every other insert-before-send email path in this + * codebase (`maybeSendKiloClawSubscriptionStartedEmail` below, + * `services/kiloclaw-billing/src/lifecycle.ts` ~L850, and the + * `kiloclaw_email_log`-gated sends in `apps/web/src/app/api/internal/kiloclaw/`): + * 1. A crash between the marker insert and the provider send permanently + * suppresses the email on retry; the marker looks "already sent". + * 2. Rolling the marker back in the catch block after an ambiguous provider + * exception can duplicate the email if the provider actually accepted it. + * + * Fixing either properly requires a real outbox (pending/sent/terminal state + * + provider idempotency keys) applied uniformly across all of the above + * call sites. Tracked as follow-up tech debt; intentionally NOT fixed in + * isolation here so the new email paths stay uniform with the existing ones. + * + * @param params User, top-up amount, Stripe payment identity, and auto-top-up flag. + * @returns A promise that resolves after the idempotency check and best-effort send attempt. + */ async function maybeSendTopUpConfirmationEmail(params: { user: User; amountInCents: number; @@ -221,10 +226,15 @@ async function maybeSendTopUpConfirmationEmail(params: { } } -// Called from the duplicate-webhook path in `processTopUp`, where the credit -// transaction is already committed but the first attempt may have exited -// before sending the email. Runs the same marker-gated send so a successful -// prior send still dedupes on the unique index. +/** + * Called from the duplicate-webhook path in `processTopUp`, where the credit + * transaction is already committed but the first attempt may have exited + * before sending the email. Runs the same marker-gated send so a successful + * prior send still dedupes on the unique index. + * + * @param params User, top-up amount, Stripe payment identity, and auto-top-up flag. + * @returns A promise that resolves after the recovery send is performed or scheduled. + */ async function recoverTopUpConfirmationEmailIfMissing(params: { user: User; amountInCents: number; diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index c31faad5ae..d93b11ea30 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -619,13 +619,14 @@ export async function applyStripeFundedKiloClawPeriod(params: { .from(kiloclaw_subscriptions) .where(eq(kiloclaw_subscriptions.id, targetRow.id)) .limit(1); - // Prefer the in-memory `before.status`. When Stripe's - // customer.subscription.created handler ran before invoice.paid, it - // already transitioned a non-hybrid row to 'active', hiding the - // pre-activation state from this snapshot. Fall back to the durable - // `kiloclaw_subscription_change_log` entry written by that handler, - // which preserves the pre-Stripe `before_state.status` and records the - // Stripe-derived plan/period in `after_state`. + + // Prefer the in-memory `before.status`. + // When Stripe's `customer.subscription.created` handler ran + // before invoice.paid, it already transitioned a non-hybrid row to 'active', + // hiding the pre-activation state from this snapshot. + // Fall back to the durable `kiloclaw_subscription_change_log` entry written + // by that handler, which preserves the pre-Stripe `before_state.status` + // and records the Stripe-derived plan/period in `after_state`. shouldSendSubscriptionStartedEmailForNewSettlement = shouldSendSubscriptionStartedEmailForActivation(before?.status ?? null) || (await didStripeSubscriptionCreatedRecordEligibleActivation({ @@ -676,8 +677,8 @@ export async function applyStripeFundedKiloClawPeriod(params: { } // Steady-state webhook replays against an already-emailed, already-settled - // period hit the duplicate-settlement branch on every retry. The real - // idempotency guard is the `kiloclaw_email_log` unique index inside + // period hit the duplicate-settlement branch on every retry. + // The real idempotency guard is the `kiloclaw_email_log` unique index in // `maybeSendKiloClawSubscriptionStartedEmail`, but we can skip the more // expensive `kiloclaw_subscription_change_log` scan (and the subsequent // no-op send call) when a matching email-log row already exists. @@ -729,11 +730,15 @@ export async function applyStripeFundedKiloClawPeriod(params: { export const KILOCLAW_SUBSCRIPTION_STARTED_EMAIL_TYPE = 'kiloclaw_subscription_started'; -// A settlement activates a paid period (and may produce a "subscription -// started" email) only when the subscription was NOT already active before -// settlement. Recovery/dunning states (past_due, unpaid) are excluded; those -// flows are payment-recovery on an active plan, not a new activation. -// Eligible: trialing, canceled (including canceled paid rows that resubscribe). +/** + * A settlement activates a paid period (and may produce a "subscription + * started" email) only when the subscription was NOT already active before + * settlement. Recovery/dunning states (past_due, unpaid) are excluded; those + * flows are payment-recovery on an active plan, not a new activation. + * Eligible: trialing, canceled (including canceled paid rows that resubscribe). + * @param beforeStatus — the previous subscription status + * @returns whether to send the "subscription started" email + */ export function shouldSendSubscriptionStartedEmailForActivation( beforeStatus: string | null ): boolean { @@ -749,10 +754,16 @@ function stringFieldOrNull(record: Record, key: string): string return typeof value === 'string' ? value : null; } -// Compare two timestamp strings by parsing them as Dates. Handles the case -// where a JSONB-serialized timestamp uses Postgres text form -// ("2026-05-04 16:52:41.287+00") while the input is ISO-8601 -// ("2026-05-04T16:52:41.287Z"). Returns false for either side unparseable. +/** + * Compare two timestamp strings by parsing them as Dates. Handles the case + * where a JSONB-serialized timestamp uses Postgres text form + * ("2026-05-04 16:52:41.287+00") while the input is ISO-8601 + * ("2026-05-04T16:52:41.287Z"). Returns false for either side unparseable. + * + * @param a First timestamp string to compare. + * @param b Second timestamp string to compare. + * @returns Whether both timestamps parse to the same millisecond value. + */ function timestampsEqual(a: string | null, b: string | null): boolean { if (a === null || b === null) return false; const aMs = new Date(a).getTime(); @@ -761,22 +772,27 @@ function timestampsEqual(a: string | null, b: string | null): boolean { return aMs === bMs; } -// Best-effort check: does `kiloclaw_subscription_change_log` contain a prior -// `period_advanced` / `stripe_invoice_settlement` entry that transitioned the -// given subscription into a paid active period for the exact plan and -// period? Used to recover the subscription-started email when a replay of -// settlement hits the duplicate-credit path and the original in-transaction -// email send may have failed. Returns false on missing/malformed rows — the -// `kiloclaw_email_log` unique index remains the final idempotency guard. -// -// Identity is established by subscription_id + action/reason scope + exact -// plan/period-boundary match on `after_state`. `stripe_invoice_settlement` -// rows are written only by `applyStripeFundedKiloClawPeriod` (once per -// successful settlement), and KiloClaw never uses Stripe proration, so -// renewals move period boundaries forward and two settlements on the same -// subscription cannot share plan+period. No time-window guard is needed: a -// legitimately delayed webhook replay (e.g., manual Stripe-dashboard -// resend well after the period started) should still recover the email. +/** + * Best-effort check: does `kiloclaw_subscription_change_log` contain a prior + * `period_advanced` / `stripe_invoice_settlement` entry that transitioned the + * given subscription into a paid active period for the exact plan and + * period? Used to recover the subscription-started email when a replay of + * settlement hits the duplicate-credit path and the original in-transaction + * email send may have failed. Returns false on missing/malformed rows; the + * `kiloclaw_email_log` unique index remains the final idempotency guard. + * + * Identity is established by subscription_id + action/reason scope + exact + * plan/period-boundary match on `after_state`. `stripe_invoice_settlement` + * rows are written only by `applyStripeFundedKiloClawPeriod` (once per + * successful settlement), and KiloClaw never uses Stripe proration, so + * renewals move period boundaries forward and two settlements on the same + * subscription cannot share plan+period. No time-window guard is needed: a + * legitimately delayed webhook replay (e.g., manual Stripe-dashboard + * resend well after the period started) should still recover the email. + * + * @param params Subscription and settlement period identity to match. + * @returns Whether a prior settlement recorded an eligible paid activation. + */ async function didPriorSettlementRecordPaidActivation(params: { subscriptionId: string; plan: 'commit' | 'standard'; @@ -826,31 +842,36 @@ async function didPriorSettlementRecordPaidActivation(params: { return false; } -// Durable pre-settlement activation signal. -// -// handleKiloClawSubscriptionCreated can run before invoice.paid and will -// transition a non-hybrid row to status='active', masking the pre-Stripe -// status from the in-transaction snapshot used to decide whether this -// settlement is a first paid activation. The handler writes a -// `stripe_subscription_created` change-log row that preserves -// `before_state.status` and records the Stripe-derived plan/period in -// `after_state`, which is the durable evidence we need. -// -// Returns true when a `stripe_subscription_created` entry for this -// subscription has: -// - `before_state.status` that `shouldSendSubscriptionStartedEmailForActivation` -// accepts (trialing or canceled), AND -// - `after_state.plan` / `after_state.current_period_start` / -// `after_state.current_period_end` matching the current settlement. -// -// Matching on identity (plan + period boundaries) instead of audit-log -// ordering avoids relying on `created_at`, which is `now()` and therefore -// transaction-start scoped rather than a reliable commit/insert chronology -// under concurrent webhook transactions. A later renewal settlement has a -// different period than the original activation, so an old -// `stripe_subscription_created` row from the initial activation cannot match -// and cannot re-fire the email. The `kiloclaw_email_log` unique index -// remains the final idempotency guard. +/** + * Durable pre-settlement activation signal. + * + * `handleKiloClawSubscriptionCreated` can run before `invoice.paid` and will + * transition a non-hybrid row to status='active', masking the pre-Stripe + * status from the in-transaction snapshot used to decide whether this + * settlement is a first paid activation. The handler writes a + * `stripe_subscription_created` change-log row that preserves + * `before_state.status` and records the Stripe-derived plan/period in + * `after_state`, which is the durable evidence we need. + * + * Returns true when a `stripe_subscription_created` entry for this + * subscription has: + * - `before_state.status` that `shouldSendSubscriptionStartedEmailForActivation` + * accepts (trialing or canceled), AND + * - `after_state.plan` / `after_state.current_period_start` / + * `after_state.current_period_end` matching the current settlement. + * + * Matching on identity (plan + period boundaries) instead of audit-log + * ordering avoids relying on `created_at`, which is `now()` and therefore + * transaction-start scoped rather than a reliable commit/insert chronology + * under concurrent webhook transactions. A later renewal settlement has a + * different period than the original activation, so an old + * `stripe_subscription_created` row from the initial activation cannot match + * and cannot re-fire the email. The `kiloclaw_email_log` unique index + * remains the final idempotency guard. + * + * @param params Transaction, subscription, and settlement period identity to match. + * @returns Whether subscription-created handling recorded an eligible activation. + */ async function didStripeSubscriptionCreatedRecordEligibleActivation(params: { tx: CreditBillingTx; subscriptionId: string; @@ -903,28 +924,34 @@ function planDisplayName(plan: 'commit' | 'standard'): string { return plan === 'commit' ? 'KiloClaw Commit' : 'KiloClaw Standard'; } -// Best-effort at-most-once dedupe via insert-before-send on -// `kiloclaw_email_log`, guarded by the unique index -// (user_id, instance_id, email_type, period_start). Each activation event -// (fresh `periodStart`) gets exactly one row; webhook replays of the same -// event collide on the index and return early. Because the -// KiloClaw subscription row is reused across cancel+resubscribe (both -// Stripe and credit paths UPDATE in place), period_start is what actually -// distinguishes a resubscribe's activation from the original — hence one -// email per activation, not one per instance lifetime. -// -// Known gaps shared with every other insert-before-send email path in this -// codebase (`maybeSendTopUpConfirmationEmail` in `apps/web/src/lib/credits.ts`, -// `services/kiloclaw-billing/src/lifecycle.ts` ~L850, and the -// `kiloclaw_email_log`-gated sends in `apps/web/src/app/api/internal/kiloclaw/`): -// 1. A crash between the marker insert and the provider send permanently -// suppresses the email on retry — the marker looks "already sent". -// 2. Rolling the marker back in the catch block after an ambiguous provider -// exception can duplicate the email if the provider actually accepted it. -// Fixing either properly requires a real outbox (pending/sent/terminal state -// + provider idempotency keys) applied uniformly across all of the above -// call sites. Tracked as follow-up tech debt; intentionally NOT fixed in -// isolation here so this new email path stays uniform with the existing ones. +/** + * Best-effort at-most-once dedupe via insert-before-send on + * `kiloclaw_email_log`, guarded by the unique index + * (user_id, instance_id, email_type, period_start). Each activation event + * (fresh `periodStart`) gets exactly one row; webhook replays of the same + * event collide on the index and return early. Because the + * KiloClaw subscription row is reused across cancel+resubscribe (both + * Stripe and credit paths UPDATE in place), period_start is what actually + * distinguishes a resubscribe's activation from the original, hence one + * email per activation, not one per instance lifetime. + * + * Known gaps shared with every other insert-before-send email path in this + * codebase (`maybeSendTopUpConfirmationEmail` in `apps/web/src/lib/credits.ts`, + * `services/kiloclaw-billing/src/lifecycle.ts` ~L850, and the + * `kiloclaw_email_log`-gated sends in `apps/web/src/app/api/internal/kiloclaw/`): + * 1. A crash between the marker insert and the provider send permanently + * suppresses the email on retry; the marker looks "already sent". + * 2. Rolling the marker back in the catch block after an ambiguous provider + * exception can duplicate the email if the provider actually accepted it. + * + * Fixing either properly requires a real outbox (pending/sent/terminal state + * + provider idempotency keys) applied uniformly across all of the above + * call sites. Tracked as follow-up tech debt; intentionally NOT fixed in + * isolation here so this new email path stays uniform with the existing ones. + * + * @param params User, instance, plan, price, and period details for the activation email. + * @returns A promise that resolves after the idempotency check and best-effort send attempt. + */ async function maybeSendKiloClawSubscriptionStartedEmail(params: { userId: string; instanceId: string; @@ -1025,10 +1052,15 @@ async function deleteSubscriptionStartedEmailLog(params: { ); } -// Fast-path existence check covered by the -// `UQ_kiloclaw_email_log_user_instance_type_period` unique index. Used to -// short-circuit the duplicate-settlement activation recovery path before -// running the more expensive `kiloclaw_subscription_change_log` scan. +/** + * Fast-path existence check covered by the + * `UQ_kiloclaw_email_log_user_instance_type_period` unique index. Used to + * short-circuit the duplicate-settlement activation recovery path before + * running the more expensive `kiloclaw_subscription_change_log` scan. + * + * @param params User, instance, and activation period to check. + * @returns Whether this activation already has an email-log marker. + */ async function subscriptionStartedEmailAlreadyLoggedForActivation(params: { userId: string; instanceId: string; From 6e772b77bb97993d20d1bade23a84c095987f367 Mon Sep 17 00:00:00 2001 From: Evan Jacobson Date: Tue, 5 May 2026 10:34:13 -0600 Subject: [PATCH 23/23] Reduce redundant comments --- apps/web/src/lib/credits.ts | 10 ---------- apps/web/src/lib/kiloclaw/credit-billing.ts | 13 ------------- 2 files changed, 23 deletions(-) diff --git a/apps/web/src/lib/credits.ts b/apps/web/src/lib/credits.ts index 15c7be88a8..7e0665803d 100644 --- a/apps/web/src/lib/credits.ts +++ b/apps/web/src/lib/credits.ts @@ -93,13 +93,6 @@ export async function processTopUp( const didInsertCreditTransaction = (attemptToInsert.rowCount ?? 0) > 0; if (!didInsertCreditTransaction) { - // A prior processTopUp call already committed the credit transaction for - // this stripe_payment_id (duplicate webhook / retry). The credit itself - // is idempotent, but the confirmation email is not guaranteed to have - // been sent — the original process could have exited between the credit - // commit and `after(processPostTopUpFreeStuff)`. Attempt to recover the - // email via the durable transactional_email_log marker. If a marker already - // exists the insert collides and no second email is sent. if (!skipPostTopUpFreeStuff) { await recoverTopUpConfirmationEmailIfMissing({ user, @@ -241,9 +234,6 @@ async function recoverTopUpConfirmationEmailIfMissing(params: { stripeChargeOrInvoiceId: string; isAutoTopUp: boolean; }): Promise { - // Reuse the same gated-send path. The marker insert with - // onConflictDoNothing() naturally skips when the original attempt already - // sent, and fires the email when it didn't. if (IS_IN_AUTOMATED_TEST) { await maybeSendTopUpConfirmationEmail(params); } else { diff --git a/apps/web/src/lib/kiloclaw/credit-billing.ts b/apps/web/src/lib/kiloclaw/credit-billing.ts index d93b11ea30..680ff75085 100644 --- a/apps/web/src/lib/kiloclaw/credit-billing.ts +++ b/apps/web/src/lib/kiloclaw/credit-billing.ts @@ -620,13 +620,6 @@ export async function applyStripeFundedKiloClawPeriod(params: { .where(eq(kiloclaw_subscriptions.id, targetRow.id)) .limit(1); - // Prefer the in-memory `before.status`. - // When Stripe's `customer.subscription.created` handler ran - // before invoice.paid, it already transitioned a non-hybrid row to 'active', - // hiding the pre-activation state from this snapshot. - // Fall back to the durable `kiloclaw_subscription_change_log` entry written - // by that handler, which preserves the pre-Stripe `before_state.status` - // and records the Stripe-derived plan/period in `after_state`. shouldSendSubscriptionStartedEmailForNewSettlement = shouldSendSubscriptionStartedEmailForActivation(before?.status ?? null) || (await didStripeSubscriptionCreatedRecordEligibleActivation({ @@ -676,12 +669,6 @@ export async function applyStripeFundedKiloClawPeriod(params: { }); } - // Steady-state webhook replays against an already-emailed, already-settled - // period hit the duplicate-settlement branch on every retry. - // The real idempotency guard is the `kiloclaw_email_log` unique index in - // `maybeSendKiloClawSubscriptionStartedEmail`, but we can skip the more - // expensive `kiloclaw_subscription_change_log` scan (and the subsequent - // no-op send call) when a matching email-log row already exists. const shouldSendSubscriptionStartedEmail = shouldSendSubscriptionStartedEmailForNewSettlement || (settlementWasDuplicate &&