feat(costs): add billing, quota, and budget control plane
This commit is contained in:
@@ -27,6 +27,8 @@ export const agents = pgTable(
|
||||
runtimeConfig: jsonb("runtime_config").$type<Record<string, unknown>>().notNull().default({}),
|
||||
budgetMonthlyCents: integer("budget_monthly_cents").notNull().default(0),
|
||||
spentMonthlyCents: integer("spent_monthly_cents").notNull().default(0),
|
||||
pauseReason: text("pause_reason"),
|
||||
pausedAt: timestamp("paused_at", { withTimezone: true }),
|
||||
permissions: jsonb("permissions").$type<Record<string, unknown>>().notNull().default({}),
|
||||
lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }),
|
||||
metadata: jsonb("metadata").$type<Record<string, unknown>>(),
|
||||
|
||||
41
packages/db/src/schema/budget_incidents.ts
Normal file
41
packages/db/src/schema/budget_incidents.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { index, integer, pgTable, text, timestamp, uuid, uniqueIndex } from "drizzle-orm/pg-core";
|
||||
import { approvals } from "./approvals.js";
|
||||
import { budgetPolicies } from "./budget_policies.js";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const budgetIncidents = pgTable(
|
||||
"budget_incidents",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
policyId: uuid("policy_id").notNull().references(() => budgetPolicies.id),
|
||||
scopeType: text("scope_type").notNull(),
|
||||
scopeId: uuid("scope_id").notNull(),
|
||||
metric: text("metric").notNull(),
|
||||
windowKind: text("window_kind").notNull(),
|
||||
windowStart: timestamp("window_start", { withTimezone: true }).notNull(),
|
||||
windowEnd: timestamp("window_end", { withTimezone: true }).notNull(),
|
||||
thresholdType: text("threshold_type").notNull(),
|
||||
amountLimit: integer("amount_limit").notNull(),
|
||||
amountObserved: integer("amount_observed").notNull(),
|
||||
status: text("status").notNull().default("open"),
|
||||
approvalId: uuid("approval_id").references(() => approvals.id),
|
||||
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyStatusIdx: index("budget_incidents_company_status_idx").on(table.companyId, table.status),
|
||||
companyScopeIdx: index("budget_incidents_company_scope_idx").on(
|
||||
table.companyId,
|
||||
table.scopeType,
|
||||
table.scopeId,
|
||||
table.status,
|
||||
),
|
||||
policyWindowIdx: uniqueIndex("budget_incidents_policy_window_threshold_idx").on(
|
||||
table.policyId,
|
||||
table.windowStart,
|
||||
table.thresholdType,
|
||||
),
|
||||
}),
|
||||
);
|
||||
43
packages/db/src/schema/budget_policies.ts
Normal file
43
packages/db/src/schema/budget_policies.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { boolean, index, integer, pgTable, text, timestamp, uuid, uniqueIndex } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
|
||||
export const budgetPolicies = pgTable(
|
||||
"budget_policies",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
scopeType: text("scope_type").notNull(),
|
||||
scopeId: uuid("scope_id").notNull(),
|
||||
metric: text("metric").notNull().default("billed_cents"),
|
||||
windowKind: text("window_kind").notNull(),
|
||||
amount: integer("amount").notNull().default(0),
|
||||
warnPercent: integer("warn_percent").notNull().default(80),
|
||||
hardStopEnabled: boolean("hard_stop_enabled").notNull().default(true),
|
||||
notifyEnabled: boolean("notify_enabled").notNull().default(true),
|
||||
isActive: boolean("is_active").notNull().default(true),
|
||||
createdByUserId: text("created_by_user_id"),
|
||||
updatedByUserId: text("updated_by_user_id"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyScopeActiveIdx: index("budget_policies_company_scope_active_idx").on(
|
||||
table.companyId,
|
||||
table.scopeType,
|
||||
table.scopeId,
|
||||
table.isActive,
|
||||
),
|
||||
companyWindowIdx: index("budget_policies_company_window_idx").on(
|
||||
table.companyId,
|
||||
table.windowKind,
|
||||
table.metric,
|
||||
),
|
||||
companyScopeMetricUniqueIdx: uniqueIndex("budget_policies_company_scope_metric_unique_idx").on(
|
||||
table.companyId,
|
||||
table.scopeType,
|
||||
table.scopeId,
|
||||
table.metric,
|
||||
table.windowKind,
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -4,6 +4,7 @@ import { agents } from "./agents.js";
|
||||
import { issues } from "./issues.js";
|
||||
import { projects } from "./projects.js";
|
||||
import { goals } from "./goals.js";
|
||||
import { heartbeatRuns } from "./heartbeat_runs.js";
|
||||
|
||||
export const costEvents = pgTable(
|
||||
"cost_events",
|
||||
@@ -14,10 +15,14 @@ export const costEvents = pgTable(
|
||||
issueId: uuid("issue_id").references(() => issues.id),
|
||||
projectId: uuid("project_id").references(() => projects.id),
|
||||
goalId: uuid("goal_id").references(() => goals.id),
|
||||
heartbeatRunId: uuid("heartbeat_run_id").references(() => heartbeatRuns.id),
|
||||
billingCode: text("billing_code"),
|
||||
provider: text("provider").notNull(),
|
||||
biller: text("biller").notNull().default("unknown"),
|
||||
billingType: text("billing_type").notNull().default("unknown"),
|
||||
model: text("model").notNull(),
|
||||
inputTokens: integer("input_tokens").notNull().default(0),
|
||||
cachedInputTokens: integer("cached_input_tokens").notNull().default(0),
|
||||
outputTokens: integer("output_tokens").notNull().default(0),
|
||||
costCents: integer("cost_cents").notNull(),
|
||||
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
||||
@@ -30,5 +35,19 @@ export const costEvents = pgTable(
|
||||
table.agentId,
|
||||
table.occurredAt,
|
||||
),
|
||||
companyProviderOccurredIdx: index("cost_events_company_provider_occurred_idx").on(
|
||||
table.companyId,
|
||||
table.provider,
|
||||
table.occurredAt,
|
||||
),
|
||||
companyBillerOccurredIdx: index("cost_events_company_biller_occurred_idx").on(
|
||||
table.companyId,
|
||||
table.biller,
|
||||
table.occurredAt,
|
||||
),
|
||||
companyHeartbeatRunIdx: index("cost_events_company_heartbeat_run_idx").on(
|
||||
table.companyId,
|
||||
table.heartbeatRunId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
67
packages/db/src/schema/finance_events.ts
Normal file
67
packages/db/src/schema/finance_events.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { pgTable, uuid, text, timestamp, integer, index, boolean, jsonb } from "drizzle-orm/pg-core";
|
||||
import { companies } from "./companies.js";
|
||||
import { agents } from "./agents.js";
|
||||
import { issues } from "./issues.js";
|
||||
import { projects } from "./projects.js";
|
||||
import { goals } from "./goals.js";
|
||||
import { heartbeatRuns } from "./heartbeat_runs.js";
|
||||
import { costEvents } from "./cost_events.js";
|
||||
|
||||
export const financeEvents = pgTable(
|
||||
"finance_events",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
companyId: uuid("company_id").notNull().references(() => companies.id),
|
||||
agentId: uuid("agent_id").references(() => agents.id),
|
||||
issueId: uuid("issue_id").references(() => issues.id),
|
||||
projectId: uuid("project_id").references(() => projects.id),
|
||||
goalId: uuid("goal_id").references(() => goals.id),
|
||||
heartbeatRunId: uuid("heartbeat_run_id").references(() => heartbeatRuns.id),
|
||||
costEventId: uuid("cost_event_id").references(() => costEvents.id),
|
||||
billingCode: text("billing_code"),
|
||||
description: text("description"),
|
||||
eventKind: text("event_kind").notNull(),
|
||||
direction: text("direction").notNull().default("debit"),
|
||||
biller: text("biller").notNull(),
|
||||
provider: text("provider"),
|
||||
executionAdapterType: text("execution_adapter_type"),
|
||||
pricingTier: text("pricing_tier"),
|
||||
region: text("region"),
|
||||
model: text("model"),
|
||||
quantity: integer("quantity"),
|
||||
unit: text("unit"),
|
||||
amountCents: integer("amount_cents").notNull(),
|
||||
currency: text("currency").notNull().default("USD"),
|
||||
estimated: boolean("estimated").notNull().default(false),
|
||||
externalInvoiceId: text("external_invoice_id"),
|
||||
metadataJson: jsonb("metadata_json").$type<Record<string, unknown> | null>(),
|
||||
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
},
|
||||
(table) => ({
|
||||
companyOccurredIdx: index("finance_events_company_occurred_idx").on(table.companyId, table.occurredAt),
|
||||
companyBillerOccurredIdx: index("finance_events_company_biller_occurred_idx").on(
|
||||
table.companyId,
|
||||
table.biller,
|
||||
table.occurredAt,
|
||||
),
|
||||
companyKindOccurredIdx: index("finance_events_company_kind_occurred_idx").on(
|
||||
table.companyId,
|
||||
table.eventKind,
|
||||
table.occurredAt,
|
||||
),
|
||||
companyDirectionOccurredIdx: index("finance_events_company_direction_occurred_idx").on(
|
||||
table.companyId,
|
||||
table.direction,
|
||||
table.occurredAt,
|
||||
),
|
||||
companyHeartbeatRunIdx: index("finance_events_company_heartbeat_run_idx").on(
|
||||
table.companyId,
|
||||
table.heartbeatRunId,
|
||||
),
|
||||
companyCostEventIdx: index("finance_events_company_cost_event_idx").on(
|
||||
table.companyId,
|
||||
table.costEventId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
@@ -7,6 +7,8 @@ export { companyMemberships } from "./company_memberships.js";
|
||||
export { principalPermissionGrants } from "./principal_permission_grants.js";
|
||||
export { invites } from "./invites.js";
|
||||
export { joinRequests } from "./join_requests.js";
|
||||
export { budgetPolicies } from "./budget_policies.js";
|
||||
export { budgetIncidents } from "./budget_incidents.js";
|
||||
export { agentConfigRevisions } from "./agent_config_revisions.js";
|
||||
export { agentApiKeys } from "./agent_api_keys.js";
|
||||
export { agentRuntimeState } from "./agent_runtime_state.js";
|
||||
@@ -31,6 +33,7 @@ export { issueDocuments } from "./issue_documents.js";
|
||||
export { heartbeatRuns } from "./heartbeat_runs.js";
|
||||
export { heartbeatRunEvents } from "./heartbeat_run_events.js";
|
||||
export { costEvents } from "./cost_events.js";
|
||||
export { financeEvents } from "./finance_events.js";
|
||||
export { approvals } from "./approvals.js";
|
||||
export { approvalComments } from "./approval_comments.js";
|
||||
export { activityLog } from "./activity_log.js";
|
||||
|
||||
@@ -15,6 +15,8 @@ export const projects = pgTable(
|
||||
leadAgentId: uuid("lead_agent_id").references(() => agents.id),
|
||||
targetDate: date("target_date"),
|
||||
color: text("color"),
|
||||
pauseReason: text("pause_reason"),
|
||||
pausedAt: timestamp("paused_at", { withTimezone: true }),
|
||||
executionWorkspacePolicy: jsonb("execution_workspace_policy").$type<Record<string, unknown>>(),
|
||||
archivedAt: timestamp("archived_at", { withTimezone: true }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
|
||||
Reference in New Issue
Block a user