From 04b3383e87ab1fcad076a0dabe5901b30dfccb7a Mon Sep 17 00:00:00 2001
From: Tobias Studer
Date: Mon, 5 Oct 2026 10:44:00 +0200
Subject: [PATCH 1/2] feat(045): admin UI for credit top-ups and the opening
balance
Spec 045 shipped recordCreditPurchase, deleteCreditPurchase and
setCreditOpeningBalance, but no surface called them, so a Console top-up
could not be recorded and the derived credit balance stayed unavailable.
Adds both to the Monthly Billing Budget card on /claude:
- Opening balance: amount + "as of end of" date, edit and clear. The
dialog spells out that only events dated after that day move the
balance (C4).
- Credit top-ups: record amount, date, optional invoice link and note;
list with confirm-before-delete. Rows on or before the opening date
are flagged as already included in it.
- getCreditPurchaseOverview loads the purchases and the recent vendor
invoices not yet recorded as a purchase, offered for linking, never
picked automatically (D9).
- parseCreditAmountCents / parseOpeningBalanceCents turn typed dollars
into integer cents; unit-tested.
Co-Authored-By: Claude Opus 5.5
---
src/actions/credits.ts | 79 ++++-
src/app/claude/page.tsx | 12 +-
.../claude/credit-opening-balance.tsx | 191 +++++++++++
src/components/claude/credit-purchases.tsx | 317 ++++++++++++++++++
src/components/claude/org-credits-panel.tsx | 23 ++
src/lib/credits.ts | 25 ++
tests/unit/credits.test.ts | 34 ++
7 files changed, 678 insertions(+), 3 deletions(-)
create mode 100644 src/components/claude/credit-opening-balance.tsx
create mode 100644 src/components/claude/credit-purchases.tsx
diff --git a/src/actions/credits.ts b/src/actions/credits.ts
index 270d4f3..7ddd66b 100644
--- a/src/actions/credits.ts
+++ b/src/actions/credits.ts
@@ -3,7 +3,7 @@
import { requireAdmin } from "@/lib/auth-helpers";
import { db } from "@/lib/db";
import { aiTools, creditPurchases, invoices } from "@/lib/db/schema";
-import { eq, sql } from "drizzle-orm";
+import { desc, eq, sql } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { recordCreation, recordUpdate, recordDeletion } from "@/lib/history";
@@ -24,7 +24,10 @@ const recordPurchaseSchema = z.object({
toolId: z.number().int().positive(),
invoiceId: z.number().int().positive().optional(),
purchasedAt: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Expected YYYY-MM-DD"),
- amountCents: z.number().int().positive("A purchase must be a positive amount"),
+ amountCents: z
+ .number()
+ .int()
+ .positive("A purchase must be a positive amount"),
note: z.string().max(200).optional(),
});
@@ -197,6 +200,78 @@ export async function setCreditOpeningBalance(
return { success: true, data: undefined };
}
+export interface CreditPurchaseRow {
+ id: number;
+ purchasedAt: string;
+ amountCents: number;
+ note: string | null;
+ invoiceNumber: string | null;
+}
+
+export interface LinkableInvoice {
+ id: number;
+ invoiceNumber: string;
+ invoiceDate: string;
+ amountCents: number;
+}
+
+export interface CreditPurchaseOverview {
+ purchases: CreditPurchaseRow[];
+ /** Recent vendor invoices not yet recorded as a credit purchase. */
+ linkableInvoices: LinkableInvoice[];
+}
+
+/**
+ * The recorded purchases for a tool, plus the invoices a new one could be
+ * linked to. Candidates are offered, never picked: a seat invoice and a top-up
+ * share a vendor, so only the admin can tell them apart (D9).
+ */
+export async function getCreditPurchaseOverview(
+ toolId: number,
+): Promise> {
+ const admin = await requireAdmin();
+ if (!admin) return { success: false, error: "Unauthorized" };
+
+ const tool = await db.query.aiTools.findFirst({
+ where: eq(aiTools.id, toolId),
+ columns: { id: true, vendor: true },
+ });
+ if (!tool) return { success: false, error: "Tool not found" };
+
+ const purchases = await db
+ .select({
+ id: creditPurchases.id,
+ purchasedAt: creditPurchases.purchasedAt,
+ amountCents: creditPurchases.amountCents,
+ note: creditPurchases.note,
+ invoiceNumber: invoices.invoiceNumber,
+ })
+ .from(creditPurchases)
+ .leftJoin(invoices, eq(invoices.id, creditPurchases.invoiceId))
+ .where(eq(creditPurchases.toolId, toolId))
+ .orderBy(desc(creditPurchases.purchasedAt), desc(creditPurchases.id));
+
+ const linkableInvoices = (
+ await db.execute(sql`
+ SELECT i.id,
+ i.invoice_number AS "invoiceNumber",
+ i.invoice_date::text AS "invoiceDate",
+ i.amount_cents AS "amountCents"
+ FROM invoices i
+ WHERE i.vendor ILIKE ${`%${tool.vendor}%`}
+ AND i.filtered_out = false
+ AND i.invoice_date >= CURRENT_DATE - INTERVAL '180 days'
+ AND NOT EXISTS (
+ SELECT 1 FROM credit_purchases cp WHERE cp.invoice_id = i.id
+ )
+ ORDER BY i.invoice_date DESC, i.id DESC
+ LIMIT 50
+ `)
+ ).rows as unknown as LinkableInvoice[];
+
+ return { success: true, data: { purchases, linkableInvoices } };
+}
+
/** The derived balance for a tool, with everything it was derived from. */
export async function getCreditBalance(
toolId: number,
diff --git a/src/app/claude/page.tsx b/src/app/claude/page.tsx
index 38ff9a8..e9456d8 100644
--- a/src/app/claude/page.tsx
+++ b/src/app/claude/page.tsx
@@ -14,7 +14,10 @@ import {
getTopMovers,
getWorkspaceSparklines,
} from "@/actions/anthropic-global";
-import { getAnthropicCreditBalance } from "@/actions/credits";
+import {
+ getAnthropicCreditBalance,
+ getCreditPurchaseOverview,
+} from "@/actions/credits";
import { GlobalMetricsClient } from "@/components/claude/global-metrics-client";
import { WorkspaceBudgetList } from "@/components/claude/workspace-budget-list";
import { OrgBillingBudgetCard } from "@/components/claude/org-credits-panel";
@@ -80,6 +83,12 @@ export default async function ClaudePage({
]);
const creditBalance = claudeConsoleCredits;
+ const creditPurchasesResult = creditBalance
+ ? await getCreditPurchaseOverview(creditBalance.toolId)
+ : null;
+ const creditPurchases = creditPurchasesResult?.success
+ ? creditPurchasesResult.data
+ : null;
return (
@@ -130,6 +139,7 @@ export default async function ClaudePage({
projectedMonthEndCents={kpis.projectedMonthEndCents}
todayEstimate={kpis.todayEstimate}
creditBalance={creditBalance}
+ creditPurchases={creditPurchases}
/>
diff --git a/src/components/claude/credit-opening-balance.tsx b/src/components/claude/credit-opening-balance.tsx
new file mode 100644
index 0000000..ebe746c
--- /dev/null
+++ b/src/components/claude/credit-opening-balance.tsx
@@ -0,0 +1,191 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter } from "next/navigation";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { StatusText, useInlineStatus } from "@/components/ui/status-text";
+import { setCreditOpeningBalance } from "@/actions/credits";
+import { parseOpeningBalanceCents } from "@/lib/credits";
+import { formatCurrency } from "@/lib/utils";
+import { todayLocal } from "@/components/claude/credit-purchases";
+
+type CreditOpeningBalanceProps = {
+ toolId: number;
+ /** Null when no opening balance is recorded. */
+ openingCents: number | null;
+ /** YYYY-MM-DD, or null when no opening balance is recorded. */
+ openingAt: string | null;
+};
+
+/**
+ * Spec 045 — the balance an admin read in the Anthropic Console, with the day
+ * it stood at. The Admin API exposes no balance, so this is the anchor every
+ * derived figure is counted from.
+ */
+export function CreditOpeningBalance({
+ toolId,
+ openingCents,
+ openingAt,
+}: CreditOpeningBalanceProps) {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [amount, setAmount] = useState("");
+ const [asOf, setAsOf] = useState(todayLocal);
+ const [isPending, startTransition] = useTransition();
+ const formStatus = useInlineStatus();
+ const rowStatus = useInlineStatus();
+
+ const amountCents = parseOpeningBalanceCents(amount);
+ const recorded = openingCents !== null && openingAt !== null;
+
+ function openDialog() {
+ setAmount(openingCents !== null ? (openingCents / 100).toFixed(2) : "");
+ setAsOf(openingAt ?? todayLocal());
+ setOpen(true);
+ }
+
+ function save(cents: number | null, date: string | null) {
+ startTransition(async () => {
+ const result = await setCreditOpeningBalance({
+ toolId,
+ openingBalanceCents: cents,
+ openingBalanceAt: date,
+ });
+ if (result.success) {
+ setOpen(false);
+ rowStatus.ok(cents === null ? "Opening balance cleared" : "Saved");
+ router.refresh();
+ } else {
+ formStatus.error(result.error);
+ }
+ });
+ }
+
+ function handleSave() {
+ if (amountCents === null) {
+ formStatus.error("Enter the balance in dollars, e.g. 44.79");
+ return;
+ }
+ if (asOf > todayLocal()) {
+ formStatus.error("The as-of date can't be in the future");
+ return;
+ }
+ save(amountCents, asOf);
+ }
+
+ return (
+
+
+
+ Opening balance
+
+
+
+
+ {recorded ? "Edit" : "Set"}
+
+
+
+
+ {openingCents !== null && openingAt !== null ? (
+ <>
+
+ {formatCurrency(openingCents)}
+ {" "}
+
+ at the end of {openingAt}
+
+ >
+ ) : (
+
+ Not recorded — the credit balance can't be derived until it is.
+
+ )}
+
+
+
+
+
+ Opening credit balance
+
+ The balance shown in the Anthropic Console. The Hub counts from
+ here: top-ups and usage dated after this day move the balance;
+ anything on or before it is treated as already included.
+
+
+
+
+
+ Read the balance before a top-up you're about to record? Date
+ it the day before, so the top-up counts on top of it.
+
+
+
+
+ {recorded && (
+ save(null, null)}
+ disabled={isPending}
+ >
+ Clear
+
+ )}
+ setOpen(false)}
+ disabled={isPending}
+ >
+ Cancel
+
+
+ {isPending ? "Saving…" : "Save"}
+
+
+
+
+
+ );
+}
diff --git a/src/components/claude/credit-purchases.tsx b/src/components/claude/credit-purchases.tsx
new file mode 100644
index 0000000..de256fc
--- /dev/null
+++ b/src/components/claude/credit-purchases.tsx
@@ -0,0 +1,317 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter } from "next/navigation";
+import { Trash2 } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+} from "@/components/ui/alert-dialog";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { StatusText, useInlineStatus } from "@/components/ui/status-text";
+import {
+ deleteCreditPurchase,
+ recordCreditPurchase,
+ type CreditPurchaseOverview,
+} from "@/actions/credits";
+import { parseCreditAmountCents } from "@/lib/credits";
+import { formatCurrency } from "@/lib/utils";
+
+// Radix Select forbids `value=""`, so "no invoice" needs a sentinel.
+const NO_INVOICE = "__none__";
+
+export function todayLocal(): string {
+ // en-CA formats as YYYY-MM-DD in the admin's own timezone.
+ return new Date().toLocaleDateString("en-CA");
+}
+
+type CreditPurchasesProps = {
+ toolId: number;
+ overview: CreditPurchaseOverview;
+ /** The opening balance's as-of date; purchases on or before it don't count. */
+ openingAt: string | null;
+};
+
+/**
+ * Spec 045 — record a prepaid credit top-up (cash paid for credits, never the
+ * cost of the month it lands in) and list the ones already on file.
+ */
+export function CreditPurchases({
+ toolId,
+ overview,
+ openingAt,
+}: CreditPurchasesProps) {
+ const router = useRouter();
+ const [open, setOpen] = useState(false);
+ const [amount, setAmount] = useState("");
+ const [purchasedAt, setPurchasedAt] = useState(todayLocal);
+ const [invoiceId, setInvoiceId] = useState(NO_INVOICE);
+ const [note, setNote] = useState("");
+ const [isPending, startTransition] = useTransition();
+ const formStatus = useInlineStatus();
+ const listStatus = useInlineStatus();
+
+ const amountCents = parseCreditAmountCents(amount);
+
+ function reset() {
+ setAmount("");
+ setPurchasedAt(todayLocal());
+ setInvoiceId(NO_INVOICE);
+ setNote("");
+ }
+
+ function handleInvoiceChange(value: string) {
+ setInvoiceId(value);
+ // Prefill from the invoice, but only into empty fields — never overwrite
+ // what the admin already typed.
+ const invoice = overview.linkableInvoices.find(
+ (i) => String(i.id) === value,
+ );
+ if (invoice && amount.trim() === "") {
+ setAmount((invoice.amountCents / 100).toFixed(2));
+ }
+ }
+
+ function handleSubmit() {
+ if (amountCents === null) {
+ formStatus.error("Enter a positive amount, e.g. 610.43");
+ return;
+ }
+ startTransition(async () => {
+ const result = await recordCreditPurchase({
+ toolId,
+ purchasedAt,
+ amountCents,
+ invoiceId: invoiceId === NO_INVOICE ? undefined : Number(invoiceId),
+ note: note.trim() === "" ? undefined : note.trim(),
+ });
+ if (result.success) {
+ setOpen(false);
+ reset();
+ listStatus.ok("Top-up recorded");
+ router.refresh();
+ } else {
+ formStatus.error(result.error);
+ }
+ });
+ }
+
+ function handleDelete(id: number) {
+ startTransition(async () => {
+ const result = await deleteCreditPurchase(id);
+ if (result.success) {
+ listStatus.ok("Top-up deleted");
+ router.refresh();
+ } else {
+ listStatus.error(result.error);
+ }
+ });
+ }
+
+ return (
+
+
+
+ Credit top-ups
+
+
+
+ setOpen(true)}>
+ Record top-up
+
+
+
+
+ {overview.purchases.length === 0 ? (
+
+ No top-ups recorded yet.
+
+ ) : (
+
+ {overview.purchases.map((p) => (
+
+
+
+ {formatCurrency(p.amountCents)}
+ {" "}
+
+ on {p.purchasedAt}
+ {p.invoiceNumber && <> · invoice {p.invoiceNumber}>}
+ {p.note && <> · {p.note}>}
+
+ {openingAt !== null && p.purchasedAt <= openingAt && (
+
+ On or before the opening balance date ({openingAt}), so
+ treated as already included in it.
+
+ )}
+
+
+
+
+
+
+
+
+
+ Delete this top-up?
+
+ {formatCurrency(p.amountCents)} on {p.purchasedAt} will no
+ longer count towards the credit balance.
+ {p.invoiceNumber &&
+ ` Invoice ${p.invoiceNumber} will count as period cost again.`}
+
+
+
+ Cancel
+ handleDelete(p.id)}>
+ Delete
+
+
+
+
+
+ ))}
+
+ )}
+
+
{
+ setOpen(next);
+ if (!next) reset();
+ }}
+ >
+
+
+ Record credit top-up
+
+ Cash paid for prepaid credits. It adds to the credit balance and
+ is never counted as the cost of the month it lands in.
+
+
+
+
+
+
+
+
+ Invoice{" "}
+ (optional)
+
+
+
+
+
+
+ None
+ {overview.linkableInvoices.map((i) => (
+
+ {i.invoiceNumber} · {i.invoiceDate} ·{" "}
+ {formatCurrency(i.amountCents)}
+
+ ))}
+
+
+
+ Linking the top-up's invoice stops it counting as period
+ cost. If the invoice hasn't arrived yet, record without one
+ and re-record once it has.
+
+
+
+
+
+ Note (optional)
+
+ setNote(e.target.value)}
+ maxLength={200}
+ placeholder="e.g. Console top-up"
+ />
+
+
+
+
+
+ setOpen(false)}
+ disabled={isPending}
+ >
+ Cancel
+
+
+ {isPending ? "Saving…" : "Record top-up"}
+
+
+
+
+
+ );
+}
diff --git a/src/components/claude/org-credits-panel.tsx b/src/components/claude/org-credits-panel.tsx
index af6818e..b432675 100644
--- a/src/components/claude/org-credits-panel.tsx
+++ b/src/components/claude/org-credits-panel.tsx
@@ -20,6 +20,9 @@ import {
creditBalanceProvenance,
type CreditBalance,
} from "@/lib/credits";
+import type { CreditPurchaseOverview } from "@/actions/credits";
+import { CreditPurchases } from "@/components/claude/credit-purchases";
+import { CreditOpeningBalance } from "@/components/claude/credit-opening-balance";
type OrgBillingBudgetCardProps = {
orgConfig: { billingBudgetLimitCents: number | null } | null;
@@ -29,6 +32,8 @@ type OrgBillingBudgetCardProps = {
todayEstimate?: TodayEstimate | null;
/** Spec 045 — the Hub-derived prepaid credit balance, or an unavailable one. */
creditBalance?: CreditBalance | null;
+ /** Spec 045 — recorded top-ups and linkable invoices for that tool. */
+ creditPurchases?: CreditPurchaseOverview | null;
};
export function OrgBillingBudgetCard({
@@ -37,6 +42,7 @@ export function OrgBillingBudgetCard({
projectedMonthEndCents,
todayEstimate,
creditBalance,
+ creditPurchases,
}: OrgBillingBudgetCardProps) {
const [editing, setEditing] = useState(false);
const [inputValue, setInputValue] = useState(
@@ -239,6 +245,23 @@ export function OrgBillingBudgetCard({
/>
)}
+
+ {creditBalance && creditPurchases && (
+ <>
+
+
+ >
+ )}
);
diff --git a/src/lib/credits.ts b/src/lib/credits.ts
index 6956758..1b7e9d1 100644
--- a/src/lib/credits.ts
+++ b/src/lib/credits.ts
@@ -96,3 +96,28 @@ export function creditBalanceProvenance(balance: CreditBalance): string {
}
return `Derived by the Hub from the opening balance recorded on ${balance.asOf}, plus credit purchases, minus measured consumption since. Not read from Anthropic.`;
}
+
+/** Dollars typed into a form → integer cents, or null if not a plain amount. */
+function parseUsdCents(input: string): number | null {
+ const trimmed = input.trim();
+ // Two decimals at most: a third is a typo, not a fraction of a cent.
+ if (!/^\d+(\.\d{1,2})?$/.test(trimmed)) return null;
+ return Math.round(Number(trimmed) * 100);
+}
+
+/**
+ * Parse a top-up amount into cents. Null for anything the server would
+ * reject: empty, non-numeric, zero, negative, or more than two decimals.
+ */
+export function parseCreditAmountCents(input: string): number | null {
+ const cents = parseUsdCents(input);
+ return cents !== null && cents > 0 ? cents : null;
+}
+
+/**
+ * Parse an opening balance into cents. Unlike a top-up, zero is a real
+ * reading — an exhausted balance — so only negatives and junk are rejected.
+ */
+export function parseOpeningBalanceCents(input: string): number | null {
+ return parseUsdCents(input);
+}
diff --git a/tests/unit/credits.test.ts b/tests/unit/credits.test.ts
index d26df13..8b9b46c 100644
--- a/tests/unit/credits.test.ts
+++ b/tests/unit/credits.test.ts
@@ -3,6 +3,8 @@ import { describe, it, expect } from "vitest";
import {
creditBalanceProvenance,
deriveCreditBalance,
+ parseCreditAmountCents,
+ parseOpeningBalanceCents,
type DeriveCreditBalanceInput,
} from "@/lib/credits";
@@ -135,3 +137,35 @@ describe("deriveCreditBalance", () => {
);
});
});
+
+describe("parseCreditAmountCents", () => {
+ it("parses dollars and cents into integer cents", () => {
+ expect(parseCreditAmountCents("610.43")).toBe(61043);
+ expect(parseCreditAmountCents(" 500 ")).toBe(50000);
+ expect(parseCreditAmountCents("0.1")).toBe(10);
+ });
+
+ it("does not lose a cent to floating point", () => {
+ expect(parseCreditAmountCents("1.15")).toBe(115);
+ expect(parseCreditAmountCents("4.35")).toBe(435);
+ });
+
+ it("rejects what the server would reject", () => {
+ for (const bad of ["", "0", "0.00", "-5", "abc", "1.234", "1,000.00", "1e3"]) {
+ expect(parseCreditAmountCents(bad)).toBeNull();
+ }
+ });
+});
+
+describe("parseOpeningBalanceCents", () => {
+ it("accepts zero, an exhausted balance", () => {
+ expect(parseOpeningBalanceCents("0")).toBe(0);
+ expect(parseOpeningBalanceCents("44.79")).toBe(4479);
+ });
+
+ it("rejects negatives and junk", () => {
+ for (const bad of ["", "-1", "abc", "1.234"]) {
+ expect(parseOpeningBalanceCents(bad)).toBeNull();
+ }
+ });
+});
From 426731414e84ca4bf585132c53352de87d1c42b7 Mon Sep 17 00:00:00 2001
From: Tobias Studer
Date: Mon, 5 Oct 2026 10:58:51 +0200
Subject: [PATCH 2/2] fix(045): one invoice per top-up, link invoices in place,
server-side checks
Addresses review on #129:
- Unique index on credit_purchases.invoice_id (migration 0033) so two
admins can't link the same invoice and double-count the balance; the
violation maps to the same friendly error as the pre-check.
- linkCreditPurchaseInvoice attaches an invoice to an existing top-up.
The old advice to re-record once the invoice arrived would have left
two rows and doubled the cash. The list now offers "Link invoice" on
unlinked rows.
- setCreditOpeningBalance rejects negative balances and future as-of
dates on the server, not only in the form (one day of slack for the
admin's timezone running ahead of UTC).
- The delete confirmation for a top-up on or before the opening date
says the balance won't change, instead of claiming it will.
Co-Authored-By: Claude Opus 5.5
---
src/actions/credits.ts | 133 +-
src/components/claude/credit-purchases.tsx | 198 +-
.../0033_credit_purchase_invoice_unique.sql | 2 +
src/lib/db/migrations/meta/0033_snapshot.json | 5896 +++++++++++++++++
src/lib/db/migrations/meta/_journal.json | 7 +
src/lib/db/schema.ts | 5 +-
6 files changed, 6169 insertions(+), 72 deletions(-)
create mode 100644 src/lib/db/migrations/0033_credit_purchase_invoice_unique.sql
create mode 100644 src/lib/db/migrations/meta/0033_snapshot.json
diff --git a/src/actions/credits.ts b/src/actions/credits.ts
index 7ddd66b..7fdd989 100644
--- a/src/actions/credits.ts
+++ b/src/actions/credits.ts
@@ -8,6 +8,7 @@ import { revalidatePath } from "next/cache";
import { z } from "zod";
import { recordCreation, recordUpdate, recordDeletion } from "@/lib/history";
import { deriveCreditBalance, type CreditBalance } from "@/lib/credits";
+import { isUniqueViolation } from "@/lib/core/tools";
import type { ActionResult } from "@/types";
/**
@@ -31,11 +32,35 @@ const recordPurchaseSchema = z.object({
note: z.string().max(200).optional(),
});
+const linkInvoiceSchema = z.object({
+ id: z.number().int().positive(),
+ // Null unlinks: the invoice counts as period cost again.
+ invoiceId: z.number().int().positive().nullable(),
+});
+
+const INVOICE_ALREADY_LINKED =
+ "This invoice is already recorded as a credit purchase";
+
+/**
+ * Today as YYYY-MM-DD, a day ahead of UTC. The admin picks the date in their
+ * own timezone, which can already be tomorrow in UTC; a one-day slack keeps
+ * that honest reading valid while still rejecting a genuinely future anchor.
+ */
+function latestAllowedDate(): string {
+ const d = new Date();
+ d.setUTCDate(d.getUTCDate() + 1);
+ return d.toISOString().slice(0, 10);
+}
+
const openingBalanceSchema = z.object({
toolId: z.number().int().positive(),
// Nullable so an admin can clear a stale opening balance, which returns the
// panel to "unavailable" rather than leaving a wrong number on screen.
- openingBalanceCents: z.number().int().nullable(),
+ openingBalanceCents: z
+ .number()
+ .int()
+ .nonnegative("A credit balance can't be negative")
+ .nullable(),
openingBalanceAt: z
.string()
.regex(/^\d{4}-\d{2}-\d{2}$/, "Expected YYYY-MM-DD")
@@ -77,25 +102,30 @@ export async function recordCreditPurchase(
where: eq(creditPurchases.invoiceId, invoiceId),
columns: { id: true },
});
- if (existing) {
- return {
- success: false,
- error: "This invoice is already recorded as a credit purchase",
- };
- }
+ if (existing) return { success: false, error: INVOICE_ALREADY_LINKED };
}
- const [row] = await db
- .insert(creditPurchases)
- .values({
- toolId,
- invoiceId: invoiceId ?? null,
- purchasedAt,
- amountCents,
- note: note ?? null,
- createdBy: Number(admin.id),
- })
- .returning({ id: creditPurchases.id });
+ let row: { id: number };
+ try {
+ [row] = await db
+ .insert(creditPurchases)
+ .values({
+ toolId,
+ invoiceId: invoiceId ?? null,
+ purchasedAt,
+ amountCents,
+ note: note ?? null,
+ createdBy: Number(admin.id),
+ })
+ .returning({ id: creditPurchases.id });
+ } catch (e) {
+ // The pre-check above gives the common case its message; the unique index
+ // closes the race where two admins link the same invoice at once.
+ if (isUniqueViolation(e)) {
+ return { success: false, error: INVOICE_ALREADY_LINKED };
+ }
+ throw e;
+ }
await recordCreation("credit_purchase", row.id, Number(admin.id), {
source: "ui",
@@ -105,6 +135,60 @@ export async function recordCreditPurchase(
return { success: true, data: { id: row.id } };
}
+/**
+ * Attach the invoice to a top-up recorded before it arrived, or detach it.
+ *
+ * Updating the existing row matters: re-recording the top-up with its invoice
+ * would leave two rows, and the balance would count the cash twice.
+ */
+export async function linkCreditPurchaseInvoice(
+ input: unknown,
+): Promise> {
+ const admin = await requireAdmin();
+ if (!admin) return { success: false, error: "Unauthorized" };
+
+ const parsed = linkInvoiceSchema.safeParse(input);
+ if (!parsed.success) return { success: false, error: "Validation failed" };
+ const { id, invoiceId } = parsed.data;
+
+ const existing = await db.query.creditPurchases.findFirst({
+ where: eq(creditPurchases.id, id),
+ columns: { id: true, invoiceId: true },
+ });
+ if (!existing) return { success: false, error: "Credit purchase not found" };
+
+ if (invoiceId !== null) {
+ const invoice = await db.query.invoices.findFirst({
+ where: eq(invoices.id, invoiceId),
+ columns: { id: true },
+ });
+ if (!invoice) return { success: false, error: "Invoice not found" };
+ }
+
+ try {
+ await db
+ .update(creditPurchases)
+ .set({ invoiceId, updatedAt: new Date() })
+ .where(eq(creditPurchases.id, id));
+ } catch (e) {
+ if (isUniqueViolation(e)) {
+ return { success: false, error: INVOICE_ALREADY_LINKED };
+ }
+ throw e;
+ }
+
+ await recordUpdate(
+ "credit_purchase",
+ id,
+ Number(admin.id),
+ { invoiceId: { old: existing.invoiceId, new: invoiceId } },
+ { source: "ui" },
+ );
+
+ revalidateCreditSurfaces();
+ return { success: true, data: undefined };
+}
+
/**
* Undo a reclassification.
*
@@ -147,10 +231,21 @@ export async function setCreditOpeningBalance(
if (!admin) return { success: false, error: "Unauthorized" };
const parsed = openingBalanceSchema.safeParse(input);
- if (!parsed.success) return { success: false, error: "Validation failed" };
+ if (!parsed.success) {
+ return {
+ success: false,
+ error: parsed.error.issues[0]?.message ?? "Validation failed",
+ };
+ }
const { toolId, openingBalanceCents, openingBalanceAt } = parsed.data;
+ // A future anchor would silently swallow every top-up and day of usage up
+ // to it (C4), so it is refused here, not only in the form.
+ if (openingBalanceAt !== null && openingBalanceAt > latestAllowedDate()) {
+ return { success: false, error: "The as-of date can't be in the future" };
+ }
+
// Both or neither: a balance without its as-of date cannot be reasoned
// about, and a date without a balance says nothing.
if ((openingBalanceCents === null) !== (openingBalanceAt === null)) {
diff --git a/src/components/claude/credit-purchases.tsx b/src/components/claude/credit-purchases.tsx
index de256fc..609e94b 100644
--- a/src/components/claude/credit-purchases.tsx
+++ b/src/components/claude/credit-purchases.tsx
@@ -35,6 +35,7 @@ import {
import { StatusText, useInlineStatus } from "@/components/ui/status-text";
import {
deleteCreditPurchase,
+ linkCreditPurchaseInvoice,
recordCreditPurchase,
type CreditPurchaseOverview,
} from "@/actions/credits";
@@ -74,6 +75,10 @@ export function CreditPurchases({
const [isPending, startTransition] = useTransition();
const formStatus = useInlineStatus();
const listStatus = useInlineStatus();
+ const linkStatus = useInlineStatus();
+ const [linkingId, setLinkingId] = useState(null);
+ const [linkInvoiceId, setLinkInvoiceId] = useState("");
+ const linking = overview.purchases.find((p) => p.id === linkingId) ?? null;
const amountCents = parseCreditAmountCents(amount);
@@ -120,6 +125,23 @@ export function CreditPurchases({
});
}
+ function handleLink() {
+ if (linkingId === null || linkInvoiceId === "") return;
+ startTransition(async () => {
+ const result = await linkCreditPurchaseInvoice({
+ id: linkingId,
+ invoiceId: Number(linkInvoiceId),
+ });
+ if (result.success) {
+ setLinkingId(null);
+ listStatus.ok("Invoice linked");
+ router.refresh();
+ } else {
+ linkStatus.error(result.error);
+ }
+ });
+ }
+
function handleDelete(id: number) {
startTransition(async () => {
const result = await deleteCreditPurchase(id);
@@ -152,61 +174,133 @@ export function CreditPurchases({
) : (
- {overview.purchases.map((p) => (
-
-
-
- {formatCurrency(p.amountCents)}
- {" "}
-
- on {p.purchasedAt}
- {p.invoiceNumber && <> · invoice {p.invoiceNumber}>}
- {p.note && <> · {p.note}>}
-
- {openingAt !== null && p.purchasedAt <= openingAt && (
-
- On or before the opening balance date ({openingAt}), so
- treated as already included in it.
+ {overview.purchases.map((p) => {
+ // Already inside the opening balance, so the derived figure
+ // doesn't include it either way (C4).
+ const coveredByOpening =
+ openingAt !== null && p.purchasedAt <= openingAt;
+ return (
+
+
+
+ {formatCurrency(p.amountCents)}
+ {" "}
+
+ on {p.purchasedAt}
+ {p.invoiceNumber && <> · invoice {p.invoiceNumber}>}
+ {p.note && <> · {p.note}>}
- )}
-
-
-
-
-
-
-
-
-
- Delete this top-up?
-
- {formatCurrency(p.amountCents)} on {p.purchasedAt} will no
- longer count towards the credit balance.
- {p.invoiceNumber &&
- ` Invoice ${p.invoiceNumber} will count as period cost again.`}
-
-
-
- Cancel
- handleDelete(p.id)}>
- Delete
-
-
-
-
-
- ))}
+ {coveredByOpening && (
+
+ On or before the opening balance date ({openingAt}), so
+ treated as already included in it.
+
+ )}
+
+
+ {p.invoiceNumber === null &&
+ overview.linkableInvoices.length > 0 && (
+
{
+ setLinkInvoiceId("");
+ setLinkingId(p.id);
+ }}
+ >
+ Link invoice
+
+ )}
+
+
+
+
+
+
+
+
+ Delete this top-up?
+
+ {coveredByOpening
+ ? `${formatCurrency(p.amountCents)} on ${p.purchasedAt} is on or before the opening balance date, so the derived balance already leaves it out — deleting it won't change the balance.`
+ : `${formatCurrency(p.amountCents)} on ${p.purchasedAt} will no longer count towards the credit balance.`}
+ {p.invoiceNumber &&
+ ` Invoice ${p.invoiceNumber} will count as period cost again.`}
+
+
+
+ Cancel
+ handleDelete(p.id)}>
+ Delete
+
+
+
+
+
+
+ );
+ })}
)}
+ {
+ if (!next) setLinkingId(null);
+ }}
+ >
+
+
+ Link invoice
+
+ {linking &&
+ `Attach the invoice for the ${formatCurrency(linking.amountCents)} top-up on ${linking.purchasedAt}. The invoice then stops counting as period cost; the credit balance is unchanged.`}
+
+
+
+ Invoice
+
+
+
+
+
+ {overview.linkableInvoices.map((i) => (
+
+ {i.invoiceNumber} · {i.invoiceDate} ·{" "}
+ {formatCurrency(i.amountCents)}
+
+ ))}
+
+
+
+
+
+ setLinkingId(null)}
+ disabled={isPending}
+ >
+ Cancel
+
+
+ {isPending ? "Saving…" : "Link invoice"}
+
+
+
+
+
{
@@ -276,7 +370,7 @@ export function CreditPurchases({
Linking the top-up's invoice stops it counting as period
cost. If the invoice hasn't arrived yet, record without one
- and re-record once it has.
+ and use “Link invoice” on the top-up once it has.
diff --git a/src/lib/db/migrations/0033_credit_purchase_invoice_unique.sql b/src/lib/db/migrations/0033_credit_purchase_invoice_unique.sql
new file mode 100644
index 0000000..a47e0a5
--- /dev/null
+++ b/src/lib/db/migrations/0033_credit_purchase_invoice_unique.sql
@@ -0,0 +1,2 @@
+CREATE UNIQUE INDEX "credit_purchases_invoice_id_unique" ON "credit_purchases" USING btree ("invoice_id");--> statement-breakpoint
+DROP INDEX "credit_purchases_invoice_id_idx";
\ No newline at end of file
diff --git a/src/lib/db/migrations/meta/0033_snapshot.json b/src/lib/db/migrations/meta/0033_snapshot.json
new file mode 100644
index 0000000..31a5635
--- /dev/null
+++ b/src/lib/db/migrations/meta/0033_snapshot.json
@@ -0,0 +1,5896 @@
+{
+ "id": "c9788ab7-ed36-492d-96b9-df4e3205df4d",
+ "prevId": "21fadf76-6cfb-449e-af1e-f68c33fce3fe",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.access_tiers": {
+ "name": "access_tiers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "tool_id": {
+ "name": "tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "monthly_cost_cents": {
+ "name": "monthly_cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pricing_model": {
+ "name": "pricing_model",
+ "type": "pricing_model",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'seat'"
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "access_tiers_tool_id_idx": {
+ "name": "access_tiers_tool_id_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "access_tiers_tool_name_idx": {
+ "name": "access_tiers_tool_name_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "access_tiers_tool_id_ai_tools_id_fk": {
+ "name": "access_tiers_tool_id_ai_tools_id_fk",
+ "tableFrom": "access_tiers",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ai_tools": {
+ "name": "ai_tools",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_licenses": {
+ "name": "max_licenses",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "tool_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "requires_api_key": {
+ "name": "requires_api_key",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "credit_opening_balance_cents": {
+ "name": "credit_opening_balance_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credit_opening_balance_at": {
+ "name": "credit_opening_balance_at",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "ai_tools_name_idx": {
+ "name": "ai_tools_name_idx",
+ "columns": [
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ai_tools_vendor_idx": {
+ "name": "ai_tools_vendor_idx",
+ "columns": [
+ {
+ "expression": "vendor",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.annual_budgets": {
+ "name": "annual_budgets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "fiscal_year": {
+ "name": "fiscal_year",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_amount_cents": {
+ "name": "total_amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "original_amount_cents": {
+ "name": "original_amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "period_type": {
+ "name": "period_type",
+ "type": "period_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "budget_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "annual_budgets_fiscal_year_idx": {
+ "name": "annual_budgets_fiscal_year_idx",
+ "columns": [
+ {
+ "expression": "fiscal_year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "annual_budgets_status_idx": {
+ "name": "annual_budgets_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.anthropic_org_config": {
+ "name": "anthropic_org_config",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "default": 1
+ },
+ "billing_budget_limit_cents": {
+ "name": "billing_budget_limit_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "anthropic_org_config_updated_by_users_id_fk": {
+ "name": "anthropic_org_config_updated_by_users_id_fk",
+ "tableFrom": "anthropic_org_config",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "anthropic_org_config_id_check": {
+ "name": "anthropic_org_config_id_check",
+ "value": "\"anthropic_org_config\".\"id\" = 1"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.anthropic_sync_status": {
+ "name": "anthropic_sync_status",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_sync_started_at": {
+ "name": "last_sync_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_completed_at": {
+ "name": "last_sync_completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_error": {
+ "name": "last_sync_error",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "synced_days": {
+ "name": "synced_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "resolved_api_key_id": {
+ "name": "resolved_api_key_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resolved_workspace_id": {
+ "name": "resolved_workspace_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_sync_completed_at": {
+ "name": "workspace_sync_completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "anthropic_sync_status_user_id_idx": {
+ "name": "anthropic_sync_status_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.anthropic_usage_metrics": {
+ "name": "anthropic_usage_metrics",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uncached_input_tokens": {
+ "name": "uncached_input_tokens",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_read_input_tokens": {
+ "name": "cache_read_input_tokens",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "cache_creation_input_tokens": {
+ "name": "cache_creation_input_tokens",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "output_tokens": {
+ "name": "output_tokens",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "computed_cost_cents": {
+ "name": "computed_cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "pricing_resolved": {
+ "name": "pricing_resolved",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "attributed_cost_cents": {
+ "name": "attributed_cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attribution_mode": {
+ "name": "attribution_mode",
+ "type": "attribution_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "anthropic_usage_metrics_user_date_model_idx": {
+ "name": "anthropic_usage_metrics_user_date_model_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_usage_metrics_user_date_idx": {
+ "name": "anthropic_usage_metrics_user_date_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_usage_metrics_date_idx": {
+ "name": "anthropic_usage_metrics_date_idx",
+ "columns": [
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_usage_metrics_pricing_resolved_idx": {
+ "name": "anthropic_usage_metrics_pricing_resolved_idx",
+ "columns": [
+ {
+ "expression": "pricing_resolved",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "anthropic_usage_metrics_user_id_users_id_fk": {
+ "name": "anthropic_usage_metrics_user_id_users_id_fk",
+ "tableFrom": "anthropic_usage_metrics",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.anthropic_workspace_cost_items": {
+ "name": "anthropic_workspace_cost_items",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "model": {
+ "name": "model",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_type": {
+ "name": "cost_type",
+ "type": "varchar(40)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_type": {
+ "name": "token_type",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "context_window": {
+ "name": "context_window",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "service_tier": {
+ "name": "service_tier",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "inference_geo": {
+ "name": "inference_geo",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_microcents": {
+ "name": "cost_microcents",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "anthropic_workspace_cost_items_grain_idx": {
+ "name": "anthropic_workspace_cost_items_grain_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"model\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"cost_type\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"token_type\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"context_window\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"service_tier\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"inference_geo\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_cost_items\".\"workspace_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_cost_items_default_grain_idx": {
+ "name": "anthropic_workspace_cost_items_default_grain_idx",
+ "columns": [
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"model\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"cost_type\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"token_type\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"context_window\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"service_tier\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"inference_geo\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_cost_items\".\"workspace_id\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_cost_items_date_idx": {
+ "name": "anthropic_workspace_cost_items_date_idx",
+ "columns": [
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_cost_items_workspace_date_idx": {
+ "name": "anthropic_workspace_cost_items_workspace_date_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "anthropic_workspace_cost_items_cost_microcents_check": {
+ "name": "anthropic_workspace_cost_items_cost_microcents_check",
+ "value": "\"anthropic_workspace_cost_items\".\"cost_microcents\" >= 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.anthropic_workspace_costs": {
+ "name": "anthropic_workspace_costs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cost_cents": {
+ "name": "cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "anthropic_workspace_costs_workspace_date_idx": {
+ "name": "anthropic_workspace_costs_workspace_date_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_costs\".\"workspace_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_costs_default_date_idx": {
+ "name": "anthropic_workspace_costs_default_date_idx",
+ "columns": [
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_costs\".\"workspace_id\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_costs_date_idx": {
+ "name": "anthropic_workspace_costs_date_idx",
+ "columns": [
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_costs_workspace_id_idx": {
+ "name": "anthropic_workspace_costs_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "anthropic_workspace_costs_cost_cents_check": {
+ "name": "anthropic_workspace_costs_cost_cents_check",
+ "value": "\"anthropic_workspace_costs\".\"cost_cents\" >= 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.anthropic_workspace_limits": {
+ "name": "anthropic_workspace_limits",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "limit_cents": {
+ "name": "limit_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "confirmed_at": {
+ "name": "confirmed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmed_by": {
+ "name": "confirmed_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "anthropic_workspace_limits_workspace_id_idx": {
+ "name": "anthropic_workspace_limits_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_limits\".\"workspace_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_limits_default_idx": {
+ "name": "anthropic_workspace_limits_default_idx",
+ "columns": [
+ {
+ "expression": "(1)",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_limits\".\"workspace_id\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_limits_confirmed_by_idx": {
+ "name": "anthropic_workspace_limits_confirmed_by_idx",
+ "columns": [
+ {
+ "expression": "confirmed_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "anthropic_workspace_limits_confirmed_by_users_id_fk": {
+ "name": "anthropic_workspace_limits_confirmed_by_users_id_fk",
+ "tableFrom": "anthropic_workspace_limits",
+ "tableTo": "users",
+ "columnsFrom": [
+ "confirmed_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.anthropic_workspace_owners": {
+ "name": "anthropic_workspace_owners",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'resolved'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "anthropic_workspace_owners_workspace_user_idx": {
+ "name": "anthropic_workspace_owners_workspace_user_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_owners\".\"workspace_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_owners_default_user_idx": {
+ "name": "anthropic_workspace_owners_default_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspace_owners\".\"workspace_id\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspace_owners_user_id_idx": {
+ "name": "anthropic_workspace_owners_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "anthropic_workspace_owners_user_id_users_id_fk": {
+ "name": "anthropic_workspace_owners_user_id_users_id_fk",
+ "tableFrom": "anthropic_workspace_owners",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.anthropic_workspaces": {
+ "name": "anthropic_workspaces",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(200)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_color": {
+ "name": "display_color",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "is_archived": {
+ "name": "is_archived",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "anthropic_created_at": {
+ "name": "anthropic_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deprecated_at": {
+ "name": "deprecated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deprecated_reason": {
+ "name": "deprecated_reason",
+ "type": "varchar(200)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "anthropic_workspaces_workspace_id_idx": {
+ "name": "anthropic_workspaces_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspaces\".\"workspace_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspaces_is_default_idx": {
+ "name": "anthropic_workspaces_is_default_idx",
+ "columns": [
+ {
+ "expression": "is_default",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"anthropic_workspaces\".\"is_default\" = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "anthropic_workspaces_archived_idx": {
+ "name": "anthropic_workspaces_archived_idx",
+ "columns": [
+ {
+ "expression": "is_archived",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.assignment_comments": {
+ "name": "assignment_comments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "assignment_id": {
+ "name": "assignment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "author_id": {
+ "name": "author_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "varchar(2000)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "assignment_comments_assignment_id_idx": {
+ "name": "assignment_comments_assignment_id_idx",
+ "columns": [
+ {
+ "expression": "assignment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "assignment_comments_author_id_idx": {
+ "name": "assignment_comments_author_id_idx",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "assignment_comments_created_at_idx": {
+ "name": "assignment_comments_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "assignment_comments_assignment_id_license_assignments_id_fk": {
+ "name": "assignment_comments_assignment_id_license_assignments_id_fk",
+ "tableFrom": "assignment_comments",
+ "tableTo": "license_assignments",
+ "columnsFrom": [
+ "assignment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "assignment_comments_author_id_users_id_fk": {
+ "name": "assignment_comments_author_id_users_id_fk",
+ "tableFrom": "assignment_comments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "author_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.billed_costs": {
+ "name": "billed_costs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "period_id": {
+ "name": "period_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invoice_date": {
+ "name": "invoice_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "vendor_reference": {
+ "name": "vendor_reference",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "billed_costs_period_id_idx": {
+ "name": "billed_costs_period_id_idx",
+ "columns": [
+ {
+ "expression": "period_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "billed_costs_invoice_date_idx": {
+ "name": "billed_costs_invoice_date_idx",
+ "columns": [
+ {
+ "expression": "invoice_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "billed_costs_period_id_budget_periods_id_fk": {
+ "name": "billed_costs_period_id_budget_periods_id_fk",
+ "tableFrom": "billed_costs",
+ "tableTo": "budget_periods",
+ "columnsFrom": [
+ "period_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.budget_extension_period_allocations": {
+ "name": "budget_extension_period_allocations",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "extension_id": {
+ "name": "extension_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "period_id": {
+ "name": "period_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "bepa_unique_ext_period": {
+ "name": "bepa_unique_ext_period",
+ "columns": [
+ {
+ "expression": "extension_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "period_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "bepa_period_idx": {
+ "name": "bepa_period_idx",
+ "columns": [
+ {
+ "expression": "period_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "budget_extension_period_allocations_extension_id_budget_extensions_id_fk": {
+ "name": "budget_extension_period_allocations_extension_id_budget_extensions_id_fk",
+ "tableFrom": "budget_extension_period_allocations",
+ "tableTo": "budget_extensions",
+ "columnsFrom": [
+ "extension_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "budget_extension_period_allocations_period_id_budget_periods_id_fk": {
+ "name": "budget_extension_period_allocations_period_id_budget_periods_id_fk",
+ "tableFrom": "budget_extension_period_allocations",
+ "tableTo": "budget_periods",
+ "columnsFrom": [
+ "period_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.budget_extensions": {
+ "name": "budget_extensions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "budget_id": {
+ "name": "budget_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "varchar(120)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "category": {
+ "name": "category",
+ "type": "budget_extension_category",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "linked_tool_id": {
+ "name": "linked_tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "effective_date": {
+ "name": "effective_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "budget_extensions_budget_idx": {
+ "name": "budget_extensions_budget_idx",
+ "columns": [
+ {
+ "expression": "budget_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "budget_extensions_effective_idx": {
+ "name": "budget_extensions_effective_idx",
+ "columns": [
+ {
+ "expression": "effective_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "budget_extensions_linked_tool_idx": {
+ "name": "budget_extensions_linked_tool_idx",
+ "columns": [
+ {
+ "expression": "linked_tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "budget_extensions_created_by_idx": {
+ "name": "budget_extensions_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "budget_extensions_budget_id_annual_budgets_id_fk": {
+ "name": "budget_extensions_budget_id_annual_budgets_id_fk",
+ "tableFrom": "budget_extensions",
+ "tableTo": "annual_budgets",
+ "columnsFrom": [
+ "budget_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "budget_extensions_linked_tool_id_ai_tools_id_fk": {
+ "name": "budget_extensions_linked_tool_id_ai_tools_id_fk",
+ "tableFrom": "budget_extensions",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "linked_tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "budget_extensions_created_by_users_id_fk": {
+ "name": "budget_extensions_created_by_users_id_fk",
+ "tableFrom": "budget_extensions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "budget_extensions_amount_non_zero": {
+ "name": "budget_extensions_amount_non_zero",
+ "value": "\"budget_extensions\".\"amount_cents\" <> 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.budget_periods": {
+ "name": "budget_periods",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "budget_id": {
+ "name": "budget_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "period_label": {
+ "name": "period_label",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "period_index": {
+ "name": "period_index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "start_date": {
+ "name": "start_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_date": {
+ "name": "end_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "planned_amount_cents": {
+ "name": "planned_amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "budget_periods_budget_id_idx": {
+ "name": "budget_periods_budget_id_idx",
+ "columns": [
+ {
+ "expression": "budget_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "budget_periods_budget_period_idx": {
+ "name": "budget_periods_budget_period_idx",
+ "columns": [
+ {
+ "expression": "budget_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "period_index",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "budget_periods_budget_id_annual_budgets_id_fk": {
+ "name": "budget_periods_budget_id_annual_budgets_id_fk",
+ "tableFrom": "budget_periods",
+ "tableTo": "annual_budgets",
+ "columnsFrom": [
+ "budget_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.change_history": {
+ "name": "change_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "change_type": {
+ "name": "change_type",
+ "type": "change_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "field_name": {
+ "name": "field_name",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "previous_value": {
+ "name": "previous_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "new_value": {
+ "name": "new_value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "changed_by": {
+ "name": "changed_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(20)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ui'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "change_history_entity_idx": {
+ "name": "change_history_entity_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "change_history_changed_by_idx": {
+ "name": "change_history_changed_by_idx",
+ "columns": [
+ {
+ "expression": "changed_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "change_history_created_at_idx": {
+ "name": "change_history_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "change_history_source_idx": {
+ "name": "change_history_source_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "change_history_changed_by_users_id_fk": {
+ "name": "change_history_changed_by_users_id_fk",
+ "tableFrom": "change_history",
+ "tableTo": "users",
+ "columnsFrom": [
+ "changed_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_billing_snapshots": {
+ "name": "copilot_billing_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "billing_month": {
+ "name": "billing_month",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "plan_type": {
+ "name": "plan_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_seats": {
+ "name": "total_seats",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active_seats": {
+ "name": "active_seats",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "seat_cost_cents": {
+ "name": "seat_cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_cost_cents": {
+ "name": "total_cost_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_billing_snapshots_connection_month_idx": {
+ "name": "copilot_billing_snapshots_connection_month_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_month",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_billing_snapshots_connection_id_github_connections_id_fk": {
+ "name": "copilot_billing_snapshots_connection_id_github_connections_id_fk",
+ "tableFrom": "copilot_billing_snapshots",
+ "tableTo": "github_connections",
+ "columnsFrom": [
+ "connection_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_usage_metrics": {
+ "name": "copilot_usage_metrics",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "date": {
+ "name": "date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_active_users": {
+ "name": "total_active_users",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_engaged_users": {
+ "name": "total_engaged_users",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_suggestions": {
+ "name": "total_suggestions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_acceptances": {
+ "name": "total_acceptances",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_lines_suggested": {
+ "name": "total_lines_suggested",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_lines_accepted": {
+ "name": "total_lines_accepted",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_chat_turns": {
+ "name": "total_chat_turns",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_chat_acceptances": {
+ "name": "total_chat_acceptances",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_dotcom_chat_turns": {
+ "name": "total_dotcom_chat_turns",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_pr_summaries": {
+ "name": "total_pr_summaries",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "language_breakdown": {
+ "name": "language_breakdown",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "editor_breakdown": {
+ "name": "editor_breakdown",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "used_cli": {
+ "name": "used_cli",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "used_agent": {
+ "name": "used_agent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "agent_edit_count": {
+ "name": "agent_edit_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cli_breakdown": {
+ "name": "cli_breakdown",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_usage_metrics_connection_date_idx": {
+ "name": "copilot_usage_metrics_connection_date_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_usage_metrics_date_idx": {
+ "name": "copilot_usage_metrics_date_idx",
+ "columns": [
+ {
+ "expression": "date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_usage_metrics_connection_id_github_connections_id_fk": {
+ "name": "copilot_usage_metrics_connection_id_github_connections_id_fk",
+ "tableFrom": "copilot_usage_metrics",
+ "tableTo": "github_connections",
+ "columnsFrom": [
+ "connection_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credit_purchases": {
+ "name": "credit_purchases",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "tool_id": {
+ "name": "tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invoice_id": {
+ "name": "invoice_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "purchased_at": {
+ "name": "purchased_at",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "varchar(200)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credit_purchases_created_by_idx": {
+ "name": "credit_purchases_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credit_purchases_tool_purchased_idx": {
+ "name": "credit_purchases_tool_purchased_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "purchased_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credit_purchases_invoice_id_unique": {
+ "name": "credit_purchases_invoice_id_unique",
+ "columns": [
+ {
+ "expression": "invoice_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "credit_purchases_tool_id_ai_tools_id_fk": {
+ "name": "credit_purchases_tool_id_ai_tools_id_fk",
+ "tableFrom": "credit_purchases",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credit_purchases_invoice_id_invoices_id_fk": {
+ "name": "credit_purchases_invoice_id_invoices_id_fk",
+ "tableFrom": "credit_purchases",
+ "tableTo": "invoices",
+ "columnsFrom": [
+ "invoice_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "credit_purchases_created_by_users_id_fk": {
+ "name": "credit_purchases_created_by_users_id_fk",
+ "tableFrom": "credit_purchases",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "credit_purchases_amount_cents_check": {
+ "name": "credit_purchases_amount_cents_check",
+ "value": "\"credit_purchases\".\"amount_cents\" > 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.forecast_scenarios": {
+ "name": "forecast_scenarios",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "budget_id": {
+ "name": "budget_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(60)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "params": {
+ "name": "params",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "forecast_scenarios_budget_id_idx": {
+ "name": "forecast_scenarios_budget_id_idx",
+ "columns": [
+ {
+ "expression": "budget_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "forecast_scenarios_created_by_idx": {
+ "name": "forecast_scenarios_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "forecast_scenarios_budget_name_idx": {
+ "name": "forecast_scenarios_budget_name_idx",
+ "columns": [
+ {
+ "expression": "budget_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "lower(\"name\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "forecast_scenarios_budget_id_annual_budgets_id_fk": {
+ "name": "forecast_scenarios_budget_id_annual_budgets_id_fk",
+ "tableFrom": "forecast_scenarios",
+ "tableTo": "annual_budgets",
+ "columnsFrom": [
+ "budget_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "forecast_scenarios_created_by_users_id_fk": {
+ "name": "forecast_scenarios_created_by_users_id_fk",
+ "tableFrom": "forecast_scenarios",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github_connections": {
+ "name": "github_connections",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "org_login": {
+ "name": "org_login",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_avatar_url": {
+ "name": "org_avatar_url",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_encrypted": {
+ "name": "token_encrypted",
+ "type": "varchar(700)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_scopes_csv": {
+ "name": "token_scopes_csv",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "github_connection_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "connected_by": {
+ "name": "connected_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connected_at": {
+ "name": "connected_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "disconnected_at": {
+ "name": "disconnected_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "copilot_sync_enabled": {
+ "name": "copilot_sync_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "copilot_sync_schedule": {
+ "name": "copilot_sync_schedule",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'daily'"
+ }
+ },
+ "indexes": {
+ "github_connections_status_idx": {
+ "name": "github_connections_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "github_connections_connected_by_users_id_fk": {
+ "name": "github_connections_connected_by_users_id_fk",
+ "tableFrom": "github_connections",
+ "tableTo": "users",
+ "columnsFrom": [
+ "connected_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github_profiles": {
+ "name": "github_profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "github_id": {
+ "name": "github_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "github_login": {
+ "name": "github_login",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "avatar_url": {
+ "name": "avatar_url",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bio": {
+ "name": "bio",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_repos": {
+ "name": "public_repos",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "profile_url": {
+ "name": "profile_url",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_synced_at": {
+ "name": "last_synced_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "github_profiles_user_id_idx": {
+ "name": "github_profiles_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "github_profiles_github_id_idx": {
+ "name": "github_profiles_github_id_idx",
+ "columns": [
+ {
+ "expression": "github_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "github_profiles_github_login_idx": {
+ "name": "github_profiles_github_login_idx",
+ "columns": [
+ {
+ "expression": "github_login",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "github_profiles_user_id_users_id_fk": {
+ "name": "github_profiles_user_id_users_id_fk",
+ "tableFrom": "github_profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.github_sync_events": {
+ "name": "github_sync_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "connection_id": {
+ "name": "connection_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "github_sync_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_members": {
+ "name": "total_members",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "matched_count": {
+ "name": "matched_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "imported_count": {
+ "name": "imported_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "unmatched_count": {
+ "name": "unmatched_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "conflict_count": {
+ "name": "conflict_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "manually_matched_count": {
+ "name": "manually_matched_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_count": {
+ "name": "created_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_type": {
+ "name": "sync_type",
+ "type": "copilot_sync_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'members'"
+ },
+ "seats_processed": {
+ "name": "seats_processed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metrics_processed": {
+ "name": "metrics_processed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_processed": {
+ "name": "billing_processed",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_linked": {
+ "name": "billing_linked",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_skipped": {
+ "name": "billing_skipped",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "github_sync_events_connection_id_idx": {
+ "name": "github_sync_events_connection_id_idx",
+ "columns": [
+ {
+ "expression": "connection_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "github_sync_events_triggered_by_idx": {
+ "name": "github_sync_events_triggered_by_idx",
+ "columns": [
+ {
+ "expression": "triggered_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "github_sync_events_connection_id_github_connections_id_fk": {
+ "name": "github_sync_events_connection_id_github_connections_id_fk",
+ "tableFrom": "github_sync_events",
+ "tableTo": "github_connections",
+ "columnsFrom": [
+ "connection_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "github_sync_events_triggered_by_users_id_fk": {
+ "name": "github_sync_events_triggered_by_users_id_fk",
+ "tableFrom": "github_sync_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "triggered_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ingestion_filters": {
+ "name": "ingestion_filters",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "field": {
+ "name": "field",
+ "type": "filter_field",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "mode": {
+ "name": "mode",
+ "type": "filter_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "priority": {
+ "name": "priority",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "ingestion_filters_enabled_idx": {
+ "name": "ingestion_filters_enabled_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ingestion_filters_created_by_users_id_fk": {
+ "name": "ingestion_filters_created_by_users_id_fk",
+ "tableFrom": "ingestion_filters",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ingestion_log": {
+ "name": "ingestion_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "ingestion_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'invoice'"
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "ingestion_source_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "ingestion_outcome",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "channel": {
+ "name": "channel",
+ "type": "ingestion_channel",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "varchar(40)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "filename": {
+ "name": "filename",
+ "type": "varchar(500)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "invoice_number": {
+ "name": "invoice_number",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "invoice_date": {
+ "name": "invoice_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "blob_pathname": {
+ "name": "blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "linked_invoice_id": {
+ "name": "linked_invoice_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "ingestion_log_outcome_idx": {
+ "name": "ingestion_log_outcome_idx",
+ "columns": [
+ {
+ "expression": "outcome",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ingestion_log_created_at_idx": {
+ "name": "ingestion_log_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ingestion_log_vendor_idx": {
+ "name": "ingestion_log_vendor_idx",
+ "columns": [
+ {
+ "expression": "vendor",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ingestion_log_channel_idx": {
+ "name": "ingestion_log_channel_idx",
+ "columns": [
+ {
+ "expression": "channel",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "ingestion_log_kind_idx": {
+ "name": "ingestion_log_kind_idx",
+ "columns": [
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ingestion_log_uploaded_by_users_id_fk": {
+ "name": "ingestion_log_uploaded_by_users_id_fk",
+ "tableFrom": "ingestion_log",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "ingestion_log_linked_invoice_id_invoices_id_fk": {
+ "name": "ingestion_log_linked_invoice_id_invoices_id_fk",
+ "tableFrom": "ingestion_log",
+ "tableTo": "invoices",
+ "columnsFrom": [
+ "linked_invoice_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invite_tokens": {
+ "name": "invite_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "invite_token_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "invite_tokens_token_hash_idx": {
+ "name": "invite_tokens_token_hash_idx",
+ "columns": [
+ {
+ "expression": "token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invite_tokens_user_id_idx": {
+ "name": "invite_tokens_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invite_tokens_active_user_idx": {
+ "name": "invite_tokens_active_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"invite_tokens\".\"status\" = 'active'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "invite_tokens_user_id_users_id_fk": {
+ "name": "invite_tokens_user_id_users_id_fk",
+ "tableFrom": "invite_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invoices": {
+ "name": "invoices",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "invoice_number": {
+ "name": "invoice_number",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invoice_date": {
+ "name": "invoice_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "vendor": {
+ "name": "vendor",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "linked_billed_cost_id": {
+ "name": "linked_billed_cost_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "blob_url": {
+ "name": "blob_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "blob_pathname": {
+ "name": "blob_pathname",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "filtered_out": {
+ "name": "filtered_out",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "invoices_invoice_number_idx": {
+ "name": "invoices_invoice_number_idx",
+ "columns": [
+ {
+ "expression": "invoice_number",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invoices_created_at_idx": {
+ "name": "invoices_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invoices_linked_billed_cost_id_idx": {
+ "name": "invoices_linked_billed_cost_id_idx",
+ "columns": [
+ {
+ "expression": "linked_billed_cost_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "invoices_linked_billed_cost_id_billed_costs_id_fk": {
+ "name": "invoices_linked_billed_cost_id_billed_costs_id_fk",
+ "tableFrom": "invoices",
+ "tableTo": "billed_costs",
+ "columnsFrom": [
+ "linked_billed_cost_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "invoices_uploaded_by_users_id_fk": {
+ "name": "invoices_uploaded_by_users_id_fk",
+ "tableFrom": "invoices",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.license_assignments": {
+ "name": "license_assignments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_id": {
+ "name": "tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tier_id": {
+ "name": "tier_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "cost_at_assignment_cents": {
+ "name": "cost_at_assignment_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "assignment_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace": {
+ "name": "workspace",
+ "type": "varchar(200)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "api_key_encrypted": {
+ "name": "api_key_encrypted",
+ "type": "varchar(700)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'manual'"
+ }
+ },
+ "indexes": {
+ "license_assignments_user_id_idx": {
+ "name": "license_assignments_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_assignments_tool_id_idx": {
+ "name": "license_assignments_tool_id_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_assignments_tier_id_idx": {
+ "name": "license_assignments_tier_id_idx",
+ "columns": [
+ {
+ "expression": "tier_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_assignments_status_idx": {
+ "name": "license_assignments_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_assignments_active_lookup_idx": {
+ "name": "license_assignments_active_lookup_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_assignments_one_active_idx": {
+ "name": "license_assignments_one_active_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"license_assignments\".\"status\" = 'active'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "license_assignments_user_id_users_id_fk": {
+ "name": "license_assignments_user_id_users_id_fk",
+ "tableFrom": "license_assignments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "license_assignments_tool_id_ai_tools_id_fk": {
+ "name": "license_assignments_tool_id_ai_tools_id_fk",
+ "tableFrom": "license_assignments",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "license_assignments_tier_id_access_tiers_id_fk": {
+ "name": "license_assignments_tier_id_access_tiers_id_fk",
+ "tableFrom": "license_assignments",
+ "tableTo": "access_tiers",
+ "columnsFrom": [
+ "tier_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.license_requests": {
+ "name": "license_requests",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "form_response_id": {
+ "name": "form_response_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requester_email": {
+ "name": "requester_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requester_name": {
+ "name": "requester_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "requester_user_id": {
+ "name": "requester_user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requester_role": {
+ "name": "requester_role",
+ "type": "user_discipline",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requester_profile": {
+ "name": "requester_profile",
+ "type": "license_request_profile",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "justification": {
+ "name": "justification",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_tool_id": {
+ "name": "requested_tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_tier_id": {
+ "name": "requested_tier_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "form_payload": {
+ "name": "form_payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "teams_team_id": {
+ "name": "teams_team_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "teams_channel_id": {
+ "name": "teams_channel_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "teams_parent_message_id": {
+ "name": "teams_parent_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "teams_chat_id": {
+ "name": "teams_chat_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "license_request_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending_review'"
+ },
+ "decided_by": {
+ "name": "decided_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decided_at": {
+ "name": "decided_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "decision_note": {
+ "name": "decision_note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_by": {
+ "name": "completed_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "approval_message_md": {
+ "name": "approval_message_md",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completion_message_md": {
+ "name": "completion_message_md",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assignment_id": {
+ "name": "assignment_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "license_requests_requester_user_id_idx": {
+ "name": "license_requests_requester_user_id_idx",
+ "columns": [
+ {
+ "expression": "requester_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_requests_requested_tool_id_idx": {
+ "name": "license_requests_requested_tool_id_idx",
+ "columns": [
+ {
+ "expression": "requested_tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_requests_status_idx": {
+ "name": "license_requests_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_requests_decided_by_idx": {
+ "name": "license_requests_decided_by_idx",
+ "columns": [
+ {
+ "expression": "decided_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_requests_assignment_id_idx": {
+ "name": "license_requests_assignment_id_idx",
+ "columns": [
+ {
+ "expression": "assignment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "license_requests_created_at_idx": {
+ "name": "license_requests_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "license_requests_requester_user_id_users_id_fk": {
+ "name": "license_requests_requester_user_id_users_id_fk",
+ "tableFrom": "license_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "requester_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "license_requests_requested_tool_id_ai_tools_id_fk": {
+ "name": "license_requests_requested_tool_id_ai_tools_id_fk",
+ "tableFrom": "license_requests",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "requested_tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ },
+ "license_requests_requested_tier_id_access_tiers_id_fk": {
+ "name": "license_requests_requested_tier_id_access_tiers_id_fk",
+ "tableFrom": "license_requests",
+ "tableTo": "access_tiers",
+ "columnsFrom": [
+ "requested_tier_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "license_requests_decided_by_users_id_fk": {
+ "name": "license_requests_decided_by_users_id_fk",
+ "tableFrom": "license_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "decided_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "license_requests_completed_by_users_id_fk": {
+ "name": "license_requests_completed_by_users_id_fk",
+ "tableFrom": "license_requests",
+ "tableTo": "users",
+ "columnsFrom": [
+ "completed_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "license_requests_assignment_id_license_assignments_id_fk": {
+ "name": "license_requests_assignment_id_license_assignments_id_fk",
+ "tableFrom": "license_requests",
+ "tableTo": "license_assignments",
+ "columnsFrom": [
+ "assignment_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "license_requests_form_response_id_unique": {
+ "name": "license_requests_form_response_id_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "form_response_id"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_oauth_clients": {
+ "name": "mcp_oauth_clients",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_name": {
+ "name": "client_name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uris": {
+ "name": "redirect_uris",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "mcp_oauth_clients_client_id_idx": {
+ "name": "mcp_oauth_clients_client_id_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_oauth_codes": {
+ "name": "mcp_oauth_codes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code_challenge": {
+ "name": "code_challenge",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "consumed_at": {
+ "name": "consumed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_oauth_codes_code_hash_idx": {
+ "name": "mcp_oauth_codes_code_hash_idx",
+ "columns": [
+ {
+ "expression": "code_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "mcp_oauth_codes_user_id_idx": {
+ "name": "mcp_oauth_codes_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_oauth_codes_client_id_mcp_oauth_clients_id_fk": {
+ "name": "mcp_oauth_codes_client_id_mcp_oauth_clients_id_fk",
+ "tableFrom": "mcp_oauth_codes",
+ "tableTo": "mcp_oauth_clients",
+ "columnsFrom": [
+ "client_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_oauth_codes_user_id_users_id_fk": {
+ "name": "mcp_oauth_codes_user_id_users_id_fk",
+ "tableFrom": "mcp_oauth_codes",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_oauth_tokens": {
+ "name": "mcp_oauth_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "family_id": {
+ "name": "family_id",
+ "type": "varchar(36)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token_hash": {
+ "name": "access_token_hash",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_token_hash": {
+ "name": "refresh_token_hash",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope": {
+ "name": "scope",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_expires_at": {
+ "name": "access_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "refresh_expires_at": {
+ "name": "refresh_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_oauth_tokens_access_token_hash_idx": {
+ "name": "mcp_oauth_tokens_access_token_hash_idx",
+ "columns": [
+ {
+ "expression": "access_token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "mcp_oauth_tokens_refresh_token_hash_idx": {
+ "name": "mcp_oauth_tokens_refresh_token_hash_idx",
+ "columns": [
+ {
+ "expression": "refresh_token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "mcp_oauth_tokens_user_id_idx": {
+ "name": "mcp_oauth_tokens_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "mcp_oauth_tokens_family_id_idx": {
+ "name": "mcp_oauth_tokens_family_id_idx",
+ "columns": [
+ {
+ "expression": "family_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_oauth_tokens_client_id_mcp_oauth_clients_id_fk": {
+ "name": "mcp_oauth_tokens_client_id_mcp_oauth_clients_id_fk",
+ "tableFrom": "mcp_oauth_tokens",
+ "tableTo": "mcp_oauth_clients",
+ "columnsFrom": [
+ "client_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_oauth_tokens_user_id_users_id_fk": {
+ "name": "mcp_oauth_tokens_user_id_users_id_fk",
+ "tableFrom": "mcp_oauth_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.message_templates": {
+ "name": "message_templates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "tool_id": {
+ "name": "tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tier_id": {
+ "name": "tier_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "message_template_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body_md": {
+ "name": "body_md",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "message_templates_tool_id_idx": {
+ "name": "message_templates_tool_id_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "message_templates_tier_id_idx": {
+ "name": "message_templates_tier_id_idx",
+ "columns": [
+ {
+ "expression": "tier_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "message_templates_tool_default_kind_idx": {
+ "name": "message_templates_tool_default_kind_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"message_templates\".\"tier_id\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "message_templates_tool_tier_kind_idx": {
+ "name": "message_templates_tool_tier_kind_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tier_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"message_templates\".\"tier_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "message_templates_tool_id_ai_tools_id_fk": {
+ "name": "message_templates_tool_id_ai_tools_id_fk",
+ "tableFrom": "message_templates",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "message_templates_tier_id_access_tiers_id_fk": {
+ "name": "message_templates_tier_id_access_tiers_id_fk",
+ "tableFrom": "message_templates",
+ "tableTo": "access_tiers",
+ "columnsFrom": [
+ "tier_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sync_events": {
+ "name": "sync_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "sync_source_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "operation_type": {
+ "name": "operation_type",
+ "type": "sync_operation_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'regular'"
+ },
+ "backfill_start_date": {
+ "name": "backfill_start_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outcome": {
+ "name": "outcome",
+ "type": "sync_outcome",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'in_progress'"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "triggered_by": {
+ "name": "triggered_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_count": {
+ "name": "created_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "updated_count": {
+ "name": "updated_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "skipped_count": {
+ "name": "skipped_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "error_count": {
+ "name": "error_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sync_events_source_type_idx": {
+ "name": "sync_events_source_type_idx",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sync_events_outcome_idx": {
+ "name": "sync_events_outcome_idx",
+ "columns": [
+ {
+ "expression": "outcome",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sync_events_started_at_idx": {
+ "name": "sync_events_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sync_events_source_started_idx": {
+ "name": "sync_events_source_started_idx",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sync_events_triggered_by_users_id_fk": {
+ "name": "sync_events_triggered_by_users_id_fk",
+ "tableFrom": "sync_events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "triggered_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sync_sources": {
+ "name": "sync_sources",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "sync_source_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "cron_schedule": {
+ "name": "cron_schedule",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sync_sources_source_type_idx": {
+ "name": "sync_sources_source_type_idx",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tool_mappings": {
+ "name": "tool_mappings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "user_discipline",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "profile": {
+ "name": "profile",
+ "type": "license_request_profile",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_id": {
+ "name": "tool_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default_tier_id": {
+ "name": "default_tier_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tool_mappings_tool_id_idx": {
+ "name": "tool_mappings_tool_id_idx",
+ "columns": [
+ {
+ "expression": "tool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tool_mappings_role_profile_idx": {
+ "name": "tool_mappings_role_profile_idx",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "profile",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"tool_mappings\".\"role\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tool_mappings_any_profile_idx": {
+ "name": "tool_mappings_any_profile_idx",
+ "columns": [
+ {
+ "expression": "profile",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"tool_mappings\".\"role\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tool_mappings_tool_id_ai_tools_id_fk": {
+ "name": "tool_mappings_tool_id_ai_tools_id_fk",
+ "tableFrom": "tool_mappings",
+ "tableTo": "ai_tools",
+ "columnsFrom": [
+ "tool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tool_mappings_default_tier_id_access_tiers_id_fk": {
+ "name": "tool_mappings_default_tier_id_access_tiers_id_fk",
+ "tableFrom": "tool_mappings",
+ "tableTo": "access_tiers",
+ "columnsFrom": [
+ "default_tier_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "github_username": {
+ "name": "github_username",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "circle": {
+ "name": "circle",
+ "type": "varchar(100)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "user_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'viewer'"
+ },
+ "status": {
+ "name": "status",
+ "type": "user_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "preferences": {
+ "name": "preferences",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{\"theme\":\"system\"}'::jsonb"
+ },
+ "profile": {
+ "name": "profile",
+ "type": "user_profile",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "discipline": {
+ "name": "discipline",
+ "type": "user_discipline",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'developer'"
+ },
+ "must_change_password": {
+ "name": "must_change_password",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "is_agent": {
+ "name": "is_agent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "users_email_idx": {
+ "name": "users_email_idx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "users_circle_idx": {
+ "name": "users_circle_idx",
+ "columns": [
+ {
+ "expression": "circle",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "users_status_idx": {
+ "name": "users_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.assignment_status": {
+ "name": "assignment_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "inactive"
+ ]
+ },
+ "public.attribution_mode": {
+ "name": "attribution_mode",
+ "schema": "public",
+ "values": [
+ "billed",
+ "apportioned",
+ "unattributed"
+ ]
+ },
+ "public.budget_extension_category": {
+ "name": "budget_extension_category",
+ "schema": "public",
+ "values": [
+ "new_tool",
+ "scope_increase",
+ "seat_increase",
+ "vendor_price_increase",
+ "reallocation",
+ "other"
+ ]
+ },
+ "public.budget_status": {
+ "name": "budget_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "archived"
+ ]
+ },
+ "public.change_type": {
+ "name": "change_type",
+ "schema": "public",
+ "values": [
+ "created",
+ "updated",
+ "deleted",
+ "status_change"
+ ]
+ },
+ "public.copilot_sync_type": {
+ "name": "copilot_sync_type",
+ "schema": "public",
+ "values": [
+ "members",
+ "copilot"
+ ]
+ },
+ "public.filter_field": {
+ "name": "filter_field",
+ "schema": "public",
+ "values": [
+ "vendor",
+ "invoice_number"
+ ]
+ },
+ "public.filter_mode": {
+ "name": "filter_mode",
+ "schema": "public",
+ "values": [
+ "whitelist",
+ "blacklist"
+ ]
+ },
+ "public.github_connection_status": {
+ "name": "github_connection_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "disconnected"
+ ]
+ },
+ "public.github_sync_status": {
+ "name": "github_sync_status",
+ "schema": "public",
+ "values": [
+ "in_progress",
+ "completed",
+ "partial",
+ "failed"
+ ]
+ },
+ "public.ingestion_channel": {
+ "name": "ingestion_channel",
+ "schema": "public",
+ "values": [
+ "manual",
+ "api",
+ "bulk"
+ ]
+ },
+ "public.ingestion_kind": {
+ "name": "ingestion_kind",
+ "schema": "public",
+ "values": [
+ "invoice",
+ "license_request",
+ "user_import",
+ "other"
+ ]
+ },
+ "public.ingestion_outcome": {
+ "name": "ingestion_outcome",
+ "schema": "public",
+ "values": [
+ "success",
+ "failed",
+ "filtered"
+ ]
+ },
+ "public.ingestion_source_type": {
+ "name": "ingestion_source_type",
+ "schema": "public",
+ "values": [
+ "invoice_pdf",
+ "ms_forms_license_request",
+ "csv_user_import"
+ ]
+ },
+ "public.invite_token_status": {
+ "name": "invite_token_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "consumed",
+ "invalidated"
+ ]
+ },
+ "public.license_request_profile": {
+ "name": "license_request_profile",
+ "schema": "public",
+ "values": [
+ "baseline",
+ "maxed",
+ "indie"
+ ]
+ },
+ "public.license_request_status": {
+ "name": "license_request_status",
+ "schema": "public",
+ "values": [
+ "pending_review",
+ "approved",
+ "rejected",
+ "completed",
+ "cancelled"
+ ]
+ },
+ "public.message_template_kind": {
+ "name": "message_template_kind",
+ "schema": "public",
+ "values": [
+ "approval",
+ "completion"
+ ]
+ },
+ "public.period_type": {
+ "name": "period_type",
+ "schema": "public",
+ "values": [
+ "monthly",
+ "quarterly"
+ ]
+ },
+ "public.pricing_model": {
+ "name": "pricing_model",
+ "schema": "public",
+ "values": [
+ "seat",
+ "usage"
+ ]
+ },
+ "public.sync_operation_type": {
+ "name": "sync_operation_type",
+ "schema": "public",
+ "values": [
+ "regular",
+ "backfill"
+ ]
+ },
+ "public.sync_outcome": {
+ "name": "sync_outcome",
+ "schema": "public",
+ "values": [
+ "in_progress",
+ "success",
+ "partial",
+ "failed"
+ ]
+ },
+ "public.sync_source_type": {
+ "name": "sync_source_type",
+ "schema": "public",
+ "values": [
+ "github_copilot_billing",
+ "anthropic_api_usage",
+ "anthropic_team_invoices",
+ "github_members",
+ "invoice_period_matching",
+ "anthropic_api_costs"
+ ]
+ },
+ "public.tool_status": {
+ "name": "tool_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "archived"
+ ]
+ },
+ "public.user_discipline": {
+ "name": "user_discipline",
+ "schema": "public",
+ "values": [
+ "developer",
+ "conception",
+ "business"
+ ]
+ },
+ "public.user_profile": {
+ "name": "user_profile",
+ "schema": "public",
+ "values": [
+ "boost",
+ "maxed",
+ "indie"
+ ]
+ },
+ "public.user_role": {
+ "name": "user_role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "viewer"
+ ]
+ },
+ "public.user_status": {
+ "name": "user_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "inactive"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/src/lib/db/migrations/meta/_journal.json b/src/lib/db/migrations/meta/_journal.json
index 2e3a938..7c07ee7 100644
--- a/src/lib/db/migrations/meta/_journal.json
+++ b/src/lib/db/migrations/meta/_journal.json
@@ -232,6 +232,13 @@
"when": 1789735800779,
"tag": "0032_curious_weapon_omega",
"breakpoints": true
+ },
+ {
+ "idx": 33,
+ "version": "7",
+ "when": 1791190223489,
+ "tag": "0033_credit_purchase_invoice_unique",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts
index 17c5f5e..f4c3ebc 100644
--- a/src/lib/db/schema.ts
+++ b/src/lib/db/schema.ts
@@ -1090,7 +1090,10 @@ export const creditPurchases = pgTable(
table.toolId,
table.purchasedAt,
),
- index("credit_purchases_invoice_id_idx").on(table.invoiceId),
+ // One invoice backs at most one top-up: a second row would double-count
+ // the balance. Unique, so the race between two admins is closed by the
+ // database, not a check-then-insert. NULLs (no invoice yet) stay distinct.
+ uniqueIndex("credit_purchases_invoice_id_unique").on(table.invoiceId),
check(
"credit_purchases_amount_cents_check",
sql`${table.amountCents} > 0`,