diff --git a/apps/api/src/routes/internal/cloudflare-cost.http.ts b/apps/api/src/routes/internal/cloudflare-cost.http.ts new file mode 100644 index 0000000000..f584dfeadb --- /dev/null +++ b/apps/api/src/routes/internal/cloudflare-cost.http.ts @@ -0,0 +1,50 @@ +import { HttpApiBuilder } from "effect/http-api" +import { + CostConnectionForbiddenError, + CostConnectionRemoved, + CostConnectionSaved, + CurrentTenant, + MapleInternalApi, +} from "@maple/domain/http" +import { Effect } from "effect" +import * as Policy from "@maple/domain/policy" +import { recordHttpAudit } from "@maple/backend/services/audit/AuditLogService" +import { CloudflareCostConnectionService } from "@maple/backend/services/cost/CloudflareCostConnectionService" + +// A cost source is a connection like any other: the role that manages integrations manages it. +const requireIntegrationsManager = (roles: CurrentTenant.TenantSchema["roles"]) => + Policy.check( + roles, + "integrations:manage", + (denial) => new CostConnectionForbiddenError({ message: denial.message }), + ) + +export const HttpCloudflareCostLive = HttpApiBuilder.group(MapleInternalApi, "cloudflareCost", (handlers) => + Effect.gen(function* () { + const connections = yield* CloudflareCostConnectionService + + return handlers + .handle("set", ({ payload }) => + Effect.gen(function* () { + const tenant = yield* CurrentTenant.Context + yield* requireIntegrationsManager(tenant.roles) + const saved = yield* connections.set(tenant.orgId, tenant.userId, payload.apiToken) + yield* recordHttpAudit("cost_source_key.updated", { + metadata: { provider: "cloudflare", accountId: saved.accountId }, + }) + return new CostConnectionSaved(saved) + }).pipe(Effect.withSpan("HttpCloudflareCost.set")), + ) + .handle("remove", () => + Effect.gen(function* () { + const tenant = yield* CurrentTenant.Context + yield* requireIntegrationsManager(tenant.roles) + yield* connections.remove(tenant.orgId) + yield* recordHttpAudit("cost_source_key.deleted", { + metadata: { provider: "cloudflare" }, + }) + return new CostConnectionRemoved({ removed: true }) + }).pipe(Effect.withSpan("HttpCloudflareCost.remove")), + ) + }), +) diff --git a/apps/api/src/runtime/http-graph.ts b/apps/api/src/runtime/http-graph.ts index 7f4669a5f0..b27e534baf 100644 --- a/apps/api/src/runtime/http-graph.ts +++ b/apps/api/src/runtime/http-graph.ts @@ -11,6 +11,7 @@ import { HttpAiSessionsInternalLive } from "@/routes/internal/ai-sessions.http" import { HttpMcpInternalLive } from "@/routes/internal/mcp.http" import { HttpUsersInternalLive } from "@/routes/internal/users.http" import { HttpAiTriageLive } from "@/routes/internal/ai-triage.http" +import { HttpCloudflareCostLive } from "@/routes/internal/cloudflare-cost.http" import { HttpOpenRouterCostLive } from "@/routes/internal/openrouter-cost.http" import { HttpOpenRouterKeyLive } from "@/routes/internal/openrouter-key.http" import { HttpServiceMapWorldLive } from "@/routes/internal/service-map-world.http" @@ -165,6 +166,7 @@ const ApiInternalRoutes = HttpApiBuilder.layer(MapleInternalApi).pipe( HttpDemoLive, HttpDependencyScanLive, HttpDigestLive, + HttpCloudflareCostLive, HttpOpenRouterCostLive, HttpOpenRouterKeyLive, HttpServiceMapWorldLive, diff --git a/apps/api/src/runtime/service-graph.ts b/apps/api/src/runtime/service-graph.ts index 79a5af8d58..e3d35322e5 100644 --- a/apps/api/src/runtime/service-graph.ts +++ b/apps/api/src/runtime/service-graph.ts @@ -66,6 +66,7 @@ import { SupabaseConnectionService } from "@maple/backend/services/integrations/ import { SupabaseInsightsService } from "@maple/backend/services/integrations/SupabaseInsightsService" import { SupabaseLogsService } from "@maple/backend/services/integrations/SupabaseLogsService" import { SupabaseOAuthService } from "@maple/backend/services/auth/SupabaseOAuthService" +import { CloudflareCostConnectionService } from "@maple/backend/services/cost/CloudflareCostConnectionService" import { OpenRouterCostConnectionService } from "@maple/backend/services/cost/OpenRouterCostConnectionService" import { OrgOpenRouterKeyService } from "@maple/backend/services/org/OrgOpenRouterKeyService" import { ServiceMapWorldService } from "@maple/backend/services/service-map/ServiceMapWorldService" @@ -188,6 +189,7 @@ export const HttpServicesLive = Layer.mergeAll( AwsIngestService.layer, OrgOpenRouterKeyService.layer, OpenRouterCostConnectionService.layer, + CloudflareCostConnectionService.layer, ServiceMapWorldService.layer, AuditLogService.layer, WarehouseQueryService.layer, diff --git a/apps/web/src/lab/cost-fixture.test.ts b/apps/web/src/lab/cost-fixture.test.ts index 884a79b808..9e2c811994 100644 --- a/apps/web/src/lab/cost-fixture.test.ts +++ b/apps/web/src/lab/cost-fixture.test.ts @@ -8,30 +8,30 @@ const openRouterOnly = { days: 30, filters: { provider: ["openrouter"] } } describe("cost lab fixture", () => { test("a removed OpenRouter source leaves every read, also under a filter naming it", () => { const before = labTotals(window).cost - const after = labTotals({ ...window, withoutOpenRouter: true }).cost + const after = labTotals({ ...window, removed: ["openrouter"] }).cost expect(after).toBeGreaterThan(0) expect(after).toBeLessThan(before) expect( Arr.map( - labBreakdown({ ...window, withoutOpenRouter: true, groupBy: "provider", sortBy: "cost" }) + labBreakdown({ ...window, removed: ["openrouter"], groupBy: "provider", sortBy: "cost" }) .rows, (row) => row.key, ), - ).toEqual(["aws"]) + ).toEqual(["aws", "cloudflare"]) // Filtered to OpenRouter there is nothing left: AWS cost must not show under that filter. expect(labTotals(openRouterOnly).cost).toBeGreaterThan(0) - expect(labTotals({ ...openRouterOnly, withoutOpenRouter: true }).cost).toBe(0) + expect(labTotals({ ...openRouterOnly, removed: ["openrouter"] }).cost).toBe(0) expect( labBreakdown({ ...openRouterOnly, - withoutOpenRouter: true, + removed: ["openrouter"], groupBy: "cloud_service", sortBy: "cost", }).rows, ).toEqual([]) expect( - labTimeseries({ ...openRouterOnly, withoutOpenRouter: true, groupBy: "provider", limit: 8 }) + labTimeseries({ ...openRouterOnly, removed: ["openrouter"], groupBy: "provider", limit: 8 }) .series, ).toEqual([]) }) diff --git a/apps/web/src/lab/cost-fixture.ts b/apps/web/src/lab/cost-fixture.ts index 4d520ac28b..267cb8ba29 100644 --- a/apps/web/src/lab/cost-fixture.ts +++ b/apps/web/src/lab/cost-fixture.ts @@ -8,6 +8,7 @@ import { } from "@maple/domain/http" import { Array as Arr, Number as Num, Order, Record as Rec, Schema, String as Str } from "effect" import type { CostGroupBy } from "../page/cost/derive" +import type { CredentialProvider } from "../page/cost/providers" // Synthetic cost for `/lab/cost`: invented services, regions and amounts on one made-up account. // Nothing here is read by the product. @@ -40,8 +41,6 @@ const line = ( drift, }) -/** A saved OpenRouter key, as OpenRouter masks it. */ -export const LAB_OPENROUTER_KEY = "sk-or-v1-4f2...c81" /** The OpenRouter account the lab's source reads: a personal account's user id. */ const LAB_OPENROUTER_ACCOUNT = "user_2labExampleAccount0000000000" @@ -56,6 +55,26 @@ const modelLine = (model: string, servedBy: string, daily: number, drift: number drift, }) +/** The Cloudflare account the lab's source reads. */ +const LAB_CLOUDFLARE_ACCOUNT = "1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d" + +const productLine = ( + product: string, + category: string, + metric: string, + daily: number, + drift: number, +): Line => ({ + cloud_service: product, + provider: "cloudflare", + service_category: category, + region: "global", + usage_type: metric, + account: LAB_CLOUDFLARE_ACCOUNT, + daily, + drift, +}) + /** `daily` is the charge per day today; `drift` how much it grew or shrank over the last 30 days. */ const LINES: ReadonlyArray = [ line("Example Compute", "Compute", "north-1", "north-1:instance-hours", 61.2, 0.18), @@ -79,6 +98,10 @@ const LINES: ReadonlyArray = [ modelLine("example/large-model", "Example Cloud", 6.2, 0.1), modelLine("example/fast-model", "Example Inference", 12.9, -0.15), modelLine("example/embedding-model", "Example Labs", 1.8, 0.0), + productLine("Workers", "Compute", "Workers Requests", 9.4, 0.25), + productLine("Durable Objects", "Compute", "Durable Objects Compute Duration", 5.1, 0.12), + productLine("R2", "Storage", "R2 Storage", 3.6, 0.05), + productLine("D1", "Databases", "D1 Rows Read", 1.2, -0.08), ] /** The day `offset` days before the lab's today, as `YYYY-MM-DD`. */ @@ -104,15 +127,14 @@ const matches = (entry: Line, filters: CostFilters | undefined): boolean => type Request = Readonly<{ days: number filters: CostFilters | undefined - /** The lab's reader removed OpenRouter: its lines are in no read, whatever the filters say. */ - withoutOpenRouter?: boolean + /** Providers the lab's reader removed: their lines are in no read, whatever the filters say. */ + removed?: ReadonlyArray }> /** Each matching line with its charge on every day of the window (oldest first) and of the period before. */ const windowOf = (request: Request) => Arr.flatMap(LINES, (entry, index) => - matches(entry, request.filters) && - !(request.withoutOpenRouter === true && entry.provider === "openrouter") + matches(entry, request.filters) && !Arr.contains(request.removed ?? [], entry.provider) ? [ { entry, @@ -248,36 +270,52 @@ const openRouterSource = source({ requestsThisMonth: 0, }) +/** The Cloudflare source: history back to the eleventh month before this one, and no billed requests. */ +const cloudflareSource = source({ + id: "0190f2b3-0000-7000-8000-00000000c061", + provider: "cloudflare", + accountId: LAB_CLOUDFLARE_ACCOUNT, + backfillDate: "2025-12-01", + lastSyncedAt: LAB_NOW_MS - 2 * 3_600_000, + requestsThisMonth: 0, +}) + +const LAB_CREDENTIAL_SOURCES = { openrouter: openRouterSource, cloudflare: cloudflareSource } as const + +/** What the lab's reader did to each pasted-credential source since the state loaded; null for nothing. */ +export type LabSourceChanges = Readonly> + /** - * `sources` after the lab's reader added OpenRouter (true: it waits for its first sync) or removed - * it (false); null leaves them as the state has them. + * `sources` after the lab's reader added a provider (true: it waits for its first sync) or removed + * it (false); null leaves it as the state has it. */ export const labSourcesWith = ( sources: CostSourcesResponse, - openRouter: boolean | null, -): CostSourcesResponse => { - if (openRouter === null) return sources - const others = Arr.filter(sources.sources, (entry) => entry.provider !== "openrouter") - if (!openRouter) return new CostSourcesResponse({ sources: others }) - return others.length === sources.sources.length - ? decodeSources({ - sources: [ - ...Schema.encodeSync(CostSourcesResponse)(sources).sources, - { - ...openRouterSource, - status: "pending", - watermarkDate: null, - backfillDate: null, - historyComplete: false, - lastSyncedAt: null, - }, - ], - }) - : sources -} + changes: LabSourceChanges, +): CostSourcesResponse => + Arr.reduce(Rec.toEntries(changes), sources, (current, [provider, added]) => { + if (added === null) return current + const others = Arr.filter(current.sources, (entry) => entry.provider !== provider) + if (!added) return new CostSourcesResponse({ sources: others }) + return others.length === current.sources.length + ? decodeSources({ + sources: [ + ...Schema.encodeSync(CostSourcesResponse)(current).sources, + { + ...LAB_CREDENTIAL_SOURCES[provider], + status: "pending", + watermarkDate: null, + backfillDate: null, + historyComplete: false, + lastSyncedAt: null, + }, + ], + }) + : current + }) export const LAB_SOURCES = { - synced: decodeSources({ sources: [source({}), openRouterSource] }), + synced: decodeSources({ sources: [source({}), openRouterSource, cloudflareSource] }), none: decodeSources({ sources: [] }), pending: decodeSources({ sources: [ diff --git a/apps/web/src/lab/cost-lab.ts b/apps/web/src/lab/cost-lab.ts index 0838405634..b75f70233c 100644 --- a/apps/web/src/lab/cost-lab.ts +++ b/apps/web/src/lab/cost-lab.ts @@ -1,8 +1,7 @@ -import { OpenRouterCostKeyRemoved, OpenRouterCostKeySaved } from "@maple/domain/http" import * as Policy from "@maple/domain/policy" import { toLoadFailure } from "@maple/views/data/load-failure" import * as Field from "@maple/views/ui/field" -import { Array as Arr, Effect, Option, Result, Schema } from "effect" +import { Array as Arr, Effect, Option, Record as Rec, Result, Schema } from "effect" import { Command, type Update } from "foldkit" import type { Html, HtmlBuilder } from "foldkit/html" import { defineMessageUnion } from "foldkit/message" @@ -16,14 +15,17 @@ import { FetchCostTimeseries, FetchCostTotals, ReadCostGrants, + RemoveCloudflareCostToken, RemoveOpenRouterCostKey, ResolveWindow, + SaveCloudflareCostToken, SaveOpenRouterCostKey, TimeseriesInput, WindowInput, } from "../page/cost/command" import { Message as CostMessage } from "../page/cost/message" import { Model as CostModel, type Search } from "../page/cost/model" +import type { CredentialProvider } from "../page/cost/providers" import * as Cost from "../page/cost/update" import { view as costView } from "../page/cost/view" import type { Resources } from "../platform/resources" @@ -31,9 +33,9 @@ import { urlToAppRoute } from "../route" import { perDimension } from "../page/cost/derive" import { LAB_NOW_MS, - LAB_OPENROUTER_KEY, LAB_SOURCES, LAB_TODAY, + type LabSourceChanges, labBreakdown, labSourcesWith, labTimeseries, @@ -55,11 +57,16 @@ export const LabState = Schema.Literals([ "empty-window", "loading", "by-provider", + "cloudflare", "add-source", "add-openrouter", "key-refused", "replace-key", "remove-source", + "add-cloudflare", + "token-refused", + "replace-token", + "remove-cloudflare", "member", "member-onramp", ]) @@ -77,8 +84,11 @@ export const flags: Effect.Effect = Effect.sync(() => ({ export const Model = Schema.Struct({ state: LabState, - /** OpenRouter added (true) or removed (false) since the state loaded; null before either. */ - openRouter: Schema.NullOr(Schema.Boolean), + /** Each pasted-credential source added (true) or removed (false) since the state loaded; null before either. */ + changes: Schema.Struct({ + openrouter: Schema.NullOr(Schema.Boolean), + cloudflare: Schema.NullOr(Schema.Boolean), + }), cost: CostModel, }) export type Model = typeof Model.Type @@ -87,8 +97,10 @@ export const Message = defineMessageUnion({ GotCostMessage: { message: CostMessa export type Message = typeof Message.Type type Return = Update.Return -/** What the lab answers from: the state it loaded in, and what the reader did to OpenRouter since. */ -type World = Readonly<{ state: LabState; openRouter: boolean | null }> +/** What the lab answers from: the state it loaded in, and what the reader did to its sources since. */ +type World = Readonly<{ state: LabState; changes: LabSourceChanges }> + +const NO_CHANGES: LabSourceChanges = { openrouter: null, cloudflare: null } type CostCommand = Command.Command const searchFor = (state: LabState): Search => @@ -96,7 +108,9 @@ const searchFor = (state: LabState): Search => ? { groupBy: "region", cloud_service: ["Example Compute"] } : state === "by-provider" ? { groupBy: "provider" } - : {} + : state === "cloudflare" + ? { provider: ["cloudflare"] } + : {} /** The states that start with nothing connected, so the dialog offers every provider. */ const startsEmpty = (state: LabState): boolean => @@ -104,7 +118,9 @@ const startsEmpty = (state: LabState): boolean => state === "member-onramp" || state === "add-source" || state === "add-openrouter" || - state === "key-refused" + state === "key-refused" || + state === "add-cloudflare" || + state === "token-refused" /** What the reader did before the state's screenshot: the clicks that open its dialog. */ const openingOf = (state: LabState): ReadonlyArray => { @@ -112,20 +128,35 @@ const openingOf = (state: LabState): ReadonlyArray => { case "add-source": return [CostMessage.ClickedAddSource()] case "add-openrouter": - return [CostMessage.ClickedAddSource(), CostMessage.ClickedAddOpenRouter()] + return [CostMessage.ClickedAddSource(), CostMessage.PickedProvider({ provider: "openrouter" })] case "key-refused": return [ CostMessage.ClickedAddSource(), - CostMessage.ClickedAddOpenRouter(), - CostMessage.GotKeyDraftMessage({ + CostMessage.PickedProvider({ provider: "openrouter" }), + CostMessage.GotCredentialDraftMessage({ message: Field.Message.ChangedValue({ value: "sk-or-v1-0123456789abcdef0123456789" }), }), - CostMessage.SubmittedKey(), + CostMessage.SubmittedCredential(), ] case "replace-key": - return [CostMessage.ClickedReplaceKey()] + return [CostMessage.ClickedReplaceCredential({ provider: "openrouter" })] case "remove-source": - return [CostMessage.ClickedRemoveSource()] + return [CostMessage.ClickedRemoveSource({ provider: "openrouter" })] + case "add-cloudflare": + return [CostMessage.ClickedAddSource(), CostMessage.PickedProvider({ provider: "cloudflare" })] + case "token-refused": + return [ + CostMessage.ClickedAddSource(), + CostMessage.PickedProvider({ provider: "cloudflare" }), + CostMessage.GotCredentialDraftMessage({ + message: Field.Message.ChangedValue({ value: "cfut_0123456789abcdefghij0123456789" }), + }), + CostMessage.SubmittedCredential(), + ] + case "replace-token": + return [CostMessage.ClickedReplaceCredential({ provider: "cloudflare" })] + case "remove-cloudflare": + return [CostMessage.ClickedRemoveSource({ provider: "cloudflare" })] // Without Clerk the page starts with every permission: the member states take them away. case "member": case "member-onramp": @@ -141,6 +172,11 @@ const refusedKey = { "This is a regular API key. Reading spend needs a management key, which you create under Settings, Management keys in OpenRouter.", } +const refusedToken = { + ...toLoadFailure("refused"), + message: "This token can read 3 accounts. Create one limited to a single account.", +} + /** The days a preset spans, for the fixture's window; a custom range reads as the default. */ const DAYS = new Map([ ["1w", 7], @@ -171,6 +207,9 @@ const timeseriesArgs = argsOf(TimeseriesInput) const breakdownArgs = argsOf(BreakdownInput) const facetsArgs = argsOf(FacetsInput) +const removedAnswer = (provider: CredentialProvider) => + CostMessage.CompletedRemoveCredential({ provider, result: Result.succeed(undefined) }) + /** A read's answer: the fixture's, or the outage in the `error` state. */ const settled = (state: LabState, value: () => A): Result.Result => state === "error" ? Result.fail(failure) : Result.succeed(value()) @@ -192,13 +231,15 @@ const answerOf = (world: World, search: Search, command: CostCommand): Option.Op const filtersOf = (filters: WindowInput["filters"]) => state === "empty-window" ? { account: ["000000000000"] } : filters // A removed source's cost is not read any more, under any filter. - const withoutOpenRouter = world.openRouter === false + const removed = Arr.flatMap(Rec.toEntries(world.changes), ([provider, added]) => + added === false ? [provider] : [], + ) switch (command.name) { case FetchCostSources.name: return Option.map(keyArgs(command.args), ({ key }) => CostMessage.CompletedLoadSources({ key, - result: Result.succeed(labSourcesWith(sourcesFor(state), world.openRouter)), + result: Result.succeed(labSourcesWith(sourcesFor(state), world.changes)), }), ) case FetchCostTotals.name: @@ -206,7 +247,7 @@ const answerOf = (world: World, search: Search, command: CostCommand): Option.Op CostMessage.CompletedLoadTotals({ key, result: settled(state, () => - labTotals({ days, filters: filtersOf(input.filters), withoutOpenRouter }), + labTotals({ days, filters: filtersOf(input.filters), removed }), ), }), ) @@ -218,7 +259,7 @@ const answerOf = (world: World, search: Search, command: CostCommand): Option.Op labTimeseries({ days, filters: filtersOf(input.filters), - withoutOpenRouter, + removed, groupBy: input.groupBy, limit: input.limit, }), @@ -233,7 +274,7 @@ const answerOf = (world: World, search: Search, command: CostCommand): Option.Op labBreakdown({ days, filters: filtersOf(input.filters), - withoutOpenRouter, + removed, groupBy: input.groupBy, sortBy: input.sortBy, }), @@ -250,7 +291,7 @@ const answerOf = (world: World, search: Search, command: CostCommand): Option.Op labBreakdown({ days, filters: filtersOf(input.filters[groupBy]), - withoutOpenRouter, + removed, groupBy, sortBy: "cost", }).rows, @@ -266,34 +307,37 @@ const answerOf = (world: World, search: Search, command: CostCommand): Option.Op ) case SaveOpenRouterCostKey.name: return Option.some( - CostMessage.CompletedSaveKey({ - result: - state === "key-refused" - ? Result.fail(refusedKey) - : Result.succeed(new OpenRouterCostKeySaved({ keyLabel: LAB_OPENROUTER_KEY })), + CostMessage.CompletedSaveCredential({ + provider: "openrouter", + result: state === "key-refused" ? Result.fail(refusedKey) : Result.succeed(undefined), }), ) - case RemoveOpenRouterCostKey.name: + case SaveCloudflareCostToken.name: return Option.some( - CostMessage.CompletedRemoveKey({ - result: Result.succeed(new OpenRouterCostKeyRemoved({ removed: true })), + CostMessage.CompletedSaveCredential({ + provider: "cloudflare", + result: state === "token-refused" ? Result.fail(refusedToken) : Result.succeed(undefined), }), ) + case RemoveOpenRouterCostKey.name: + return Option.some(removedAnswer("openrouter")) + case RemoveCloudflareCostToken.name: + return Option.some(removedAnswer("cloudflare")) default: return Option.none() } } type Step = Readonly<{ model: CostModel; commands: ReadonlyArray }> -type Settled = Step & Pick +type Settled = Step & Pick -/** A saved key adds OpenRouter to the sources the lab answers with next, a removal takes it out. */ -const openRouterAfter = (openRouter: boolean | null, answers: ReadonlyArray) => - Arr.reduce(answers, openRouter, (current, answer) => - answer._tag === "CompletedSaveKey" && Result.isSuccess(answer.result) - ? true - : answer._tag === "CompletedRemoveKey" && Result.isSuccess(answer.result) - ? false +/** A saved credential adds its provider to the sources the lab answers with next, a removal takes it out. */ +const changesAfter = (changes: LabSourceChanges, answers: ReadonlyArray): LabSourceChanges => + Arr.reduce(answers, changes, (current, answer) => + answer._tag === "CompletedSaveCredential" && Result.isSuccess(answer.result) + ? { ...current, [answer.provider]: true } + : answer._tag === "CompletedRemoveCredential" && Result.isSuccess(answer.result) + ? { ...current, [answer.provider]: false } : current, ) @@ -306,7 +350,7 @@ const settle = (world: World, step: Step): Settled => { Option.toArray(answerOf(world, step.model.search, command)), ) const kept = keepLocal(step.commands) - if (answers.length === 0) return { model: step.model, commands: kept, openRouter: world.openRouter } + if (answers.length === 0) return { model: step.model, commands: kept, changes: world.changes } const next = Arr.reduce( answers, { model: step.model, commands: Arr.empty() }, @@ -315,7 +359,7 @@ const settle = (world: World, step: Step): Settled => { return { model: result.model, commands: [...acc.commands, ...(result.commands ?? [])] } }, ) - const rest = settle({ ...world, openRouter: openRouterAfter(world.openRouter, answers) }, next) + const rest = settle({ ...world, changes: changesAfter(world.changes, answers) }, next) return { ...rest, commands: [...kept, ...rest.commands] } } @@ -334,18 +378,18 @@ const searchOfUrl = (url: string): Search => export const init = (flags: Flags): Return => { const entered = Cost.init({ search: searchFor(flags.state) }, LAB_CONTEXT) - const world: World = { state: flags.state, openRouter: null } + const world: World = { state: flags.state, changes: NO_CHANGES } const loaded = settle(world, { model: entered.model, commands: entered.commands ?? [] }) const ready = Arr.reduce(openingOf(flags.state), loaded, (step, message) => { const next = Cost.update(step.model, message, LAB_CONTEXT) const settled = settle( - { ...world, openRouter: step.openRouter }, + { ...world, changes: step.changes }, { model: next.model, commands: next.commands ?? [] }, ) return { ...settled, commands: [...step.commands, ...settled.commands] } }) return { - model: { state: flags.state, openRouter: ready.openRouter, cost: ready.model }, + model: { state: flags.state, changes: ready.changes, cost: ready.model }, commands: lift(ready.commands), } } @@ -360,11 +404,11 @@ export const update = (model: Model, message: Message): Return => ? Cost.routeChanged(step.model, { search: searchOfUrl(out.url) }, LAB_CONTEXT) : { model: step.model } const ready = settle( - { state: model.state, openRouter: model.openRouter }, + { state: model.state, changes: model.changes }, { model: moved.model, commands: [...(step.commands ?? []), ...(moved.commands ?? [])] }, ) return { - model: { ...model, openRouter: ready.openRouter, cost: ready.model }, + model: { ...model, changes: ready.changes, cost: ready.model }, commands: lift(ready.commands), } }, diff --git a/apps/web/src/page/cost/command.ts b/apps/web/src/page/cost/command.ts index d2bc34c558..c90e07e592 100644 --- a/apps/web/src/page/cost/command.ts +++ b/apps/web/src/page/cost/command.ts @@ -1,4 +1,9 @@ -import { CostBreakdownSortBy, CostFilters, OpenRouterKeyUpdateRequest } from "@maple/domain/http" +import { + CloudflareCostTokenRequest, + CostBreakdownSortBy, + CostFilters, + OpenRouterKeyUpdateRequest, +} from "@maple/domain/http" import { reloadSpin } from "@maple/views/data/timing" import { settleTo } from "@maple/views/data/load-failure" import { @@ -128,19 +133,46 @@ export const ReadCostGrants = Command.define("ReadCostGrants", { execute: Effect.map(readGrants, (grants) => Message.CompletedReadGrants({ grants })), }) +// A credential's save and removal answer with their provider: the page reads only whether they worked. + /** Saves the org's OpenRouter management key, which makes (or keeps) its cost source. */ export const SaveOpenRouterCostKey = Command.define("SaveOpenRouterCostKey", { args: { apiKey: Schema.String }, - messages: [Message.CompletedSaveKey], + messages: [Message.CompletedSaveCredential], execute: ({ apiKey }) => Effect.flatMap(MapleInternalApiClient, (client) => client.openRouterCost.set({ payload: new OpenRouterKeyUpdateRequest({ apiKey }) }), - ).pipe(settleTo((result) => Message.CompletedSaveKey({ result }))), + ).pipe( + Effect.asVoid, + settleTo((result) => Message.CompletedSaveCredential({ provider: "openrouter", result })), + ), }) export const RemoveOpenRouterCostKey = Command.define("RemoveOpenRouterCostKey", { - messages: [Message.CompletedRemoveKey], + messages: [Message.CompletedRemoveCredential], execute: Effect.flatMap(MapleInternalApiClient, (client) => client.openRouterCost.remove()).pipe( - settleTo((result) => Message.CompletedRemoveKey({ result })), + Effect.asVoid, + settleTo((result) => Message.CompletedRemoveCredential({ provider: "openrouter", result })), + ), +}) + +/** Saves the org's Cloudflare API token, which makes (or keeps) its cost source. */ +export const SaveCloudflareCostToken = Command.define("SaveCloudflareCostToken", { + args: { apiToken: Schema.String }, + messages: [Message.CompletedSaveCredential], + execute: ({ apiToken }) => + Effect.flatMap(MapleInternalApiClient, (client) => + client.cloudflareCost.set({ payload: new CloudflareCostTokenRequest({ apiToken }) }), + ).pipe( + Effect.asVoid, + settleTo((result) => Message.CompletedSaveCredential({ provider: "cloudflare", result })), + ), +}) + +export const RemoveCloudflareCostToken = Command.define("RemoveCloudflareCostToken", { + messages: [Message.CompletedRemoveCredential], + execute: Effect.flatMap(MapleInternalApiClient, (client) => client.cloudflareCost.remove()).pipe( + Effect.asVoid, + settleTo((result) => Message.CompletedRemoveCredential({ provider: "cloudflare", result })), ), }) diff --git a/apps/web/src/page/cost/cost.scene.test.ts b/apps/web/src/page/cost/cost.scene.test.ts index 84eb78769d..6897b42f50 100644 --- a/apps/web/src/page/cost/cost.scene.test.ts +++ b/apps/web/src/page/cost/cost.scene.test.ts @@ -5,8 +5,6 @@ import { CostSourcesResponse, CostTimeseriesResponse, CostTotalsResponse, - OpenRouterCostKeyRemoved, - OpenRouterCostKeySaved, } from "@maple/domain/http" import * as Policy from "@maple/domain/policy" import { Option, Record as Rec, Result, Schema, Array as Arr, String as Str } from "effect" @@ -183,7 +181,7 @@ describe("cost page", () => { expectView(text("Add a cost source")).toExist(), expectView(role("link", { name: /^AWS/ })).toHaveAttr("href", "/sources/new?integration=aws"), expectView(text(/AWS bills USD.0\.01 for each request Maple makes/)).toExist(), - expectView(text(/OpenRouter requests are free/)).toExist(), + expectView(text(/OpenRouter and Cloudflare requests are free/)).toExist(), // An account already connected turns its Cost module on from the AWS integration. expectView( role("link", { name: "Turn on Cost for an AWS account that is connected" }), @@ -193,7 +191,7 @@ describe("cost page", () => { ) scene( { update: pageUpdate, view }, - given(pageUpdate(opened, Message.ClickedAddOpenRouter()).model), + given(pageUpdate(opened, Message.PickedProvider({ provider: "openrouter" })).model), resolveTheme, dialogMounted, expectView(text("Add OpenRouter")).toExist(), @@ -211,31 +209,40 @@ describe("cost page", () => { test("a saved key is sent trimmed; a refusal stays under the field until the key changes", () => { const form = pageUpdate( pageUpdate(entered({}, []).model, Message.ClickedAddSource()).model, - Message.ClickedAddOpenRouter(), + Message.PickedProvider({ provider: "openrouter" }), ).model const typed = (model: Model, value: string) => - pageUpdate(model, Message.GotKeyDraftMessage({ message: Field.Message.ChangedValue({ value }) })) - .model + pageUpdate( + model, + Message.GotCredentialDraftMessage({ message: Field.Message.ChangedValue({ value }) }), + ).model // Nothing typed sends nothing. - expect(names(pageUpdate(form, Message.SubmittedKey()).commands)).toEqual([]) + expect(names(pageUpdate(form, Message.SubmittedCredential()).commands)).toEqual([]) - const saving = pageUpdate(typed(form, " sk-or-v1-regular-key-0123456789 "), Message.SubmittedKey()) + const saving = pageUpdate( + typed(form, " sk-or-v1-regular-key-0123456789 "), + Message.SubmittedCredential(), + ) expect(saving.commands).toMatchObject([ { name: "SaveOpenRouterCostKey", args: { apiKey: "sk-or-v1-regular-key-0123456789" } }, ]) // A key the request would refuse to carry is never sent, and the dialog says why. - const oversized = pageUpdate(typed(form, `sk-or-v1-${Str.repeat(300)("a")}`), Message.SubmittedKey()) + const oversized = pageUpdate( + typed(form, `sk-or-v1-${Str.repeat(300)("a")}`), + Message.SubmittedCredential(), + ) expect(names(oversized.commands)).toEqual([]) - expect([oversized.model.sourceWrite, oversized.model.keyError]).toEqual([ + expect([oversized.model.sourceWrite, oversized.model.credentialError]).toEqual([ "Idle", "That is too long to be an OpenRouter key.", ]) // A second submit while the first is in flight sends nothing more. - expect(names(pageUpdate(saving.model, Message.SubmittedKey()).commands)).toEqual([]) + expect(names(pageUpdate(saving.model, Message.SubmittedCredential()).commands)).toEqual([]) const refused = pageUpdate( saving.model, - Message.CompletedSaveKey({ + Message.CompletedSaveCredential({ + provider: "openrouter", result: Result.fail({ ...toLoadFailure("refused"), message: "This is a regular API key. Reading spend needs a management key.", @@ -252,32 +259,30 @@ describe("cost page", () => { "This is a regular API key. Reading spend needs a management key.", ), ) - expect(typed(refused, "sk-or-v1-management-key-0123456789").keyError).toBeNull() + expect(typed(refused, "sk-or-v1-management-key-0123456789").credentialError).toBeNull() }) test("a saved key closes the dialog, says when the sync runs and reads the sources again", () => { const form = pageUpdate( pageUpdate(entered({}, []).model, Message.ClickedAddSource()).model, - Message.ClickedAddOpenRouter(), + Message.PickedProvider({ provider: "openrouter" }), ).model const saving = pageUpdate( pageUpdate( form, - Message.GotKeyDraftMessage({ + Message.GotCredentialDraftMessage({ message: Field.Message.ChangedValue({ value: "sk-or-v1-management-key-0123456789" }), }), ).model, - Message.SubmittedKey(), + Message.SubmittedCredential(), ).model const saved = pageUpdate( saving, - Message.CompletedSaveKey({ - result: Result.succeed(new OpenRouterCostKeySaved({ keyLabel: "sk-or-v1-man...789" })), - }), + Message.CompletedSaveCredential({ provider: "openrouter", result: Result.succeed(undefined) }), ) expect(saved.model.addDialog.isOpen).toBe(false) // The key does not outlive the dialog. - expect(saved.model.keyDraft.value).toBe("") + expect(saved.model.credentialDraft.value).toBe("") expect(names(saved.commands)).toContain("FetchCostSources") expect(saved.outMessage).toEqual( PageOutMessage.RequestedToast({ @@ -306,16 +311,19 @@ describe("cost page", () => { // Request billing is AWS's: one line for the AWS source, none for OpenRouter. expectView(text("86 provider requests this month (USD 0.86)")).toExist(), expectView(text(/0 provider requests/)).not.toExist(), - expectView(role("link", { name: "Manage connection" })).toHaveAttr( + expectView(role("link", { name: "Manage AWS connection" })).toHaveAttr( "href", "/integrations?integration=aws", ), - expectView(role("button", { name: "Replace key" })).toExist(), - expectView(role("button", { name: "Remove" })).toExist(), + expectView(role("button", { name: "Replace OpenRouter key" })).toExist(), + expectView(role("button", { name: "Remove OpenRouter source" })).toExist(), ) // Replace opens the key form with no providers to go back to. - const replacing = pageUpdate(model, Message.ClickedReplaceKey()).model + const replacing = pageUpdate( + model, + Message.ClickedReplaceCredential({ provider: "openrouter" }), + ).model scene( { update: pageUpdate, view }, given(replacing), @@ -334,15 +342,13 @@ describe("cost page", () => { expectView(role("button", { name: /^OpenRouter/ })).not.toExist(), ) - const confirming = pageUpdate(model, Message.ClickedRemoveSource()).model + const confirming = pageUpdate(model, Message.ClickedRemoveSource({ provider: "openrouter" })).model expect(confirming.removeDialog.isOpen).toBe(true) const removing = pageUpdate(confirming, Message.ClickedConfirmRemove()) expect(names(removing.commands)).toEqual(["RemoveOpenRouterCostKey"]) const removed = pageUpdate( removing.model, - Message.CompletedRemoveKey({ - result: Result.succeed(new OpenRouterCostKeyRemoved({ removed: true })), - }), + Message.CompletedRemoveCredential({ provider: "openrouter", result: Result.succeed(undefined) }), ) expect(removed.model.removeDialog.isOpen).toBe(false) // The reads name the org's sources: without reading them again the removed source's cost stays. @@ -361,7 +367,7 @@ describe("cost page", () => { resolveTheme, dialogMounted, expectView(text("user_2openrouterAccount")).not.toExist(), - expectView(role("button", { name: "Replace key" })).not.toExist(), + expectView(role("button", { name: "Replace OpenRouter key" })).not.toExist(), expectView(role("button", { name: "Add source" })).toBeDisabled(), ) // That read failing brings no action back on the removed source: the list keeps without it. @@ -378,7 +384,7 @@ describe("cost page", () => { resolveTheme, dialogMounted, expectView(text("123456789012")).toExist(), - expectView(role("button", { name: "Replace key" })).not.toExist(), + expectView(role("button", { name: "Replace OpenRouter key" })).not.toExist(), expectView(role("button", { name: "Add source" })).toBeEnabled(), ) @@ -387,9 +393,7 @@ describe("cost page", () => { const staleKey = reloading.sources.key ?? "" const afterWrite = pageUpdate( reloading, - Message.CompletedRemoveKey({ - result: Result.succeed(new OpenRouterCostKeyRemoved({ removed: true })), - }), + Message.CompletedRemoveCredential({ provider: "openrouter", result: Result.succeed(undefined) }), ) expect(names(afterWrite.commands)).toContain("FetchCostSources") const stale = pageUpdate( @@ -407,15 +411,13 @@ describe("cost page", () => { pageUpdate( pageUpdate( replacing, - Message.GotKeyDraftMessage({ + Message.GotCredentialDraftMessage({ message: Field.Message.ChangedValue({ value: "sk-or-v1-management-key-0123456789" }), }), ).model, - Message.SubmittedKey(), + Message.SubmittedCredential(), ).model, - Message.CompletedSaveKey({ - result: Result.succeed(new OpenRouterCostKeySaved({ keyLabel: "sk-or-v1-man...789" })), - }), + Message.CompletedSaveCredential({ provider: "openrouter", result: Result.succeed(undefined) }), ) expect(replaced.outMessage).toEqual( PageOutMessage.RequestedToast({ @@ -426,6 +428,194 @@ describe("cost page", () => { ) }) + test("Cloudflare is added by pasting an API token, and a refused one says why under the field", () => { + const opened = pageUpdate(entered({}, []).model, Message.ClickedAddSource()).model + scene( + { update: pageUpdate, view }, + given(opened), + resolveTheme, + dialogMounted, + expectView(role("button", { name: /^Cloudflare/ })).toExist(), + expectView(text("Daily charges by product, plus plan fees")).toExist(), + ) + const form = pageUpdate(opened, Message.PickedProvider({ provider: "cloudflare" })).model + scene( + { update: pageUpdate, view }, + given(form), + resolveTheme, + dialogMounted, + expectView(text("Add Cloudflare")).toExist(), + expectView(text("API token")).toExist(), + expectView(role("link", { name: "Create a custom token" })).toHaveAttr( + "href", + "https://dash.cloudflare.com/profile/api-tokens", + ), + expectView(text(/with the Billing: Read permission on one account/)).toExist(), + // The other provider's form is not this one. + expectView(text("Management key")).not.toExist(), + expectView(role("button", { name: "Add source" })).toBeDisabled(), + expectView(role("button", { name: "Back" })).toExist(), + ) + const typed = (model: Model, value: string) => + pageUpdate( + model, + Message.GotCredentialDraftMessage({ message: Field.Message.ChangedValue({ value }) }), + ).model + + const saving = pageUpdate( + typed(form, " cfut_0123456789abcdefghij0123456789 "), + Message.SubmittedCredential(), + ) + expect(saving.commands).toMatchObject([ + { name: "SaveCloudflareCostToken", args: { apiToken: "cfut_0123456789abcdefghij0123456789" } }, + ]) + const oversized = pageUpdate( + typed(form, `cfut_${Str.repeat(300)("a")}`), + Message.SubmittedCredential(), + ) + expect(names(oversized.commands)).toEqual([]) + expect(oversized.model.credentialError).toBe("That is too long to be a Cloudflare API token.") + + const refused = pageUpdate( + saving.model, + Message.CompletedSaveCredential({ + provider: "cloudflare", + result: Result.fail({ + ...toLoadFailure("refused"), + message: "This token can read 3 accounts. Create one limited to a single account.", + }), + }), + ).model + expect(refused.addDialog.isOpen).toBe(true) + scene( + { update: pageUpdate, view }, + given(refused), + resolveTheme, + dialogMounted, + expectView(role("alert")).toHaveText( + "This token can read 3 accounts. Create one limited to a single account.", + ), + ) + + const saved = pageUpdate( + saving.model, + Message.CompletedSaveCredential({ provider: "cloudflare", result: Result.succeed(undefined) }), + ) + expect(saved.model.addDialog.isOpen).toBe(false) + expect(saved.model.credentialDraft.value).toBe("") + expect(names(saved.commands)).toContain("FetchCostSources") + expect(saved.outMessage).toEqual( + PageOutMessage.RequestedToast({ + type: "success", + title: "Cloudflare added", + description: "The first sync runs within a few minutes.", + }), + ) + }) + + test("a Cloudflare source has its token replaced or is removed, and the OpenRouter source beside it stays", () => { + const openRouter = source({ + id: "0190f2b3-0000-7000-8000-00000000c060", + provider: "openrouter", + accountId: "user_2openrouterAccount", + requestsThisMonth: 0, + }) + const cloudflare = source({ + id: "0190f2b3-0000-7000-8000-00000000c061", + provider: "cloudflare", + accountId: "1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d", + backfillDate: "2025-12-01", + requestsThisMonth: 0, + }) + const model = answered(entered({}, [openRouter, cloudflare]).model) + scene( + { update: pageUpdate, view }, + given(model), + resolveTheme, + expectView(text("Cloudflare")).toExist(), + expectView(text("1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d")).toExist(), + // Neither provider bills for requests. + expectView(text(/provider requests/)).not.toExist(), + expectView(role("button", { name: "Replace Cloudflare token" })).toExist(), + expectView(role("button", { name: "Replace OpenRouter key" })).toExist(), + // Two rows offer Remove: each one's name says whose source it removes. + expectView(role("button", { name: "Remove Cloudflare source" })).toExist(), + expectView(role("button", { name: "Remove OpenRouter source" })).toExist(), + ) + + const replacing = pageUpdate( + model, + Message.ClickedReplaceCredential({ provider: "cloudflare" }), + ).model + scene( + { update: pageUpdate, view }, + given(replacing), + resolveTheme, + dialogMounted, + expectView(text("Replace the Cloudflare token")).toExist(), + expectView(text(/Use a token for the same Cloudflare account/)).toExist(), + expectView(role("button", { name: "Back" })).not.toExist(), + ) + const replaced = pageUpdate( + pageUpdate( + pageUpdate( + replacing, + Message.GotCredentialDraftMessage({ + message: Field.Message.ChangedValue({ value: "cfut_0123456789abcdefghij0123456789" }), + }), + ).model, + Message.SubmittedCredential(), + ).model, + Message.CompletedSaveCredential({ provider: "cloudflare", result: Result.succeed(undefined) }), + ) + expect(replaced.outMessage).toEqual( + PageOutMessage.RequestedToast({ + type: "success", + title: "Cloudflare token replaced", + description: "The next sync runs within a few minutes.", + }), + ) + + // Both exist: the list offers neither again. + scene( + { update: pageUpdate, view }, + given(pageUpdate(model, Message.ClickedAddSource()).model), + resolveTheme, + dialogMounted, + expectView(role("button", { name: /^Cloudflare/ })).not.toExist(), + expectView(role("button", { name: /^OpenRouter/ })).not.toExist(), + expectView(text("Replace its token in the Sources panel")).toExist(), + ) + + const confirming = pageUpdate(model, Message.ClickedRemoveSource({ provider: "cloudflare" })).model + scene( + { update: pageUpdate, view }, + given(confirming), + resolveTheme, + dialogMounted, + expectView(text("Remove the Cloudflare source?")).toExist(), + ) + const removing = pageUpdate(confirming, Message.ClickedConfirmRemove()) + expect(names(removing.commands)).toEqual(["RemoveCloudflareCostToken"]) + const removed = pageUpdate( + removing.model, + Message.CompletedRemoveCredential({ provider: "cloudflare", result: Result.succeed(undefined) }), + ) + expect(removed.outMessage).toEqual( + PageOutMessage.RequestedToast({ type: "success", title: "Cloudflare removed", description: "" }), + ) + scene( + { update: pageUpdate, view }, + given(removed.model), + resolveTheme, + dialogMounted, + expectView(text("1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d")).not.toExist(), + expectView(role("button", { name: "Replace Cloudflare token" })).not.toExist(), + // The other provider's source is still listed. + expectView(text("user_2openrouterAccount")).toExist(), + ) + }) + test("a view filtered to a provider compares with the period before when that provider's history reaches it", () => { const aws = Schema.decodeSync(CostSourcesResponse)({ sources: [source()] }).sources const both = Schema.decodeSync(CostSourcesResponse)({ @@ -460,9 +650,9 @@ describe("cost page", () => { // The sources still show how their sync is doing. expectView(text("user_2openrouterAccount")).toExist(), expectView(role("button", { name: "Add source" })).not.toExist(), - expectView(role("button", { name: "Replace key" })).not.toExist(), - expectView(role("button", { name: "Remove" })).not.toExist(), - expectView(role("link", { name: "Manage connection" })).not.toExist(), + expectView(role("button", { name: /^Replace/ })).not.toExist(), + expectView(role("button", { name: /^Remove/ })).not.toExist(), + expectView(role("link", { name: /^Manage/ })).not.toExist(), ) scene( { update: pageUpdate, view }, @@ -492,13 +682,14 @@ describe("cost page", () => { ), resolveTheme, expectView(role("button", { name: "Add source" })).toExist(), - expectView(role("button", { name: "Replace key" })).toExist(), + expectView(role("button", { name: "Replace OpenRouter key" })).toExist(), ) }) test("grouped by provider, a row reads the provider's name and filters by its id", () => { expect(keyLabel("provider", "openrouter")).toBe("OpenRouter") expect(keyLabel("provider", "aws")).toBe("AWS") + expect(keyLabel("provider", "cloudflare")).toBe("Cloudflare") // A provider this build does not know still shows, as the rows name it. expect(keyLabel("provider", "somecloud")).toBe("somecloud") expect(requestFilters({ provider: ["openrouter"] })).toEqual({ provider: ["openrouter"] }) @@ -572,6 +763,28 @@ describe("cost page", () => { expectView(text(/AWS brings in this month and the last/)).toExist(), expectView(text(/OpenRouter brings in its last four weeks/)).toExist(), ) + // Cloudflare keeps a year: alone it reads like AWS, and beside AWS both are named. + const cloudflare = source({ + ...unsynced, + id: "0190f2b3-0000-7000-8000-00000000c061", + provider: "cloudflare", + status: "pending", + }) + scene( + { update: pageUpdate, view }, + given(entered({}, [cloudflare]).model), + resolveTheme, + expectView( + text(/It brings in this month and the last, and older months fill in after that/), + ).toExist(), + ) + scene( + { update: pageUpdate, view }, + given(entered({}, [failingAws, cloudflare, openRouter]).model), + resolveTheme, + expectView(text(/AWS and Cloudflare bring in this month and the last/)).toExist(), + expectView(text(/OpenRouter brings in its last four weeks/)).toExist(), + ) }) test("a synced source asks for the totals, the daily series and the breakdown of the window", () => { @@ -675,6 +888,59 @@ describe("cost page", () => { ) }) + test("Cloudflare rows read by name and drill down like the others: an account and a provider to their services", () => { + const account = "1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d" + const cloudflare = source({ + id: "0190f2b3-0000-7000-8000-00000000c061", + provider: "cloudflare", + accountId: account, + requestsThisMonth: 0, + }) + const rowsOf = (search: Search, keys: ReadonlyArray): Model => { + const model = answered(entered(search, [source(), cloudflare]).model) + return pageUpdate( + model, + Message.CompletedLoadBreakdown({ + key: model.breakdown.key ?? "", + result: Result.succeed( + Schema.decodeSync(CostBreakdownResponse)({ + currency: "USD", + rows: Arr.map(keys, (key) => ({ key, cost: 100, previousCost: 80 })), + }), + ), + }), + ).model + } + const accountHref = `/cost?groupBy=cloud_service&account=%5B%22${account}%22%5D` + scene( + { update: pageUpdate, view }, + given(rowsOf({ groupBy: "account" }, [account, "123456789012"])), + resolveTheme, + // The account reads as what it is, and filters by its id. + expectView(role("link", { name: /^Cloudflare/ })).toHaveAttr("href", accountHref), + expectView(role("link", { name: /^AWS 123456789012/ })).toExist(), + ) + scene( + { update: pageUpdate, view }, + given(rowsOf({ groupBy: "provider" }, ["cloudflare", "aws"])), + resolveTheme, + expectView(role("link", { name: "Cloudflare" })).toHaveAttr( + "href", + "/cost?groupBy=cloud_service&provider=%5B%22cloudflare%22%5D", + ), + ) + // A product under that filter moves on to regions, where Cloudflare has the one. + scene( + { update: pageUpdate, view }, + given(rowsOf({ provider: ["cloudflare"] }, ["Workers", "R2"])), + resolveTheme, + expectView(role("link", { name: "Workers" })).toHaveAttr( + "href", + "/cost?groupBy=region&cloud_service=%5B%22Workers%22%5D&provider=%5B%22cloudflare%22%5D", + ), + ) + }) + test("picking another grouping moves the URL, then re-reads the chart and the table, not the totals", () => { const model = populated({}) scene( diff --git a/apps/web/src/page/cost/credential-sources.ts b/apps/web/src/page/cost/credential-sources.ts new file mode 100644 index 0000000000..d1bd448072 --- /dev/null +++ b/apps/web/src/page/cost/credential-sources.ts @@ -0,0 +1,120 @@ +import { CLOUDFLARE_COST_TOKEN_MAX_LENGTH, OPENROUTER_KEY_MAX_LENGTH } from "@maple/domain/http" +import type { Command } from "foldkit" +import type { Resources } from "../../platform/resources" +import { + RemoveCloudflareCostToken, + RemoveOpenRouterCostKey, + SaveCloudflareCostToken, + SaveOpenRouterCostKey, +} from "./command" +import type { Message } from "./message" +import type { CredentialProvider } from "./providers" + +type Cmd = Command.Command + +/** + * A cost source added by pasting a credential: everything its dialog steps, its Sources row and + * its toasts say, and the commands that save and remove it. The form itself is shared. + */ +export type CredentialSource = Readonly<{ + /** The line under the provider's name in the list, and once its source exists. */ + pick: Readonly<{ meta: string; addedMeta: string }> + field: Readonly<{ + label: string + placeholder: string + maxLength: number + /** Shown when the text is longer than `maxLength`, which the request would refuse to carry. */ + tooLong: string + /** The help under the field: `before`, a link to where the credential is made, then `after`. */ + help: Readonly<{ before: string; link: string; href: string; after: string }> + }> + add: Readonly<{ title: string; description: string; toast: string }> + replace: Readonly<{ + title: string + description: string + /** The Sources row's action and the form's submit. */ + label: string + toast: string + }> + remove: Readonly<{ title: string; description: string; toast: string }> + save: (secret: string) => Cmd + delete: () => Cmd +}> + +export const CREDENTIAL_SOURCES = { + openrouter: { + pick: { + meta: "Daily spend by model and serving provider", + addedMeta: "Replace its key in the Sources panel", + }, + field: { + label: "Management key", + placeholder: "sk-or-v1-...", + maxLength: OPENROUTER_KEY_MAX_LENGTH, + tooLong: "That is too long to be an OpenRouter key.", + help: { + before: "Create one under ", + link: "Management keys", + href: "https://openrouter.ai/settings/management-keys", + after: " in OpenRouter. A regular API key cannot read spend. Maple checks the key with OpenRouter, then stores it encrypted.", + }, + }, + add: { + title: "Add OpenRouter", + description: "Maple reads the last four weeks at once, then every day from here on.", + toast: "OpenRouter added", + }, + replace: { + title: "Replace the OpenRouter key", + description: "The source and its history stay. Use a key from the same OpenRouter account.", + label: "Replace key", + toast: "OpenRouter key replaced", + }, + remove: { + title: "Remove the OpenRouter source?", + description: + "Maple deletes the key and stops reading the account's spend, and its cost leaves this page. Adding OpenRouter again brings back the last four weeks only.", + toast: "OpenRouter removed", + }, + save: (apiKey) => SaveOpenRouterCostKey({ apiKey }), + delete: () => RemoveOpenRouterCostKey(), + }, + cloudflare: { + pick: { + meta: "Daily charges by product, plus plan fees", + addedMeta: "Replace its token in the Sources panel", + }, + field: { + label: "API token", + placeholder: "Cloudflare API token", + maxLength: CLOUDFLARE_COST_TOKEN_MAX_LENGTH, + tooLong: "That is too long to be a Cloudflare API token.", + help: { + before: "", + link: "Create a custom token", + href: "https://dash.cloudflare.com/profile/api-tokens", + after: " with the Billing: Read permission on one account. Maple checks the token with Cloudflare, then stores it encrypted.", + }, + }, + add: { + title: "Add Cloudflare", + description: + "Maple reads this month and the last at once, then older months back to a year, and every day from here on.", + toast: "Cloudflare added", + }, + replace: { + title: "Replace the Cloudflare token", + description: "The source and its history stay. Use a token for the same Cloudflare account.", + label: "Replace token", + toast: "Cloudflare token replaced", + }, + remove: { + title: "Remove the Cloudflare source?", + description: + "Maple deletes the token and stops reading the account's charges, and its cost leaves this page. Adding Cloudflare again reads the last year again.", + toast: "Cloudflare removed", + }, + save: (apiToken) => SaveCloudflareCostToken({ apiToken }), + delete: () => RemoveCloudflareCostToken(), + }, +} satisfies Record diff --git a/apps/web/src/page/cost/derive.ts b/apps/web/src/page/cost/derive.ts index 6079ead891..df4e8fad29 100644 --- a/apps/web/src/page/cost/derive.ts +++ b/apps/web/src/page/cost/derive.ts @@ -350,3 +350,7 @@ export const requestsCost = (provider: CostProvider, requests: number): string | const { requestUsd } = COST_PROVIDERS[provider] return requestUsd === null ? null : `USD ${(requests * requestUsd).toFixed(2)}` } + +/** Whether the org has a source of `provider`: a pasted-credential provider has one at most. */ +export const hasSourceOf = (sources: ReadonlyArray, provider: CostProvider): boolean => + Arr.some(sources, (source) => source.provider === provider) diff --git a/apps/web/src/page/cost/marks.test.ts b/apps/web/src/page/cost/marks.test.ts index 70f688758c..84a82578f8 100644 --- a/apps/web/src/page/cost/marks.test.ts +++ b/apps/web/src/page/cost/marks.test.ts @@ -51,6 +51,33 @@ describe("cost key marks", () => { expect(keyMark("cloud_service", "", [])).toBeNull() }) + test("a Cloudflare product wears the Cloudflare mark, also beside an AWS source", () => { + const cloudflare = keyMark("provider", "cloudflare", [])?.icon + const both = [...sources, source("cloudflare", "1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d")] + expect(keyMark("cloud_service", "Workers", both)?.icon).toBe(cloudflare) + expect(keyMark("cloud_service", "Durable Objects", [])?.icon).toBe(cloudflare) + // Plan fees are filed under one service name. + expect(keyMark("cloud_service", "Subscriptions", both)?.icon).toBe(cloudflare) + // An AWS service beside it keeps the AWS mark. + expect(keyMark("cloud_service", "Amazon Simple Storage Service", both)?.icon).toBe( + keyMark("provider", "aws", [])?.icon, + ) + // A product Maple has no name for is Cloudflare's when no AWS source could bill it. + const alone = [source("cloudflare", "1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d")] + expect(keyMark("cloud_service", "Something New", alone)?.icon).toBe(cloudflare) + }) + + test("a Cloudflare account reads by name with its id aside, and bills in no region of its own", () => { + const account = "1a2b3c4d5e6f1a2b3c4d5e6f1a2b3c4d" + const both = [...sources, source("cloudflare", account)] + expect(keyLabel("account", account, both)).toBe("Cloudflare") + expect(keyDetail("account", account, both)).toBe(account) + expect(keyMark("account", account, both)?.icon).toBe(keyMark("provider", "cloudflare", [])?.icon) + // An AWS account's id is already in its name. + expect(keyDetail("account", "465760687006", both)).toBeNull() + expect(keyMark("region", "global", both)?.icon).not.toBe(keyMark("provider", "aws", [])?.icon) + }) + test("an AWS region says where it is; an unknown one adds nothing", () => { expect(keyDetail("region", "eu-central-1", [])).toBe("Frankfurt") expect(keyDetail("region", "mars-north-1", [])).toBeNull() diff --git a/apps/web/src/page/cost/marks.ts b/apps/web/src/page/cost/marks.ts index e65034f63e..1075c9437f 100644 --- a/apps/web/src/page/cost/marks.ts +++ b/apps/web/src/page/cost/marks.ts @@ -5,7 +5,7 @@ import { modelSlugIcon } from "@maple/views/lib/ai-vendor" import type { IconName } from "@maple/views/ui/icon" import { Array as Arr, Option, Record as Rec, Schema, String as Str } from "effect" import type { CostGroupBy } from "./derive" -import { COST_PROVIDERS } from "./providers" +import { COST_PROVIDERS, type CostProviderEntry } from "./providers" // What a key of the grouping is at a glance: the logo of whatever bills it, or its kind's glyph. @@ -14,7 +14,7 @@ export type KeyMark = SeriesMark const decodeProvider = Schema.decodeUnknownOption(CostProvider) const providerMark = (provider: CostProvider): KeyMark => { - const entry = COST_PROVIDERS[provider] + const entry: CostProviderEntry = COST_PROVIDERS[provider] return entry.iconClassName === undefined ? { icon: entry.icon } : { icon: entry.icon, className: entry.iconClassName } @@ -23,7 +23,31 @@ const providerMark = (provider: CostProvider): KeyMark => { /** OpenRouter names a model `vendor/model`; an AWS service name never has a slash. */ const isModelSlug = (key: string): boolean => Str.includes("/")(key) -/** An AWS account id is twelve digits; an OpenRouter one is its user or org id. */ +/** + * What Cloudflare bills by: its product families, and the plan fees Maple files under one name. A + * family it adds later reads as Cloudflare's once the org has no AWS source to mistake it for. + */ +const CLOUDFLARE_SERVICES = new Set([ + "Workers", + "Durable Objects", + "Containers", + "Queues", + "R2", + "D1", + "Workers KV", + "Workers AI", + "Email", + "Subscriptions", +]) + +/** The provider a service that is not a model belongs to: AWS, unless it is Cloudflare's. */ +const serviceProvider = (key: string, sources: ReadonlyArray): CostProvider => { + if (CLOUDFLARE_SERVICES.has(key)) return "cloudflare" + const reads = (provider: CostProvider) => Arr.some(sources, (source) => source.provider === provider) + return reads("cloudflare") && !reads("aws") ? "cloudflare" : "aws" +} + +/** An AWS account id is twelve digits; an OpenRouter one is its user or org id, a Cloudflare one 32 hex digits. */ const isAwsAccountId = (key: string): boolean => /^\d{12}$/u.test(key) /** The provider whose source reads the account, from the org's sources, then by the id's shape. */ @@ -70,7 +94,9 @@ export const keyMark = ( return Option.match(decodeProvider(key), { onNone: () => null, onSome: providerMark }) case "cloud_service": if (isModelSlug(key)) return { icon: modelSlugIcon(key) } - return key === "AWS Lambda" ? { icon: "AwsLambdaIcon" } : providerMark("aws") + return key === "AWS Lambda" + ? { icon: "AwsLambdaIcon" } + : providerMark(serviceProvider(key, sources)) case "service_category": return glyph(isCategory(key) ? CATEGORY_ICONS[key] : "CubeIcon") case "region": @@ -83,8 +109,8 @@ export const keyMark = ( } /** - * An account by what reads it: an AWS one by its id, an OpenRouter one by name alone while it is - * the org's only one (its id means nothing to a reader). Unknown ids read as they are. + * An account by what reads it: an AWS one by its id, an OpenRouter or Cloudflare one by name alone + * while it is the org's only one (its id means nothing to a reader). Unknown ids read as they are. */ export const accountLabel = (key: string, sources: ReadonlyArray): string => { const provider = accountProvider(key, sources) @@ -134,8 +160,8 @@ const isAwsRegion = (key: string): key is keyof typeof AWS_REGIONS => Rec.has(AWS_REGIONS, key) /** - * What the name leaves out: where an AWS region is, the id behind an OpenRouter account's name. - * Null when the name says it all. + * What the name leaves out: where an AWS region is, the id behind an account that reads by its + * provider's name. Null when the name says it all. */ export const keyDetail = ( dimension: CostGroupBy, @@ -144,7 +170,8 @@ export const keyDetail = ( ): string | null => { if (dimension === "region") return isAwsRegion(key) ? AWS_REGIONS[key] : null if (dimension === "account") { - return accountProvider(key, sources) === "openrouter" ? key : null + const provider = accountProvider(key, sources) + return provider === undefined || provider === "aws" ? null : key } return null } diff --git a/apps/web/src/page/cost/message.ts b/apps/web/src/page/cost/message.ts index 3ebb95de73..3791d557b3 100644 --- a/apps/web/src/page/cost/message.ts +++ b/apps/web/src/page/cost/message.ts @@ -5,8 +5,6 @@ import { CostSourcesResponse, CostTimeseriesResponse, CostTotalsResponse, - OpenRouterCostKeyRemoved, - OpenRouterCostKeySaved, } from "@maple/domain/http" import * as Policy from "@maple/domain/policy" import { Schema } from "effect" @@ -22,6 +20,7 @@ import { TimeWindow } from "../../data/time-range" import { COST_GROUP_BYS } from "../../route/search" import { perDimension } from "./derive" import * as TimeRangePicker from "../kit/time-range-picker" +import { CredentialProvider } from "./providers" /** A read's answer; `key` is what it was requested under, so a stale one is dropped. */ const result = (schema: Schema.Codec) => ({ @@ -62,18 +61,25 @@ export const Message = defineMessageUnion({ CompletedReadGrants: { grants: Schema.Array(Policy.PermissionName) }, /** Opens the add-source dialog on the providers. */ ClickedAddSource: {}, - ClickedAddOpenRouter: {}, + /** Picks a provider added by pasting a credential: the dialog moves to its form. */ + PickedProvider: { provider: CredentialProvider }, ClickedBackToProviders: {}, - /** Opens the dialog on the key form, for the source that exists. */ - ClickedReplaceKey: {}, + /** Opens the dialog on the provider's form, for the source that exists. */ + ClickedReplaceCredential: { provider: CredentialProvider }, GotAddDialogMessage: { message: Dialog.Message }, - GotKeyDraftMessage: { message: Field.Message }, - SubmittedKey: {}, - CompletedSaveKey: { result: Schema.Result(OpenRouterCostKeySaved, LoadFailure) }, - ClickedRemoveSource: {}, + GotCredentialDraftMessage: { message: Field.Message }, + SubmittedCredential: {}, + CompletedSaveCredential: { + provider: CredentialProvider, + result: Schema.Result(Schema.Void, LoadFailure), + }, + ClickedRemoveSource: { provider: CredentialProvider }, GotRemoveDialogMessage: { message: Dialog.Message }, ClickedConfirmRemove: {}, - CompletedRemoveKey: { result: Schema.Result(OpenRouterCostKeyRemoved, LoadFailure) }, + CompletedRemoveCredential: { + provider: CredentialProvider, + result: Schema.Result(Schema.Void, LoadFailure), + }, ...ChartEnvMessages, }) export type Message = typeof Message.Type diff --git a/apps/web/src/page/cost/model.ts b/apps/web/src/page/cost/model.ts index 5c90413e03..97b963902e 100644 --- a/apps/web/src/page/cost/model.ts +++ b/apps/web/src/page/cost/model.ts @@ -19,11 +19,12 @@ import { TimeWindow } from "../../data/time-range" import { CostSearch } from "../../route/search" import { CostFacets } from "./message" import * as TimeRangePicker from "../kit/time-range-picker" +import { CredentialProvider } from "./providers" export type Search = typeof CostSearch.Type -/** The add-source dialog's two faces: the providers, then the chosen one's form. */ -export const AddStep = Schema.Literals(["pick", "openrouter"]) +/** The add-source dialog's two faces: the providers, then the chosen one's credential form. */ +export const AddStep = Schema.Union([Schema.Literal("pick"), CredentialProvider]) export type AddStep = typeof AddStep.Type export const Model = Schema.Struct({ @@ -47,11 +48,13 @@ export const Model = Schema.Struct({ grants: Schema.NullOr(Schema.Array(Policy.PermissionName)), addDialog: Dialog.Model, addStep: AddStep, - /** The OpenRouter management key being typed; cleared whichever way the dialog closes. */ - keyDraft: Field.Model, - /** Why the last save was refused, shown under the field until the key changes. */ - keyError: Schema.NullOr(Schema.String), + /** The credential being typed (a key or a token); cleared whichever way the dialog closes. */ + credentialDraft: Field.Model, + /** Why the last save was refused, shown under the field until the credential changes. */ + credentialError: Schema.NullOr(Schema.String), removeDialog: Dialog.Model, + /** The provider whose source the remove dialog asks about; null until it first opens. */ + removeProvider: Schema.NullOr(CredentialProvider), /** `Settling`: the write answered and the sources are being read again; the list on show is the old one. */ sourceWrite: Schema.Literals(["Idle", "Saving", "Removing", "Settling"]), }) diff --git a/apps/web/src/page/cost/providers.ts b/apps/web/src/page/cost/providers.ts index 83bf9a0025..5808f9b6b1 100644 --- a/apps/web/src/page/cost/providers.ts +++ b/apps/web/src/page/cost/providers.ts @@ -1,5 +1,6 @@ import type { CostProvider } from "@maple/domain/cost" import type { IconName } from "@maple/views/ui/icon" +import { Option, Schema } from "effect" import { PROVIDERS } from "../kit/providers" /** @@ -11,20 +12,44 @@ export type CostProviderEntry = Readonly<{ icon: IconName iconClassName?: string requestUsd: number | null + /** What its first sync brings in: this month and the last with older months after, or all it keeps. */ + firstSync: "months" | "four_weeks" }> // Not every cost provider is an integration: OpenRouter supplies cost only, so it is named here. +// Cloudflare is one, but its cost source is a token of its own, not the integration's connection. export const COST_PROVIDERS = { aws: { name: PROVIDERS.aws.name, icon: PROVIDERS.aws.icon, iconClassName: PROVIDERS.aws.iconClassName, requestUsd: 0.01, + firstSync: "months", }, openrouter: { name: "OpenRouter", icon: "OpenRouterIcon", iconClassName: "text-foreground", requestUsd: null, + firstSync: "four_weeks", + }, + cloudflare: { + name: PROVIDERS.cloudflare.name, + icon: PROVIDERS.cloudflare.icon, + requestUsd: null, + firstSync: "months", }, } as const satisfies Record + +/** + * The providers added by pasting a credential on the Cost page, which can also replace it and + * remove the source. Each one's form and wording are in `credential-sources.ts`. + */ +export const CredentialProvider = Schema.Literals(["openrouter", "cloudflare"]) +export type CredentialProvider = typeof CredentialProvider.Type + +const decodeCredentialProvider = Schema.decodeUnknownOption(CredentialProvider) + +/** `provider` as one whose source is its pasted credential, or null for a connection's source. */ +export const credentialProviderOf = (provider: CostProvider): CredentialProvider | null => + Option.getOrNull(decodeCredentialProvider(provider)) diff --git a/apps/web/src/page/cost/update.ts b/apps/web/src/page/cost/update.ts index fb70b385ef..1a1f07ccdc 100644 --- a/apps/web/src/page/cost/update.ts +++ b/apps/web/src/page/cost/update.ts @@ -1,4 +1,4 @@ -import { CostSourcesResponse, OPENROUTER_KEY_MAX_LENGTH } from "@maple/domain/http" +import { CostSourcesResponse } from "@maple/domain/http" import * as Policy from "@maple/domain/policy" import { Array as Arr, Option, Result, String as Str } from "effect" import { AsyncData, Command } from "foldkit" @@ -30,9 +30,7 @@ import { FetchCostTimeseries, FetchCostTotals, ReadCostGrants, - RemoveOpenRouterCostKey, ResolveWindow, - SaveOpenRouterCostKey, type TimeseriesInput, type WindowInput, } from "./command" @@ -41,6 +39,7 @@ import { facetFilters, perDimension, groupByOf, + hasSourceOf, onlyFilter, toggledFilter, hasSynced, @@ -51,8 +50,10 @@ import { withoutFilter, withoutFilters, } from "./derive" +import { CREDENTIAL_SOURCES } from "./credential-sources" import { Message } from "./message" import type { AddStep, Model, Search } from "./model" +import type { CredentialProvider } from "./providers" type Return = PageReturn type Cmd = Command.Command @@ -171,9 +172,10 @@ export const init = (route: { readonly search: Search }, context: PageContext): grants: isClerkAuthEnabled ? null : Policy.allPermissions, addDialog: Dialog.init("cost-add-source", "dialog"), addStep: "pick", - keyDraft: Field.init(), - keyError: null, + credentialDraft: Field.init(), + credentialError: null, removeDialog: Dialog.init("cost-remove-source", "alert-dialog"), + removeProvider: null, sourceWrite: "Idle", } return chain( @@ -210,12 +212,16 @@ const liftRemoveDialog = (model: Model, step: ReturnType): }) /** Nothing typed and no refusal on show: how the dialog opens and what it leaves behind. */ -const clearedKey = (model: Model): Model => ({ ...model, keyDraft: Field.init(), keyError: null }) +const clearedCredential = (model: Model): Model => ({ + ...model, + credentialDraft: Field.init(), + credentialError: null, +}) const openAdd = (model: Model, addStep: AddStep): Return => model.sourceWrite !== "Idle" ? { model } - : liftAddDialog({ ...clearedKey(model), addStep }, Dialog.open(model.addDialog)) + : liftAddDialog({ ...clearedCredential(model), addStep }, Dialog.open(model.addDialog)) /** * The sources and the window's reads again, after a write's own step: the reads name the org's @@ -229,10 +235,10 @@ const withReread = (step: Return, context: PageContext): Return => ) /** - * The list on show without the OpenRouter source, which a removal just deleted: its row is gone + * The list on show without `provider`'s source, which a removal just deleted: its row is gone * at once, so no refresh, late or failed, leaves actions on a source that no longer exists. */ -const withoutOpenRouterSource = (model: Model): Model => ({ +const withoutSourceOf = (model: Model, provider: CredentialProvider): Model => ({ ...model, sources: { ...model.sources, @@ -240,17 +246,15 @@ const withoutOpenRouterSource = (model: Model): Model => ({ model.sources.data, (response) => new CostSourcesResponse({ - sources: Arr.filter(response.sources, (source) => source.provider !== "openrouter"), + sources: Arr.filter(response.sources, (source) => source.provider !== provider), }), ), }, }) -/** Whether the org has an OpenRouter source, which a saved key then replaces the key of. */ -const hasOpenRouterSource = (model: Model): boolean => - Option.exists(AsyncData.getData(model.sources.data), ({ sources }) => - Arr.some(sources, (source) => source.provider === "openrouter"), - ) +/** Whether the org has `provider`'s source, which a saved credential then replaces the credential of. */ +const hasSource = (model: Model, provider: CredentialProvider): boolean => + Option.exists(AsyncData.getData(model.sources.data), ({ sources }) => hasSourceOf(sources, provider)) export const routeChanged = ( model: Model, @@ -384,79 +388,82 @@ export const update = (model: Model, message: Message, context: PageContext): Re }, CompletedReadGrants: ({ grants }) => ({ model: { ...model, grants } }), ClickedAddSource: () => openAdd(model, "pick"), - ClickedAddOpenRouter: () => ({ model: { ...clearedKey(model), addStep: "openrouter" } }), + PickedProvider: ({ provider }) => ({ model: { ...clearedCredential(model), addStep: provider } }), ClickedBackToProviders: () => - model.sourceWrite === "Idle" ? { model: { ...clearedKey(model), addStep: "pick" } } : { model }, - ClickedReplaceKey: () => openAdd(model, "openrouter"), + model.sourceWrite === "Idle" + ? { model: { ...clearedCredential(model), addStep: "pick" } } + : { model }, + ClickedReplaceCredential: ({ provider }) => openAdd(model, provider), // A save in flight keeps the dialog: its answer decides whether it closes. GotAddDialogMessage: ({ message: inner }) => { if (model.sourceWrite === "Saving") return { model } const step = Dialog.update(model.addDialog, inner) const lifted = liftAddDialog(model, step) return step.outMessage?._tag === "Closed" - ? { ...lifted, model: clearedKey(lifted.model) } + ? { ...lifted, model: clearedCredential(lifted.model) } : lifted }, - GotKeyDraftMessage: ({ message: inner }) => + GotCredentialDraftMessage: ({ message: inner }) => model.sourceWrite === "Saving" ? { model } : { model: { ...model, - keyDraft: Field.update(model.keyDraft, inner).model, - // The refusal was about the key that was there. - keyError: inner._tag === "ChangedValue" ? null : model.keyError, + credentialDraft: Field.update(model.credentialDraft, inner).model, + // The refusal was about the credential that was there. + credentialError: inner._tag === "ChangedValue" ? null : model.credentialError, }, }, - SubmittedKey: () => { - const apiKey = Str.trim(model.keyDraft.value) - if (apiKey === "" || model.sourceWrite !== "Idle") return { model } - // The request refuses a longer key before it is sent, which would leave the save unanswered. - if (apiKey.length > OPENROUTER_KEY_MAX_LENGTH) { - return { model: { ...model, keyError: "That is too long to be an OpenRouter key." } } + SubmittedCredential: () => { + const secret = Str.trim(model.credentialDraft.value) + if (secret === "" || model.sourceWrite !== "Idle" || model.addStep === "pick") return { model } + const source = CREDENTIAL_SOURCES[model.addStep] + // The request refuses a longer one before it is sent, which would leave the save unanswered. + if (secret.length > source.field.maxLength) { + return { model: { ...model, credentialError: source.field.tooLong } } } return { - model: { ...model, sourceWrite: "Saving", keyError: null }, - commands: [SaveOpenRouterCostKey({ apiKey })], + model: { ...model, sourceWrite: "Saving", credentialError: null }, + commands: [source.save(secret)], } }, - // A refused key keeps the dialog open with the reason under the field. - CompletedSaveKey: ({ result }) => + // A refused credential keeps the dialog open with the reason under the field. + CompletedSaveCredential: ({ provider, result }) => Result.match(result, { onFailure: (failure): Return => ({ - model: { ...model, sourceWrite: "Idle", keyError: failureMessage(failure) }, + model: { ...model, sourceWrite: "Idle", credentialError: failureMessage(failure) }, }), onSuccess: (): Return => { const closed = liftAddDialog( - clearedKey({ ...model, sourceWrite: "Settling" }), + clearedCredential({ ...model, sourceWrite: "Settling" }), Dialog.close(model.addDialog), ) - const outMessage = hasOpenRouterSource(model) - ? toast( - "success", - "OpenRouter key replaced", - "The next sync runs within a few minutes.", - ) - : toast("success", "OpenRouter added", "The first sync runs within a few minutes.") + const source = CREDENTIAL_SOURCES[provider] + const outMessage = hasSource(model, provider) + ? toast("success", source.replace.toast, "The next sync runs within a few minutes.") + : toast("success", source.add.toast, "The first sync runs within a few minutes.") return withReread({ ...closed, outMessage }, context) }, }), - ClickedRemoveSource: () => + ClickedRemoveSource: ({ provider }) => model.sourceWrite !== "Idle" ? { model } - : liftRemoveDialog(model, Dialog.open(model.removeDialog)), + : liftRemoveDialog({ ...model, removeProvider: provider }, Dialog.open(model.removeDialog)), GotRemoveDialogMessage: ({ message: inner }) => model.sourceWrite === "Removing" ? { model } : liftRemoveDialog(model, Dialog.update(model.removeDialog, inner)), ClickedConfirmRemove: () => - model.sourceWrite !== "Idle" + model.sourceWrite !== "Idle" || model.removeProvider === null ? { model } - : { model: { ...model, sourceWrite: "Removing" }, commands: [RemoveOpenRouterCostKey()] }, - CompletedRemoveKey: ({ result }) => { + : { + model: { ...model, sourceWrite: "Removing" }, + commands: [CREDENTIAL_SOURCES[model.removeProvider].delete()], + }, + CompletedRemoveCredential: ({ provider, result }) => { const closed = liftRemoveDialog( Result.isSuccess(result) - ? withoutOpenRouterSource({ ...model, sourceWrite: "Settling" }) + ? withoutSourceOf({ ...model, sourceWrite: "Settling" }, provider) : { ...model, sourceWrite: "Idle" }, Dialog.close(model.removeDialog), ) @@ -466,7 +473,13 @@ export const update = (model: Model, message: Message, context: PageContext): Re outMessage: toast("error", "Couldn't remove the source", failureMessage(failure)), }), onSuccess: (): Return => - withReread({ ...closed, outMessage: toast("success", "OpenRouter removed") }, context), + withReread( + { + ...closed, + outMessage: toast("success", CREDENTIAL_SOURCES[provider].remove.toast), + }, + context, + ), }) }, ...chartEnvHandlers(model), diff --git a/apps/web/src/page/cost/view-add-source.ts b/apps/web/src/page/cost/view-add-source.ts index 42b233f638..33b8c4b09a 100644 --- a/apps/web/src/page/cost/view-add-source.ts +++ b/apps/web/src/page/cost/view-add-source.ts @@ -15,9 +15,11 @@ import { vstack } from "@maple/views/ui/stack" import { textLink } from "@maple/views/ui/text-link" import { typography } from "@maple/views/ui/typography" import { AppRoute, hrefOf } from "../../route" +import { CREDENTIAL_SOURCES, type CredentialSource } from "./credential-sources" +import { hasSourceOf } from "./derive" import { Message } from "./message" import type { Model } from "./model" -import { COST_PROVIDERS, type CostProviderEntry } from "./providers" +import { COST_PROVIDERS, type CostProviderEntry, CredentialProvider } from "./providers" type H = HtmlBuilder @@ -26,17 +28,47 @@ const CONNECT_AWS_HREF = hrefOf(AppRoute.SourcesNew({ search: { integration: "aw /** Where an AWS connection and its Cost module are changed. */ export const AWS_INTEGRATION_HREF = hrefOf(AppRoute.Integrations({ search: { integration: "aws" } })) -const OPENROUTER_KEYS_URL = "https://openrouter.ai/settings/management-keys" - -/** Whether the org has its OpenRouter source, which there is at most one of. */ -const hasOpenRouterSource = (sources: ReadonlyArray): boolean => - Arr.some(sources, (source) => source.provider === "openrouter") - const mark = (h: H, provider: CostProviderEntry): Html => icon(h, provider.icon, { size: 18, className: cn("shrink-0", provider.iconClassName) }) -/** The providers to add: AWS goes to its connect flow, OpenRouter to the key form. */ -const providers = (h: H, hasOpenRouter: boolean): Html => +/** A provider added by pasting a credential: to its form, or named as added once its source exists. */ +const credentialRow = ( + h: H, + provider: CredentialProvider, + sources: ReadonlyArray, + isLast: boolean, +): Html => { + const entry: CostProviderEntry = COST_PROVIDERS[provider] + const { pick } = CREDENTIAL_SOURCES[provider] + return hasSourceOf(sources, provider) + ? listRow(h, { + divided: !isLast, + leading: mark(h, entry), + title: entry.name, + meta: pick.addedMeta, + trailing: badge(h, { variant: "muted", size: "sm" }, ["Already added"]), + }) + : listRow(h, { + divided: !isLast, + onClick: Message.PickedProvider({ provider }), + leading: mark(h, entry), + title: entry.name, + meta: pick.meta, + trailing: icon(h, "ChevronRightIcon", { size: 14 }), + }) +} + +/** The providers that bill nothing for the requests Maple makes, by name. */ +const FREE_REQUESTS = Arr.join( + Arr.map( + Arr.filter(CredentialProvider.literals, (provider) => COST_PROVIDERS[provider].requestUsd === null), + (provider) => COST_PROVIDERS[provider].name, + ), + " and ", +) + +/** The providers to add: AWS goes to its connect flow, the others to their credential form. */ +const providers = (h: H, sources: ReadonlyArray): Html => vstack(h, { gap: "3" }, [ vstack(h, { gap: "0" }, [ listRow(h, { @@ -47,23 +79,12 @@ const providers = (h: H, hasOpenRouter: boolean): Html => meta: "Daily cost by service, region and usage type", trailing: icon(h, "ChevronRightIcon", { size: 14 }), }), - hasOpenRouter - ? listRow(h, { - leading: mark(h, COST_PROVIDERS.openrouter), - title: COST_PROVIDERS.openrouter.name, - meta: "Replace its key in the Sources panel", - trailing: badge(h, { variant: "muted", size: "sm" }, ["Already added"]), - }) - : listRow(h, { - onClick: Message.ClickedAddOpenRouter(), - leading: mark(h, COST_PROVIDERS.openrouter), - title: COST_PROVIDERS.openrouter.name, - meta: "Daily spend by model and serving provider", - trailing: icon(h, "ChevronRightIcon", { size: 14 }), - }), + ...Arr.map(CredentialProvider.literals, (provider, index) => + credentialRow(h, provider, sources, index === CredentialProvider.literals.length - 1), + ), ]), typography(h, { size: "xs", tone: "muted", as: "p", className: "font-prose" }, [ - "AWS bills USD\u00a00.01 for each request Maple makes, a few dollars a month per account. OpenRouter requests are free. ", + `AWS bills USD\u00a00.01 for each request Maple makes, a few dollars a month per account. ${FREE_REQUESTS} requests are free. `, textLink(h, { href: AWS_INTEGRATION_HREF, tone: "inherit", className: "underline" }, [ "Turn on Cost for an AWS account that is connected", ]), @@ -71,60 +92,105 @@ const providers = (h: H, hasOpenRouter: boolean): Html => ]), ]) -const KEY_IDS: FieldIds = { - control: "cost-openrouter-key", - label: "cost-openrouter-key-label", - error: "cost-openrouter-key-error", - description: "cost-openrouter-key-description", +const CREDENTIAL_IDS: FieldIds = { + control: "cost-source-credential", + label: "cost-source-credential-label", + error: "cost-source-credential-error", + description: "cost-source-credential-description", } -const keyField = (h: H, model: Model): Html => { - const state = Field.stateOf(model.keyDraft, model.keyError !== null) +const credentialField = (h: H, model: Model, { field: text }: CredentialSource): Html => { + const state = Field.stateOf(model.credentialDraft, model.credentialError !== null) return field(h, { state }, [ - fieldLabel(h, { ids: KEY_IDS, state }, "Management key"), + fieldLabel(h, { ids: CREDENTIAL_IDS, state }, text.label), input(h, { - ids: KEY_IDS, + ids: CREDENTIAL_IDS, state, attributes: [ h.Type("password"), h.Attribute("autocomplete", "off"), - h.Placeholder("sk-or-v1-..."), + h.Placeholder(text.placeholder), ...(model.sourceWrite === "Saving" ? [h.Disabled(true)] : []), - ...Field.controlAttributes(h, model.keyDraft, (message) => - Message.GotKeyDraftMessage({ message }), + ...Field.controlAttributes(h, model.credentialDraft, (message) => + Message.GotCredentialDraftMessage({ message }), ), ], }), - ...(model.keyError === null + ...(model.credentialError === null ? [] - : [fieldError(h, { ids: KEY_IDS, state, attributes: [h.Role("alert")] }, [model.keyError])]), - fieldDescription(h, { ids: KEY_IDS, state }, [ - "Create one under ", + : [ + fieldError(h, { ids: CREDENTIAL_IDS, state, attributes: [h.Role("alert")] }, [ + model.credentialError, + ]), + ]), + fieldDescription(h, { ids: CREDENTIAL_IDS, state }, [ + text.help.before, textLink( h, { - href: OPENROUTER_KEYS_URL, + href: text.help.href, tone: "inherit", className: "underline", attributes: [h.Target("_blank"), h.Rel("noreferrer")], }, - ["Management keys"], + [text.help.link], ), - " in OpenRouter. A regular API key cannot read spend. Maple checks the key with OpenRouter, then stores it encrypted.", + text.help.after, + ]), + ]) +} + +/** The chosen provider's credential form: adding its source, or replacing the credential of the one that exists. */ +const credentialForm = ( + h: H, + model: Model, + provider: CredentialProvider, + sources: ReadonlyArray, + close: Parameters[0], +): Html => { + const id = model.addDialog.id + const source = CREDENTIAL_SOURCES[provider] + const isReplacing = hasSourceOf(sources, provider) + const isSaving = model.sourceWrite === "Saving" + const canSubmit = !isSaving && Str.trim(model.credentialDraft.value) !== "" + const text = isReplacing ? source.replace : source.add + return formDialogForm(h, canSubmit ? Message.SubmittedCredential() : undefined, [ + DialogView.dialogHeader(h, "dialog", [ + DialogView.dialogTitle(h, "dialog", id, text.title), + DialogView.dialogDescription(h, "dialog", id, text.description), + ]), + DialogView.dialogPanel(h, "dialog", id, [credentialField(h, model, source)]), + DialogView.dialogFooter(h, "dialog", [ + // A replace has no providers to go back to. + isReplacing + ? button( + h, + { variant: "outline", isDisabled: isSaving, attributes: isSaving ? [] : [...close] }, + ["Cancel"], + ) + : button( + h, + { + variant: "outline", + isDisabled: isSaving, + attributes: [h.OnClick(Message.ClickedBackToProviders())], + }, + ["Back"], + ), + button(h, { type: "submit", isLoading: isSaving, isDisabled: !canSubmit }, [ + isReplacing ? source.replace.label : "Add source", + ]), ]), ]) } /** - * Add a source, or replace the OpenRouter key: the providers first, then OpenRouter's key form. - * Opened on the form for a replace, which has no providers to go back to. + * Add a source, or replace a source's credential: the providers first, then the chosen one's + * form. Opened on the form for a replace. */ export const addSourceDialog = (h: H, model: Model, sources: ReadonlyArray): Html => { const id = model.addDialog.id - const isReplacing = hasOpenRouterSource(sources) - const isSaving = model.sourceWrite === "Saving" - const canSubmit = !isSaving && Str.trim(model.keyDraft.value) !== "" - const onPick = model.addStep === "pick" + const step = model.addStep return h.submodel({ slotId: "cost-add-source", model: model.addDialog, @@ -135,7 +201,7 @@ export const addSourceDialog = (h: H, model: Model, sources: ReadonlyArray portal ?? h.empty, toContent: (close) => - onPick + step === "pick" ? [ DialogView.dialogHeader(h, "dialog", [ DialogView.dialogTitle(h, "dialog", id, "Add a cost source"), @@ -146,76 +212,28 @@ export const addSourceDialog = (h: H, model: Model, sources: ReadonlyArray - confirmDialog(h, { +/** Removing a credential's source: what leaves the page, and what adding it again brings back. */ +export const removeSourceDialog = (h: H, model: Model): Html => { + if (model.removeProvider === null) return h.empty + const { remove } = CREDENTIAL_SOURCES[model.removeProvider] + return confirmDialog(h, { slotId: "cost-remove-source", model: model.removeDialog, toParentMessage: (message) => Message.GotRemoveDialogMessage({ message }), - title: "Remove the OpenRouter source?", - description: - "Maple deletes the key and stops reading the account's spend, and its cost leaves this page. Adding OpenRouter again brings back the last four weeks only.", + title: remove.title, + description: remove.description, confirmLabel: "Remove source", onConfirm: Message.ClickedConfirmRemove(), isPending: model.sourceWrite === "Removing", }) +} diff --git a/apps/web/src/page/cost/view-sources.ts b/apps/web/src/page/cost/view-sources.ts index 4eff5e349d..6bee88c1e7 100644 --- a/apps/web/src/page/cost/view-sources.ts +++ b/apps/web/src/page/cost/view-sources.ts @@ -1,7 +1,7 @@ import type { CostSourceStatus, CostSourceSummary } from "@maple/domain/http" import { formatNumber, pluralize } from "@maple/ui/lib/format" import { cn } from "@maple/ui/lib/utils" -import { Array as Arr } from "effect" +import { Array as Arr, String as Str } from "effect" import type { Html, HtmlBuilder } from "foldkit/html" import { button, linkButton } from "@maple/views/ui/button" import { badge } from "@maple/views/ui/display" @@ -11,9 +11,10 @@ import { stripClass } from "@maple/views/ui/rule" import { hstack, vstack } from "@maple/views/ui/stack" import { typography } from "@maple/views/ui/typography" import { relativeTime } from "../kit/relative-time" +import { CREDENTIAL_SOURCES } from "./credential-sources" import { formatDay, requestsCost } from "./derive" import { Message } from "./message" -import { COST_PROVIDERS } from "./providers" +import { COST_PROVIDERS, type CostProviderEntry, credentialProviderOf } from "./providers" import { AWS_INTEGRATION_HREF } from "./view-add-source" type H = HtmlBuilder @@ -30,32 +31,50 @@ const STATUS = { Readonly<{ label: string; variant: "ok" | "info" | "muted" | "crit" }> > -/** What a row offers: an AWS source is its connection's, an OpenRouter one is its key. */ -const actions = (h: H, source: CostSourceSummary, isBusy: boolean): ReadonlyArray => - source.provider === "openrouter" - ? [ - button( - h, - { - variant: "ghost", - size: "sm", - isDisabled: isBusy, - attributes: [h.OnClick(Message.ClickedReplaceKey())], - }, - ["Replace key"], - ), - button( - h, - { - variant: "ghost", - size: "sm", - isDisabled: isBusy, - attributes: [h.OnClick(Message.ClickedRemoveSource())], - }, - ["Remove"], - ), - ] - : [linkButton(h, { variant: "ghost", size: "sm", href: AWS_INTEGRATION_HREF }, ["Manage connection"])] +/** A row's action. Its accessible name says whose source it acts on: two rows can offer the same one. */ +const rowButton = (h: H, label: string, name: string, onClick: Message, isBusy: boolean): Html => + button( + h, + { + variant: "ghost", + size: "sm", + isDisabled: isBusy, + attributes: [h.AriaLabel(name), h.OnClick(onClick)], + }, + [label], + ) + +/** What a row offers: a connection's source is managed there, a pasted credential's here. */ +const actions = (h: H, source: CostSourceSummary, isBusy: boolean): ReadonlyArray => { + const { name } = COST_PROVIDERS[source.provider] + const provider = credentialProviderOf(source.provider) + if (provider === null) { + return [ + linkButton( + h, + { + variant: "ghost", + size: "sm", + href: AWS_INTEGRATION_HREF, + attributes: [h.AriaLabel(`Manage ${name} connection`)], + }, + ["Manage connection"], + ), + ] + } + const { label } = CREDENTIAL_SOURCES[provider].replace + return [ + rowButton( + h, + label, + // "Replace key" as "Replace OpenRouter key". + Str.replace("Replace", `Replace ${name}`)(label), + Message.ClickedReplaceCredential({ provider }), + isBusy, + ), + rowButton(h, "Remove", `Remove ${name} source`, Message.ClickedRemoveSource({ provider }), isBusy), + ] +} /** What the provider billed the account for Maple's requests this month; nothing where requests are free. */ const requestsFact = (source: CostSourceSummary): ReadonlyArray => { @@ -113,7 +132,7 @@ const sourceRow = ( timeZone: string, options: Readonly<{ isBusy: boolean; canManage: boolean }>, ): Html => { - const provider = COST_PROVIDERS[source.provider] + const provider: CostProviderEntry = COST_PROVIDERS[source.provider] const status = STATUS[source.status] return hstack( h, @@ -166,7 +185,7 @@ export const sourcesSection = ( nowMs: number, timeZone: string, options: Readonly<{ - /** A key write is in flight: the actions wait for it. */ + /** A credential write is in flight: the actions wait for it. */ isBusy: boolean /** The viewer may add, replace and remove sources; without it no such action is offered. */ canManage: boolean diff --git a/apps/web/src/page/cost/view.ts b/apps/web/src/page/cost/view.ts index f379aa8441..d4f39f6d82 100644 --- a/apps/web/src/page/cost/view.ts +++ b/apps/web/src/page/cost/view.ts @@ -1,3 +1,4 @@ +import type { CostProvider } from "@maple/domain/cost" import type { CostSourceSummary } from "@maple/domain/http" import { Option, Array as Arr } from "effect" import { AsyncData, Submodel } from "foldkit" @@ -39,6 +40,7 @@ import { Message } from "./message" import { LONG_RANGE_MAX_SECONDS } from "../../route/session-time-range" import { keyMark } from "./marks" import { canManageSources, cannotManageSources, sourcesOf, type Model } from "./model" +import { COST_PROVIDERS } from "./providers" import { DEFAULT_PRESET } from "./update" import { addSourceDialog, removeSourceDialog } from "./view-add-source" import { dailyChart } from "./view-chart" @@ -65,7 +67,7 @@ const onRamp = ( emptyMedia(h, { variant: "icon" }, [icon(h, "CircleDollarIcon")]), emptyTitle(h, ["No cost source yet"]), emptyDescription(h, [ - "Add a source and Maple reads what it bills each day: an AWS account by service, region and account, an OpenRouter account by model.", + "Add a source and Maple reads what it bills each day: an AWS account by service, region and account, an OpenRouter account by model, a Cloudflare account by product.", ]), // A member's save would be refused: say who can, and offer no form. ...(!options.cannotManage @@ -106,19 +108,33 @@ const onRamp = ( emptyFrameClass("dashed"), ) +const MONTHS_SCOPE = "this month and the last, and older months fill in after that" + +const namesOf = (providers: ReadonlyArray): string => + Arr.join( + Arr.map(providers, (provider) => COST_PROVIDERS[provider].name), + " and ", + ) + /** * What the first syncs bring in, by the providers that have no cost yet (waiting or failing - * alike): AWS keeps over a year, OpenRouter four weeks. + * alike): some keep a year or more, some four weeks. */ const firstSyncScope = (sources: ReadonlyArray): string => { - const unsynced = Arr.filter(sources, (source) => source.watermarkDate === null) - const hasOpenRouter = Arr.some(unsynced, (source) => source.provider === "openrouter") - const hasOther = Arr.some(unsynced, (source) => source.provider !== "openrouter") - return hasOpenRouter && hasOther - ? "AWS brings in this month and the last, and older months fill in after that. OpenRouter brings in its last four weeks." - : hasOpenRouter - ? "It brings in the last four weeks, which is all OpenRouter keeps." - : "It brings in this month and the last, and older months fill in after that." + const unsynced = Arr.dedupe( + Arr.map( + Arr.filter(sources, (source) => source.watermarkDate === null), + (source) => source.provider, + ), + ) + const keepsWeeks = (provider: CostProvider) => COST_PROVIDERS[provider].firstSync === "four_weeks" + const weeks = Arr.filter(unsynced, keepsWeeks) + const months = Arr.filter(unsynced, (provider) => !keepsWeeks(provider)) + if (weeks.length === 0) return `It brings in ${MONTHS_SCOPE}.` + if (months.length === 0) { + return `It brings in the last four weeks, which is all ${namesOf(weeks)} ${weeks.length === 1 ? "keeps" : "keep"}.` + } + return `${namesOf(months)} ${months.length === 1 ? "brings" : "bring"} in ${MONTHS_SCOPE}. ${namesOf(weeks)} ${weeks.length === 1 ? "brings in its" : "bring in their"} last four weeks.` } /** Sources exist and none has cost yet: what the reader is waiting for. */ diff --git a/docs/cloud-cost-plan.md b/docs/cloud-cost-plan.md index 79b3525cc8..2aa30ace9b 100644 --- a/docs/cloud-cost-plan.md +++ b/docs/cloud-cost-plan.md @@ -1,7 +1,7 @@ # Cloud cost: plan Status: **phase 1 built, 2026-11-11, behind the `cloudCost` org flag.** AWS spend is synced daily -into `cost_daily` and shown on `/cost`. OpenRouter followed on 2026-11-12 as the second source. +into `cost_daily` and shown on `/cost`. OpenRouter and Cloudflare followed on 2026-11-12. Phase 0 checked the provider APIs against docs, public schemas and one real AWS account. Recommendations (phase 2) and the other providers are not built. @@ -44,8 +44,14 @@ warehouse are not. One adapter per provider, in `packages/backend/src/services/cost/providers/`, behind one port. The registry covers cost only. The integrations themselves are not refactored. A provider that is -not an integration (OpenRouter) keeps its credential in a table of its own, and -`cost_sources.connection_ref` points at that row. +not read through an integration's connection keeps a pasted credential, and +`cost_sources.connection_ref` points at its row. OpenRouter has a table of its own +(`openrouter_cost_connections`). Every later provider shares `cost_connections`: one row per org +and provider, with the sealed credential, the vendor's account id and a label. `CostConnectionStore` +seals, upserts (refusing a credential of another account), removes and loads it, and +`reconcileCostConnectionSources` keeps `cost_sources` in step for each provider named in +`COST_CONNECTION_PROVIDERS`. A new provider of this kind needs no migration, slot or reconcile: it +adds its literal there, and brings its own vendor check, adapter and endpoints. ```ts interface CostProvider { @@ -113,7 +119,8 @@ The windows above are the AWS ones. Each provider has a profile (`COST_SYNC_PROF `sync-plan.ts`): the oldest day it serves and how far its newest day trails today. No window reaches outside them, because a day the provider no longer serves comes back empty, and an empty day replaces the stored one. OpenRouter serves 30 completed days: its sync asks for 28 at most and -never for today. +never for today. Cloudflare serves a year of completed days: its history stops at the first day of +the eleventh month back, and it never asks for today either. Each step writes its days whole under one `IngestedAt`. A day a pull returned nothing for gets a marker row (`ChargeCategory = 'none'`), so it supersedes the older pull. Reads leave it out. @@ -140,12 +147,13 @@ currency the figures are in and lists the others. Nothing is added across curren - Web: `/cost` behind the `cloudCost` flag. One page: the window against the period before, a daily chart, a breakdown by service, provider, category, region, usage type or account with filters, and each source's sync state. Sources are added from the page: AWS through its - connection (which has a Cost switch), OpenRouter by pasting a management key, which the page can - also replace or remove. Recommendations (list and detail with the evidence chart) come with + connection (which has a Cost switch), OpenRouter by pasting a management key and Cloudflare by + pasting an API token, which the page can also replace or remove. The pasted-credential step is + one form described by data (`credential-sources.ts`). Recommendations (list and detail with the evidence chart) come with phase 2. - API: four internal endpoints in the query-engine group (`cost-totals`, `cost-timeseries`, - `cost-breakdown`, `cost-sources`), and `PUT` / `DELETE /internal/cost/openrouter` for the - OpenRouter key. A public `/v2/cost/*` is not built. + `cost-breakdown`, `cost-sources`), and `PUT` / `DELETE` on `/internal/cost/openrouter` and + `/internal/cost/cloudflare` for the pasted credentials. A public `/v2/cost/*` is not built. - MCP: `cost_overview`, `cost_breakdown`, `compare_cost_periods`, `list_cost_recommendations`, `inspect_cost_recommendation`. @@ -229,14 +237,73 @@ currency the figures are in and lists the others. Nothing is added across curren overprovisioned, Cloud Run cost, commitments. Negative cost is the saving. - Until the export exists, Maple can still show the recommender's savings and its own rules. -### Cloudflare (billed for pay-as-you-go, else estimated) - -- Schema: `GET /accounts/{id}/billable-usage` returns FOCUS-style daily rows with `BilledCost`. It - is marked alpha, covers usage overages (not plan fees), and is not available to enterprise - accounts. Plan fees come from the subscription endpoints. -- All of it needs the "Billing Read" permission. Open: whether the OAuth app can be granted a - billing scope. If not, this source needs an API token. -- Fallback: usage from the GraphQL analytics datasets (already authorized) times list price. +### Cloudflare (billed, pay-as-you-go accounts) + +"Live" here means checked against a real pay-as-you-go account on 2026-11-12. + +- It needs an API token with the "Billing: Read" permission on one account. No billing scope is + known for the OAuth app, so the integration's connection is not used: the org pastes a token on + the Cost page, which Maple checks and stores sealed in `cost_connections`. +- Live: `GET /accounts` lists the accounts the token reads. A token for one account lists one, + and its id is the source's account. A token that lists none or several is refused. A + replacement token must read the same account, since another account's charges would overwrite + the source's days. Live: a token Cloudflare does not know answers 403 on this call, and 401 on + the billing calls. +- Live: `GET /accounts/{id}/billable-usage/info` answers `covered`. Schema: usage-based billing + data is not available to enterprise accounts. A save is refused and a pull reports "not ready" + while it is false. +- Live: `GET /accounts/{id}/billable-usage?from=&to=` (marked "Version 1, Alpha") answers + FOCUS-style rows, one per UTC day and priced metric, in one answer without paging (about 1,100 + rows for six weeks). Most rows bill nothing, being inside the included allowance. +- Live: `to` is exclusive. `from=2026-10-01&to=2026-10-02` answers 1 October only, and equal + dates are refused. +- Live: a billing period's rows come back only when the period starts inside `[from, to)`. A + range that starts a day after the period did answers nothing for it. So every request asks from + 31 days before its first day to the day after its last, and keeps only its own days. +- Live: a range over 90 days is refused (`max_date_range_days`), so a window is read in pieces of + 55 days. Requests are free, so the source counts none. +- Live: yesterday is served, today is not, and the oldest day served is one year before today. + The profile asks from the first day of the eleventh month back through yesterday. The two newest + days are stored as not final. +- Live: a range that reaches past the year is answered, not refused, with the older days left out, + and a period that started before the year still returns its days inside it. So the 31 days asked + before the oldest month cost nothing. +- Live: a wrong or revoked token answers 401 (sync reason `auth`), a token without billing access + 403 (`not_ready`). A 400 is a request Maple wrote wrong (`upstream`). +- Rows: the product family (`Workers`, `R2`, `D1` ...) is `CloudService`, the priced metric is + `UsageType`, the region is `global`, and the category comes from a small map of the families. + Rows with no billed and no list cost are left out. +- Live: usage rows do not include plan fees. `GET /accounts/{id}/subscriptions` (paged) lists them + with `price`, `frequency` and `current_period_start`. Each paid subscription becomes a purchase + row (`CloudService` "Subscriptions", the rate plan as `UsageType`, the zone as the resource) on + the day each billing period started, stepped back from the current period by its frequency + (a week, a month or three months) and never before the subscription was created. A yearly one + gets one row on its current period's start. +- A subscription made on the 29th to 31st has later starts clamped to shorter months, so stepping + back lands days before it was made: that first period's fee goes on the day it was made. +- Live: `created_date` was on every subscription. Schema: the published response does not list + it. Without it only the current period's fee is stored, since earlier periods cannot be told + from months before the subscription existed. +- A priced subscription Maple cannot place on a day (another frequency, or no period start) has + no row. The pull's span counts them as `maple.cost.cloudflare.fees_skipped`. +- Assumed: Cloudflare reports the current period only, so earlier months use today's price. A plan + whose price changed shows the new price for its past months too. +- Schema: a subscription's `state` is one of `Trial`, `Provisioned`, `Paid`, `AwaitingPayment`, + `Cancelled`, `Failed`, `Expired`. Live: every listed one was `Paid`, free items included. + Assumed: `Trial`, `Failed` and `Expired` were not charged, so they get no fee row. +- Known limit: a fee is known only while its subscription is listed. If Cloudflare drops a + cancelled subscription from the list, the next re-pull of the open months stores those days + without its fee. Months already walked as history keep theirs. +- Live: the listing's `result_info` has `per_page`, `total_pages` and `total_count`. Schema: only + the count is documented, so the page count falls back to the count over the page size. A list + read short of its count, over 10 pages, or a full page that says nothing of its paging, fails + the pull as an answer Maple cannot read: part of the list would under-count. +- Every answer must say `success: true`. Read as an answer, a 200 with an empty result would + store every day of the window as empty. Live: the usage answer has no `result_info`. One that + appears and lists more rows than it returned fails the pull too. +- Open: whether a completed day is ever restated. The four newest days are pulled again daily. +- Not built: enterprise contracts, zone-level analytics, the restricted "Version 2" endpoints, + invoices and credits. ### Railway (estimated per service, billed per workspace) @@ -272,9 +339,12 @@ Each phase is one PR stack. Plan imports. First Maple rules, each backed by a metric already ingested: load balancers with no requests, databases with idle connections and low CPU, container services reserving far more than they use. -3. **More providers.** OpenRouter is built. Railway, GCP, PlanetScale, Supabase, Cloudflare, one - PR each. Each provider adds a literal to `CostProvider`, an adapter, a reconcile function and a - sync profile. +3. **More providers.** OpenRouter and Cloudflare are built. Railway, GCP, PlanetScale, Supabase, + one PR each. Each provider adds a literal to `CostProvider`, an adapter and a sync profile. One + read with a pasted credential also adds itself to `COST_CONNECTION_PROVIDERS`, a connection + service that checks the credential with the vendor, its `PUT` / `DELETE` endpoints, and on the + Cost page two commands and an entry in `CREDENTIAL_SOURCES`. One read through a connection adds + a reconcile function instead. 4. **Closing the loop.** Budgets and cost anomalies as managed alert signals, a cost block in the weekly digest, cost per request on service pages, realized savings once an accepted recommendation stops firing. @@ -291,7 +361,8 @@ Each phase is one PR stack. ## Open questions -- Cloudflare: is there an OAuth billing scope. +- Cloudflare: is there an OAuth billing scope. With one, the integration's connection could + supply cost and no token would be pasted. - Railway: can a workspace token read `customer`. - PlanetScale: the time unit of SKU `rate`, and whether the open invoice is listed. - AWS: the request rate limit of Cost Explorer is not published. The adapter backs off on diff --git a/packages/backend/src/platform/secret-slots.ts b/packages/backend/src/platform/secret-slots.ts index 2b0e2dd46c..b6a7d1c5c5 100644 --- a/packages/backend/src/platform/secret-slots.ts +++ b/packages/backend/src/platform/secret-slots.ts @@ -96,6 +96,16 @@ export const openRouterCostKeySlot: SecretSlot = { binding: orgBinding, } +export interface CostConnectionRef extends OrgRef { + readonly provider: string +} + +/** A pasted credential one provider's spend is read with; the table is one row per org and provider. */ +export const costConnectionSecretSlot: SecretSlot = { + name: "cost_connections.secret", + binding: (ref) => [ref.orgId, ref.provider], +} + export const planetScaleWebhookSecretSlot: SecretSlot = { name: "planetscale_connections.webhook_secret", binding: orgRowBinding, diff --git a/packages/backend/src/services/cost/CloudflareCostConnectionService.test.ts b/packages/backend/src/services/cost/CloudflareCostConnectionService.test.ts new file mode 100644 index 0000000000..806358c0fe --- /dev/null +++ b/packages/backend/src/services/cost/CloudflareCostConnectionService.test.ts @@ -0,0 +1,320 @@ +import { afterEach, assert, describe, it } from "@effect/vitest" +import { OrgId, UserId } from "@maple/domain/http" +import { DateTime, Effect, Layer, Schema, Array as Arr, String as Str } from "effect" +import { TestClock } from "effect/testing" +import { cleanupTestDbs, createTestDb, queryFirstRow, type TestDb } from "@maple/backend/testing/test-pglite" +import { CloudflareCostConnectionService } from "./CloudflareCostConnectionService" +import { + FAKE_ACCOUNT, + type FakeCloudflare, + fakeCloudflareDeps, + makeFakeCloudflare, +} from "./providers/cloudflare/fake-cloudflare" + +const trackedDbs: TestDb[] = [] +afterEach(() => cleanupTestDbs(trackedDbs)) + +const ORG = Schema.decodeSync(OrgId)("org_cost") +const USER = Schema.decodeSync(UserId)("user_cost") +const TOKEN = "cfut_0123456789abcdefghijABCDEFGHIJ0123456789" +const OTHER_TOKEN = "cfat_9876543210zyxwvutsrqZYXWVUTSRQ9876543210" +const ACCOUNT_PATH = `/client/v4/accounts/${FAKE_ACCOUNT.id}` +const NOW = DateTime.toEpochMillis(DateTime.makeUnsafe("2026-11-11T06:00:00Z")) +const VALIDATION = "@maple/http/cost-connections/CostConnectionValidationError" +const PERSISTENCE = "@maple/http/cost-connections/CostConnectionPersistenceError" + +const run = ( + testDb: TestDb, + world: FakeCloudflare, + effect: Effect.Effect, +) => + Effect.andThen(TestClock.setTime(NOW), effect).pipe( + Effect.provide( + Layer.effect(CloudflareCostConnectionService, CloudflareCostConnectionService.make).pipe( + Layer.provide(fakeCloudflareDeps(testDb, world)), + ), + ), + ) + +const set = (token: string) => CloudflareCostConnectionService.use((service) => service.set(ORG, USER, token)) +const remove = CloudflareCostConnectionService.use((service) => service.remove(ORG)) + +interface Stored { + readonly id: string + readonly provider: string + readonly secret_ciphertext: string + readonly account_id: string + readonly account_label: string +} + +const storedToken = (testDb: TestDb) => + Effect.promise(() => + queryFirstRow( + testDb, + "SELECT id, provider, secret_ciphertext, account_id, account_label FROM cost_connections WHERE org_id = 'org_cost'", + ), + ) + +interface Source { + readonly id: string + readonly connection_ref: string + readonly account_id: string + readonly enabled: boolean +} + +const source = (testDb: TestDb) => + Effect.promise(() => + queryFirstRow( + testDb, + "SELECT id, connection_ref, account_id, enabled FROM cost_sources WHERE org_id = 'org_cost' AND provider = 'cloudflare'", + ), + ) + +describe("CloudflareCostConnectionService", () => { + it.effect("checks a token with Cloudflare, seals it and makes the cost source", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + const saved = yield* set(` ${TOKEN} `) + assert.deepStrictEqual(saved, { accountId: FAKE_ACCOUNT.id, accountLabel: "Example Account" }) + // Which account, then whether it has billing data, then proof the token reads it. + assert.deepStrictEqual(world.urls, [ + "/client/v4/accounts?per_page=50", + `${ACCOUNT_PATH}/billable-usage/info`, + `${ACCOUNT_PATH}/billable-usage?from=2026-10-10&to=2026-11-11`, + ]) + assert.deepStrictEqual(world.authorizations, Arr.replicate(`Bearer ${TOKEN}`, 3)) + + const stored = yield* storedToken(testDb) + assert.isFalse(Str.includes(TOKEN)(stored?.secret_ciphertext ?? TOKEN)) + assert.deepStrictEqual( + [stored?.provider, stored?.account_id, stored?.account_label], + ["cloudflare", FAKE_ACCOUNT.id, "Example Account"], + ) + const made = yield* source(testDb) + // The source is named after the token's account, which a later token keeps. + assert.deepStrictEqual( + [made?.connection_ref, made?.account_id, made?.enabled], + [stored?.id, FAKE_ACCOUNT.id, true], + ) + }), + ) + }) + + it.effect("refuses a token that reads several accounts or none, before asking for billing", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ + accounts: [FAKE_ACCOUNT, { id: "fedcba9876543210fedcba9876543210", name: "Second Account" }], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const several = yield* Effect.flip(set(TOKEN)) + assert.strictEqual(several._tag, VALIDATION) + assert.strictEqual( + several.message, + "This token can read 2 accounts. Create one limited to a single account.", + ) + world.accounts = [] + assert.include((yield* Effect.flip(set(TOKEN))).message, "can read 0 accounts") + assert.deepStrictEqual(world.urls, Arr.replicate("/client/v4/accounts?per_page=50", 2)) + assert.isUndefined(yield* storedToken(testDb)) + assert.isUndefined(yield* source(testDb)) + }), + ) + }) + + it.effect("refuses a token Cloudflare does not know, and one without billing access", () => { + const testDb = createTestDb(trackedDbs) + // Cloudflare answers 403 for an unknown token on the account listing. + const world = makeFakeCloudflare({ accountsStatus: 403 }) + return run( + testDb, + world, + Effect.gen(function* () { + const unknown = yield* Effect.flip(set(TOKEN)) + assert.deepStrictEqual( + [unknown._tag, unknown.message], + [VALIDATION, "Cloudflare rejected this token."], + ) + world.accountsStatus = 401 + assert.strictEqual( + (yield* Effect.flip(set(TOKEN))).message, + "Cloudflare rejected this token.", + ) + + world.accountsStatus = 200 + world.billingStatus = 403 + const noBilling = yield* Effect.flip(set(TOKEN)) + assert.strictEqual(noBilling._tag, VALIDATION) + assert.include(noBilling.message, "Billing: Read permission") + assert.isUndefined(yield* storedToken(testDb)) + }), + ) + }) + + it.effect("refuses an account Cloudflare has no usage-based billing data for", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ covered: false }) + return run( + testDb, + world, + Effect.gen(function* () { + const error = yield* Effect.flip(set(TOKEN)) + assert.strictEqual(error._tag, VALIDATION) + assert.include(error.message, "no usage-based billing data") + assert.isUndefined(yield* storedToken(testDb)) + assert.isUndefined(yield* source(testDb)) + }), + ) + }) + + it.effect("reports a Cloudflare outage as a retryable failure, not as a bad token", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ billingStatus: 503 }) + return run( + testDb, + world, + Effect.gen(function* () { + const error = yield* Effect.flip(set(TOKEN)) + assert.strictEqual(error._tag, PERSISTENCE) + world.billingStatus = 200 + world.usage = "maintenance" + assert.strictEqual((yield* Effect.flip(set(TOKEN)))._tag, PERSISTENCE) + assert.isUndefined(yield* storedToken(testDb)) + }), + ) + }) + + it.effect("never sends text that cannot be a token, nor any token of an org outside the rollout", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + const refusals = yield* Effect.forEach( + ["", " ", "two words", `cfut_${Str.repeat(300)("a")}`, "line\nbreak"], + (text) => Effect.map(Effect.flip(set(text)), (error) => error._tag), + ) + assert.deepStrictEqual(refusals, Arr.replicate(VALIDATION, 5)) + + world.cloudCost = false + const outside = yield* Effect.flip(set(TOKEN)) + assert.strictEqual(outside._tag, "@maple/http/errors/CloudCostDisabledError") + assert.deepStrictEqual(world.urls, []) + }), + ) + }) + + it.effect("keeps the connection and its source when the account's token is replaced", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + yield* set(TOKEN) + const first = yield* storedToken(testDb) + const firstSource = yield* source(testDb) + + // The account was renamed since: the label follows, the id is what is compared. + world.accounts = [{ id: FAKE_ACCOUNT.id, name: "Renamed Account" }] + const replaced = yield* set(OTHER_TOKEN) + assert.strictEqual(replaced.accountLabel, "Renamed Account") + const second = yield* storedToken(testDb) + assert.strictEqual(second?.id, first?.id) + assert.strictEqual(second?.account_label, "Renamed Account") + assert.notStrictEqual(second?.secret_ciphertext, first?.secret_ciphertext) + assert.deepStrictEqual(yield* source(testDb), firstSource) + }), + ) + }) + + it.effect( + "refuses a token of another Cloudflare account while a source exists, and keeps the saved one", + () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + yield* set(TOKEN) + const first = yield* storedToken(testDb) + + // The other account's charges would overwrite the days the source already holds. + world.accounts = [{ id: "fedcba9876543210fedcba9876543210", name: "Second Account" }] + const refused = yield* Effect.flip(set(OTHER_TOKEN)) + assert.strictEqual(refused._tag, VALIDATION) + assert.include(refused.message, "another Cloudflare account") + assert.deepStrictEqual(yield* storedToken(testDb), first) + }), + ) + }, + ) + + it.effect("makes a source held for its old token's failure due again once the token is replaced", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + const held = Effect.promise(() => + queryFirstRow<{ + error: string | null + reason: string | null + held: boolean + failures: number + leased: boolean + }>( + testDb, + `SELECT last_error AS error, last_error_reason AS reason, next_attempt_at IS NOT NULL AS held, + consecutive_failures AS failures, lease_until IS NOT NULL AS leased + FROM cost_sources WHERE org_id = 'org_cost'`, + ), + ) + return run( + testDb, + world, + Effect.gen(function* () { + yield* set(TOKEN) + yield* Effect.promise(() => + testDb.pglite.query( + // A pull on the old token is in flight: it holds the source's lease. + `UPDATE cost_sources SET last_error = 'Cloudflare rejected the saved token.', + last_error_reason = 'auth', next_attempt_at = now() + interval '6 hours', + consecutive_failures = 3, lease_until = now() + interval '20 minutes'`, + ), + ) + assert.strictEqual((yield* held)?.held, true) + yield* set(OTHER_TOKEN) + // The lease goes too: the old pull's failure, arriving later, finds the claim gone and writes nothing. + assert.deepStrictEqual(yield* held, { + error: null, + reason: null, + held: false, + failures: 0, + leased: false, + }) + }), + ) + }) + + it.effect("removes the token and its source together, without needing the rollout", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + yield* set(TOKEN) + world.cloudCost = false + yield* remove + assert.isUndefined(yield* storedToken(testDb)) + assert.isUndefined(yield* source(testDb)) + }), + ) + }) +}) diff --git a/packages/backend/src/services/cost/CloudflareCostConnectionService.ts b/packages/backend/src/services/cost/CloudflareCostConnectionService.ts new file mode 100644 index 0000000000..e679589788 --- /dev/null +++ b/packages/backend/src/services/cost/CloudflareCostConnectionService.ts @@ -0,0 +1,170 @@ +// The Cloudflare API token an org's Cloudflare charges are read with: checked with Cloudflare, then +// kept in the shared cost connection store. Saving it makes the org's Cloudflare cost source, +// replacing it keeps the source and its history, removing it removes the source. +import { + CLOUDFLARE_COST_TOKEN_MAX_LENGTH, + CloudCostDisabledError, + CostConnectionPersistenceError, + CostConnectionValidationError, + type OrgId, + type UserId, +} from "@maple/domain/http" +import { Clock, Context, Duration, Effect, Layer, Redacted, String as Str } from "effect" +import { FetchHttpClient, HttpClient } from "effect/http" +import { OrganizationFeatureFlagsService } from "@maple/backend/services/org/OrganizationFeatureFlagsService" +import { CostConnectionStore, type CostConnectionStoreError } from "./CostConnectionStore" +import * as CloudflareBilling from "./providers/cloudflare/cloudflare-billing-api" +import { addDays, utcToday } from "./sync-plan" + +const VALIDATE_TIMEOUT = Duration.seconds(20) + +const NO_BILLING_ACCESS = + "This token cannot read the account's billing. Create a custom token with the Billing: Read permission." + +const refused = (message: string) => new CostConnectionValidationError({ message }) + +/** Which call of the check failed decides what a 403 means: the token itself, or its permission. */ +const toTokenError = + (step: "accounts" | "billing") => + ( + error: CloudflareBilling.CloudflareBillingError, + ): CostConnectionValidationError | CostConnectionPersistenceError => { + // Listing accounts answers 403 for a token Cloudflare does not know, where billing answers 401. + if (error.kind === "unauthorized" || (error.kind === "forbidden" && step === "accounts")) { + return refused("Cloudflare rejected this token.") + } + if (error.kind === "forbidden") return refused(NO_BILLING_ACCESS) + return new CostConnectionPersistenceError({ + message: "Could not check this token with Cloudflare. Try again.", + }) + } + +const toPersistenceError = (error: CostConnectionStoreError) => + new CostConnectionPersistenceError({ message: error.message }) + +/** Printable and unbroken, as every Cloudflare token is: nothing else is ever sent as one. */ +const couldBeToken = (token: string): boolean => + token.length <= CLOUDFLARE_COST_TOKEN_MAX_LENGTH && /^[\x21-\x7e]+$/.test(token) + +export interface CloudflareCostConnectionServiceApi { + /** Saves the token and answers the Cloudflare account it reads. */ + readonly set: ( + orgId: OrgId, + userId: UserId, + apiToken: string, + ) => Effect.Effect< + { readonly accountId: string; readonly accountLabel: string }, + CostConnectionPersistenceError | CostConnectionValidationError | CloudCostDisabledError + > + readonly remove: (orgId: OrgId) => Effect.Effect +} + +export class CloudflareCostConnectionService extends Context.Service< + CloudflareCostConnectionService, + CloudflareCostConnectionServiceApi +>()("@maple/backend/services/cost/CloudflareCostConnectionService", { + make: Effect.gen(function* () { + const connections = yield* CostConnectionStore + const featureFlags = yield* OrganizationFeatureFlagsService + const httpClient = yield* HttpClient.HttpClient + + const asked = ( + step: "accounts" | "billing", + call: Effect.Effect, + ) => Effect.mapError(call, toTokenError(step)) + + /** + * The one account the token reads, once it has proved it can read that account's billing: + * a token that cannot would save and then fail every sync. + */ + const validateWithCloudflare = Effect.fn("CloudflareCostConnectionService.validateWithCloudflare")( + function* (token: Redacted.Redacted) { + const { accounts, total } = yield* asked( + "accounts", + CloudflareBilling.listAccounts(httpClient, token), + ) + const [account] = accounts + // A token for several accounts would make one source of whichever is listed first. + if (account === undefined || total !== 1) { + return yield* refused( + `This token can read ${total} accounts. Create one limited to a single account.`, + ) + } + const info = yield* asked( + "billing", + CloudflareBilling.getBillableUsageInfo(httpClient, token, account.id), + ) + if (!info.covered) { + return yield* refused(CloudflareBilling.NOT_COVERED) + } + const now = yield* Clock.currentTimeMillis + // Yesterday is the newest day Cloudflare serves. + const yesterday = addDays(utcToday(now), -1) + yield* asked( + "billing", + CloudflareBilling.getBillableUsage(httpClient, token, account.id, { + startDay: yesterday, + endDay: yesterday, + }), + ) + return { accountId: account.id, accountLabel: account.name } + }, + ) + + const set = Effect.fn("CloudflareCostConnectionService.set")(function* ( + orgId: OrgId, + userId: UserId, + rawToken: string, + ) { + yield* Effect.annotateCurrentSpan({ orgId }) + const flags = yield* featureFlags.flags(orgId) + if (!flags.cloudCost) { + return yield* new CloudCostDisabledError({ + message: "Cloud cost is not enabled for this organization.", + }) + } + const apiToken = Str.trim(rawToken) + if (!couldBeToken(apiToken)) { + return yield* refused("That is not a Cloudflare API token.") + } + const secret = Redacted.make(apiToken) + const account = yield* validateWithCloudflare(secret).pipe( + Effect.timeoutOrElse({ + duration: VALIDATE_TIMEOUT, + orElse: () => + Effect.fail( + new CostConnectionPersistenceError({ + message: "Could not reach Cloudflare to check this token. Try again.", + }), + ), + }), + ) + const saved = yield* connections + .save({ orgId, userId, provider: "cloudflare", secret, ...account }) + .pipe(Effect.mapError(toPersistenceError)) + if (saved === "other_account") { + return yield* refused( + "This token reads another Cloudflare account than the source does. Remove the source first to switch accounts.", + ) + } + return account + }) + + const remove = Effect.fn("CloudflareCostConnectionService.remove")(function* (orgId: OrgId) { + yield* Effect.annotateCurrentSpan({ orgId }) + yield* connections.remove(orgId, "cloudflare").pipe(Effect.mapError(toPersistenceError)) + }) + + return { set, remove } satisfies CloudflareCostConnectionServiceApi + }), +}) { + static readonly layer = Layer.effect(this, this.make).pipe( + Layer.provide( + Layer.mergeAll( + FetchHttpClient.layer, + OrganizationFeatureFlagsService.layer, + CostConnectionStore.layer, + ), + ), + ) +} diff --git a/packages/backend/src/services/cost/CostConnectionStore.ts b/packages/backend/src/services/cost/CostConnectionStore.ts new file mode 100644 index 0000000000..8dcad146d4 --- /dev/null +++ b/packages/backend/src/services/cost/CostConnectionStore.ts @@ -0,0 +1,217 @@ +// Where a pasted cost credential lives: sealed in `cost_connections`, one per org and provider. +// Saving one makes the provider's cost source, replacing it keeps the source and its history, +// removing it removes the source. A provider checks its credential with its vendor before saving. +import * as PG from "@maple-dev/effect-orm/postgres" +import { CostConnections, CostSources } from "@maple/db/tables" +import type { OrgId, UserId } from "@maple/primitives" +import { Clock, Context, Effect, Layer, Option, Redacted, Schema } from "effect" +import { randomUUID } from "node:crypto" +import { Database } from "@maple/backend/platform/DatabaseLive" +import { makeDbExecute } from "@maple/backend/platform/db-execute" +import { Env } from "@maple/backend/platform/Env" +import { openSecret, sealSecret, secretKeyringFromEnv } from "@maple/backend/platform/SecretBox" +import { costConnectionSecretSlot } from "@maple/backend/platform/secret-slots" +import { CostProviderError, type CostPullSource } from "./CostProvider" +import { type CostConnectionProvider, reconcileCostConnectionSources } from "./cost-connection-sources" + +/** `secret`: the deployment's key could not seal or open the value. `database`: Postgres failed. */ +export class CostConnectionStoreError extends Schema.TaggedError()( + "@maple/backend/cost/CostConnectionStoreError", + { + message: Schema.String, + reason: Schema.Literals(["database", "secret"]), + }, +) {} + +const secretFailed = (message: string) => new CostConnectionStoreError({ message, reason: "secret" }) + +/** A failed `load`, as what the sync does next: a credential that will not open needs replacing. */ +export const loadFailureOf = + (provider: CostConnectionProvider) => + (error: CostConnectionStoreError): CostProviderError => + new CostProviderError({ + message: error.reason === "secret" ? error.message : "Cost connection database error", + provider, + reason: error.reason === "secret" ? "auth" : "upstream", + requests: 0, + }) + +export interface CostConnectionInput { + readonly orgId: OrgId + readonly userId: UserId + readonly provider: CostConnectionProvider + readonly secret: Redacted.Redacted + /** The vendor's account the credential reads, as the vendor answered when it was checked. */ + readonly accountId: string + readonly accountLabel: string +} + +export interface CostConnectionCredential { + readonly secret: Redacted.Redacted + readonly accountId: string +} + +export interface CostConnectionStoreApi { + /** + * Seals and saves the credential, and makes the provider's cost source. `other_account`: the org's + * source reads another account, whose days this one's spend would overwrite, so nothing is written. + */ + readonly save: ( + input: CostConnectionInput, + ) => Effect.Effect<"saved" | "other_account", CostConnectionStoreError> + /** Deletes the credential and its cost source. */ + readonly remove: ( + orgId: OrgId, + provider: CostConnectionProvider, + ) => Effect.Effect + /** The credential a source's pull reads with; none when the connection is gone or another org's. */ + readonly load: ( + provider: CostConnectionProvider, + source: CostPullSource, + ) => Effect.Effect, CostConnectionStoreError> +} + +export class CostConnectionStore extends Context.Service()( + "@maple/backend/services/cost/CostConnectionStore", + { + make: Effect.gen(function* () { + const database = yield* Database + const env = yield* Env + // Read at first use, not here: an unusable key must not stop the sync of other providers. + const keyring = yield* Effect.cached(secretKeyringFromEnv(env, secretFailed)) + const dbExecute = makeDbExecute( + database, + "CostConnectionStore", + (error) => new CostConnectionStoreError({ message: error.message, reason: "database" }), + ) + + const save = Effect.fn("CostConnectionStore.save")(function* (input: CostConnectionInput) { + const { orgId, provider, accountId } = input + yield* Effect.annotateCurrentSpan({ orgId, "maple.cost.provider": provider }) + const secrets = yield* keyring + // Bound to the org and the provider: a row copied onto another of either no longer opens. + const sealed = yield* sealSecret( + secrets, + costConnectionSecretSlot, + { orgId, provider }, + Redacted.value(input.secret), + secretFailed, + ) + const now = yield* Clock.currentTimeMillis + const next = { + secretCiphertext: sealed.ciphertext, + secretIv: sealed.iv, + secretTag: sealed.tag, + accountLabel: input.accountLabel, + updatedAt: now, + updatedBy: input.userId, + } + const saved = yield* dbExecute((db) => + db.transaction( + Effect.gen(function* () { + // A replaced credential keeps the row's id, which the cost source points at. + // Another account's credential matches no row. + const [row] = yield* db.run( + PG.insertInto(CostConnections) + .values({ + id: randomUUID(), + orgId, + provider, + accountId, + createdAt: now, + ...next, + }) + .onConflictDoUpdate({ + target: ["orgId", "provider"], + set: next, + where: ($) => $.accountId.eq(accountId), + }) + .returning(($) => ({ id: $.id })), + ) + if (row === undefined) return false + // The source shows on the Cost page at once, not at the next sync tick. + yield* reconcileCostConnectionSources(db, now, provider, orgId) + // A source held for its old credential's failure is due again with the new one. A + // pull still in flight on the old one loses its claim, so its failure writes no hold. + yield* db.run( + PG.update(CostSources) + .set({ + leaseUntil: null, + lastError: null, + lastErrorReason: null, + nextAttemptAt: null, + consecutiveFailures: 0, + updatedAt: now, + }) + .where(($) => [ + $.orgId.eq(orgId), + $.provider.eq(provider), + $.connectionRef.eq(row.id), + ]), + ) + return true + }), + ), + ) + return saved ? "saved" : "other_account" + }) + + const remove = Effect.fn("CostConnectionStore.remove")(function* ( + orgId: OrgId, + provider: CostConnectionProvider, + ) { + yield* Effect.annotateCurrentSpan({ orgId, "maple.cost.provider": provider }) + const now = yield* Clock.currentTimeMillis + yield* dbExecute((db) => + db.transaction( + Effect.gen(function* () { + yield* db.run( + PG.deleteFrom(CostConnections).where(($) => [ + $.orgId.eq(orgId), + $.provider.eq(provider), + ]), + ) + yield* reconcileCostConnectionSources(db, now, provider, orgId) + }), + ), + ) + }) + + const load = Effect.fn("CostConnectionStore.load")(function* ( + provider: CostConnectionProvider, + source: CostPullSource, + ) { + // By org and provider as well as id: a source never reads another org's credential. + const [row] = yield* dbExecute((db) => + db.run( + PG.from(CostConnections) + .select() + .where(($) => [ + $.orgId.eq(source.orgId), + $.provider.eq(provider), + $.id.eq(source.connectionRef), + ]) + .limit(1), + ), + ) + if (row === undefined) return Option.none() + const secrets = yield* keyring + const secret = yield* openSecret( + secrets, + costConnectionSecretSlot, + { orgId: row.orgId, provider: row.provider }, + { ciphertext: row.secretCiphertext, iv: row.secretIv, tag: row.secretTag }, + secretFailed, + ) + return Option.some({ + secret: Redacted.make(secret), + accountId: row.accountId, + }) + }) + + return { save, remove, load } satisfies CostConnectionStoreApi + }), + }, +) { + static readonly layer = Layer.effect(this, this.make) +} diff --git a/packages/backend/src/services/cost/CostSyncService.test.ts b/packages/backend/src/services/cost/CostSyncService.test.ts index ddfe364642..96bef50ff4 100644 --- a/packages/backend/src/services/cost/CostSyncService.test.ts +++ b/packages/backend/src/services/cost/CostSyncService.test.ts @@ -28,6 +28,7 @@ import { WarehouseQueryService } from "@maple/backend/services/warehouse/Warehou import { type CostProviderAdapter, CostProviderError, type CostPullRow } from "./CostProvider" import { CostSyncService } from "./CostSyncService" import { AwsCostProvider } from "./providers/aws/AwsCostProvider" +import { CloudflareCostProvider } from "./providers/cloudflare/CloudflareCostProvider" import { OpenRouterCostProvider } from "./providers/openrouter/OpenRouterCostProvider" import { BACKFILL_REQUEST_BUDGET, daysOfWindow, MONTHLY_REQUEST_BUDGET } from "./sync-plan" @@ -43,6 +44,7 @@ const CONNECTION_A = "0190f2b3-0000-7000-8000-0000000000a1" const CONNECTION_B = "0190f2b3-0000-7000-8000-0000000000b1" const CONNECTION_C = "0190f2b3-0000-7000-8000-0000000000c1" const OPENROUTER_KEY_A = "0190f2b3-0000-7000-8000-0000000000d1" +const CLOUDFLARE_TOKEN_A = "0190f2b3-0000-7000-8000-0000000000e1" /** What every fake pull reports as billable. */ const REQUESTS_PER_PULL = 2 @@ -240,6 +242,7 @@ const harness = (options: FakeProviderOptions = {}) => { Layer.mergeAll( Layer.succeed(AwsCostProvider, adapterOf("aws")), Layer.succeed(OpenRouterCostProvider, adapterOf("openrouter")), + Layer.succeed(CloudflareCostProvider, adapterOf("cloudflare")), warehouse, featureFlags, ), @@ -282,6 +285,18 @@ const harness = (options: FakeProviderOptions = {}) => { VALUES ($1, $2, 'sealed', 'iv', 'tag', 'sk-or-v1-abc...123', 'user_account_a', now(), now(), 'user_a')`, [key.id, key.orgId ?? "org_a"], ) + /** An org's pasted credential for a provider that keeps it in `cost_connections`. */ + const saveCostConnection = (connection: { + readonly id: string + readonly provider: string + readonly orgId?: string + }) => + sql( + `INSERT INTO cost_connections + (id, org_id, provider, secret_ciphertext, secret_iv, secret_tag, account_id, account_label, created_at, updated_at, updated_by) + VALUES ($1, $2, $3, 'sealed', 'iv', 'tag', 'account_a', 'Example Account', now(), now(), 'user_a')`, + [connection.id, connection.orgId ?? "org_a", connection.provider], + ) const sourceRefs = Effect.promise(() => testDb.pglite.query<{ org: string; ref: string; account: string }>( "SELECT org_id AS org, connection_ref AS ref, account_id AS account FROM cost_sources ORDER BY org_id, connection_ref", @@ -322,6 +337,7 @@ const harness = (options: FakeProviderOptions = {}) => { sql, connect, saveOpenRouterKey, + saveCostConnection, sourceRefs, sourceState, sourceStateOf, @@ -430,6 +446,84 @@ describe("CostSyncService OpenRouter sources", () => { }) }) +describe("CostSyncService sources of a pasted credential", () => { + it.effect( + "makes a Cloudflare source for a saved token and pulls up to yesterday, then walks a year back", + () => { + const h = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(NOW) + yield* h.saveCostConnection({ id: CLOUDFLARE_TOKEN_A, provider: "cloudflare" }) + const summary = yield* poll + assert.deepStrictEqual(yield* h.sourceRefs, [["org_a", CLOUDFLARE_TOKEN_A, "account_a"]]) + assert.deepStrictEqual([summary.due, summary.synced], [1, 1]) + // Last month and this one through yesterday: Cloudflare has no rows for today. + assert.deepStrictEqual(h.windows()[0], ["2026-10-01", "2026-11-10"]) + assert.isTrue(Arr.every(h.writtenRows(), (row) => row.Provider === "cloudflare")) + + // History then walks down a month per step and stops at the eleventh month back. + yield* Effect.forEach(Arr.range(1, 12), () => + Effect.andThen(TestClock.adjust("5 minutes"), poll), + ) + const state = yield* h.sourceStateOf("org_a", CLOUDFLARE_TOKEN_A) + assert.deepStrictEqual( + [state?.watermark, state?.backfill, state?.error], + ["2026-11-10", "2025-12-01", null], + ) + assert.deepStrictEqual(Arr.last(h.windows()), Option.some(["2025-12-01", "2025-12-31"])) + assert.isTrue(Arr.every(h.windows(), ([startDay]) => startDay >= "2025-12-01")) + }).pipe(Effect.provide(h.layer)) + }, + ) + + it.effect("removes the source with its token, and leaves the org's other sources alone", () => { + const h = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(NOW) + yield* h.saveCostConnection({ id: CLOUDFLARE_TOKEN_A, provider: "cloudflare" }) + yield* h.saveOpenRouterKey({ id: OPENROUTER_KEY_A }) + yield* h.connect({ id: CONNECTION_A }) + yield* poll + assert.deepStrictEqual(yield* h.sourceRefs, [ + ["org_a", CONNECTION_A, "123456789012"], + ["org_a", OPENROUTER_KEY_A, "user_account_a"], + ["org_a", CLOUDFLARE_TOKEN_A, "account_a"], + ]) + + yield* h.sql("DELETE FROM cost_connections") + yield* TestClock.adjust("1 hour") + yield* poll + assert.deepStrictEqual(yield* h.sourceRefs, [ + ["org_a", CONNECTION_A, "123456789012"], + ["org_a", OPENROUTER_KEY_A, "user_account_a"], + ]) + }).pipe(Effect.provide(h.layer)) + }) + + it.effect( + "makes one source per org, and none for a credential of a provider it does not reconcile", + () => { + const h = harness() + return Effect.gen(function* () { + yield* TestClock.setTime(NOW) + yield* h.saveCostConnection({ id: CLOUDFLARE_TOKEN_A, provider: "cloudflare" }) + yield* h.saveCostConnection({ + id: "0190f2b3-0000-7000-8000-0000000000e2", + provider: "cloudflare", + orgId: "org_b", + }) + // A row for a provider that keeps its credential elsewhere is not this table's to reconcile. + yield* h.saveCostConnection({ id: "0190f2b3-0000-7000-8000-0000000000e3", provider: "aws" }) + yield* poll + assert.deepStrictEqual(yield* h.sourceRefs, [ + ["org_a", CLOUDFLARE_TOKEN_A, "account_a"], + ["org_b", "0190f2b3-0000-7000-8000-0000000000e2", "account_a"], + ]) + }).pipe(Effect.provide(h.layer)) + }, + ) +}) + describe("CostSyncService sources", () => { it.effect("makes a source only for a verified connection that asks for cost and is granted it", () => { const h = harness() diff --git a/packages/backend/src/services/cost/CostSyncService.ts b/packages/backend/src/services/cost/CostSyncService.ts index c978694bca..81edfd7eed 100644 --- a/packages/backend/src/services/cost/CostSyncService.ts +++ b/packages/backend/src/services/cost/CostSyncService.ts @@ -18,6 +18,7 @@ import { Schedule, Schema, Array as Arr, + Number as Num, String as Str, } from "effect" import { Database } from "@maple/backend/platform/DatabaseLive" @@ -29,8 +30,10 @@ import { leaseClaimable, leaseStillOurs } from "../integrations/lease" import { pollFleet } from "../integrations/poll-fleet" import type { CostProviderAdapter, CostProviderError } from "./CostProvider" import { reconcileAwsCostSources } from "./aws-cost-sources" +import { COST_CONNECTION_PROVIDERS, reconcileCostConnectionSources } from "./cost-connection-sources" import { reconcileOpenRouterCostSources } from "./openrouter-cost-sources" import { AwsCostProvider } from "./providers/aws/AwsCostProvider" +import { CloudflareCostProvider } from "./providers/cloudflare/CloudflareCostProvider" import { OpenRouterCostProvider } from "./providers/openrouter/OpenRouterCostProvider" import { advanceCostSync, @@ -165,6 +168,7 @@ export class CostSyncService extends Context.Service const dbExecute = makeDbExecute( database, @@ -204,9 +208,26 @@ export class CostSyncService extends Context.Service + Effect.forEach(COST_CONNECTION_PROVIDERS, (provider) => + reconcileCostConnectionSources(db, now, provider), + ), + ) + yield* Effect.annotateCurrentSpan({ + "maple.cost.sources_created": Num.sumAll(Arr.map(reconciled, (entry) => entry.created)), + "maple.cost.sources_removed": Num.sumAll(Arr.map(reconciled, (entry) => entry.removed)), + }) + }) + + /** One function per connection table; each owns the sources of its own. */ const reconcile = (now: number) => - Effect.all([reconcileAws(now), reconcileOpenRouter(now)], { discard: true }) + Effect.all([reconcileAws(now), reconcileOpenRouter(now), reconcileConnections(now)], { + discard: true, + }) /** Enabled sources with a pull due and no backoff, healthy ones first. */ const loadDue = (now: number) => { @@ -668,6 +689,7 @@ export class CostSyncService extends Context.Service +export type CostConnectionProvider = (typeof COST_CONNECTION_PROVIDERS)[number] + +/** Sources one run creates and removes, each. */ +const RECONCILE_LIMIT = 200 + +/** Every org's sources of `provider`, or only `orgId`'s. Joined by org, so a ref never crosses orgs. */ +export const reconcileCostConnectionSources = Effect.fnUntraced(function* ( + db: MapleDb, + now: number, + provider: CostConnectionProvider, + orgId?: OrgId, +) { + const missing = yield* db.run( + PG.from(CostConnections, "c") + .leftJoin(CostSources, "s", (connection, source) => + source.orgId + .eq(connection.orgId) + .and(source.provider.eq(provider)) + .and(source.connectionRef.eq(connection.id)), + ) + .select(($) => ({ id: $.id, orgId: $.orgId, accountId: $.accountId })) + .where(($) => [ + $.provider.eq(provider), + $.s.id.isNull(), + ...(orgId === undefined ? [] : [$.orgId.eq(orgId)]), + ]) + .orderBy(["id", "asc"]) + .limit(RECONCILE_LIMIT), + ) + const created = yield* Effect.forEach(missing, (connection) => + db.run( + PG.insertInto(CostSources) + .values({ + id: CostSourceId.make(randomUUID()), + orgId: connection.orgId, + provider, + connectionRef: connection.id, + accountId: connection.accountId, + createdAt: now, + updatedAt: now, + }) + // Another run made it first. + .onConflictDoNothing() + .returning(($) => ({ id: $.id })), + ), + ) + + const orphaned = yield* db.run( + PG.from(CostSources, "s") + .leftJoin(CostConnections, "c", (source, connection) => + source.orgId + .eq(connection.orgId) + .and(source.connectionRef.eq(connection.id)) + .and(connection.provider.eq(provider)), + ) + .select(($) => ({ id: $.id })) + .where(($) => [ + $.provider.eq(provider), + $.c.id.isNull(), + ...(orgId === undefined ? [] : [$.orgId.eq(orgId)]), + ]) + .orderBy(["id", "asc"]) + .limit(RECONCILE_LIMIT), + ) + const removed = Arr.isReadonlyArrayNonEmpty(orphaned) + ? yield* db.run( + PG.deleteFrom(CostSources) + .where(($) => [ + $.provider.eq(provider), + PG.inList( + $.id, + Arr.map(orphaned, (source) => source.id), + ), + ]) + .returning(($) => ({ id: $.id })), + ) + : [] + + return { created: Arr.flatten(created).length, removed: removed.length } +}) diff --git a/packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.test.ts b/packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.test.ts new file mode 100644 index 0000000000..bdf861f4ed --- /dev/null +++ b/packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.test.ts @@ -0,0 +1,460 @@ +import { afterEach, assert, describe, it } from "@effect/vitest" +import { OrgId, UserId } from "@maple/domain/http" +import { DateTime, Effect, Layer, Schema, Array as Arr } from "effect" +import { TestClock } from "effect/testing" +import { cleanupTestDbs, createTestDb, queryFirstRow, type TestDb } from "@maple/backend/testing/test-pglite" +import { CloudflareCostConnectionService } from "../../CloudflareCostConnectionService" +import type { CostProviderError } from "../../CostProvider" +import { CloudflareCostProvider } from "./CloudflareCostProvider" +import { + FAKE_ACCOUNT, + type FakeCloudflare, + fakeCloudflareDeps, + makeFakeCloudflare, + usageRow, +} from "./fake-cloudflare" + +const trackedDbs: TestDb[] = [] +afterEach(() => cleanupTestDbs(trackedDbs)) + +const ORG = Schema.decodeSync(OrgId)("org_cost") +const OTHER_ORG = Schema.decodeSync(OrgId)("org_other") +const USER = Schema.decodeSync(UserId)("user_cost") +const TOKEN = "cfut_0123456789abcdefghijABCDEFGHIJ0123456789" +const ACCOUNT_PATH = `/client/v4/accounts/${FAKE_ACCOUNT.id}` +const WINDOW = { startDay: "2026-10-20", endDay: "2026-11-10" } +/** The pull runs the morning after the window's last day. */ +const NOW = DateTime.toEpochMillis(DateTime.makeUnsafe("2026-11-11T06:00:00Z")) + +const run = ( + testDb: TestDb, + world: FakeCloudflare, + effect: Effect.Effect, +) => + effect.pipe( + Effect.provide( + Layer.mergeAll( + Layer.effect(CloudflareCostProvider, CloudflareCostProvider.make), + Layer.effect(CloudflareCostConnectionService, CloudflareCostConnectionService.make), + ).pipe(Layer.provide(fakeCloudflareDeps(testDb, world))), + ), + ) + +/** Saves `TOKEN` for `ORG` and answers the connection's id, with the fake's call log emptied. */ +const saveToken = (testDb: TestDb, world: FakeCloudflare) => + Effect.gen(function* () { + yield* TestClock.setTime(NOW) + yield* CloudflareCostConnectionService.use((service) => service.set(ORG, USER, TOKEN)) + const row = yield* Effect.promise(() => + queryFirstRow<{ id: string }>(testDb, "SELECT id FROM cost_connections"), + ) + world.urls.length = 0 + world.authorizations.length = 0 + return row?.id ?? "" + }) + +const pull = (orgId: OrgId, connectionRef: string, window = WINDOW) => + CloudflareCostProvider.use((provider) => provider.pull({ orgId, connectionRef }, window)) + +const reasonOf = (error: CostProviderError) => [error.reason, error.requests, error.provider] + +describe("CloudflareCostProvider", () => { + it.effect( + "reads usage from before the window's billing period started, and keeps the window's days", + () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ + usage: [ + // October's period started on the 1st: a range starting on the 20th would miss it. + usageRow("2026-10-19", "R2", "R2 Storage", 9), + usageRow("2026-10-20", "R2", "R2 Storage", 1.5), + usageRow("2026-10-31", "Workers", "Workers Requests", 0), + usageRow("2026-11-09", "Workers", "Workers Requests", 2), + // The window's last day is only served when the range ends the day after. + usageRow("2026-11-10", "D1", "D1 Rows Read", 0.25), + ], + subscriptions: [ + { + price: 5, + currency: "USD", + frequency: "monthly", + current_period_start: "2026-11-01T00:00:00Z", + created_date: "2025-01-01T00:00:00Z", + rate_plan: { public_name: "Workers Paid" }, + }, + ], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const pulled = yield* pull(ORG, connectionRef) + assert.deepStrictEqual(world.urls, [ + `${ACCOUNT_PATH}/billable-usage/info`, + `${ACCOUNT_PATH}/billable-usage?from=2026-09-19&to=2026-11-11`, + `${ACCOUNT_PATH}/subscriptions?per_page=50&page=1`, + ]) + assert.deepStrictEqual(world.authorizations, Arr.replicate(`Bearer ${TOKEN}`, 3)) + // Cloudflare bills none of the requests. + assert.strictEqual(pulled.requests, 0) + assert.deepStrictEqual( + Arr.map(pulled.rows, (row) => [ + row.day, + row.cloudService, + row.chargeCategory, + row.billedCost, + row.accountId, + row.estimated, + ]), + [ + ["2026-10-20", "R2", "usage", 1.5, FAKE_ACCOUNT.id, false], + ["2026-11-09", "Workers", "usage", 2, FAKE_ACCOUNT.id, true], + ["2026-11-10", "D1", "usage", 0.25, FAKE_ACCOUNT.id, true], + ["2026-11-01", "Subscriptions", "purchase", 5, FAKE_ACCOUNT.id, false], + ], + ) + }), + ) + }, + ) + + it.effect("reads a billing period that starts mid-month, whose days span two months", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ + anchorDay: 21, + usage: [ + // Both days belong to the period that started on 21 September. + usageRow("2026-10-20", "R2", "R2 Storage", 1), + usageRow("2026-10-21", "R2", "R2 Storage", 2), + ], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const pulled = yield* pull(ORG, connectionRef) + assert.deepStrictEqual( + Arr.map(pulled.rows, (row) => [row.day, row.billedCost]), + [ + ["2026-10-20", 1], + ["2026-10-21", 2], + ], + ) + }), + ) + }) + + it.effect("reads a long window in pieces under the range cap, each day once", () => { + const testDb = createTestDb(trackedDbs) + const window = { startDay: "2025-12-01", endDay: "2026-03-31" } + const world = makeFakeCloudflare({ + usage: [ + usageRow("2025-12-01", "R2", "R2 Storage", 1), + // The last day of the first piece and the first of the second. + usageRow("2026-01-24", "R2", "R2 Storage", 2), + usageRow("2026-01-25", "R2", "R2 Storage", 3), + usageRow("2026-03-31", "R2", "R2 Storage", 4), + ], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const pulled = yield* pull(ORG, connectionRef, window) + // Three pieces of 55 days at most, each led by 31 days. The fake refuses a range over 90 + // days, as Cloudflare does. + assert.deepStrictEqual(world.urls.slice(1, 4), [ + `${ACCOUNT_PATH}/billable-usage?from=2025-10-31&to=2026-01-25`, + `${ACCOUNT_PATH}/billable-usage?from=2025-12-25&to=2026-03-21`, + `${ACCOUNT_PATH}/billable-usage?from=2026-02-18&to=2026-04-01`, + ]) + assert.deepStrictEqual( + Arr.map(pulled.rows, (row) => [row.day, row.billedCost]), + [ + ["2025-12-01", 1], + ["2026-01-24", 2], + ["2026-01-25", 3], + ["2026-03-31", 4], + ], + ) + }), + ) + }) + + it.effect("reads a window that reaches past the year Cloudflare serves, and gets the served days", () => { + const testDb = createTestDb(trackedDbs) + // The oldest day served falls inside the window, in a billing period that started before it. + const world = makeFakeCloudflare({ + oldestDay: "2025-11-11", + anchorDay: 5, + usage: [ + usageRow("2025-11-10", "R2", "R2 Storage", 9), + usageRow("2025-11-11", "R2", "R2 Storage", 1), + usageRow("2025-12-04", "R2", "R2 Storage", 2), + usageRow("2025-12-31", "R2", "R2 Storage", 3), + ], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const pulled = yield* pull(ORG, connectionRef, { + startDay: "2025-11-01", + endDay: "2025-12-31", + }) + // The range starts before anything served: it is answered, not refused. + assert.deepStrictEqual(world.urls.slice(1, 3), [ + `${ACCOUNT_PATH}/billable-usage?from=2025-10-01&to=2025-12-26`, + `${ACCOUNT_PATH}/billable-usage?from=2025-11-25&to=2026-01-01`, + ]) + assert.deepStrictEqual( + Arr.map(pulled.rows, (row) => [row.day, row.billedCost]), + [ + ["2025-11-11", 1], + ["2025-12-04", 2], + ["2025-12-31", 3], + ], + ) + }), + ) + }) + + it.effect( + "fails a subscription listing longer than it reads, since part of the fees would under-count", + () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ + subscriptionsPageSize: 1, + subscriptions: Arr.makeBy(11, (index) => ({ + price: 5, + frequency: "monthly", + current_period_start: "2026-11-01T00:00:00Z", + rate_plan: { public_name: `Plan ${index}` }, + })), + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + assert.strictEqual( + (yield* Effect.flip(pull(ORG, connectionRef))).reason, + "invalid_response", + ) + }), + ) + }, + ) + + it.effect("reads every page of a listing that gives its count and no page count", () => { + const testDb = createTestDb(trackedDbs) + const fee = (name: string) => ({ + price: 5, + frequency: "monthly", + current_period_start: "2026-11-01T00:00:00Z", + rate_plan: { public_name: name }, + }) + const world = makeFakeCloudflare({ + subscriptionsPageSize: 2, + subscriptionsInfo: "count", + subscriptions: [fee("Plan A"), fee("Plan B"), fee("Plan C")], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const pulled = yield* pull(ORG, connectionRef) + assert.deepStrictEqual( + Arr.map(pulled.rows, (row) => row.usageType), + ["Plan A", "Plan B", "Plan C"], + ) + // A listing that says nothing of its paging is whole only while its page is not full: + // 50 were asked for, so 50 may be part of more. + world.subscriptionsInfo = "none" + world.subscriptionsPageSize = 50 + assert.lengthOf((yield* pull(ORG, connectionRef)).rows, 3) + world.subscriptions = Arr.makeBy(50, (index) => fee(`Plan ${index}`)) + assert.strictEqual((yield* Effect.flip(pull(ORG, connectionRef))).reason, "invalid_response") + }), + ) + }) + + it.effect("fails a listing that counts more subscriptions than it returned", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare({ + subscriptionsNeverReturned: 1, + subscriptions: [ + { + price: 5, + frequency: "monthly", + current_period_start: "2026-11-01T00:00:00Z", + rate_plan: { public_name: "Plan A" }, + }, + ], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + assert.strictEqual((yield* Effect.flip(pull(ORG, connectionRef))).reason, "invalid_response") + }), + ) + }) + + it.effect("fails an answer that does not say it succeeded, or that holds part of the usage", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + const row = + '{"ServiceName":"R2 Storage","ChargePeriodStart":"2026-11-01T00:00:00Z","BillingCurrency":"USD","BilledCost":1,"EffectiveCost":1,"ListCost":1}' + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + // Read as an answer, the empty result would store every day of the window as empty. + const answers = [ + '{"success":false,"errors":[{"code":1000,"message":"try later"}],"result":[]}', + '{"errors":[],"result":[]}', + `{"success":true,"result":[${row}],"result_info":{"page":1,"per_page":1,"total_pages":3,"total_count":3}}`, + `{"success":true,"result":[${row}],"result_info":{"total_count":2}}`, + ] + const reasons = yield* Effect.forEach(answers, (answer) => + Effect.gen(function* () { + world.usage = answer + return (yield* Effect.flip(pull(ORG, connectionRef))).reason + }), + ) + assert.deepStrictEqual(reasons, Arr.replicate("invalid_response", 4)) + // The same row in an answer that holds all it lists is read. + world.usage = `{"success":true,"result":[${row}],"result_info":{"total_count":1}}` + assert.lengthOf((yield* pull(ORG, connectionRef)).rows, 1) + }), + ) + }) + + it.effect("reads every page of the subscriptions", () => { + const testDb = createTestDb(trackedDbs) + const fee = (name: string) => ({ + price: 5, + frequency: "monthly", + current_period_start: "2026-11-01T00:00:00Z", + rate_plan: { public_name: name }, + }) + const world = makeFakeCloudflare({ + subscriptionsPageSize: 2, + subscriptions: [fee("Plan A"), fee("Plan B"), fee("Plan C")], + }) + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const pulled = yield* pull(ORG, connectionRef) + assert.deepStrictEqual( + Arr.map(pulled.rows, (row) => row.usageType), + ["Plan A", "Plan B", "Plan C"], + ) + }), + ) + }) + + it.effect("says what the sync should do about each way Cloudflare refuses", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + const reasons = yield* Effect.forEach([401, 403, 400, 429, 502], (status) => + Effect.gen(function* () { + world.billingStatus = status + return (yield* Effect.flip(pull(ORG, connectionRef))).reason + }), + ) + assert.deepStrictEqual(reasons, ["auth", "not_ready", "upstream", "rate_limited", "upstream"]) + }), + ) + }) + + it.effect("holds an account Cloudflare has no usage-based billing for, before reading any usage", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + world.covered = false + const error = yield* Effect.flip(pull(ORG, connectionRef)) + assert.deepStrictEqual(reasonOf(error), ["not_ready", 0, "cloudflare"]) + assert.deepStrictEqual(world.urls, [`${ACCOUNT_PATH}/billable-usage/info`]) + }), + ) + }) + + it.effect("fails an answer it cannot read as invalid, with nothing to store", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + world.usage = "maintenance" + assert.deepStrictEqual(reasonOf(yield* Effect.flip(pull(ORG, connectionRef))), [ + "invalid_response", + 0, + "cloudflare", + ]) + // A row without its cost is not guessed at. + world.usage = + '{"result":[{"ServiceName":"R2 Storage","ChargePeriodStart":"2026-11-01T00:00:00Z"}]}' + assert.strictEqual((yield* Effect.flip(pull(ORG, connectionRef))).reason, "invalid_response") + }), + ) + }) + + it.effect("is built on a deployment whose secret key is unusable, and fails only its own pulls", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return Effect.gen(function* () { + yield* Effect.promise(() => + testDb.pglite.query( + `INSERT INTO cost_connections + (id, org_id, provider, secret_ciphertext, secret_iv, secret_tag, account_id, account_label, created_at, updated_at, updated_by) + VALUES ('conn_a', 'org_cost', 'cloudflare', 'sealed', 'iv', 'tag', 'account_a', 'Example Account', now(), now(), 'user_cost')`, + ), + ) + const error = yield* Effect.flip(pull(ORG, "conn_a")) + assert.deepStrictEqual([error.reason, world.urls], ["auth", []]) + }).pipe( + Effect.provide( + Layer.effect(CloudflareCostProvider, CloudflareCostProvider.make).pipe( + Layer.provide(fakeCloudflareDeps(testDb, world, "not-a-32-byte-key")), + ), + ), + ) + }) + + it.effect("never reads a token through another org's source, or a connection that is gone", () => { + const testDb = createTestDb(trackedDbs) + const world = makeFakeCloudflare() + return run( + testDb, + world, + Effect.gen(function* () { + const connectionRef = yield* saveToken(testDb, world) + assert.strictEqual((yield* Effect.flip(pull(OTHER_ORG, connectionRef))).reason, "auth") + assert.strictEqual((yield* Effect.flip(pull(ORG, "no-such-connection"))).reason, "auth") + assert.deepStrictEqual(world.urls, []) + }), + ) + }) +}) diff --git a/packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.ts b/packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.ts new file mode 100644 index 0000000000..9307cccd36 --- /dev/null +++ b/packages/backend/src/services/cost/providers/cloudflare/CloudflareCostProvider.ts @@ -0,0 +1,129 @@ +/** + * Cloudflare as a cost provider: an account's daily usage charges by product and priced metric, + * plus its subscriptions' fees, read with the API token the org saved. + */ +import { Clock, Context, Effect, Layer, Option, Array as Arr } from "effect" +import { FetchHttpClient, HttpClient } from "effect/http" +import { + type CostProviderAdapter, + CostProviderError, + type CostPull, + type CostPullSource, + type CostPullWindow, +} from "../../CostProvider" +import { CostConnectionStore, loadFailureOf } from "../../CostConnectionStore" +import { addDays, utcToday } from "../../sync-plan" +import * as CloudflareBilling from "./cloudflare-billing-api" +import { toSubscriptionRows, toUsageRows, unplacedFees } from "./normalize" + +/** Days of a window one request reads: with the period it leads with, under the API's 90 day cap. */ +const PIECE_DAYS = 55 +/** The newest days Cloudflare may still be counting. */ +const OPEN_DAYS = 2 + +type Reason = CostProviderError["reason"] + +// Cloudflare does not bill for API requests, so no failure counts any. +const failed = (reason: Reason, message: string) => + new CostProviderError({ message, provider: "cloudflare", reason, requests: 0 }) + +/** Cloudflare's failures, as what the sync does next and the Cost page shows. */ +const toCostProviderError = (error: CloudflareBilling.CloudflareBillingError): CostProviderError => { + switch (error.kind) { + case "unauthorized": + return failed( + "auth", + "Cloudflare rejected the saved token. Replace the token to resume the sync.", + ) + // The token passed this check when it was saved: its billing access was taken away since. + case "forbidden": + return failed( + "not_ready", + "Cloudflare no longer lets the saved token read the account's billing. Replace it with a token that has the Billing: Read permission.", + ) + case "invalid_response": + return failed("invalid_response", "Cloudflare's billing answer could not be read.") + case "rate_limited": + return failed("rate_limited", "Cloudflare is rate limiting billing requests.") + // A 400 is a request Maple wrote wrong, which a retry does not fix but a deploy does. + default: + return failed("upstream", "Cloudflare did not answer the billing request.") + } +} + +/** The window as consecutive runs of at most `PIECE_DAYS` days, oldest first. */ +const piecesOf = (window: CostPullWindow): ReadonlyArray => + Arr.unfold(window.startDay, (startDay): Option.Option => { + if (startDay > window.endDay) return Option.none() + const last = addDays(startDay, PIECE_DAYS - 1) + const endDay = last < window.endDay ? last : window.endDay + return Option.some([{ startDay, endDay }, addDays(endDay, 1)]) + }) + +export class CloudflareCostProvider extends Context.Service()( + "@maple/backend/services/cost/CloudflareCostProvider", + { + make: Effect.gen(function* () { + const connections = yield* CostConnectionStore + const httpClient = yield* HttpClient.HttpClient + + const pull = Effect.fn("CloudflareCostProvider.pull")( + function* (source: CostPullSource, window: CostPullWindow) { + yield* Effect.annotateCurrentSpan({ + orgId: source.orgId, + "maple.cost.provider": "cloudflare", + "maple.cost.start_day": window.startDay, + "maple.cost.end_day": window.endDay, + }) + const credential = yield* connections + .load("cloudflare", source) + .pipe(Effect.mapError(loadFailureOf("cloudflare"))) + if (Option.isNone(credential)) { + return yield* failed("auth", "No Cloudflare token is saved for this source.") + } + const { secret, accountId } = credential.value + const asked = (call: Effect.Effect) => + Effect.mapError(call, toCostProviderError) + + const info = yield* asked( + CloudflareBilling.getBillableUsageInfo(httpClient, secret, accountId), + ) + if (!info.covered) { + return yield* failed("not_ready", CloudflareBilling.NOT_COVERED) + } + const now = yield* Clock.currentTimeMillis + const openDay = addDays(utcToday(now), -OPEN_DAYS) + // One request at a time. Each answer is cut to its own piece: it also holds days from + // before it, which belong to the piece before. + const usage = yield* Effect.forEach(piecesOf(window), (piece) => + asked(CloudflareBilling.getBillableUsage(httpClient, secret, accountId, piece)).pipe( + Effect.map((items) => toUsageRows(items, accountId, piece, openDay)), + ), + ) + const subscriptions = yield* asked( + CloudflareBilling.listSubscriptions(httpClient, secret, accountId), + ) + const fees = toSubscriptionRows(subscriptions, accountId, window, openDay) + const rows = [...Arr.flatten(usage), ...fees] + yield* Effect.annotateCurrentSpan({ + "maple.cost.rows": rows.length, + "maple.cost.cloudflare.fee_rows": fees.length, + // Charged subscriptions no row holds: a frequency Maple does not step, or no period. + "maple.cost.cloudflare.fees_skipped": unplacedFees(subscriptions), + "maple.cost.cloudflare.usage_requests": usage.length, + }) + return { rows, requests: 0 } satisfies CostPull + }, + Effect.tapError((error) => + Effect.annotateCurrentSpan({ "maple.cost.error_reason": error.reason }), + ), + ) + + return { provider: "cloudflare", pull } satisfies CostProviderAdapter + }), + }, +) { + static readonly layer = Layer.effect(this, this.make).pipe( + Layer.provide(Layer.mergeAll(FetchHttpClient.layer, CostConnectionStore.layer)), + ) +} diff --git a/packages/backend/src/services/cost/providers/cloudflare/cloudflare-billing-api.ts b/packages/backend/src/services/cost/providers/cloudflare/cloudflare-billing-api.ts new file mode 100644 index 0000000000..2efa31c752 --- /dev/null +++ b/packages/backend/src/services/cost/providers/cloudflare/cloudflare-billing-api.ts @@ -0,0 +1,277 @@ +// The slice of Cloudflare's REST API the cost source uses: the accounts a token reads, an account's +// daily billable usage and its subscriptions. Each call reads the status off a plain request, and +// every failure is one `CloudflareBillingError` whose `kind` says what the caller does next. +import { Duration, Effect, Redacted, Schema, Array as Arr } from "effect" +import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/http" +import type { CostPullWindow } from "../../CostProvider" +import { addDays } from "../../sync-plan" + +const BASE_URL = "https://api.cloudflare.com/client/v4" +const REQUEST_TIMEOUT = Duration.seconds(30) +const PAGE_SIZE = 50 +const MAX_SUBSCRIPTION_PAGES = 10 + +/** What a save and a pull both say about an account `billable-usage/info` reports as not covered. */ +export const NOT_COVERED = + "Cloudflare has no usage-based billing data for this account. Accounts on an enterprise contract are not supported yet." + +export class CloudflareBillingError extends Schema.TaggedError()( + "@maple/backend/cost/CloudflareBillingError", + { + message: Schema.String, + kind: Schema.Literals([ + "unauthorized", + "forbidden", + "bad_request", + "rate_limited", + "invalid_response", + "upstream", + ]), + status: Schema.optionalKey(Schema.Number), + cause: Schema.optionalKey(Schema.Defect()), + }, +) {} + +const errorKind = (status: number): CloudflareBillingError["kind"] => + status === 401 + ? "unauthorized" + : status === 403 + ? "forbidden" + : status === 400 + ? "bad_request" + : status === 429 + ? "rate_limited" + : "upstream" + +const ResultInfo = Schema.Struct({ + per_page: Schema.optionalKey(Schema.NullOr(Schema.Number)), + total_pages: Schema.optionalKey(Schema.NullOr(Schema.Number)), + total_count: Schema.optionalKey(Schema.NullOr(Schema.Number)), +}) + +/** + * Cloudflare's envelope: `result` is the answer, `result_info` the paging of a listing. A 200 that + * does not say `success: true` is not an answer: read as one, its empty result would store empty days. + */ +const envelope = (result: S) => + Schema.Struct({ + success: Schema.Literal(true), + result, + result_info: Schema.optionalKey(Schema.NullOr(ResultInfo)), + }) + +/** A listing Maple holds only part of: stored as it is, the missing rest would read as no cost. */ +const partial = (what: string, held: number, total: number) => + Effect.gen(function* () { + yield* Effect.annotateCurrentSpan({ + "maple.cost.cloudflare.error": "partial_listing", + "maple.cost.cloudflare.listed": total, + "maple.cost.cloudflare.read": held, + }) + return yield* new CloudflareBillingError({ + message: `Cloudflare lists ${total} ${what} and Maple read ${held}`, + kind: "invalid_response", + }) + }) + +const call = ( + httpClient: HttpClient.HttpClient, + token: Redacted.Redacted, + request: HttpClientRequest.HttpClientRequest, + result: S, +) => { + const failed = (kind: CloudflareBillingError["kind"], what: string) => (cause: unknown) => + Effect.fail(new CloudflareBillingError({ message: `Cloudflare API ${what}`, kind, cause })) + return Effect.gen(function* () { + const response = yield* httpClient.execute(HttpClientRequest.bearerToken(request, token)) + if (response.status >= 300) { + return yield* new CloudflareBillingError({ + message: `Cloudflare API answered ${response.status}`, + kind: errorKind(response.status), + status: response.status, + }) + } + // A 2xx that is not JSON is an answer Maple cannot read, not a failed request. + const body = yield* response.json.pipe( + Effect.catchTag( + "HttpClientError", + failed("invalid_response", "answered with a body that is not JSON"), + ), + ) + return yield* Schema.decodeUnknownEffect(envelope(result))(body) + }).pipe( + Effect.timeout(REQUEST_TIMEOUT), + Effect.catchTags({ + HttpClientError: failed("upstream", "request failed"), + TimeoutError: failed("upstream", "request timed out"), + SchemaError: failed("invalid_response", "answered with a body Maple cannot read"), + }), + // Callers show their own message; which failure it was stays on the span. + Effect.tapError((error) => + Effect.annotateCurrentSpan({ + "maple.cost.cloudflare.error": error.kind, + "maple.cost.cloudflare.status": error.status ?? 0, + }), + ), + // A redirect is not Cloudflare answering, so the token is never followed to another host. + Effect.provideService(FetchHttpClient.RequestInit, { redirect: "manual" }), + Effect.annotateSpans("peer.service", "cloudflare"), + ) +} + +const Account = Schema.Struct({ id: Schema.String, name: Schema.String }) + +/** + * The accounts the token can see, and how many there are in all. A token limited to one account + * lists exactly that one. A token Cloudflare does not know answers 403 here, not 401. + */ +export const listAccounts = (httpClient: HttpClient.HttpClient, token: Redacted.Redacted) => + call( + httpClient, + token, + HttpClientRequest.setUrlParam( + HttpClientRequest.get(`${BASE_URL}/accounts`), + "per_page", + `${PAGE_SIZE}`, + ), + Schema.Array(Account), + ).pipe( + Effect.map((body) => ({ + accounts: body.result, + total: Math.max(body.result.length, body.result_info?.total_count ?? 0), + })), + ) + +const UsageInfo = Schema.Struct({ covered: Schema.Boolean }) + +/** Whether the account has usage-based billing data at all: not so on an enterprise contract. */ +export const getBillableUsageInfo = ( + httpClient: HttpClient.HttpClient, + token: Redacted.Redacted, + accountId: string, +) => + call( + httpClient, + token, + HttpClientRequest.get(`${BASE_URL}/accounts/${encodeURIComponent(accountId)}/billable-usage/info`), + UsageInfo, + ).pipe(Effect.map((body) => body.result)) + +const isoDay = Schema.String.check( + Schema.isPattern(/^\d{4}-\d{2}-\d{2}/, { description: "Expected to start with YYYY-MM-DD" }), +) + +/** One UTC day of one priced metric. Money is in `BillingCurrency`; most rows bill 0. */ +export const UsageRow = Schema.Struct({ + ServiceName: Schema.String, + ServiceFamilyName: Schema.optionalKey(Schema.NullOr(Schema.String)), + ChargePeriodStart: isoDay, + BillingCurrency: Schema.String, + BilledCost: Schema.Number, + EffectiveCost: Schema.Number, + ListCost: Schema.Number, + ConsumedQuantity: Schema.optionalKey(Schema.NullOr(Schema.Number)), + ConsumedUnit: Schema.optionalKey(Schema.NullOr(Schema.String)), +}) +export type UsageRow = typeof UsageRow.Type + +/** A billing period is 31 days at most: a range starting this long before a day includes its period's start. */ +const PERIOD_LEAD_DAYS = 31 + +/** + * Every usage row of the window's days. Cloudflare answers the days before `to` of each billing + * period that starts in `[from, to)`, so the range starts a period before the window and ends the + * day after it, and the answer also holds days before the window. 90 days at most, no paging. + */ +export const getBillableUsage = ( + httpClient: HttpClient.HttpClient, + token: Redacted.Redacted, + accountId: string, + window: CostPullWindow, +) => + call( + httpClient, + token, + HttpClientRequest.setUrlParams( + HttpClientRequest.get(`${BASE_URL}/accounts/${encodeURIComponent(accountId)}/billable-usage`), + { from: addDays(window.startDay, -PERIOD_LEAD_DAYS), to: addDays(window.endDay, 1) }, + ), + Schema.Array(UsageRow), + ).pipe( + // The answer has no paging today. One that starts to page must not be read as the whole window. + Effect.flatMap((body) => { + const info = body.result_info + const total = info?.total_count ?? body.result.length + return (info?.total_pages ?? 1) > 1 || total > body.result.length + ? partial("usage rows", body.result.length, total) + : Effect.succeed(body.result) + }), + ) + +/** A plan or add-on the account pays a fixed fee for. Free items carry no period. */ +export const Subscription = Schema.Struct({ + price: Schema.optionalKey(Schema.NullOr(Schema.Number)), + currency: Schema.optionalKey(Schema.NullOr(Schema.String)), + frequency: Schema.optionalKey(Schema.NullOr(Schema.String)), + /** `Paid`, `Trial`, `Cancelled`, `Failed`, `Expired` and others. */ + state: Schema.optionalKey(Schema.NullOr(Schema.String)), + current_period_start: Schema.optionalKey(Schema.NullOr(isoDay)), + created_date: Schema.optionalKey(Schema.NullOr(isoDay)), + rate_plan: Schema.optionalKey( + Schema.NullOr(Schema.Struct({ public_name: Schema.optionalKey(Schema.NullOr(Schema.String)) })), + ), + product: Schema.optionalKey( + Schema.NullOr(Schema.Struct({ name: Schema.optionalKey(Schema.NullOr(Schema.String)) })), + ), + zone: Schema.optionalKey( + Schema.NullOr(Schema.Struct({ name: Schema.optionalKey(Schema.NullOr(Schema.String)) })), + ), +}) +export type Subscription = typeof Subscription.Type + +const subscriptionsPage = ( + httpClient: HttpClient.HttpClient, + token: Redacted.Redacted, + accountId: string, + page: number, +) => + call( + httpClient, + token, + HttpClientRequest.setUrlParams( + HttpClientRequest.get(`${BASE_URL}/accounts/${encodeURIComponent(accountId)}/subscriptions`), + { per_page: `${PAGE_SIZE}`, page: `${page}` }, + ), + Schema.Array(Subscription), + ) + +/** + * Every subscription of the account, zone plans included. The page count comes from `total_pages`, + * or from `total_count` where the listing does not say. A list Maple cannot read whole fails: + * part of it would under-count the fees. + */ +export const listSubscriptions = ( + httpClient: HttpClient.HttpClient, + token: Redacted.Redacted, + accountId: string, +) => + Effect.gen(function* () { + const first = yield* subscriptionsPage(httpClient, token, accountId, 1) + const info = first.result_info + const total = info?.total_count ?? null + const perPage = info?.per_page ?? PAGE_SIZE + const stated = info?.total_pages ?? (total === null ? null : Math.ceil(total / Math.max(1, perPage))) + // With neither figure, a page that is not full is the whole list. A full one may be part of it. + const pages = stated ?? (first.result.length < perPage ? 1 : null) + if (pages === null || pages > MAX_SUBSCRIPTION_PAGES) { + return yield* partial("subscriptions", first.result.length, total ?? first.result.length) + } + const rest = yield* Effect.forEach(pages < 2 ? [] : Arr.range(2, pages), (page) => + Effect.map(subscriptionsPage(httpClient, token, accountId, page), (body) => body.result), + ) + const subscriptions = [...first.result, ...Arr.flatten(rest)] + if (total !== null && subscriptions.length < total) { + return yield* partial("subscriptions", subscriptions.length, total) + } + return subscriptions + }) diff --git a/packages/backend/src/services/cost/providers/cloudflare/fake-cloudflare.ts b/packages/backend/src/services/cost/providers/cloudflare/fake-cloudflare.ts new file mode 100644 index 0000000000..ef7c50a3c4 --- /dev/null +++ b/packages/backend/src/services/cost/providers/cloudflare/fake-cloudflare.ts @@ -0,0 +1,191 @@ +// A fake Cloudflare for tests: the accounts, billable usage and subscription calls answered from +// a mutable world, with every call recorded. Usage is served the way the real API was seen to: +// a billing period's days before `to`, and only when the period starts inside `[from, to)`. +import { DISABLED_ORGANIZATION_FEATURE_FLAGS } from "@maple/domain/organization-feature-flags" +import { DateTime, Effect, Layer, Array as Arr, String as Str } from "effect" +import { FetchHttpClient } from "effect/http" +import { Env } from "@maple/backend/platform/Env" +import { OrganizationFeatureFlagsService } from "@maple/backend/services/org/OrganizationFeatureFlagsService" +import type { TestDb } from "@maple/backend/testing/test-pglite" +import { CostConnectionStore } from "../../CostConnectionStore" +import { fakeCostEnvConfig } from "../fake-cost-env" +import type { Subscription, UsageRow } from "./cloudflare-billing-api" + +export type FakeUsageRow = typeof UsageRow.Encoded +export type FakeSubscription = typeof Subscription.Encoded + +export interface FakeCloudflare { + /** Status of `GET /accounts`. */ + accountsStatus: number + /** The accounts the token can see. */ + accounts: ReadonlyArray<{ readonly id: string; readonly name: string }> + /** Status of the billable usage calls, `info` included. */ + billingStatus: number + /** Whether the account has usage-based billing data. */ + covered: boolean + /** Every usage row the account has, or raw text for a malformed answer. */ + usage: ReadonlyArray | string + /** The day of the month a billing period starts on. */ + anchorDay: number + /** The oldest day served: a year back on the real API. Older days of a range are left out, not refused. */ + oldestDay: string + subscriptions: ReadonlyArray + /** Subscriptions one page holds, to make a listing span pages. */ + subscriptionsPageSize: number + /** What the listing says of its paging: both figures, the count alone as the docs have it, or nothing. */ + subscriptionsInfo: "pages_and_count" | "count" | "none" + /** Subscriptions the listing counts and never returns. */ + subscriptionsNeverReturned: number + /** Whether the org is in the `cloudCost` rollout. */ + cloudCost: boolean + readonly urls: Array + readonly authorizations: Array +} + +export const FAKE_ACCOUNT = { id: "0123456789abcdef0123456789abcdef", name: "Example Account" } + +export const makeFakeCloudflare = (overrides: Partial = {}): FakeCloudflare => ({ + accountsStatus: 200, + accounts: [FAKE_ACCOUNT], + billingStatus: 200, + covered: true, + usage: [], + anchorDay: 1, + oldestDay: "2000-01-01", + subscriptions: [], + subscriptionsPageSize: 50, + subscriptionsInfo: "pages_and_count", + subscriptionsNeverReturned: 0, + cloudCost: true, + urls: [], + authorizations: [], + ...overrides, +}) + +/** One usage row of `day`, with the fields a real row carries that Maple reads. */ +export const usageRow = ( + day: string, + family: string, + metric: string, + cost: number, + over: Partial = {}, +): FakeUsageRow => ({ + ServiceName: metric, + ServiceFamilyName: family, + ChargePeriodStart: `${day}T00:00:00Z`, + BillingCurrency: "USD", + BilledCost: cost, + EffectiveCost: cost, + ListCost: cost, + ConsumedQuantity: 1, + ConsumedUnit: "", + ...over, +}) + +const DAY_MS = 86_400_000 +const dayMs = (day: string) => DateTime.toEpochMillis(DateTime.makeUnsafe(`${day}T00:00:00Z`)) + +/** The day the billing period of `day` started: the newest anchor day at or before it. */ +const periodStart = (day: string, anchorDay: number): string => { + const anchor = Str.padStart(2, "0")(`${anchorDay}`) + const sameMonth = `${Str.slice(0, 8)(day)}${anchor}` + if (sameMonth <= day) return sameMonth + const previous = DateTime.formatIsoDateUtc( + DateTime.subtract(DateTime.makeUnsafe(`${Str.slice(0, 8)(day)}01T00:00:00Z`), { months: 1 }), + ) + return `${Str.slice(0, 8)(previous)}${anchor}` +} + +/** A listing's answer. The usage and info calls answer without `result_info`, as the real API does. */ +const envelope = (result: unknown, info?: Record) => + Response.json({ + success: true, + errors: [], + messages: [], + result, + ...(info === undefined ? undefined : { result_info: info }), + }) + +const refused = (status: number) => + Response.json( + { success: false, errors: [{ code: 10000, message: "Authentication error" }], result: null }, + { status }, + ) + +const usageAnswer = (world: FakeCloudflare, params: URLSearchParams): Response => { + const from = params.get("from") ?? "" + const to = params.get("to") ?? "" + // The real API refuses an empty range and one over 90 days. + const days = (dayMs(to) - dayMs(from)) / DAY_MS + if (!(days >= 1 && days <= 90)) return refused(400) + if (typeof world.usage === "string") return new Response(world.usage, { status: 200 }) + return envelope( + Arr.filter(world.usage, (row) => { + const day = Str.slice(0, 10)(row.ChargePeriodStart) + const start = periodStart(day, world.anchorDay) + return start >= from && start < to && day < to && day >= world.oldestDay + }), + ) +} + +const subscriptionsAnswer = (world: FakeCloudflare, params: URLSearchParams): Response => { + const page = Number(params.get("page") ?? "1") + const size = world.subscriptionsPageSize + const total = world.subscriptions.length + world.subscriptionsNeverReturned + const result = Arr.take(Arr.drop(world.subscriptions, (page - 1) * size), size) + const count = { page, per_page: size, total_count: total } + return world.subscriptionsInfo === "none" + ? Response.json({ success: true, errors: [], messages: [], result }) + : envelope( + result, + world.subscriptionsInfo === "count" + ? count + : { ...count, total_pages: Math.max(1, Math.ceil(total / size)) }, + ) +} + +const fetchStub = + (world: FakeCloudflare): typeof fetch => + async (input: string | URL | Request, init?: RequestInit) => { + const href = typeof input === "string" ? input : input instanceof URL ? input.href : input.url + const url = new URL(href) + world.urls.push(`${url.pathname}${url.search}`) + world.authorizations.push(new Headers(init?.headers).get("authorization")) + if (Str.endsWith("/accounts")(url.pathname)) { + return world.accountsStatus === 200 + ? envelope(world.accounts, { total_count: world.accounts.length, total_pages: 1 }) + : refused(world.accountsStatus) + } + if (world.billingStatus !== 200) return refused(world.billingStatus) + if (Str.endsWith("/billable-usage/info")(url.pathname)) { + return envelope({ covered: world.covered, subscriptions: [] }) + } + if (Str.endsWith("/billable-usage")(url.pathname)) return usageAnswer(world, url.searchParams) + return subscriptionsAnswer(world, url.searchParams) + } + +/** Everything the Cloudflare cost services need, over the fake and a test database. */ +export const fakeCloudflareDeps = ( + testDb: TestDb, + world: FakeCloudflare, + /** `MAPLE_INGEST_KEY_ENCRYPTION_KEY`, for a deployment whose secret key is unusable. */ + encryptionKey?: string, +) => + Layer.effect(CostConnectionStore, CostConnectionStore.make).pipe( + Layer.provideMerge( + Layer.mergeAll( + FetchHttpClient.layer, + Layer.succeed(OrganizationFeatureFlagsService, { + flags: () => + Effect.sync(() => ({ + ...DISABLED_ORGANIZATION_FEATURE_FLAGS, + cloudCost: world.cloudCost, + })), + }), + testDb.layer, + Env.layer, + ), + ), + Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchStub(world))), + Layer.provide(fakeCostEnvConfig(encryptionKey)), + ) diff --git a/packages/backend/src/services/cost/providers/cloudflare/normalize.test.ts b/packages/backend/src/services/cost/providers/cloudflare/normalize.test.ts new file mode 100644 index 0000000000..7b9d7708f9 --- /dev/null +++ b/packages/backend/src/services/cost/providers/cloudflare/normalize.test.ts @@ -0,0 +1,349 @@ +import { Schema, Array as Arr } from "effect" +import { describe, expect, it } from "vitest" +import { Subscription, UsageRow } from "./cloudflare-billing-api" +import { FAKE_ACCOUNT, type FakeSubscription, usageRow } from "./fake-cloudflare" +import { toSubscriptionRows, toUsageRows, unplacedFees } from "./normalize" + +const ACCOUNT = FAKE_ACCOUNT.id +const WINDOW = { startDay: "2026-10-01", endDay: "2026-11-10" } +/** The two newest days of a pull on 2026-11-11 can still change. */ +const OPEN_DAY = "2026-11-09" + +const usage = (rows: ReadonlyArray) => + toUsageRows(Schema.decodeSync(Schema.Array(UsageRow))(rows), ACCOUNT, WINDOW, OPEN_DAY) + +const fees = (subscriptions: ReadonlyArray, window = WINDOW) => + toSubscriptionRows( + Schema.decodeSync(Schema.Array(Subscription))(subscriptions), + ACCOUNT, + window, + OPEN_DAY, + ) + +/** A row as the API answers it, with the fields Maple does not read. */ +const recorded = { + ServiceName: "Durable Objects Compute Duration (First 400,000 GB-s are included)", + ServiceFamilyName: "Durable Objects", + ChargeCategory: "Usage", + ChargeClass: null, + ChargeDescription: "Durable Objects Compute Duration", + ChargePeriodStart: "2026-10-03T00:00:00Z", + ChargePeriodEnd: "2026-10-04T00:00:00Z", + BillingPeriodStart: "2026-10-01T00:00:00Z", + BillingCurrency: "USD", + ConsumedUnit: "GB-seconds", + ConsumedQuantity: 120000.5, + PricingQuantity: 120000.5, + PricingUnit: "GB-seconds", + CumulatedPricingQuantity: 610000, + ListCost: 1.5, + ContractedCost: 1.5, + EffectiveCost: 1.25, + BilledCost: 1.25, + CumulatedContractedCost: 2.6, + SubscriptionId: "sub_example", + BillingAccountId: ACCOUNT, + BillingAccountName: "Example Account", + ServiceProviderName: "Cloudflare, Inc.", + InvoiceIssuerName: "Cloudflare, Inc.", + HostProviderName: "Cloudflare, Inc.", +} + +describe("toUsageRows", () => { + it("maps a recorded row: the product is the service, the priced metric the usage type", () => { + expect(usage([recorded])).toEqual([ + { + day: "2026-10-03", + accountId: ACCOUNT, + cloudService: "Durable Objects", + serviceCategory: "Compute", + usageType: "Durable Objects Compute Duration (First 400,000 GB-s are included)", + region: "global", + resourceId: "", + resourceType: "", + serviceName: "", + chargeCategory: "usage", + basis: "billed", + currency: "USD", + billedCost: 1.25, + effectiveCost: 1.25, + listCost: 1.5, + consumedQuantity: 120000.5, + consumedUnit: "GB-seconds", + tags: {}, + estimated: false, + }, + ]) + }) + + it("leaves out rows inside the included allowance, and keeps one billed nothing at a list price", () => { + const rows = usage([ + usageRow("2026-10-03", "Workers", "Workers Requests (First 10M are included)", 0), + usageRow("2026-10-03", "R2", "R2 Storage", 0, { ListCost: 0.4 }), + usageRow("2026-10-03", "Workers KV", "KV Reads", 0, { ConsumedQuantity: 3 }), + ]) + expect(Arr.map(rows, (row) => [row.usageType, row.billedCost, row.listCost])).toEqual([ + ["R2 Storage", 0, 0.4], + ]) + }) + + it("keeps only the window's days, and marks the newest two as open to change", () => { + const rows = usage([ + usageRow("2026-09-30", "R2", "R2 Storage", 1), + usageRow("2026-10-01", "R2", "R2 Storage", 2), + usageRow("2026-11-08", "R2", "R2 Storage", 3), + usageRow("2026-11-09", "R2", "R2 Storage", 4), + usageRow("2026-11-10", "R2", "R2 Storage", 5), + usageRow("2026-11-11", "R2", "R2 Storage", 6), + ]) + expect(Arr.map(rows, (row) => [row.day, row.billedCost, row.estimated])).toEqual([ + ["2026-10-01", 2, false], + ["2026-11-08", 3, false], + ["2026-11-09", 4, true], + ["2026-11-10", 5, true], + ]) + }) + + it("puts each product in its category, and one it does not know in Other", () => { + const families = ["Workers", "Queues", "Containers", "R2", "D1", "Workers KV", "Workers AI", "Email"] + const rows = usage([ + ...Arr.map(families, (family) => usageRow("2026-10-05", family, `${family} metric`, 1)), + usageRow("2026-10-05", "Something New", "New metric", 1), + usageRow("2026-10-05", "Workers", "No family", 1, { ServiceFamilyName: null }), + ]) + expect(Arr.map(rows, (row) => [row.cloudService, row.serviceCategory])).toEqual([ + ["Workers", "Compute"], + ["Queues", "Compute"], + ["Containers", "Compute"], + ["R2", "Storage"], + ["D1", "Databases"], + ["Workers KV", "Databases"], + ["Workers AI", "AI and Machine Learning"], + ["Email", "Other"], + ["Something New", "Other"], + ["", "Other"], + ]) + }) + + it("never adds quantities of another unit to a row, though their cost is one", () => { + const rows = usage([ + usageRow("2026-10-05", "R2", "R2 Storage", 1.5, { + ConsumedQuantity: 10, + ConsumedUnit: "GB-months", + }), + usageRow("2026-10-05", "R2", "R2 Storage", 2, { + ConsumedQuantity: 700, + ConsumedUnit: "Gigabytes", + }), + ]) + expect(Arr.map(rows, (row) => [row.billedCost, row.consumedQuantity, row.consumedUnit])).toEqual([ + [3.5, 10, "GB-months"], + ]) + }) + + it("adds up two rows of one day and metric, which the warehouse would read as one", () => { + const rows = usage([ + usageRow("2026-10-05", "R2", "R2 Storage", 1.5, { + ConsumedQuantity: 10, + ConsumedUnit: "GB-months", + }), + usageRow("2026-10-05", "R2", "R2 Storage", 2.25, { + ConsumedQuantity: 5, + ConsumedUnit: "GB-months", + }), + ]) + expect(Arr.map(rows, (row) => [row.billedCost, row.listCost, row.consumedQuantity])).toEqual([ + [3.75, 3.75, 15], + ]) + }) + + it("keeps the row's own currency", () => { + expect( + usage([usageRow("2026-10-05", "R2", "R2 Storage", 1, { BillingCurrency: "EUR" })]), + ).toMatchObject([{ currency: "EUR" }]) + }) +}) + +const plan = (over: FakeSubscription = {}): FakeSubscription => ({ + price: 5, + currency: "USD", + frequency: "monthly", + current_period_start: "2026-11-04T09:12:44Z", + created_date: "2025-03-04T09:12:44Z", + rate_plan: { public_name: "Workers Paid" }, + product: { name: "workers" }, + ...over, +}) + +describe("toSubscriptionRows", () => { + it("puts a monthly fee on each period start in the window, stepped back from the current one", () => { + const rows = fees([plan()]) + expect(Arr.map(rows, (row) => [row.day, row.billedCost, row.estimated])).toEqual([ + ["2026-11-04", 5, false], + ["2026-10-04", 5, false], + ]) + expect(rows[0]).toEqual({ + day: "2026-11-04", + accountId: ACCOUNT, + cloudService: "Subscriptions", + serviceCategory: "Other", + usageType: "Workers Paid", + region: "global", + resourceId: "", + resourceType: "", + serviceName: "", + chargeCategory: "purchase", + basis: "billed", + currency: "USD", + billedCost: 5, + effectiveCost: 5, + listCost: 5, + consumedQuantity: 0, + consumedUnit: "", + tags: {}, + estimated: false, + }) + }) + + it("names the zone a zone's plan belongs to", () => { + const [row] = fees([ + plan({ + price: 10, + rate_plan: { public_name: "Advanced Certificate Manager" }, + zone: { name: "example.com" }, + }), + ]) + expect([row?.usageType, row?.resourceId, row?.resourceType]).toEqual([ + "Advanced Certificate Manager", + "example.com", + "zone", + ]) + }) + + it("never charges a period from before the subscription was created", () => { + const rows = fees([ + plan({ created_date: "2026-10-20T00:00:00Z", current_period_start: "2026-11-01T00:00:00Z" }), + ]) + expect(Arr.map(rows, (row) => row.day)).toEqual(["2026-11-01"]) + }) + + it("walks a year of monthly periods back, and keeps to the month's last day where it is shorter", () => { + const rows = fees( + [plan({ current_period_start: "2026-10-31T00:00:00Z", created_date: "2020-01-01T00:00:00Z" })], + { + startDay: "2025-12-01", + endDay: "2026-11-10", + }, + ) + expect(Arr.map(rows, (row) => row.day)).toEqual([ + "2026-10-31", + "2026-09-30", + "2026-08-31", + "2026-07-31", + "2026-06-30", + "2026-05-31", + "2026-04-30", + "2026-03-31", + "2026-02-28", + "2026-01-31", + "2025-12-31", + ]) + }) + + it("charges a yearly fee once, on its current period's start", () => { + const yearly = plan({ price: 120, frequency: "yearly", current_period_start: "2026-10-15T00:00:00Z" }) + expect(Arr.map(fees([yearly]), (row) => [row.day, row.billedCost])).toEqual([["2026-10-15", 120]]) + // A period that started before the window is not in it. + expect(fees([{ ...yearly, current_period_start: "2026-03-15T00:00:00Z" }])).toEqual([]) + }) + + it("leaves out free items, items with no period and frequencies it does not bill by", () => { + expect( + fees([ + plan({ price: 0 }), + { price: 0, frequency: "not-applicable", created_date: "2025-01-01T00:00:00Z" }, + plan({ frequency: "not-applicable" }), + plan({ frequency: "biennial" }), + plan({ current_period_start: null }), + ]), + ).toEqual([]) + }) + + it("counts the charged subscriptions it could not place on a day, and no free or unpaid one", () => { + const decode = Schema.decodeSync(Schema.Array(Subscription)) + expect( + unplacedFees( + decode([ + plan(), + plan({ frequency: "biennial" }), + plan({ current_period_start: null }), + plan({ frequency: "biennial", price: 0 }), + plan({ frequency: "biennial", state: "Trial" }), + ]), + ), + ).toBe(2) + }) + + it("keeps the first period of a subscription made on the 31st, whose later starts were clamped", () => { + const rows = fees( + [plan({ created_date: "2025-10-31T08:00:00Z", current_period_start: "2026-02-28T00:00:00Z" })], + { startDay: "2025-10-01", endDay: "2026-03-10" }, + ) + expect(Arr.map(rows, (row) => row.day)).toEqual([ + "2026-02-28", + "2026-01-28", + "2025-12-28", + "2025-11-28", + "2025-10-31", + ]) + // A start that is early for another reason is not a period: nothing is charged before the 20th. + const moved = plan({ + created_date: "2026-10-20T00:00:00Z", + current_period_start: "2026-11-15T00:00:00Z", + }) + expect( + Arr.map(fees([moved], { startDay: "2026-10-01", endDay: "2026-11-20" }), (row) => row.day), + ).toEqual(["2026-11-15"]) + }) + + it("charges only the current period when Cloudflare does not say when the subscription was made", () => { + const { created_date: _made, ...undated } = plan() + expect(Arr.map(fees([undated]), (row) => row.day)).toEqual(["2026-11-04"]) + expect(Arr.map(fees([plan({ created_date: null })]), (row) => row.day)).toEqual(["2026-11-04"]) + }) + + it("steps a quarterly fee by three months and a weekly one by seven days", () => { + const quarterly = plan({ + frequency: "quarterly", + price: 30, + current_period_start: "2026-11-01T00:00:00Z", + }) + expect( + Arr.map(fees([quarterly], { startDay: "2026-01-01", endDay: "2026-11-10" }), (row) => row.day), + ).toEqual(["2026-11-01", "2026-08-01", "2026-05-01", "2026-02-01"]) + const weekly = plan({ + frequency: "weekly", + current_period_start: "2026-11-09T00:00:00Z", + created_date: "2026-10-21T00:00:00Z", + }) + // Made on a Wednesday, billed on Mondays: the week it was made in is not a period of its own. + expect(Arr.map(fees([weekly]), (row) => row.day)).toEqual(["2026-11-09", "2026-11-02", "2026-10-26"]) + }) + + it("charges a paid or cancelled subscription, not one in trial or one that was never paid", () => { + const charged = (state: string) => fees([plan({ state })]).length + expect([charged("Paid"), charged("Cancelled"), charged("Provisioned")]).toEqual([2, 2, 2]) + expect([charged("Trial"), charged("Failed"), charged("Expired")]).toEqual([0, 0, 0]) + }) + + it("adds up two subscriptions to one plan on one day, and marks an open day", () => { + const rows = fees([ + plan({ current_period_start: "2026-11-09T00:00:00Z" }), + plan({ current_period_start: "2026-11-09T12:00:00Z", price: 7 }), + ]) + expect(Arr.map(rows, (row) => [row.day, row.billedCost, row.estimated])).toEqual([ + ["2026-11-09", 12, true], + ["2026-10-09", 12, false], + ]) + }) +}) diff --git a/packages/backend/src/services/cost/providers/cloudflare/normalize.ts b/packages/backend/src/services/cost/providers/cloudflare/normalize.ts new file mode 100644 index 0000000000..f653fb6949 --- /dev/null +++ b/packages/backend/src/services/cost/providers/cloudflare/normalize.ts @@ -0,0 +1,210 @@ +// Cloudflare billing as `cost_daily` rows: usage charges per day and priced metric, and each paid +// subscription's fee on the day its billing period starts. +import { DateTime, Array as Arr, Record as Rec, String as Str } from "effect" +import type { CostPullRow, CostPullWindow } from "../../CostProvider" +import type { Subscription, UsageRow } from "./cloudflare-billing-api" + +/** FOCUS `ServiceCategory` of each Cloudflare product family; one not listed is "Other". */ +const FAMILY_CATEGORIES = new Map([ + ["Workers", "Compute"], + ["Durable Objects", "Compute"], + ["Containers", "Compute"], + ["Queues", "Compute"], + ["R2", "Storage"], + ["D1", "Databases"], + ["Workers KV", "Databases"], + ["Workers AI", "AI and Machine Learning"], +]) +const OTHER_CATEGORY = "Other" + +/** `CloudService` of every subscription fee: the plans are not products with usage of their own. */ +const SUBSCRIPTIONS_SERVICE = "Subscriptions" + +const dayOf = (timestamp: string): string => Str.slice(0, 10)(timestamp) + +const inWindow = (day: string, window: CostPullWindow): boolean => + day >= window.startDay && day <= window.endDay + +// NUL cannot be in a day, a name or a currency, so the key keeps the parts apart. +const keyOf = (parts: ReadonlyArray): string => Arr.join(parts, "\u0000") + +const sum = (items: ReadonlyArray, of: (item: A) => number): number => + Arr.reduce(items, 0, (total, item) => total + of(item)) + +/** + * The window's usage charges, one row per day, product and priced metric. Cloudflare answers a + * row for every metric every day, and most are inside the included allowance: a row with no + * billed and no list cost is left out. Days from `openDay` on can still change. + */ +export const toUsageRows = ( + items: ReadonlyArray, + accountId: string, + window: CostPullWindow, + openDay: string, +): ReadonlyArray => { + const charged = Arr.filter( + items, + (item) => + inWindow(dayOf(item.ChargePeriodStart), window) && (item.BilledCost !== 0 || item.ListCost !== 0), + ) + // A day's rows are replaced whole and read per key, so two rows of one key would count once. + const groups = Arr.groupBy(charged, (item) => + keyOf([ + dayOf(item.ChargePeriodStart), + item.ServiceFamilyName ?? "", + item.ServiceName, + item.BillingCurrency, + ]), + ) + return Arr.map(Rec.values(groups), (group): CostPullRow => { + const first = Arr.headNonEmpty(group) + const day = dayOf(first.ChargePeriodStart) + const family = first.ServiceFamilyName ?? "" + const unit = first.ConsumedUnit ?? "" + return { + day, + accountId, + cloudService: family, + serviceCategory: FAMILY_CATEGORIES.get(family) ?? OTHER_CATEGORY, + usageType: first.ServiceName, + region: "global", + resourceId: "", + resourceType: "", + serviceName: "", + chargeCategory: "usage", + basis: "billed", + currency: first.BillingCurrency, + billedCost: sum(group, (item) => item.BilledCost), + effectiveCost: sum(group, (item) => item.EffectiveCost), + listCost: sum(group, (item) => item.ListCost), + // Rows of one key are one cost. Only quantities in the row's own unit add up. + consumedQuantity: sum(group, (item) => + (item.ConsumedUnit ?? "") === unit ? (item.ConsumedQuantity ?? 0) : 0, + ), + consumedUnit: unit, + tags: {}, + estimated: day >= openDay, + } + }) +} + +/** States whose listed price was not charged: a trial is free, the other two were never paid. */ +const UNCHARGED_STATES = ["Trial", "Failed", "Expired"] + +/** Months back a fee is stepped over: the sync reads a year, and a period starts within the last one. */ +const MONTHS_BACK = 14 + +/** How often a subscription bills, as the step between its period starts and how many reach `MONTHS_BACK`. */ +const FREQUENCIES = new Map< + string, + { readonly step: { readonly months?: number; readonly days?: number }; readonly periods: number } +>([ + ["weekly", { step: { days: 7 }, periods: 62 }], + ["monthly", { step: { months: 1 }, periods: MONTHS_BACK }], + ["quarterly", { step: { months: 3 }, periods: 5 }], + // A year back is before anything the sync reads. + ["yearly", { step: { months: 12 }, periods: 1 }], +]) + +const monthOf = (day: string): string => Str.slice(0, 7)(day) +const dayOfMonth = (day: string): number => Number(Str.slice(8, 10)(day)) +/** A month step can only move a start earlier in its month from this day on. */ +const SHORTEST_MONTH_DAYS = 28 + +/** + * The days a subscription's billing periods started on, newest first: stepped back from the + * current one, never before the subscription was made. None when it cannot be placed on a day. + */ +const periodStarts = (subscription: Subscription): ReadonlyArray => { + const start = subscription.current_period_start + const frequency = FREQUENCIES.get(subscription.frequency ?? "") + if (start === undefined || start === null || frequency === undefined) return [] + const current = dayOf(start) + const made = subscription.created_date + // Without the day it was made there is no telling which earlier periods existed: only this one. + if (made === undefined || made === null) return [current] + const created = dayOf(made) + const from = DateTime.makeUnsafe(`${current}T00:00:00Z`) + const starts = Arr.makeBy(frequency.periods, (index) => { + const day = DateTime.formatIsoDateUtc( + DateTime.subtract(from, { + months: (frequency.step.months ?? 0) * index, + days: (frequency.step.days ?? 0) * index, + }), + ) + // A start on the 29th to 31st is clamped in shorter months, which shifts every step before + // it: the first period then lands days before the subscription was made, in the same month. + const isClamped = + frequency.step.months !== undefined && + day < created && + monthOf(day) === monthOf(created) && + dayOfMonth(day) >= SHORTEST_MONTH_DAYS + return isClamped ? created : day + }) + return Arr.dedupe(Arr.filter(starts, (day) => day >= created)) +} + +/** Whether a subscription's listed price is charged: it has one, and its state is not a free or unpaid one. */ +const isCharged = (subscription: Subscription): boolean => + (subscription.price ?? 0) > 0 && !Arr.contains(UNCHARGED_STATES, subscription.state ?? "") + +/** Charged subscriptions whose fee is in no row: they bill by a frequency Maple does not step, or name no period. */ +export const unplacedFees = (subscriptions: ReadonlyArray): number => + Arr.filter( + subscriptions, + (subscription) => isCharged(subscription) && periodStarts(subscription).length === 0, + ).length + +/** + * Each paid subscription's fee, as a purchase on the days its billing periods started in the window. + * Cloudflare reports the current period only: earlier ones use today's price, as if it never changed. + */ +export const toSubscriptionRows = ( + subscriptions: ReadonlyArray, + accountId: string, + window: CostPullWindow, + openDay: string, +): ReadonlyArray => { + const fees = Arr.flatMap(subscriptions, (subscription) => { + const price = subscription.price ?? 0 + return isCharged(subscription) + ? Arr.map( + Arr.filter(periodStarts(subscription), (day) => inWindow(day, window)), + (day) => ({ + day, + price, + plan: subscription.rate_plan?.public_name ?? subscription.product?.name ?? "", + zone: subscription.zone?.name ?? "", + currency: subscription.currency ?? "USD", + }), + ) + : [] + }) + // Two subscriptions to one plan (and zone) on one day are one key, so their fees add up. + const groups = Arr.groupBy(fees, (fee) => keyOf([fee.day, fee.plan, fee.zone, fee.currency])) + return Arr.map(Rec.values(groups), (group): CostPullRow => { + const first = Arr.headNonEmpty(group) + const price = sum(group, (fee) => fee.price) + return { + day: first.day, + accountId, + cloudService: SUBSCRIPTIONS_SERVICE, + serviceCategory: OTHER_CATEGORY, + usageType: first.plan, + region: "global", + resourceId: first.zone, + resourceType: first.zone === "" ? "" : "zone", + serviceName: "", + chargeCategory: "purchase", + basis: "billed", + currency: first.currency, + billedCost: price, + effectiveCost: price, + listCost: price, + consumedQuantity: 0, + consumedUnit: "", + tags: {}, + estimated: first.day >= openDay, + } + }) +} diff --git a/packages/backend/src/services/cost/providers/fake-cost-env.ts b/packages/backend/src/services/cost/providers/fake-cost-env.ts new file mode 100644 index 0000000000..8927470380 --- /dev/null +++ b/packages/backend/src/services/cost/providers/fake-cost-env.ts @@ -0,0 +1,22 @@ +// The deployment config the cost provider fakes run their services on. +import { ConfigProvider } from "effect" + +const FAKE_ENCRYPTION_KEY = Buffer.alloc(32, 9).toString("base64") + +/** `encryptionKey` is `MAPLE_INGEST_KEY_ENCRYPTION_KEY`, for a deployment whose secret key is unusable. */ +export const fakeCostEnvConfig = (encryptionKey = FAKE_ENCRYPTION_KEY) => + ConfigProvider.layer( + ConfigProvider.fromUnknown({ + PORT: "3472", + TINYBIRD_HOST: "https://api.tinybird.co", + TINYBIRD_TOKEN: "test-token", + MAPLE_AUTH_MODE: "self_hosted", + MAPLE_ROOT_PASSWORD: "test-root-password", + MAPLE_DEFAULT_ORG_ID: "default", + MAPLE_INGEST_KEY_ENCRYPTION_KEY: encryptionKey, + MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY: "maple-test-lookup-secret", + MAPLE_INGEST_PUBLIC_URL: "https://ingest.test", + // OpenRouter's management host is the global one on every instance. + MAPLE_REGION: "eu", + }), + ) diff --git a/packages/backend/src/services/cost/providers/openrouter/fake-openrouter.ts b/packages/backend/src/services/cost/providers/openrouter/fake-openrouter.ts index bf5a23ee6d..e8ebc126c8 100644 --- a/packages/backend/src/services/cost/providers/openrouter/fake-openrouter.ts +++ b/packages/backend/src/services/cost/providers/openrouter/fake-openrouter.ts @@ -1,12 +1,13 @@ // A fake OpenRouter for tests: `GET /key` and `GET /activity` answered from a mutable world, with // every call recorded. import { DISABLED_ORGANIZATION_FEATURE_FLAGS } from "@maple/domain/organization-feature-flags" -import { ConfigProvider, Effect, Layer, String as Str } from "effect" +import { Effect, Layer, String as Str } from "effect" import { FetchHttpClient } from "effect/http" import { Env } from "@maple/backend/platform/Env" import { OrganizationFeatureFlagsService } from "@maple/backend/services/org/OrganizationFeatureFlagsService" import type { TestDb } from "@maple/backend/testing/test-pglite" import type { ActivityItem } from "../../../org/openrouter-api" +import { fakeCostEnvConfig } from "../fake-cost-env" export interface FakeOpenRouter { /** Status of `GET /key`. */ @@ -74,31 +75,12 @@ const fetchStub = : Response.json({ data: world.activity }) } -const config = (encryptionKey: string) => - ConfigProvider.layer( - ConfigProvider.fromUnknown({ - PORT: "3472", - TINYBIRD_HOST: "https://api.tinybird.co", - TINYBIRD_TOKEN: "test-token", - MAPLE_AUTH_MODE: "self_hosted", - MAPLE_ROOT_PASSWORD: "test-root-password", - MAPLE_DEFAULT_ORG_ID: "default", - MAPLE_INGEST_KEY_ENCRYPTION_KEY: encryptionKey, - MAPLE_INGEST_KEY_LOOKUP_HMAC_KEY: "maple-test-lookup-secret", - MAPLE_INGEST_PUBLIC_URL: "https://ingest.test", - // The management host is the global one on every instance. - MAPLE_REGION: "eu", - }), - ) - -const ENCRYPTION_KEY = Buffer.alloc(32, 9).toString("base64") - /** Everything the OpenRouter cost services need, over the fake and a test database. */ export const fakeOpenRouterDeps = ( testDb: TestDb, world: FakeOpenRouter, /** `MAPLE_INGEST_KEY_ENCRYPTION_KEY`, for a deployment whose secret key is unusable. */ - encryptionKey = ENCRYPTION_KEY, + encryptionKey?: string, ) => Layer.mergeAll( FetchHttpClient.layer, @@ -110,5 +92,5 @@ export const fakeOpenRouterDeps = ( Env.layer, ).pipe( Layer.provide(Layer.succeed(FetchHttpClient.Fetch, fetchStub(world))), - Layer.provide(config(encryptionKey)), + Layer.provide(fakeCostEnvConfig(encryptionKey)), ) diff --git a/packages/backend/src/services/cost/sync-plan.test.ts b/packages/backend/src/services/cost/sync-plan.test.ts index b56cd2bb0f..0a5ebbdb4c 100644 --- a/packages/backend/src/services/cost/sync-plan.test.ts +++ b/packages/backend/src/services/cost/sync-plan.test.ts @@ -359,6 +359,70 @@ describe("planCostSync for a provider that serves 30 completed days", () => { }) }) +describe("planCostSync for a provider that serves a year of completed days", () => { + const CLOUDFLARE_NEW: CostSyncState = { ...NEW, provider: "cloudflare" } + const cloudflarePulled = ( + now: number, + backfillDate: string, + ago: { readonly head: number; readonly settle: number }, + ): CostSyncState => ({ + ...pulled(now, backfillDate, ago), + provider: "cloudflare", + // The newest day it serves is the day before the pull. + watermarkDate: utcToday(now - ago.head - DAY_MS), + }) + + it("starts at the previous month and ends yesterday", () => { + const now = at("2026-11-20T12:00:00Z") + assert.deepStrictEqual(planCostSync(CLOUDFLARE_NEW, now), { + mode: "settle", + startDay: "2026-10-01", + endDay: "2026-11-19", + }) + }) + + it("takes the four newest served days as its head", () => { + const now = at("2026-11-20T12:00:00Z") + const state = cloudflarePulled(now, "2026-10-01", { head: DAY_MS, settle: 2 * DAY_MS }) + assert.deepStrictEqual(planCostSync(state, now), { + mode: "head", + startDay: "2026-11-16", + endDay: "2026-11-19", + }) + }) + + it("walks history a month per step down to the eleventh month back, and no further", () => { + const now = at("2026-11-20T12:00:00Z") + const current = { head: HOUR_MS, settle: HOUR_MS } + assert.strictEqual(historyFloor("cloudflare", "2026-11-20"), "2025-12-01") + assert.deepStrictEqual(planCostSync(cloudflarePulled(now, "2026-01-01", current), now), { + mode: "backfill", + startDay: "2025-12-01", + endDay: "2025-12-31", + }) + assert.deepStrictEqual(planCostSync(cloudflarePulled(now, "2025-12-01", current), now), { + mode: "idle", + }) + }) + + it("keeps its floor inside the year Cloudflare serves on every day of a month", () => { + // One year back from the pull is the oldest day served: the floor is never before it. + assert.isTrue(historyFloor("cloudflare", "2026-11-01") >= "2025-11-01") + assert.isTrue(historyFloor("cloudflare", "2026-11-30") >= "2025-11-30") + assert.strictEqual(historyFloor("cloudflare", "2027-01-31"), "2026-02-01") + }) + + it("resumes at the floor after a hold longer than what the provider serves", () => { + const now = at("2026-11-20T12:00:00Z") + const state = cloudflarePulled(now, "2025-01-01", { head: 500 * DAY_MS, settle: 500 * DAY_MS }) + assert.deepStrictEqual(planCostSync(state, now), { + mode: "settle", + startDay: "2025-12-01", + endDay: "2026-11-19", + }) + }) +}) + describe("planCostSync request budgets", () => { const now = at("2026-11-11T12:00:00Z") const current = { head: HOUR_MS, settle: HOUR_MS } diff --git a/packages/backend/src/services/cost/sync-plan.ts b/packages/backend/src/services/cost/sync-plan.ts index b178bb1ec7..89c73089a5 100644 --- a/packages/backend/src/services/cost/sync-plan.ts +++ b/packages/backend/src/services/cost/sync-plan.ts @@ -31,6 +31,11 @@ export const HISTORY_MONTHS = 13 * dropped comes back empty, and an empty day replaces the stored one. */ const OPENROUTER_HISTORY_DAYS = 28 +/** + * Cloudflare serves the last year of days. History stops at the first day of the month this many + * months before the current one, which is always inside that year. + */ +const CLOUDFLARE_HISTORY_MONTHS = 11 // Through this day of the month the previous month's bill is still open; the first settle after // it takes the previous month once more, so its last pull is never from before it closed. export const PREVIOUS_MONTH_OPEN_DAYS = 10 @@ -66,6 +71,7 @@ interface CostSyncProfile { const COST_SYNC_PROFILES = { aws: { floor: (today) => monthStart(today, -HISTORY_MONTHS), lagDays: 0 }, openrouter: { floor: (today) => addDays(today, -OPENROUTER_HISTORY_DAYS), lagDays: 1 }, + cloudflare: { floor: (today) => monthStart(today, -CLOUDFLARE_HISTORY_MONTHS), lagDays: 1 }, } as const satisfies Record /** The oldest day the sync pulls from `provider`. */ diff --git a/packages/backend/src/services/org/OrganizationService.ts b/packages/backend/src/services/org/OrganizationService.ts index 0cb34c0f9e..154b3b4988 100644 --- a/packages/backend/src/services/org/OrganizationService.ts +++ b/packages/backend/src/services/org/OrganizationService.ts @@ -174,6 +174,8 @@ const PURGE_ORDER: ReadonlyArray = [ "OrgOpenRouterKeys", // A surviving key would keep Maple reading the deleted org's OpenRouter spend. "OpenRouterCostConnections", + // The same for every provider read with a pasted credential. + "CostConnections", // A deleted member org stops being billed to anyone. A billing org with members is refused. "OrgBillingLinks", // Drop rules and the hash salt: a recreated org must not silently lose data to old rules. diff --git a/packages/backend/src/services/secrets/reseal-targets.ts b/packages/backend/src/services/secrets/reseal-targets.ts index cf6302d0a0..329ec67713 100644 --- a/packages/backend/src/services/secrets/reseal-targets.ts +++ b/packages/backend/src/services/secrets/reseal-targets.ts @@ -10,6 +10,7 @@ import { AwsConnections, ChatWorkspaces, CliDeviceAuthorizations, + CostConnections, DashboardShares, ExternalDataSources, GcpConnectors, @@ -274,6 +275,14 @@ export const RESEAL_TARGETS = { keys: One, ref: org, }), + costConnectionSecretSlot: tripleTarget({ + slot: slots.costConnectionSecretSlot, + table: CostConnections, + columns: { ciphertext: "secretCiphertext", iv: "secretIv", tag: "secretTag" }, + identity: ["orgId", "provider"], + keys: Two, + ref: ([orgId, provider]) => ({ orgId, provider }), + }), planetScaleWebhookSecretSlot: tripleTarget({ slot: slots.planetScaleWebhookSecretSlot, table: PlanetscaleConnections, diff --git a/packages/db/drizzle/20261112221939_cost_connections/migration.sql b/packages/db/drizzle/20261112221939_cost_connections/migration.sql new file mode 100644 index 0000000000..38b900c6d8 --- /dev/null +++ b/packages/db/drizzle/20261112221939_cost_connections/migration.sql @@ -0,0 +1,18 @@ +-- metadata +CREATE TABLE IF NOT EXISTS "cost_connections" ( + "id" text NOT NULL, + "org_id" text NOT NULL, + "provider" text NOT NULL, + "secret_ciphertext" text NOT NULL, + "secret_iv" text NOT NULL, + "secret_tag" text NOT NULL, + "account_id" text NOT NULL, + "account_label" text NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + "updated_by" text NOT NULL, + CONSTRAINT "cost_connections_pkey" PRIMARY KEY ("id") +); +--> statement-breakpoint +-- metadata +CREATE UNIQUE INDEX IF NOT EXISTS "cost_connections_org_provider_idx" ON "cost_connections" USING btree ("org_id", "provider"); diff --git a/packages/db/drizzle/20261112221939_cost_connections/snapshot.json b/packages/db/drizzle/20261112221939_cost_connections/snapshot.json new file mode 100644 index 0000000000..0b95e41202 --- /dev/null +++ b/packages/db/drizzle/20261112221939_cost_connections/snapshot.json @@ -0,0 +1,22723 @@ +{ + "version": "1", + "dialect": "postgres", + "id": "e24847153356524297eba7b70f21b8155780037b367cbe68be5c37437fe1d03e", + "prevIds": [ + "67bf09ed830b16a5d08ab4b0bc5eb1cf7cdb14d990a5a5d8817a133b73c9b593" + ], + "entities": [ + { + "kind": "table", + "name": "actors", + "primaryKey": { + "name": "actors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "agent_feedback", + "primaryKey": { + "name": "agent_feedback_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "ai_triage_settings", + "primaryKey": { + "name": "ai_triage_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "alert_delivery_events", + "primaryKey": { + "name": "alert_delivery_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_destinations", + "primaryKey": { + "name": "alert_destinations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_escalation_runs", + "primaryKey": { + "name": "alert_escalation_runs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incident_events", + "primaryKey": { + "name": "alert_incident_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_incidents", + "primaryKey": { + "name": "alert_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_linear_issues", + "primaryKey": { + "name": "alert_linear_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_maintenance_windows", + "primaryKey": { + "name": "alert_maintenance_windows_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_member_notifications", + "primaryKey": { + "name": "alert_member_notifications_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_claims", + "primaryKey": { + "name": "alert_rule_claims_pkey", + "columns": [ + "rule_id" + ] + } + }, + { + "kind": "table", + "name": "alert_rule_states", + "primaryKey": { + "name": "alert_rule_states_org_id_rule_id_group_key_pk", + "columns": [ + "org_id", + "rule_id", + "group_key" + ] + } + }, + { + "kind": "table", + "name": "alert_rules", + "primaryKey": { + "name": "alert_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "alert_silences", + "primaryKey": { + "name": "alert_silences_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_settings", + "primaryKey": { + "name": "anomaly_detector_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "anomaly_detector_states", + "primaryKey": { + "name": "anomaly_detector_states_org_id_detector_key_pk", + "columns": [ + "org_id", + "detector_key" + ] + } + }, + { + "kind": "table", + "name": "anomaly_incidents", + "primaryKey": { + "name": "anomaly_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "api_keys", + "primaryKey": { + "name": "api_keys_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "aws_connections", + "primaryKey": { + "name": "aws_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cancellation_reviews", + "primaryKey": { + "name": "cancellation_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_identities", + "primaryKey": { + "name": "chat_identities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "chat_workspaces", + "primaryKey": { + "name": "chat_workspaces_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cli_device_authorizations", + "primaryKey": { + "name": "cli_device_authorizations_pkey", + "columns": [ + "device_code_hash" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_analytics_state", + "primaryKey": { + "name": "cloudflare_analytics_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_hyperdrive_configs", + "primaryKey": { + "name": "cloudflare_hyperdrive_configs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_logpush_connectors", + "primaryKey": { + "name": "cloudflare_logpush_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cloudflare_zone_settings", + "primaryKey": { + "name": "cloudflare_zone_settings_pkey", + "columns": [ + "org_id", + "account_id" + ] + } + }, + { + "kind": "table", + "name": "cost_connections", + "primaryKey": { + "name": "cost_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "cost_sources", + "primaryKey": { + "name": "cost_sources_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_shares", + "primaryKey": { + "name": "dashboard_shares_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboard_versions", + "primaryKey": { + "name": "dashboard_versions_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dashboards", + "primaryKey": { + "name": "dashboards_org_id_id_pk", + "columns": [ + "org_id", + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_findings", + "primaryKey": { + "name": "dependency_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scan_matches", + "primaryKey": { + "name": "dependency_scan_matches_pkey", + "columns": [ + "scan_id", + "lockfile_path", + "ecosystem", + "package_name", + "version", + "advisory_id" + ] + } + }, + { + "kind": "table", + "name": "dependency_scans", + "primaryKey": { + "name": "dependency_scans_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_credentials", + "primaryKey": { + "name": "device_credentials_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "device_proof_replays", + "primaryKey": { + "name": "device_proof_replays_pkey", + "columns": [ + "key" + ] + } + }, + { + "kind": "table", + "name": "digest_subscriptions", + "primaryKey": { + "name": "digest_subscriptions_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_fingerprint_candidates", + "primaryKey": { + "name": "error_fingerprint_candidates_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_incidents", + "primaryKey": { + "name": "error_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_events", + "primaryKey": { + "name": "error_issue_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_fingerprints", + "primaryKey": { + "name": "error_issue_fingerprints_org_id_fingerprint_hash_pk", + "columns": [ + "org_id", + "fingerprint_hash" + ] + } + }, + { + "kind": "table", + "name": "error_issue_pull_requests", + "primaryKey": { + "name": "error_issue_pull_requests_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_states", + "primaryKey": { + "name": "error_issue_states_org_id_issue_id_pk", + "columns": [ + "org_id", + "issue_id" + ] + } + }, + { + "kind": "table", + "name": "error_issue_verifications", + "primaryKey": { + "name": "error_issue_verifications_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_issues", + "primaryKey": { + "name": "error_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_deliveries", + "primaryKey": { + "name": "error_notification_deliveries_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "error_notification_policies", + "primaryKey": { + "name": "error_notification_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "error_tick_states", + "primaryKey": { + "name": "error_tick_states_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "escalation_chains", + "primaryKey": { + "name": "escalation_chains_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "external_data_source_catalog_entries", + "primaryKey": { + "name": "external_data_source_catalog_entries_pk", + "columns": [ + "source_id", + "name" + ] + } + }, + { + "kind": "table", + "name": "external_data_sources", + "primaryKey": { + "name": "external_data_sources_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "gcp_connectors", + "primaryKey": { + "name": "gcp_connectors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "gcp_resources", + "primaryKey": { + "name": "gcp_resources_pkey", + "columns": [ + "connector_id", + "name" + ] + } + }, + { + "kind": "table", + "name": "investigations", + "primaryKey": { + "name": "investigations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalation_policies", + "primaryKey": { + "name": "issue_escalation_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "issue_escalations", + "primaryKey": { + "name": "issue_escalations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "live_activities", + "primaryKey": { + "name": "live_activities_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_authorizations", + "primaryKey": { + "name": "mcp_oauth_authorizations_pkey", + "columns": [ + "request_id_hash" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_clients", + "primaryKey": { + "name": "mcp_oauth_clients_pkey", + "columns": [ + "client_id" + ] + } + }, + { + "kind": "table", + "name": "mcp_oauth_refresh_tokens", + "primaryKey": { + "name": "mcp_oauth_refresh_tokens_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "mobile_devices", + "primaryKey": { + "name": "mobile_devices_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "oauth_auth_states", + "primaryKey": { + "name": "oauth_auth_states_pkey", + "columns": [ + "state" + ] + } + }, + { + "kind": "table", + "name": "oauth_connections", + "primaryKey": { + "name": "oauth_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "oncall_overrides", + "primaryKey": { + "name": "oncall_overrides_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "oncall_schedules", + "primaryKey": { + "name": "oncall_schedules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "openrouter_cost_connections", + "primaryKey": { + "name": "openrouter_cost_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_billing_links", + "primaryKey": { + "name": "org_billing_links_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_schema_apply_runs", + "primaryKey": { + "name": "org_clickhouse_schema_apply_runs_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_clickhouse_settings", + "primaryKey": { + "name": "org_clickhouse_settings_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_attribute_mappings", + "primaryKey": { + "name": "org_ingest_attribute_mappings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_keys", + "primaryKey": { + "name": "org_ingest_keys_org_id_pk", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_settings", + "primaryKey": { + "name": "org_ingest_rule_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rule_stats", + "primaryKey": { + "name": "org_ingest_rule_stats_pkey", + "columns": [ + "org_id", + "rule_id", + "bucket", + "signal" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_rules", + "primaryKey": { + "name": "org_ingest_rules_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_ingest_sampling_policies", + "primaryKey": { + "name": "org_ingest_sampling_policies_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_onboarding_state", + "primaryKey": { + "name": "org_onboarding_state_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_openrouter_keys", + "primaryKey": { + "name": "org_openrouter_keys_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "org_recommendation_issues", + "primaryKey": { + "name": "org_recommendation_issues_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "org_support_channels", + "primaryKey": { + "name": "org_support_channels_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_connections", + "primaryKey": { + "name": "planetscale_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_databases", + "primaryKey": { + "name": "planetscale_databases_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_events", + "primaryKey": { + "name": "planetscale_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_issue_receipts", + "primaryKey": { + "name": "planetscale_issue_receipts_org_id_event_id_pk", + "columns": [ + "org_id", + "event_id" + ] + } + }, + { + "kind": "table", + "name": "planetscale_poll_state", + "primaryKey": { + "name": "planetscale_poll_state_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_edits", + "primaryKey": { + "name": "pr_review_edits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_finding_embeddings", + "primaryKey": { + "name": "pr_review_finding_embeddings_pkey", + "columns": [ + "finding_id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_findings", + "primaryKey": { + "name": "pr_review_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_merge_steps", + "primaryKey": { + "name": "pr_review_merge_steps_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_replies", + "primaryKey": { + "name": "pr_review_replies_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "pr_review_settings", + "primaryKey": { + "name": "pr_review_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "pr_reviews", + "primaryKey": { + "name": "pr_reviews_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_connections", + "primaryKey": { + "name": "railway_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "railway_environments", + "primaryKey": { + "name": "railway_environments_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_target_checks", + "primaryKey": { + "name": "scrape_target_checks_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "scrape_targets", + "primaryKey": { + "name": "scrape_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "search_console_sites", + "primaryKey": { + "name": "search_console_sites_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_events", + "primaryKey": { + "name": "security_audit_events_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_findings", + "primaryKey": { + "name": "security_audit_findings_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_reservations", + "primaryKey": { + "name": "security_audit_org_reservations_pkey", + "columns": [ + "run_id", + "agent_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_org_settings", + "primaryKey": { + "name": "security_audit_org_settings_pkey", + "columns": [ + "org_id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_run_findings", + "primaryKey": { + "name": "security_audit_run_findings_pkey", + "columns": [ + "run_id", + "fingerprint" + ] + } + }, + { + "kind": "table", + "name": "security_audit_runs", + "primaryKey": { + "name": "security_audit_runs_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_targets", + "primaryKey": { + "name": "security_audit_targets_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "security_audit_units", + "primaryKey": { + "name": "security_audit_units_pkey", + "columns": [ + "run_id", + "coverage_id" + ] + } + }, + { + "kind": "table", + "name": "service_map_world_entities", + "primaryKey": { + "name": "service_map_world_entities_pkey", + "columns": [ + "org_id", + "entity_id" + ] + } + }, + { + "kind": "table", + "name": "slos", + "primaryKey": { + "name": "slos_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "sources", + "primaryKey": { + "name": "sources_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_cleanups", + "primaryKey": { + "name": "status_page_cleanups_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_components", + "primaryKey": { + "name": "status_page_components_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_custom_domains", + "primaryKey": { + "name": "status_page_custom_domains_pkey", + "columns": [ + "page_id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_components", + "primaryKey": { + "name": "status_page_incident_components_pk", + "columns": [ + "incident_id", + "component_id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incident_updates", + "primaryKey": { + "name": "status_page_incident_updates_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_page_incidents", + "primaryKey": { + "name": "status_page_incidents_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "status_pages", + "primaryKey": { + "name": "status_pages_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_connections", + "primaryKey": { + "name": "supabase_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "supabase_project_insights", + "primaryKey": { + "name": "supabase_project_insights_pkey", + "columns": [ + "project_id" + ] + } + }, + { + "kind": "table", + "name": "supabase_projects", + "primaryKey": { + "name": "supabase_projects_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_daily", + "primaryKey": { + "name": "uptime_monitor_daily_pk", + "columns": [ + "monitor_id", + "day" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitor_states", + "primaryKey": { + "name": "uptime_monitor_states_pkey", + "columns": [ + "monitor_id" + ] + } + }, + { + "kind": "table", + "name": "uptime_monitors", + "primaryKey": { + "name": "uptime_monitors_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_commits", + "primaryKey": { + "name": "vcs_commits_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_installations", + "primaryKey": { + "name": "vcs_installations_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repositories", + "primaryKey": { + "name": "vcs_repositories_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vcs_repository_branches", + "primaryKey": { + "name": "vcs_repository_branches_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vercel_connections", + "primaryKey": { + "name": "vercel_connections_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vercel_deployments", + "primaryKey": { + "name": "vercel_deployments_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "table", + "name": "vercel_projects", + "primaryKey": { + "name": "vercel_projects_pkey", + "columns": [ + "id" + ] + } + }, + { + "kind": "column", + "table": "actors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "user_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "agent_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "model", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "capabilities_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "actors", + "name": "last_active_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "impact", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "summary", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "reason", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "details", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "related_to", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_type", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_name", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_model", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "agent_version", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "agent_feedback", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_runs_per_day", + "position": 2, + "type": "integer", + "notNull": true, + "default": "20", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "max_passes_per_day", + "position": 3, + "type": "integer", + "notNull": true, + "default": "90", + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "ai_triage_settings", + "name": "updated_by", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "rule_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "destination_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "delivery_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempt_number", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "scheduled_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claim_expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "claimed_by", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "attempted_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_message", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "provider_reference", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "response_code", + "position": 16, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "error_message", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "silence_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "payload_json", + "position": 19, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_delivery_events", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "config_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "secret_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_tested_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_test_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "consecutive_failures", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "last_failure_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "disabled_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_destinations", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "chain_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "chain_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "severity", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "steps_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "repeat_count", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "step_index", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "pass_index", + "position": 9, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "next_step_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "status", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "payload_json", + "position": 12, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "steps_sent", + "position": 13, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "last_step_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "ended_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_escalation_runs", + "name": "updated_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "actor_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "source", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incident_events", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "incident_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "rule_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_key", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "signal_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "severity", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "peak_severity", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "comparator", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "threshold_upper", + "position": 12, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "first_triggered_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_triggered_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "resolved_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_observed_value", + "position": 16, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_sample_count", + "position": 17, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_evaluated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "dedupe_key", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_delivered_event_type", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "last_notified_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "hold_reason", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "held_since", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_user_id", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_by_name", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "acknowledged_via", + "position": 27, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "error_issue_id", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "anomaly_incident_id", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "group_label", + "position": 30, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "labels_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "silence_id", + "position": 32, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "silenced_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "created_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_incidents", + "name": "updated_at", + "position": 35, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "thread_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_team_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_issue_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_identifier", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "linear_url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_linear_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "reason", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "matchers_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "schedule_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "exclude_from_baselines", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "once_ends_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "revision", + "position": 9, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "created_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_maintenance_windows", + "name": "updated_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "run_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "channel", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "attempt_number", + "position": 7, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "scheduled_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "claimed_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "claim_expires_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "payload_json", + "position": 11, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "dedupe_key", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "error_message", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "attempted_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_member_notifications", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "rule_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "last_scheduled_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_claims", + "name": "consecutive_limit_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "group_key", + "position": 2, + "type": "text", + "notNull": true, + "default": "'__total__'", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_breaches", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "consecutive_healthy", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "history_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_status", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_value", + "position": 7, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_sample_count", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_evaluated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "last_error_kind", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rule_states", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notes", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "notification_template_json", + "position": 4, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "severity", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "service_names_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "exclude_service_names_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "environments_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "anomaly_signals_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "tags_json", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "signal_type", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "comparator", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "threshold_upper", + "position": 15, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "thresholds_json", + "position": 16, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "window_minutes", + "position": 17, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "evaluation_interval_minutes", + "position": 18, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_interval_minutes", + "position": 19, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "slowed_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "minimum_sample_count", + "position": 21, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_breaches_required", + "position": 22, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "breaches_out_of", + "position": 23, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "consecutive_healthy_required", + "position": 24, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "renotify_interval_minutes", + "position": 25, + "type": "integer", + "notNull": true, + "default": "30", + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "apdex_threshold_ms", + "position": 26, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_builder_draft_json", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "raw_query_sql", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "group_by", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "destination_ids_json", + "position": 30, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "query_spec_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "reducer", + "position": 32, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "sample_count_strategy", + "position": 33, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "no_data_behavior", + "position": 34, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "last_scheduled_at", + "position": 35, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_at", + "position": 36, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_at", + "position": 37, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "created_by", + "position": 38, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_rules", + "name": "updated_by", + "position": 39, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "reason", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "matchers_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "starts_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "ends_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "maintenance_window_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "exclude_from_baselines", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "created_by", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "alert_silences", + "name": "updated_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "sensitivity", + "position": 2, + "type": "text", + "notNull": true, + "default": "'normal'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "muted_signals_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "last_tick_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_settings", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "detector_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "signal_type", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "service_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "deployment_env", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "fingerprint_hash", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_breaches", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "consecutive_healthy", + "position": 7, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_value", + "position": 9, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "baseline_median", + "position": 10, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_sample_count", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_evaluated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "open_incident_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_resolved_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "last_incident_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_detector_states", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "detector_key", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "signal_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "deployment_env", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprint_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "error_issue_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "opened_value", + "position": 10, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_median", + "position": 11, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "baseline_sigma", + "position": 12, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "threshold_value", + "position": 13, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_observed_value", + "position": 14, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_sample_count", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "first_triggered_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_triggered_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolved_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "resolve_reason", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "triage_status", + "position": 20, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "dedupe_key", + "position": 21, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "fingerprints_json", + "position": 22, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "reopen_count", + "position": 23, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "last_reopened_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "created_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "anomaly_incidents", + "name": "updated_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "key_prefix", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "revoked_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "last_used_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "metadata_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "scopes", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "kind", + "position": 12, + "type": "text", + "notNull": true, + "default": "'standard'", + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "api_keys", + "name": "created_by_email", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "account_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "modules", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "granted_modules", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "template_version", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "verified_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_checked_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "last_check_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "check_lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "metric_stream_changed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_secret_hash", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_received_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "stream_last_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "aws_connections", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "plan_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "subscription_started_at", + "position": 3, + "type": "bigint", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "canceled_at", + "position": 4, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "rule_reason", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "model_read", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "posted_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cancellation_reviews", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "external_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "display_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_identities", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "connector", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "external_workspace_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "settings", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "credentials_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "chat_workspaces", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_code_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "user_code_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "device_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_org_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_roles", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_user_email", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "api_key_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_ciphertext", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_iv", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "token_tag", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "approved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "denied_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cli_device_authorizations", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "dataset", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_id", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "zone_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "watermark_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backfill_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_json", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "settings_fetched_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "quantiles_available", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "discovered_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "live_scripts_json", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "grant_call_cap", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_success_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "last_error_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backoff_until", + "position": 19, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backoff_streak", + "position": 20, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "backoff_reason", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "created_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_analytics_state", + "name": "updated_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "account_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "config_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_host", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_port", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_scheme", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_database", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "origin_user", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "deleted_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_hyperdrive_configs", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "zone_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "service_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "dataset", + "position": 6, + "type": "text", + "notNull": true, + "default": "'http_requests'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_ciphertext", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_iv", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_tag", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_hash", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "enabled", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "secret_rotated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "created_by", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_logpush_connectors", + "name": "updated_by", + "position": 18, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "account_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "watch_new_zones", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "zone_overrides", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cloudflare_zone_settings", + "name": "updated_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "secret_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "secret_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "secret_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "account_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "account_label", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_connections", + "name": "updated_by", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "connection_ref", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "account_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "watermark_date", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "backfill_date", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "last_synced_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "last_settled_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "last_error", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "last_error_reason", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "lease_until", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "next_attempt_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "consecutive_failures", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "requests_month", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "requests_count", + "position": 16, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "cost_sources", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "widget_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "mode", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_hash", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "token_suffix", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "updated_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_shares", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "dashboard_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "version_number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "snapshot_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "change_summary", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "source_version_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboard_versions", + "name": "created_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "payload_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dashboards", + "name": "version", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "lockfile_path", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "ecosystem", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "package_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "advisory_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "aliases", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "severity", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "summary", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "versions", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "relation", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_scan_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_scan_id", + "position": 15, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "first_seen_sha", + "position": 16, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "last_seen_sha", + "position": 17, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_scan_id", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "fixed_sha", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_reason", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_by", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "status_changed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_findings", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "scan_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "lockfile_path", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "ecosystem", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "package_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "version", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "relation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "advisory_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "aliases", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "summary", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scan_matches", + "name": "fixed_in", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "commit_sha", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "ref", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "trigger", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "default_branch_head", + "position": 6, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'running'", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "lockfiles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "package_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "vulnerable_count", + "position": 10, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "started_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "dependency_scans", + "name": "finished_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "public_jwk", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "jkt", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "roles", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "scopes", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attested", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "attestation", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "last_used_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "expires_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "hard_expires_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_credentials", + "name": "revoked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "key", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "device_proof_replays", + "name": "expires_at", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "email", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "opted_out_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "day_of_week", + "position": 6, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "timezone", + "position": 7, + "type": "text", + "notNull": true, + "default": "'UTC'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "send_hour", + "position": 8, + "type": "integer", + "notNull": true, + "default": "9", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "namespaces_json", + "position": 9, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "environments_json", + "position": 10, + "type": "text", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "last_attempted_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_enabled", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_opted_out_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_sent_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "web_analytics_last_attempted_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "digest_subscriptions", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "exception_message", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "error_label", + "position": 5, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "top_frame", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "service_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "first_seen_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "last_seen_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_fingerprint_candidates", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "reason", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "first_triggered_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "last_triggered_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "resolved_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_incidents", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "actor_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "type", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "from_state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "to_state", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "payload_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_events", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "fingerprint_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "source", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "origin_issue_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_fingerprints", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "repo_full_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "number", + "position": 6, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "url", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "title", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "author_login", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "state", + "position": 10, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merged_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "merge_commit_sha", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "link_source", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "linked_by_actor_id", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_pull_requests", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "issue_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_observed_occurrence_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "last_evaluated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "open_incident_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "pull_request_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "status", + "position": 4, + "type": "text", + "notNull": true, + "default": "'waiting'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "merged_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verify_after", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_versions_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_occurrence_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "baseline_rate_per_hour", + "position": 9, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "investigation_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "verdict_note", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "post_merge_occurrence_count", + "position": 13, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "attempt", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issue_verifications", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "kind", + "position": 2, + "type": "text", + "notNull": true, + "default": "'error'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "source_ref_json", + "position": 3, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_hash", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "fingerprint_version", + "position": 5, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "service_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_type", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "exception_message", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "error_label", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "top_frame", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "workflow_state", + "position": 11, + "type": "text", + "notNull": true, + "default": "'triage'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "priority", + "position": 12, + "type": "integer", + "notNull": true, + "default": "3", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "severity_source", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "assigned_actor_id", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_holder_actor_id", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "lease_expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "claimed_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "notes", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "first_seen_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "occurrence_count", + "position": 22, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_by_actor_id", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_resolved_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "last_regressed_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "regression_count", + "position": 27, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "seen_versions_json", + "position": 28, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "resolved_versions_json", + "position": 29, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "snooze_until", + "position": 30, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "archived_at", + "position": 31, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "merged_into_issue_id", + "position": 32, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "created_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_issues", + "name": "updated_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "destination_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "delivery_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "payload_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "status", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempt_count", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "scheduled_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claim_expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "claimed_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "attempted_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "error_message", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_deliveries", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "destination_ids_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_first_seen", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_regression", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_resolve", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_in_review", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_transition_done", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "notify_on_claim", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "min_occurrence_count", + "position": 9, + "type": "integer", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "severity", + "position": 10, + "type": "text", + "notNull": true, + "default": "'warning'", + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_notification_policies", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "processed_through", + "position": 1, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "bootstrap_completed", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_token", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "claim_expires_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "error_tick_states", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "steps_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "repeat_count", + "position": 5, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "created_by", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "escalation_chains", + "name": "updated_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "source_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "row_estimate", + "position": 4, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "source_note", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "detected_time_field", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "fields", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "exposed", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "note", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "time_field_override", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "field_notes", + "position": 11, + "type": "jsonb", + "notNull": true, + "default": "'{}'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "external_data_source_catalog_entries", + "name": "seen_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "description", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "environment", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "tags", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": "'[]'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "agent_access", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "agent_pii", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "config", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "secret_ciphertext", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "secret_iv", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "secret_tag", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "secret_hint", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "status", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "last_checked_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "last_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "catalog_refreshed_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "catalog_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "examples", + "position": 19, + "type": "jsonb", + "notNull": true, + "default": "'[]'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "updated_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "created_by", + "position": 22, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "external_data_sources", + "name": "updated_by", + "position": 23, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "scope_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "project_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "logs_enabled", + "position": 6, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_ciphertext", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_iv", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_tag", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "secret_hash", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_received_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_logs_enabled", + "position": 14, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "applied_metrics_enabled", + "position": 15, + "type": "boolean", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "setup_reported_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_watermark_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_received_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_metrics_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "metrics_lease_until", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "resources_synced_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "last_resources_error", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_by", + "position": 23, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "created_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_connectors", + "name": "updated_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "connector_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "asset_type", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "project_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "location", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "display_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "labels", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "details", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "resource_updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "gcp_resources", + "name": "last_seen_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'investigating'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "seeded_by", + "position": 3, + "type": "text", + "notNull": true, + "default": "'user'", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "subject_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "snapshot_json", + "position": 5, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_kind", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "incident_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "issue_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "report_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "progress_json", + "position": 10, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "severity", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "confidence", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "input_tokens", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "output_tokens", + "position": 15, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "started_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "autonomous_turns", + "position": 18, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_by", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "created_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "diagnosed_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "investigations", + "name": "updated_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "enabled", + "position": 1, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "rules_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalation_policies", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "issue_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "severity", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "source", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "reason", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "run_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "investigation_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "payload_json", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "delivery_results_json", + "position": 9, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "attempts", + "position": 11, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "dedupe_key", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "issue_escalations", + "name": "processed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "device_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "incident_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "activity_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "push_token", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "ended_reason", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "live_activities", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "request_id_hash", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "client_name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "redirect_uri", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "state", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "resource", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "scopes", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "code_challenge", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "authorization_code_hash", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_org_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_roles", + "position": 11, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_user_email", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "approved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "denied_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "used_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_authorizations", + "name": "expires_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_name", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "redirect_uris", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "client_uri", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_clients", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "token_hash", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "client_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "resource", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "scopes", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "org_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "roles", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "user_email", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "access_key_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "replaced_by_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "revoked_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "expires_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mcp_oauth_refresh_tokens", + "name": "family_expires_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "user_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "platform", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "token", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "environment", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "bundle_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "app_version", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "live_activity_start_token", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "device_name", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "preferences", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "disabled_reason", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_pushed_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "last_seen_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "mobile_devices", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "state", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "initiated_by_user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "redirect_uri", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "return_to", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "code_verifier", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "github_installation_ids", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_auth_states", + "name": "expires_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_user_email", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "external_account_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "granted_accounts_json", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "connected_by_user_id", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "scope", + "position": 9, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_ciphertext", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_iv", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "access_token_tag", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_ciphertext", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_iv", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "refresh_token_tag", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "expires_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "revoked_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oauth_connections", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "schedule_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "user_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "starts_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "ends_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_overrides", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "time_zone", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "layers_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "created_by", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "oncall_schedules", + "name": "updated_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "key_ciphertext", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "key_iv", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "key_tag", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "key_label", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "account_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "updated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "openrouter_cost_connections", + "name": "updated_by", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "billing_org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "linked_by", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_billing_links", + "name": "created_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "workflow_instance_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "phase", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "current_migration", + "position": 4, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_total", + "position": 5, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "steps_done", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "applied_versions", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "skipped", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "error_message", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "started_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "finished_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_schema_apply_runs", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_url", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_user", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_ciphertext", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_iv", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_password_tag", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "ch_database", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "sync_status", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "last_sync_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "schema_version", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_clickhouse_settings", + "name": "updated_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_context", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "source_key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "target_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "operation", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_attribute_mappings", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_key_hash", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_key_hash", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "public_rotated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "private_rotated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "created_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_keys", + "name": "updated_by", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "hash_salt", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_settings", + "name": "created_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "rule_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "bucket", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "signal", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "matched", + "position": 4, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped", + "position": 5, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "dropped_bytes", + "position": 6, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rule_stats", + "name": "redacted_values", + "position": 7, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "enabled", + "position": 3, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "position", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "spec_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_rules", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "trace_sample_ratio", + "position": 1, + "type": "double precision", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_error_spans", + "position": 2, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "always_keep_slow_spans_ms", + "position": 3, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "created_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_ingest_sampling_policies", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "user_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "email", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "role", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "demo_data_requested", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "onboarding_completed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "checklist_dismissed_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "first_data_received_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "welcome_email_sent_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "connect_nudge_email_sent_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "stalled_email_sent_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "activation_email_sent_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_claimed_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "reward_reserved_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_onboarding_state", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_ciphertext", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_iv", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_tag", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "key_hint", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_openrouter_keys", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "number", + "position": 2, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "recommendation_key", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "source_key", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "canonical_key", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "status", + "position": 7, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "usage_count", + "position": 8, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "opened_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": "now()", + "identity": null + }, + { + "kind": "column", + "table": "org_recommendation_issues", + "name": "resolved_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_id", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "slack_channel_name", + "position": 2, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reservation_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "reserved_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_by_user_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "created_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "org_support_channels", + "name": "updated_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "ps_organization", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "connected_by_user_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "scrape_target_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_ciphertext", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_iv", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "webhook_secret_tag", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "detected_permissions_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "last_inventory_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_connections", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": "'mysql'", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "state", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "region", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "plan", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "branches_json", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_ciphertext", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_iv", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_secret_tag", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "webhook_error", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "deleted_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_databases", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_id", + "position": 2, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "database_name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "branch_name", + "position": 4, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "category", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "event_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "state", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "external_id", + "position": 8, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "title", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "source", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "actor_login", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "payload_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "occurred_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_events", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "event_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_issue_receipts", + "name": "processed_at", + "position": 2, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "dataset", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "database_id", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "enabled", + "position": 4, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "watermark_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_success_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "last_error_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "lease_until", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "planetscale_poll_state", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "reply_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "seq", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "path", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "old_text", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "new_text", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_edits", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "finding_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "embedding", + "position": 4, + "type": "real[]", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_finding_embeddings", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "review_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "handle", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "path", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "line", + "position": 7, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "category", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "severity", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "title", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "status", + "position": 11, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "comment_id", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "resolved_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_up", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "reactions_down", + "position": 15, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "created_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_findings", + "name": "updated_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "key", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "title", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "source", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "path", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_by", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "done_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "reminded_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "first_review_id", + "position": 13, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "last_review_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_merge_steps", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "comment_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "surface", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "thread_root_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "author_login", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "command", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "head_sha", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "reply_url", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "commit_sha", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "error", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_replies", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "model", + "position": 1, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "defaults", + "position": 2, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_review_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "repository_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "number", + "position": 3, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "head_sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "base_sha", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "url", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "title", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'queued'", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "skip_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "session_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_attempt", + "position": 12, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "report_json", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "score", + "position": 14, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "check_run_url", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "comment_url", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "review_url", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "publish_error", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "input_tokens", + "position": 21, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "output_tokens", + "position": 22, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "cost_microusd", + "position": 23, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "started_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "finished_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merged_at", + "position": 26, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "telemetry_json", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "merge_commit_sha", + "position": 28, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_status", + "position": 29, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_after", + "position": 30, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "post_merge_json", + "position": 31, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "created_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "pr_reviews", + "name": "updated_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_ciphertext", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_iv", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "token_tag", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "workspace_names", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "connected_by_user_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "auth_failed_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "discovered_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_success_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "last_error_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_connections", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "project_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_id", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "environment_name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "services_json", + "position": 7, + "type": "text", + "notNull": true, + "default": "'{}'", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "enabled", + "position": 8, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "watermark_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_success_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "last_error_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "railway_environments", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "id", + "position": 0, + "type": "integer", + "notNull": true, + "default": null, + "identity": "by default" + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "target_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "sub_target_key", + "position": 3, + "type": "text", + "notNull": true, + "default": "''", + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "error", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "duration_ms", + "position": 6, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_scraped", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_target_checks", + "name": "samples_post_relabel", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "url", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "target_type", + "position": 6, + "type": "text", + "notNull": true, + "default": "'prometheus'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "discovery_config_json", + "position": 7, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "scrape_interval_seconds", + "position": 8, + "type": "integer", + "notNull": true, + "default": "15", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "labels_json", + "position": 9, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_type", + "position": 10, + "type": "text", + "notNull": true, + "default": "'none'", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "managed_by", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_ciphertext", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_iv", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "auth_credentials_tag", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "last_scrape_error", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "created_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "scrape_targets", + "name": "updated_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "oauth_connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "site_url", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "permission_level", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "watermark_date", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "backfill_date", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "last_synced_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "last_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "lease_until", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "next_attempt_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "consecutive_failures", + "position": 12, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "search_console_sites", + "name": "updated_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "run_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "at", + "position": 3, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "kind", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "role", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "agent_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "outcome", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "detail", + "position": 8, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "input_tokens", + "position": 9, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_events", + "name": "output_tokens", + "position": 10, + "type": "bigint", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fingerprint", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "verdict", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "overall_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "estimated_severity", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "record", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status", + "position": 8, + "type": "text", + "notNull": true, + "default": "'open'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_run_id", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_run_id", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "first_seen_ref", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "last_seen_ref", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "fixed_run_id", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_reason", + "position": 14, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_by", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "status_changed_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "created_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_findings", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "agent_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "cost_usd", + "position": 3, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "sandbox_fee_usd", + "position": 4, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_reservations", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "monthly_spend_limit_usd", + "position": 1, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "discovery_model", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "validation_model", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_by", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_org_settings", + "name": "updated_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "fingerprint", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "verdict", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "overall_severity", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "estimated_severity", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_run_findings", + "name": "record", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "target_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "source_ref", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "profile", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "scope_paths", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "budget", + "position": 7, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "metadata", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "status", + "position": 9, + "type": "text", + "notNull": true, + "default": "'in_progress'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "incomplete_reason", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "architecture_md", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "reports", + "position": 12, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "execution_profile", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "invocations_spent", + "position": 14, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "input_tokens", + "position": 15, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "output_tokens", + "position": 16, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "cost_usd", + "position": 17, + "type": "double precision", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "sandbox_usd", + "position": 18, + "type": "double precision", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "discovery_model", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "validation_model", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "workflow_instance_id", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "started_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "finished_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_runs", + "name": "error", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "repository", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "vcs_repository_id", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "branch", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "enabled", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "profile", + "position": 6, + "type": "text", + "notNull": true, + "default": "'deep'", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "scope_paths", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "slice_interval_hours", + "position": 8, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "revalidate_on_push", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "budget_per_run", + "position": 10, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "monthly_invocation_budget", + "position": 11, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "discovery_model", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "validation_model", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_targets", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "run_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "coverage_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "wave", + "position": 4, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "agent_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "security_audit_units", + "name": "unit", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "org_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "entity_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "kind", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "data", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "revision", + "position": 4, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "created_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "updated_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "updated_by", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "service_map_world_entities", + "name": "deleted_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "service_name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "environments_json", + "position": 5, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "sli_json", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "target_percent", + "position": 7, + "type": "double precision", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "window_days", + "position": 8, + "type": "integer", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "alert_rule_id", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_by", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_by", + "position": 11, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "created_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "slos", + "name": "updated_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "description", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "platform", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "kind", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_hash", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "key_prefix", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "allowed_signals", + "position": 8, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rate_limit_rps", + "position": 9, + "type": "integer", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "resource_attributes", + "position": 10, + "type": "jsonb", + "notNull": true, + "default": "'[]'::jsonb", + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "expires_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "paused_until", + "position": 13, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_used_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejected_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_rejection", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_at", + "position": 17, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_ok_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "last_poll_error", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "disconnected_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "poll_resources", + "position": 21, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "rolled_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "created_by", + "position": 25, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "sources", + "name": "updated_by", + "position": 26, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "slug", + "position": 3, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "delete_snapshot", + "position": 4, + "type": "boolean", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "hostname", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "cloudflare_hostname_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "cloudflare_route_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_cleanups", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "name", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "group_name", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "position", + "position": 6, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "monitor_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "manual_status", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "show_uptime", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_components", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "page_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "hostname", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "cloudflare_hostname_id", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "cloudflare_route_id", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "dns_records", + "position": 6, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "last_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "last_checked_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "activated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_custom_domains", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "incident_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "component_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_components", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "incident_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "status", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "body", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "source", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_by", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "overlay_entry_id", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incident_updates", + "name": "created_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "page_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "title", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "impact", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "published", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "alert_incident_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_start", + "position": 9, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "scheduled_end", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "started_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "resolved_at", + "position": 12, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_by", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "created_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_page_incidents", + "name": "updated_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "slug", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "description", + "position": 4, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "website_url", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "logo_url", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "logo_dark_url", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "favicon_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "support_url", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "privacy_url", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "terms_url", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "theme", + "position": 12, + "type": "jsonb", + "notNull": true, + "default": "'{\"preset\":\"default\",\"appearance\":\"auto\",\"accent\":null,\"background\":null,\"text\":null,\"card\":null,\"border\":null,\"radius\":\"md\",\"font\":\"sans\"}'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "show_powered_by", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "auto_incidents", + "position": 14, + "type": "text", + "notNull": true, + "default": "'off'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_sha256", + "position": 15, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "break_glass_token_suffix", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "visibility", + "position": 17, + "type": "text", + "notNull": true, + "default": "'public'", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "password_hash", + "position": 18, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "draft", + "position": 19, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "revision", + "position": 20, + "type": "bigint", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_revision", + "position": 21, + "type": "bigint", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "last_publish_error", + "position": 23, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "published_hash", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "publish_attempted_at", + "position": 25, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_by", + "position": 26, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "created_at", + "position": 27, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "status_pages", + "name": "updated_at", + "position": 28, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_slug", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "supabase_organization_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "connected_by_user_id", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "discovery_lease_until", + "position": 7, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "last_discovery_error", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "created_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_connections", + "name": "updated_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "project_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_json", + "position": 2, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "top_queries_json", + "position": 3, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_checked_at", + "position": 4, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_checked_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "advisors_error", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "queries_error", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "lease_until", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "created_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_project_insights", + "name": "updated_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "project_ref", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "region", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "status", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "metrics_enabled", + "position": 7, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "scrape_target_id", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_enabled", + "position": 9, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_id", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_source_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drain_error", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_json", + "position": 13, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "log_drains_checked_at", + "position": 14, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_watermark_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_boundary_ids", + "position": 17, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_lease_until", + "position": 18, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_last_polled_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "logs_error", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "last_seen_at", + "position": 21, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "removed_at", + "position": 22, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "created_at", + "position": 23, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "supabase_projects", + "name": "updated_at", + "position": 24, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "day", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "org_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "checks", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "failures", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "down_checks", + "position": 5, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_daily", + "name": "last_check_at", + "position": 6, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "monitor_id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status", + "position": 2, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_failures", + "position": 3, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "consecutive_successes", + "position": 4, + "type": "integer", + "notNull": true, + "default": "0", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "status_changed_at", + "position": 5, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_check_json", + "position": 6, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "recent_json", + "position": 7, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "last_ping_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitor_states", + "name": "updated_at", + "position": 9, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "name", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "kind", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "config_json", + "position": 4, + "type": "jsonb", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "headers_ciphertext", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "headers_iv", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "headers_tag", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "interval_seconds", + "position": 8, + "type": "integer", + "notNull": true, + "default": "60", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "failures_to_down", + "position": 9, + "type": "integer", + "notNull": true, + "default": "2", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "service_name", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "alert_rule_id", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "heartbeat_token", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "enabled", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_by", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "uptime_monitors", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "sha", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "message", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_name", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_email", + "position": 7, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_login", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "author_avatar_url", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "authored_at", + "position": 10, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "committed_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "html_url", + "position": 12, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_commits", + "name": "created_at", + "position": 13, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "provider", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_installation_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_login", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_type", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "external_account_id", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "account_avatar_url", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_selection", + "position": 9, + "type": "text", + "notNull": true, + "default": "'all'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "status", + "position": 10, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "suspended_at", + "position": 11, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "ci_traces_enabled", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "repository_discovery", + "position": 13, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "installed_by_user_id", + "position": 14, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "created_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_installations", + "name": "updated_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "installation_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "external_repo_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "owner", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "name", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "full_name", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "default_branch", + "position": 8, + "type": "text", + "notNull": true, + "default": "'main'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "tracked_branch", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "html_url", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_private", + "position": 11, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "is_archived", + "position": 12, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "status", + "position": 13, + "type": "text", + "notNull": true, + "default": "'active'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "sync_status", + "position": 14, + "type": "text", + "notNull": true, + "default": "'pending'", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_synced_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "last_sync_error", + "position": 16, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_enabled", + "position": 17, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "pr_review_config", + "position": 18, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "created_at", + "position": 19, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repositories", + "name": "updated_at", + "position": 20, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "provider", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "repository_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "is_default", + "position": 5, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "head_sha", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vcs_repository_branches", + "name": "updated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "source_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "team_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "team_slug", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "team_name", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "token_ciphertext", + "position": 6, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "token_iv", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "token_tag", + "position": 8, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "connected_by_user_id", + "position": 9, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "project_scope", + "position": 10, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "project_ids", + "position": 11, + "type": "jsonb", + "notNull": true, + "default": "'[]'", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "sampling_rate", + "position": 12, + "type": "double precision", + "notNull": true, + "default": "1", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "logs_enabled", + "position": 13, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "traces_enabled", + "position": 14, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "ai_gateway_enabled", + "position": 15, + "type": "boolean", + "notNull": true, + "default": "true", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "speed_insights_enabled", + "position": 16, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "web_analytics_enabled", + "position": 17, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "audit_log_enabled", + "position": 18, + "type": "boolean", + "notNull": true, + "default": "false", + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "log_drain_id", + "position": 19, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "trace_drain_id", + "position": 20, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "ai_gateway_drain_id", + "position": 21, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "speed_insights_drain_id", + "position": 22, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "web_analytics_drain_id", + "position": 23, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "audit_log_drain_id", + "position": 24, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "log_drain_state", + "position": 25, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "trace_drain_state", + "position": 26, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "ai_gateway_drain_state", + "position": 27, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "speed_insights_drain_state", + "position": 28, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "web_analytics_drain_state", + "position": 29, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "audit_log_drain_state", + "position": 30, + "type": "jsonb", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "drain_key_hash", + "position": 31, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "auth_failed_at", + "position": 32, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "discovered_at", + "position": 33, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "deployments_watermark_at", + "position": 34, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "lease_until", + "position": 35, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "last_success_at", + "position": 36, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "last_error", + "position": 37, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "last_error_at", + "position": 38, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "created_at", + "position": 39, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_connections", + "name": "updated_at", + "position": 40, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "project_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "deployment_id", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "url", + "position": 5, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "target", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "state", + "position": 7, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "branch", + "position": 8, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "commit_sha", + "position": 9, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "commit_message", + "position": 10, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "creator", + "position": 11, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "error_code", + "position": 12, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "error_message", + "position": 13, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "created_at_vercel", + "position": 14, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "building_at", + "position": 15, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "ready_at", + "position": 16, + "type": "timestamp with time zone", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "inspector_url", + "position": 17, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_deployments", + "name": "updated_at", + "position": 18, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "id", + "position": 0, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "org_id", + "position": 1, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "connection_id", + "position": 2, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "project_id", + "position": 3, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "name", + "position": 4, + "type": "text", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "framework", + "position": 5, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "production_deployment_id", + "position": 6, + "type": "text", + "notNull": false, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "created_at", + "position": 7, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "column", + "table": "vercel_projects", + "name": "updated_at", + "position": 8, + "type": "timestamp with time zone", + "notNull": true, + "default": null, + "identity": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_agent_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"agent_name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"" + ], + "where": null + }, + { + "kind": "index", + "table": "actors", + "name": "actors_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_kind_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"kind\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "agent_feedback", + "name": "agent_feedback_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"claim_expires_at\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_delivery_attempt_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"attempt_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_delivery_events", + "name": "alert_delivery_events_org_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_destinations", + "name": "alert_destinations_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_escalation_runs", + "name": "alert_escalation_runs_live_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"next_step_at\"" + ], + "where": "\"status\" in ('active', 'acknowledged')" + }, + { + "kind": "index", + "table": "alert_escalation_runs", + "name": "alert_escalation_runs_live_incident_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"incident_id\"" + ], + "where": "\"status\" IN ('active', 'acknowledged')" + }, + { + "kind": "index", + "table": "alert_escalation_runs", + "name": "alert_escalation_runs_org_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_incident_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"incident_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_open_group_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"", + "\"group_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_rule_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"rule_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_incidents", + "name": "alert_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_linear_issues", + "name": "alert_linear_issues_thread_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"destination_id\"", + "\"thread_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_maintenance_windows", + "name": "alert_maintenance_windows_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"enabled\"", + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_maintenance_windows", + "name": "alert_maintenance_windows_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_member_notifications", + "name": "alert_member_notifications_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"dedupe_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_member_notifications", + "name": "alert_member_notifications_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_member_notifications", + "name": "alert_member_notifications_org_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_claims", + "name": "alert_rule_claims_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rule_states", + "name": "alert_rule_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_rules", + "name": "alert_rules_org_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "alert_silences", + "name": "alert_silences_maintenance_occurrence_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"maintenance_window_id\"", + "\"starts_at\"" + ], + "where": "\"maintenance_window_id\" IS NOT NULL" + }, + { + "kind": "index", + "table": "alert_silences", + "name": "alert_silences_org_ends_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"ends_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_evaluated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"last_evaluated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_detector_states", + "name": "anomaly_detector_states_open_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"open_incident_id\"" + ], + "where": "\"open_incident_id\" is not null" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_open_detector_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": "\"status\" = 'open'" + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_detector_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"detector_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"error_issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_status_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"", + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "anomaly_incidents", + "name": "anomaly_incidents_org_triggered_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "api_keys", + "name": "api_keys_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_org_account_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "aws_connections", + "name": "aws_connections_stream_secret_hash_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"stream_secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cancellation_reviews", + "name": "cancellation_reviews_subscription_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"plan_id\"", + "\"subscription_started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"external_user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_connector_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"connector\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_identities", + "name": "chat_identities_org_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_connector_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connector\"", + "\"external_workspace_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "chat_workspaces", + "name": "chat_workspaces_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cli_device_authorizations", + "name": "cli_device_authorizations_user_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"user_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_account_dataset_zone_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"account_id\"", + "\"dataset\"", + "\"zone_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_analytics_state", + "name": "cf_analytics_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_config_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"config_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_hyperdrive_configs", + "name": "cloudflare_hyperdrive_configs_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cost_connections", + "name": "cost_connections_org_provider_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"" + ], + "where": null + }, + { + "kind": "index", + "table": "cost_sources", + "name": "cost_sources_org_provider_ref_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"connection_ref\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_live_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "coalesce(widget_id, '')" + ], + "where": "revoked_at is null" + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_shares", + "name": "dashboard_shares_token_hash_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboard_versions", + "name": "dashboard_versions_org_dashboard_version_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dashboard_id\"", + "\"version_number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_name_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dashboards", + "name": "dashboards_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"lockfile_path\"", + "\"ecosystem\"", + "\"package_name\"", + "\"advisory_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_findings", + "name": "dependency_findings_org_updated_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"updated_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scan_matches", + "name": "dependency_scan_matches_org_scan_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scan_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_commit_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"commit_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "dependency_scans", + "name": "dependency_scans_repo_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_jkt_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"jkt\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_live_installation_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"installation_id\"" + ], + "where": "\"revoked_at\" IS NULL" + }, + { + "kind": "index", + "table": "device_credentials", + "name": "device_credentials_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "device_proof_replays", + "name": "device_proof_replays_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "digest_subscriptions", + "name": "digest_subscriptions_org_user_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_fingerprint_candidates", + "name": "error_fingerprint_candidates_last_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_incidents", + "name": "error_incidents_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"", + "\"last_triggered_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_actor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"actor_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_events", + "name": "error_issue_events_type_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"type\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_fingerprints", + "name": "error_issue_fingerprints_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_issue_pr_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_pull_requests", + "name": "error_issue_pull_requests_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"repo_full_name\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"verify_after\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issue_verifications", + "name": "error_issue_verifications_open_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": "\"status\" in ('waiting', 'running')" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_lease_expiry_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"lease_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_archived_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"archived_at\"" + ], + "where": "\"archived_at\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_assignee_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"assigned_actor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_fp_version_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"fingerprint_version\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_kind_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"kind\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_live_seen_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"last_seen_at\"", + "\"id\"" + ], + "where": "\"archived_at\" is null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_merged_into_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"merged_into_issue_id\"" + ], + "where": "\"merged_into_issue_id\" is not null" + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_severity_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"severity\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_issues", + "name": "error_issues_org_workflow_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"workflow_state\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"scheduled_at\"", + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_key_destination_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"delivery_key\"", + "\"destination_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_notification_deliveries", + "name": "error_notification_deliveries_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "error_tick_states", + "name": "error_tick_states_claim_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"claim_expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "escalation_chains", + "name": "escalation_chains_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "external_data_source_catalog_entries", + "name": "external_data_source_catalog_entries_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "external_data_sources", + "name": "external_data_sources_org_name_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_org_scope_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"scope_type\"", + "\"scope_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_secret_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"secret_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "gcp_connectors", + "name": "gcp_connectors_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_incident_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_kind\"", + "\"incident_id\"" + ], + "where": "\"incident_id\" is not null" + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "investigations", + "name": "investigations_org_status_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"status\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"dedupe_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"status\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "issue_escalations", + "name": "issue_escalations_org_issue_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"issue_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_device_incident_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"device_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "live_activities", + "name": "live_activities_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_code_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"authorization_code_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_authorizations", + "name": "mcp_oauth_authorizations_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_family_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"family_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mcp_oauth_refresh_tokens", + "name": "mcp_oauth_refresh_tokens_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"token_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_org_platform_token_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"platform\"", + "\"token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "mobile_devices", + "name": "mobile_devices_user_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"user_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_auth_states", + "name": "oauth_auth_states_expires_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"expires_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_org_provider_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oauth_connections", + "name": "oauth_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oncall_overrides", + "name": "oncall_overrides_org_schedule_ends_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"schedule_id\"", + "\"ends_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "oncall_schedules", + "name": "oncall_schedules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "openrouter_cost_connections", + "name": "openrouter_cost_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_billing_links", + "name": "org_billing_links_billing_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"billing_org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_attribute_mappings", + "name": "org_ingest_attribute_mappings_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_private_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"private_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_keys", + "name": "org_ingest_keys_public_key_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"public_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rule_stats", + "name": "org_ingest_rule_stats_org_bucket_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"bucket\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_ingest_rules", + "name": "org_ingest_rules_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "org_recommendation_issues", + "name": "org_recommendation_issues_org_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"recommendation_key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_connections", + "name": "planetscale_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_databases", + "name": "planetscale_databases_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_dedupe_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"event_type\"", + "\"external_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_db_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"database_name\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_events", + "name": "planetscale_events_org_time_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"occurred_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_issue_receipts", + "name": "planetscale_issue_receipts_processed_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"processed_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_dataset_db_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"dataset\"", + "\"database_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "planetscale_poll_state", + "name": "planetscale_poll_state_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_edits", + "name": "pr_review_edits_reply_seq_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"reply_id\"", + "\"seq\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_finding_embeddings", + "name": "pr_review_finding_embeddings_org_model_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"model\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_findings", + "name": "pr_review_findings_pr_handle_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"handle\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_merge_steps", + "name": "pr_review_merge_steps_pr_key_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"key\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_review_replies", + "name": "pr_review_replies_repo_comment_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"comment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_post_merge_due_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"post_merge_after\"" + ], + "where": "\"post_merge_status\" = 'waiting'" + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_head_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"", + "\"head_sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "pr_reviews", + "name": "pr_reviews_repo_number_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"number\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_connections", + "name": "railway_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_connection_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"connection_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "railway_environments", + "name": "railway_environments_org_env_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"environment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_checked_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"checked_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_enabled_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"enabled\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "scrape_targets", + "name": "scrape_targets_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "search_console_sites", + "name": "search_console_sites_connection_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"oauth_connection_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "search_console_sites", + "name": "search_console_sites_org_site_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"site_url\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_events", + "name": "security_audit_events_run_at_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"run_id\"", + "\"at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_findings", + "name": "security_audit_findings_target_fingerprint_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"fingerprint\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_org_reservations", + "name": "security_audit_org_reservations_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_one_running_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"target_id\"" + ], + "where": "\"finished_at\" IS NULL" + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_org_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_runs", + "name": "security_audit_runs_target_started_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"target_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "security_audit_targets", + "name": "security_audit_targets_org_repo_branch_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"repository\"", + "\"branch\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "slos", + "name": "slos_org_name_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_key_hash_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"key_hash\"" + ], + "where": null + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_default_kind_unique", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": "\"kind\" = 'default'" + }, + { + "kind": "index", + "table": "sources", + "name": "sources_org_id_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_cleanups", + "name": "status_page_cleanups_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_monitor_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"monitor_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_components", + "name": "status_page_components_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"position\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_custom_domains", + "name": "status_page_custom_domains_checked_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"last_checked_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_custom_domains", + "name": "status_page_custom_domains_hostname_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"hostname\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_custom_domains", + "name": "status_page_custom_domains_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_components", + "name": "status_page_incident_components_component_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"component_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_incident_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"incident_id\"", + "\"created_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_overlay_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"overlay_entry_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_alert_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"alert_incident_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_open_maintenance_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"scheduled_start\"" + ], + "where": "\"kind\" = 'maintenance' and \"resolved_at\" is null" + }, + { + "kind": "index", + "table": "status_page_incidents", + "name": "status_page_incidents_page_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"page_id\"", + "\"started_at\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_slug_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"slug\"" + ], + "where": null + }, + { + "kind": "index", + "table": "status_pages", + "name": "status_pages_unpublished_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"revision\"", + "\"published_revision\"" + ], + "where": "revision > published_revision" + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_connections", + "name": "supabase_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_project_insights", + "name": "supabase_project_insights_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_connection_ref_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"connection_id\"", + "\"project_ref\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"log_drain_source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "supabase_projects", + "name": "supabase_projects_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_enabled_kind_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"enabled\"", + "\"kind\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_heartbeat_token_unq", + "unique": true, + "method": "btree", + "columns": [ + "\"heartbeat_token\"" + ], + "where": null + }, + { + "kind": "index", + "table": "uptime_monitors", + "name": "uptime_monitors_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_org_sha_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_commits", + "name": "vcs_commits_repo_sha_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"sha\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_provider_external_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"provider\"", + "\"external_installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_installations", + "name": "vcs_installations_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_installation_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"installation_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repositories", + "name": "vcs_repositories_org_repo_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"provider\"", + "\"external_repo_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_org_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vcs_repository_branches", + "name": "vcs_repository_branches_repo_name_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"repository_id\"", + "\"name\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vercel_connections", + "name": "vercel_connections_org_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vercel_connections", + "name": "vercel_connections_source_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"source_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vercel_deployments", + "name": "vercel_deployments_org_deployment_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"deployment_id\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vercel_deployments", + "name": "vercel_deployments_org_project_created_idx", + "unique": false, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"project_id\"", + "\"created_at_vercel\"" + ], + "where": null + }, + { + "kind": "index", + "table": "vercel_projects", + "name": "vercel_projects_org_project_idx", + "unique": true, + "method": "btree", + "columns": [ + "\"org_id\"", + "\"project_id\"" + ], + "where": null + }, + { + "kind": "foreign_key", + "table": "alert_escalation_runs", + "name": "alert_escalation_runs_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_incident_events", + "name": "alert_incident_events_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_linear_issues", + "name": "alert_linear_issues_destination_id_alert_destinations_id_fk", + "columns": [ + "destination_id" + ], + "foreignTable": "alert_destinations", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "alert_member_notifications", + "name": "alert_member_notifications_incident_id_alert_incidents_id_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "alert_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "aws_connections", + "name": "aws_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "cloudflare_logpush_connectors", + "name": "cloudflare_logpush_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "dashboard_shares", + "name": "dashboard_shares_dashboard_fk", + "columns": [ + "org_id", + "dashboard_id" + ], + "foreignTable": "dashboards", + "foreignColumns": [ + "org_id", + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "external_data_source_catalog_entries", + "name": "external_data_source_catalog_entries_x60VonL2VphD_fk", + "columns": [ + "source_id" + ], + "foreignTable": "external_data_sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_connectors", + "name": "gcp_connectors_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "gcp_resources", + "name": "gcp_resources_connector_id_gcp_connectors_id_fk", + "columns": [ + "connector_id" + ], + "foreignTable": "gcp_connectors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "oauth_connections", + "name": "oauth_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "oncall_overrides", + "name": "oncall_overrides_schedule_id_oncall_schedules_id_fk", + "columns": [ + "schedule_id" + ], + "foreignTable": "oncall_schedules", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "planetscale_connections", + "name": "planetscale_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "railway_connections", + "name": "railway_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_target_checks", + "name": "scrape_target_checks_target_id_scrape_targets_id_fk", + "columns": [ + "target_id" + ], + "foreignTable": "scrape_targets", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "scrape_targets", + "name": "scrape_targets_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "search_console_sites", + "name": "search_console_sites_jBAHYyW36dnu_fk", + "columns": [ + "oauth_connection_id" + ], + "foreignTable": "oauth_connections", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_components", + "name": "status_page_components_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_custom_domains", + "name": "status_page_custom_domains_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_Bnqlt6wwKJty_fk", + "columns": [ + "component_id" + ], + "foreignTable": "status_page_components", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_components", + "name": "status_page_incident_components_tBn6mEz4FenY_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incident_updates", + "name": "status_page_incident_updates_3LDDUAhznHPM_fk", + "columns": [ + "incident_id" + ], + "foreignTable": "status_page_incidents", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "status_page_incidents", + "name": "status_page_incidents_page_id_status_pages_id_fk", + "columns": [ + "page_id" + ], + "foreignTable": "status_pages", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_connections", + "name": "supabase_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_project_insights", + "name": "supabase_project_insights_project_id_supabase_projects_id_fk", + "columns": [ + "project_id" + ], + "foreignTable": "supabase_projects", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_connection_id_supabase_connections_id_fk", + "columns": [ + "connection_id" + ], + "foreignTable": "supabase_connections", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "supabase_projects", + "name": "supabase_projects_log_drain_source_id_sources_id_fk", + "columns": [ + "log_drain_source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "SET NULL", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_daily", + "name": "uptime_monitor_daily_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "uptime_monitor_states", + "name": "uptime_monitor_states_monitor_id_uptime_monitors_id_fk", + "columns": [ + "monitor_id" + ], + "foreignTable": "uptime_monitors", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "vcs_installations", + "name": "vcs_installations_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + }, + { + "kind": "foreign_key", + "table": "vercel_connections", + "name": "vercel_connections_source_id_sources_id_fk", + "columns": [ + "source_id" + ], + "foreignTable": "sources", + "foreignColumns": [ + "id" + ], + "onDelete": "CASCADE", + "onUpdate": "NO ACTION" + } + ] +} diff --git a/packages/db/src/tables/cost-connections.ts b/packages/db/src/tables/cost-connections.ts new file mode 100644 index 0000000000..543270f250 --- /dev/null +++ b/packages/db/src/tables/cost-connections.ts @@ -0,0 +1,34 @@ +import * as PG from "@maple-dev/effect-orm/postgres" +import { CostProvider } from "@maple/domain/cost" +import { OrgId, UserId } from "@maple/primitives" + +/** + * A credential an org pasted so Maple can read one provider's spend (AES-GCM, bound to the org and + * the provider), one per provider and org. `cost_sources.connection_ref` holds `id`, which a + * replaced credential keeps. OpenRouter predates this table and keeps its own. + */ +export const CostConnections = PG.table("cost_connections", { + columns: { + id: PG.text, + orgId: PG.column(PG.brand(PG.text, OrgId), { name: "org_id" }), + provider: PG.brand(PG.text, CostProvider), + secretCiphertext: PG.column(PG.text, { name: "secret_ciphertext" }), + secretIv: PG.column(PG.text, { name: "secret_iv" }), + secretTag: PG.column(PG.text, { name: "secret_tag" }), + /** + * The provider's account the credential reads. A replacement must name the same one, so one + * source never mixes two accounts. + */ + accountId: PG.column(PG.text, { name: "account_id" }), + /** The account as the provider names it, for people. */ + accountLabel: PG.column(PG.text, { name: "account_label" }), + createdAt: PG.column(PG.timestamptzMillis, { name: "created_at" }), + updatedAt: PG.column(PG.timestamptzMillis, { name: "updated_at" }), + updatedBy: PG.column(PG.brand(PG.text, UserId), { name: "updated_by" }), + }, + primaryKey: ["id"], + indexes: [PG.uniqueIndex("cost_connections_org_provider_idx", ["orgId", "provider"])], + tenantColumn: "orgId", +}) + +export type CostConnectionRow = PG.SelectRowOf diff --git a/packages/db/src/tables/index.ts b/packages/db/src/tables/index.ts index 553d7db627..6c31e0bdc1 100644 --- a/packages/db/src/tables/index.ts +++ b/packages/db/src/tables/index.ts @@ -71,6 +71,7 @@ import { PlanetscaleEvents, PlanetscaleIssueReceipts, } from "./planetscale-inventory" +import { CostConnections } from "./cost-connections" import { OpenRouterCostConnections } from "./openrouter-cost-connections" import { OrgOpenRouterKeys } from "./openrouter-keys" import { RailwayConnections, RailwayEnvironments } from "./railway" @@ -159,6 +160,7 @@ export * from "./org-ingest-sampling-policies" export * from "./org-recommendation-issues" export * from "./planetscale-connections" export * from "./planetscale-inventory" +export * from "./cost-connections" export * from "./openrouter-cost-connections" export * from "./openrouter-keys" export * from "./railway" @@ -248,6 +250,7 @@ export const allTables: ReadonlyArray> = [ OrgIngestKeys, OrgIngestSamplingPolicies, OpenRouterCostConnections, + CostConnections, OrgOpenRouterKeys, OrgBillingLinks, Sources, diff --git a/packages/domain/src/cost.ts b/packages/domain/src/cost.ts index b18d84f62e..96af48a814 100644 --- a/packages/domain/src/cost.ts +++ b/packages/domain/src/cost.ts @@ -24,7 +24,7 @@ export const CostDimension = Schema.Literals([ export type CostDimension = typeof CostDimension.Type /** Providers Maple reads cost from. One literal per built adapter. */ -export const CostProvider = Schema.Literals(["aws", "openrouter"]) +export const CostProvider = Schema.Literals(["aws", "openrouter", "cloudflare"]) export type CostProvider = typeof CostProvider.Type /** `billed`: the provider reported the money. `estimated`: usage times list price. */ diff --git a/packages/domain/src/generated/anticipated-error-identifiers.ts b/packages/domain/src/generated/anticipated-error-identifiers.ts index 9099172dd0..fb5356e9ca 100644 --- a/packages/domain/src/generated/anticipated-error-identifiers.ts +++ b/packages/domain/src/generated/anticipated-error-identifiers.ts @@ -11,6 +11,8 @@ export const ANTICIPATED_ERROR_IDENTIFIER_LIST: ReadonlyArray = [ "@maple/http/anomalies/AnomalyForbiddenError", "@maple/http/anomalies/AnomalyIncidentNotFoundError", "@maple/http/anomalies/AnomalyLinkedIssueNotFoundError", + "@maple/http/cost-connections/CostConnectionForbiddenError", + "@maple/http/cost-connections/CostConnectionValidationError", "@maple/http/data-sources/CatalogEntryNotFoundError", "@maple/http/data-sources/DataSourceInvalidError", "@maple/http/data-sources/DataSourceNotFoundError", diff --git a/packages/domain/src/http/cloudflare-cost.ts b/packages/domain/src/http/cloudflare-cost.ts new file mode 100644 index 0000000000..2cc35601c5 --- /dev/null +++ b/packages/domain/src/http/cloudflare-cost.ts @@ -0,0 +1,43 @@ +import { HttpApiEndpoint, HttpApiGroup } from "effect/http-api" +import { Schema } from "effect" +import { + CostConnectionForbiddenError, + CostConnectionPersistenceError, + CostConnectionRemoved, + CostConnectionSaved, + CostConnectionValidationError, +} from "./cost-connections" +import { SessionAuthorization } from "./current-tenant" +import { CloudCostDisabledError } from "./query-engine" + +/** Far above any Cloudflare token (about 40 to 60 characters): it only bounds what the request carries. */ +export const CLOUDFLARE_COST_TOKEN_MAX_LENGTH = 256 + +export class CloudflareCostTokenRequest extends Schema.Class( + "CloudflareCostTokenRequest", +)({ + apiToken: Schema.String.check(Schema.isMaxLength(CLOUDFLARE_COST_TOKEN_MAX_LENGTH)), +}) {} + +/** Saving a token makes the org's Cloudflare cost source, replacing one keeps it, removing deletes it. */ +export class CloudflareCostApiGroup extends HttpApiGroup.make("cloudflareCost") + .add( + HttpApiEndpoint.put("set", "/", { + payload: CloudflareCostTokenRequest, + success: CostConnectionSaved, + error: [ + CostConnectionPersistenceError, + CostConnectionForbiddenError, + CostConnectionValidationError, + CloudCostDisabledError, + ], + }), + ) + .add( + HttpApiEndpoint.delete("remove", "/", { + success: CostConnectionRemoved, + error: [CostConnectionPersistenceError, CostConnectionForbiddenError], + }), + ) + .prefix("/internal/cost/cloudflare") + .middleware(SessionAuthorization) {} diff --git a/packages/domain/src/http/cost-connections.ts b/packages/domain/src/http/cost-connections.ts new file mode 100644 index 0000000000..ceec11915a --- /dev/null +++ b/packages/domain/src/http/cost-connections.ts @@ -0,0 +1,57 @@ +import { Schema } from "effect" +import { HttpTaggedError } from "./error-policy" + +// What every provider read with a pasted credential answers when one is saved or removed. The +// Cost page shows these to the person saving it, so each carries a public body. + +/** The saved credential never comes back: only the account it reads. */ +export class CostConnectionSaved extends Schema.Class("CostConnectionSaved")({ + accountId: Schema.String, + accountLabel: Schema.String, +}) {} + +export class CostConnectionRemoved extends Schema.Class("CostConnectionRemoved")({ + removed: Schema.Boolean, +}) {} + +/** The database or the provider could not be reached; its own text can carry internals, so it stays server-side. */ +export class CostConnectionPersistenceError extends HttpTaggedError()( + "@maple/http/cost-connections/CostConnectionPersistenceError", + { message: Schema.String }, + { + status: 503, + code: "cost_connection_unavailable", + title: "Cost sources are temporarily unavailable", + message: "The credential could not be checked or saved right now. Try again in a few seconds.", + retry: "backoff", + recovery: "retry", + exposure: "redacted", + }, +) {} + +export class CostConnectionForbiddenError extends HttpTaggedError()( + "@maple/http/cost-connections/CostConnectionForbiddenError", + { message: Schema.String }, + { + status: 403, + code: "cost_connection_forbidden", + title: "Permission required", + retry: "never", + recovery: "request_access", + exposure: "public_message", + }, +) {} + +/** The credential is malformed, or the provider refused it. The message says which, and what to use instead. */ +export class CostConnectionValidationError extends HttpTaggedError()( + "@maple/http/cost-connections/CostConnectionValidationError", + { message: Schema.String }, + { + status: 400, + code: "cost_connection_invalid", + title: "Credential not accepted", + retry: "never", + recovery: "fix_request", + exposure: "public_message", + }, +) {} diff --git a/packages/domain/src/http/index.ts b/packages/domain/src/http/index.ts index e777872091..2311b78dd1 100644 --- a/packages/domain/src/http/index.ts +++ b/packages/domain/src/http/index.ts @@ -9,6 +9,8 @@ export * from "./dependency-scan" export * from "./ai-triage" export * from "./openrouter-key" export * from "./openrouter-cost" +export * from "./cost-connections" +export * from "./cloudflare-cost" export * from "./service-map-world" export * from "./incident-triage" export * from "./cancellation-review" diff --git a/packages/domain/src/http/internal-api.ts b/packages/domain/src/http/internal-api.ts index 6613fd5b0d..32958e59e6 100644 --- a/packages/domain/src/http/internal-api.ts +++ b/packages/domain/src/http/internal-api.ts @@ -6,6 +6,7 @@ import { AiTriageApiGroup } from "./ai-triage" import { BillingApiGroup } from "./billing" import { CodeReviewApiGroup } from "./code-review" import { DemoApiGroup } from "./demo" +import { CloudflareCostApiGroup } from "./cloudflare-cost" import { DependencyScanApiGroup } from "./dependency-scan" import { DigestApiGroup } from "./digest" import { ErrorsApiGroup } from "./errors" @@ -61,6 +62,7 @@ export class MapleInternalApi extends HttpApi.make("MapleInternalApi") .add(McpInternalApiGroup) .add(OpenRouterKeyApiGroup) .add(OpenRouterCostApiGroup) + .add(CloudflareCostApiGroup) .add(OrgClickHouseSettingsApiGroup) .add(OrganizationsApiGroup) .add(OrganizationCreationApiGroup) diff --git a/packages/web-data/src/lib/error-messages.test.ts b/packages/web-data/src/lib/error-messages.test.ts index e96f23be23..51e83dee38 100644 --- a/packages/web-data/src/lib/error-messages.test.ts +++ b/packages/web-data/src/lib/error-messages.test.ts @@ -5,6 +5,8 @@ import { QueryEngineExecutionError, WarehouseQuotaExceededError } from "@maple/d import { BillingConflictError, BillingPaymentRequiredError, + CostConnectionPersistenceError, + CostConnectionValidationError, OpenRouterKeyPersistenceError, OpenRouterKeyValidationError, } from "@maple/domain/http" @@ -148,6 +150,22 @@ describe("displayError", () => { ) }) + it("keeps the reason a cost credential was refused, and hides what a failed save answered", () => { + const refused = new CostConnectionValidationError({ + message: "This token can read 3 accounts. Create one limited to a single account.", + }) + expect(displayError(refused).message).toBe( + "This token can read 3 accounts. Create one limited to a single account.", + ) + + const failed = new CostConnectionPersistenceError({ + message: 'insert into "cost_connections" failed: connection reset', + }) + expect(displayError(failed).message).toBe( + "The credential could not be checked or saved right now. Try again in a few seconds.", + ) + }) + it("finds a declared error in an Effect cause", () => { const input = errorEnvelope({ _tag: "@maple/http/errors/DashboardNotFoundError",