From 249720940204d3a5d61125574661fd8819f38efe Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Wed, 17 Jun 2026 20:21:33 +0200 Subject: [PATCH 1/3] feat(security-agent): add audit report backend --- .specs/security-agent.md | 150 +- CONTEXT.md | 22 + .../src/lib/security-agent/core/schemas.ts | 21 + .../security-agent/db/dashboard-stats.test.ts | 179 + .../lib/security-agent/db/dashboard-stats.ts | 169 +- .../security-agent/db/security-analysis.ts | 169 +- .../db/security-audit-report.test.ts | 782 + .../db/security-audit-report.ts | 994 + .../db/security-findings.test.ts | 12 +- .../security-agent/db/security-findings.ts | 75 +- .../security-agent/posthog-tracking.test.ts | 145 + .../lib/security-agent/posthog-tracking.ts | 62 + .../router/shared-handlers.test.ts | 427 +- .../security-agent/router/shared-handlers.ts | 257 +- .../services/analysis-service.test.ts | 20 + .../services/analysis-service.ts | 5 + .../services/audit-log-service.ts | 18 +- .../services/auto-dismiss-service.test.ts | 285 +- .../services/auto-dismiss-service.ts | 243 +- .../services/extraction-service.ts | 46 +- .../services/manual-analysis-client.test.ts | 2 + .../services/manual-analysis-client.ts | 2 + .../services/manual-dismiss-client.test.ts | 9 +- .../services/manual-dismiss-client.ts | 2 - .../manual-remediation-client.test.ts | 88 + .../services/manual-remediation-client.ts | 83 +- .../security-agent/services/triage-service.ts | 43 +- apps/web/src/lib/user/index.test.ts | 3 + .../organization-security-agent-router.ts | 7 + apps/web/src/routers/security-agent-router.ts | 6 + .../src/migrations/0166_curved_praxagora.sql | 16 + .../db/src/migrations/meta/0166_snapshot.json | 31219 ++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + packages/db/src/schema-types.ts | 42 + packages/db/src/schema.test.ts | 20 + packages/db/src/schema.ts | 32 + packages/worker-utils/package.json | 1 + packages/worker-utils/src/index.ts | 30 + .../src/security-finding-audit.test.ts | 236 + .../src/security-finding-audit.ts | 463 + .../src/security-remediation-policy.test.ts | 162 +- .../src/security-remediation-policy.ts | 165 +- ...alysis-start-lifecycle.integration.test.ts | 223 +- .../src/analysis-start-lifecycle.ts | 194 +- .../src/auto-dismiss.test.ts | 780 +- .../src/auto-dismiss.ts | 194 +- .../src/callbacks.test.ts | 9 +- .../security-auto-analysis/src/callbacks.ts | 20 - .../src/db/queries.integration.test.ts | 223 + .../security-auto-analysis/src/db/queries.ts | 130 +- .../src/extraction.test.ts | 194 + .../security-auto-analysis/src/extraction.ts | 48 +- .../security-auto-analysis/src/index.test.ts | 83 + services/security-auto-analysis/src/index.ts | 2 +- .../security-auto-analysis/src/launch.test.ts | 86 +- services/security-auto-analysis/src/launch.ts | 4 + .../src/manual-analysis.test.ts | 214 +- .../src/manual-analysis.ts | 148 +- .../remediation-admission.integration.test.ts | 166 + .../src/remediation.test.ts | 44 +- .../security-auto-analysis/src/remediation.ts | 437 +- .../security-auto-analysis/src/triage.test.ts | 128 + services/security-auto-analysis/src/triage.ts | 34 +- services/security-auto-analysis/src/types.ts | 3 + services/security-sync/src/dismiss.test.ts | 92 +- services/security-sync/src/dismiss.ts | 103 +- services/security-sync/src/index.test.ts | 18 +- services/security-sync/src/index.ts | 6 +- .../src/notifications/sweep.test.ts | 63 +- .../security-sync/src/notifications/sweep.ts | 240 +- services/security-sync/src/sync.test.ts | 93 + services/security-sync/src/sync.ts | 370 +- 72 files changed, 40028 insertions(+), 1040 deletions(-) create mode 100644 apps/web/src/lib/security-agent/db/dashboard-stats.test.ts create mode 100644 apps/web/src/lib/security-agent/db/security-audit-report.test.ts create mode 100644 apps/web/src/lib/security-agent/db/security-audit-report.ts create mode 100644 apps/web/src/lib/security-agent/posthog-tracking.test.ts create mode 100644 apps/web/src/lib/security-agent/services/manual-remediation-client.test.ts create mode 100644 packages/db/src/migrations/0166_curved_praxagora.sql create mode 100644 packages/db/src/migrations/meta/0166_snapshot.json create mode 100644 packages/worker-utils/src/security-finding-audit.test.ts create mode 100644 packages/worker-utils/src/security-finding-audit.ts create mode 100644 services/security-auto-analysis/src/extraction.test.ts create mode 100644 services/security-auto-analysis/src/remediation-admission.integration.test.ts create mode 100644 services/security-auto-analysis/src/triage.test.ts diff --git a/.specs/security-agent.md b/.specs/security-agent.md index 6d60007efb..d049021551 100644 --- a/.specs/security-agent.md +++ b/.specs/security-agent.md @@ -2,22 +2,23 @@ ## Role of This Document -This spec defines the business rules and outcome guarantees for Security Agent Auto Remediation and Security Agent Notifications. It is the source of truth for what users should be able to rely on when Security Agent creates or manages remediation work and sends New-finding, SLA Warning, or SLA Breach Notifications. +This spec defines the business rules and outcome guarantees for Security Agent Auto Remediation, Security Agent Notifications, and Security Agent Audit Reports. It is the source of truth for what users should be able to rely on when Security Agent creates or manages remediation work, sends New-finding, SLA Warning, or SLA Breach Notifications, and reports recorded Security Finding activity. This document deliberately does not specify database tables, queue design, worker names, router names, UI layout, email markup, or prompt implementation details. Those belong in plans and code. ## Status -Draft -- created 2026-06-09; notification rules added 2026-06-11. +Draft -- created 2026-06-09; notification rules added 2026-06-11; audit report rules added 2026-06-12. ## Scope -This spec covers two Security Agent capabilities: +This spec covers three Security Agent capabilities: - Auto Remediation; -- New-finding, SLA Warning, and SLA Breach Notifications. +- New-finding, SLA Warning, and SLA Breach Notifications; +- Security Agent Audit Reports. -It does not backfill the complete Security Agent product spec. Existing Security Agent behavior such as finding sync, Auto Analysis, Auto Dismiss, dashboard statistics, SLA calculation, and Dependabot writeback is included only where it affects Auto Remediation or notification outcomes. +It does not backfill the complete Security Agent product spec. Existing Security Agent behavior such as finding sync, Auto Analysis, Auto Dismiss, dashboard statistics, SLA calculation, and Dependabot writeback is included only where it affects Auto Remediation, notification outcomes, or audit report evidence. ## Conventions @@ -41,6 +42,8 @@ BDD-style scenarios use "Given", "When", and "Then" to describe user-visible beh - **SLA Breach Notification**: A Security Agent Notification admitted when an eligible open finding reaches or passes its persisted SLA deadline. - **Notification Recipient**: The personal owner or a current organization owner authorized to receive a notification for a finding. - **Email Delivery**: An attempt to render and send one Security Agent Notification through the email provider. +- **Security Finding Activity Event**: An immutable owner-scoped record of one material user, system-policy, or source-driven action or outcome that changes or explains a Security Finding. +- **Security Agent Audit Report**: An owner-scoped, period-bounded audit view of Security Finding Activity Events grouped by Security Finding. ## Auto Remediation configuration @@ -425,6 +428,134 @@ Given Auto Remediation starts a remediation after analysis When the remediation appears in history Then Security Agent SHOULD identify it as policy-driven automatic remediation. +## Security Agent Audit Reports + +### Report purpose and evidence basis + +Security Agent Audit Reports MUST report Security Finding activity recorded by Kilo. They MUST NOT claim that legacy history is complete, prove repository scan coverage, reconstruct activity Kilo did not record, or calculate authoritative historical SLA compliance. + +The report evidence basis MUST be Security Finding Activity Events. A Security Finding Activity Event MUST belong to exactly one Security Agent owner and one Security Finding, including after that finding is deleted. + +Security Agent MAY include supplemental legacy audit records when they can be mapped to a Security Finding without ambiguity. Legacy supplemental activity MUST be labeled as potentially incomplete. Ambiguous legacy records MUST NOT be guessed into a Security Finding group. + +Every report MUST display the reliable event-coverage start. Baseline events for existing Security Findings, if produced, MUST use actual capture time and MUST NOT be backdated or presented as original creation events. + +### Reportable activity + +Security Agent Audit Reports MUST include material Security Finding activity when recorded during the selected period: + +- finding imported into Kilo; +- severity changed; +- status changed, including reopened and fixed; +- finding manually dismissed, automatically dismissed, or superseded; +- terminal analysis completed or failed when the outcome explains a disposition or remediation decision; +- remediation requested; +- remediation ended with PR opened, failed, blocked, cancelled, or no changes needed; +- finding deleted. + +Security Agent Audit Reports MUST NOT include reads, page views, unchanged sync observations, queue claims, heartbeats, retries with no new finding-level outcome, analysis admission or start, stale cleanup, recipient-level notification transitions, repository scan-coverage evidence, configuration timelines, or report-generation events inside the report itself. + +### Periods and ordering + +Reports MUST use UTC calendar-day boundaries. The default period SHOULD end on the current UTC calendar day and include the preceding 89 calendar days. + +Report ranges MUST be valid, non-future, non-reversed, and no longer than 90 inclusive calendar days. Period inclusion MUST use when Kilo recorded or applied the event. External source timestamps MAY be shown as supporting evidence but MUST NOT determine report inclusion. + +A report MUST include a Security Finding when at least one reportable Security Finding Activity Event falls inside the selected period. The report MUST group events by Security Finding. Events inside each Security Finding group MUST be chronological. Security Finding groups MUST be deterministically ordered by first in-period event, repository, title, and Security Finding ID. + +The interactive report MUST let viewers filter Security Finding groups by severity, recorded state, and repository. Filters MUST retain the complete in-period timeline for every matching Security Finding group. + +Repository filter options MUST come from repository names recorded in report evidence, not current Security Agent repository selection or current repository accessibility. Repository matching MUST use the exact recorded full name. Renamed or transferred repositories MAY appear as separate options when report evidence contains both names. Security Findings without recorded repository identity MUST remain visible when all repositories are selected. + +### Authorization and availability + +Personal Security Agent Audit Reports MUST be available only to the owning user. + +Organization Security Agent Audit Reports MUST be available to organization owners, billing managers, and Kilo platform admins. Ordinary organization members and non-members MUST NOT access organization reports solely because they can access other organization surfaces. + +Report access MUST NOT have a separate plan, active-subscription, enabled Security Agent, or active GitHub integration entitlement. Authorized viewers MUST retain read-only historical access after Security Agent or the GitHub integration is disabled. + +Every platform-admin report generation MUST be audited after successful report assembly. Ordinary customer access MAY rely on existing operational request logs in v1. + +### Report content + +Successful reports MUST include every matching reportable Security Finding Activity Event recorded through the displayed cutoff. Timeout, over-budget, or query failure MUST return no report content and MUST NOT return a partial report. + +Each Security Finding group SHOULD show stable Security Finding and source identity, repository, title, severity, status, safe advisory metadata, first detected time, canonical Security Finding ID when recorded, and deletion status when applicable. + +Human actions MUST show an event-time display name and stable typed actor reference. Automated actions MUST show explicit system attribution. Actor email and notification recipient identity MUST NOT be report evidence. + +Internal Kilo admin actors MUST be masked for non-admin viewers. Deleting an actor's Kilo account MUST anonymize dedicated identity fields in organization-owned Security Finding Activity Events while preserving stable non-PII attribution and event evidence. + +Security Agent Audit Reports MAY show recorded SLA evidence for a Security Finding when trustworthy event or snapshot data exists: + +- persisted SLA deadline; +- recorded terminal timestamp; +- whether terminal timestamp was before or at/after recorded deadline; +- whether an open finding was before or at/after recorded deadline at report cutoff; +- `unknown` when legacy or missing history prevents trustworthy classification. + +Security Agent Audit Reports MUST NOT publish aggregate SLA compliance percentages. They MUST NOT classify ignored or superseded findings as compliant controls. They MUST NOT change SLA enable, disable, severity, warning, breach, reopen, or deadline behavior. + +### Privacy and redaction + +Security Finding Activity Event snapshots and metadata MUST contain only structured, sanitized evidence needed for the report. They MUST NOT contain actor identity, notification recipient identity, prompts, raw analysis markdown, transcripts, assistant messages, full execution logs, provider responses, raw source payloads, credentials, tokens, auth headers, cookies, webhook secrets, or unredacted raw errors. + +External links in reports MUST be validated and rendered safely. Source-controlled text MUST be rendered as escaped text. + +### Scenario: UTC report period + +Given an authorized owner requests a same-day UTC report +When Security Agent assembles the report +Then events recorded at or after `00:00:00.000Z` on that day and before `00:00:00.000Z` on the next day MUST be eligible. +And events outside that range MUST NOT be included. + +### Scenario: Invalid report period + +Given an authorized owner requests a future, reversed, or longer-than-90-day range +When Security Agent validates the request +Then Security Agent MUST reject the request before scanning report events. + +### Scenario: Complete query failure + +Given a report scan has loaded some matching events +When a later page fails, times out, or exceeds the tested budget +Then Security Agent MUST discard accumulated data. +And Security Agent MUST return a complete-query failure state rather than a partial report. + +### Scenario: Deleted finding remains reportable + +Given a Security Finding has been deleted +And a deletion Security Finding Activity Event with a final compact snapshot was recorded +When an authorized owner requests a period containing that event +Then the report MUST show the deleted Security Finding from immutable event evidence. +And the report MUST NOT rely on joining through the mutable Security Finding row. + +### Scenario: Organization report permissions + +Given an organization has Security Finding Activity Events +When an organization owner, billing manager, or Kilo platform admin requests its report +Then Security Agent MUST allow the report. + +Given an ordinary organization member or non-member requests the report +When Security Agent checks authorization +Then Security Agent MUST reject access before loading counts or report data. + +### Scenario: Legacy coverage wording + +Given a report period overlaps activity before reliable event coverage began +When Security Agent renders the report +Then the report MUST state that it contains activity recorded by Kilo. +And it MUST label supplemental legacy activity as potentially incomplete. + +### Scenario: Repository report filter + +Given a report contains Security Finding groups with recorded repository identity +When an authorized viewer selects one repository +Then the report MUST show only groups whose recorded repository full name matches that selection. +And every event in each matching group's in-period timeline MUST remain visible. +And current Security Agent repository selection or accessibility MUST NOT remove recorded repository options from the report. + ## Security Agent Notifications ### Delivery and policy ownership @@ -635,4 +766,11 @@ The following are intentionally outside the guaranteed v1 behavior: - Settings-time or launch-time repository write-permission preflight by Security Agent. - Notification channels other than email. - Per-member organization notification overrides. -- Backfilling complete specifications for Security Agent features unrelated to Auto Remediation or Security Agent Notifications. +- Historical SLA compliance metrics. +- Repository scan-coverage appendices. +- Configuration policy appendices. +- Exhaustive notification delivery history in Audit Reports. +- Report ranges longer than 90 days. +- Server-side stored report artifacts or server-generated PDFs. +- Server-side report result caching. +- Backfilling complete specifications for Security Agent features unrelated to Auto Remediation, Security Agent Notifications, or Security Agent Audit Reports. diff --git a/CONTEXT.md b/CONTEXT.md index d3ecee3387..a103550273 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -29,6 +29,8 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr | **SLA Breach Notification** | Security Agent Notification admitted when eligible finding reaches or passes persisted SLA deadline | Referring to at-or-after-deadline event | Overdue alert, breach reminder | | **Notification Recipient** | User authorized to receive one Security Agent Notification: personal owner or current organization owner | Referring to per-user event identity and authorization | Subscriber, watcher, all organization members | | **Email Delivery** | Attempt to render and send one Security Agent Notification through Mailgun | Referring to provider side effect, retry, or acceptance | Notification event | +| **Security Finding Activity Event** | Immutable record of one material user, system-policy, or source-driven action or outcome that changes or explains a Security Finding | Referring to evidence included in a Security Agent Audit Report | Page view, unchanged sync observation, queue claim, heartbeat | +| **Security Agent Audit Report** | Owner-scoped, period-bounded audit view of Security Finding Activity Events grouped by Security Finding | Referring to the interactive audit report | Generic audit-log export, activity dump | ## Relationships @@ -36,9 +38,26 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr - A **Security Finding** can create at most one **Security Agent Notification** of each kind per **Notification Recipient**. - A **New-finding Notification** depends on first insertion into Kilo, not source alert creation time. - An **SLA Warning Notification** and **SLA Breach Notification** use persisted `sla_due_at`; warning does not suppress later breach. +- A Security Agent Audit Report may show a persisted SLA deadline and recorded outcome when trustworthy evidence exists. V1 does not redefine live SLA behavior or calculate authoritative historical SLA compliance. - A **Notification Recipient** for an organization finding is a current organization member with role `owner`. - An **Email Delivery** realizes a durable **Security Agent Notification** and may be retried without creating new event identity. - A **Security Remediation** belongs to one **Security Finding** and can have one or more **Security Remediation Attempts**. +- A **Security Finding Activity Event** belongs to one Security Agent owner and one Security Finding, including after that finding is deleted. +- A **Security Finding Activity Event** falls into a report period based on when Kilo recorded or applied it. External source timestamps are supporting evidence and do not determine report inclusion. +- A **Security Agent Audit Report** groups every matching reportable **Security Finding Activity Event** recorded by Kilo in the selected period. +- V1 reports persisted SLA evidence only when it can do so from trustworthy recorded data. It does not calculate historical SLA compliance percentages or introduce new SLA lifecycle semantics. +- A personal **Security Agent Audit Report** is available only to its owning user. An organization report is available to organization owners, billing managers, and audited Kilo platform admins, not ordinary members. +- Security Agent Audit Report access has no separate plan or active-subscription gate; authorized owners retain read-only historical access after cancellation or disablement. +- A **Security Agent Audit Report** includes owner history from current, deselected, unavailable, and deleted repository scope. Current Security Agent repository selection does not limit historical evidence; an explicit report repository filter may narrow displayed Security Finding groups by exact recorded repository full name. +- Human activity in a **Security Agent Audit Report** uses an event-time display name and stable typed actor reference; automated activity uses explicit system attribution. Actor and notification recipient emails are not report evidence. +- Deleting an actor's Kilo account anonymizes their dedicated identity fields in organization-owned Security Finding Activity Events while preserving stable non-PII attribution and event evidence. Identity-bearing values do not belong in event snapshots or arbitrary metadata. +- Superseded Security Findings remain separate report groups and show their canonical Security Finding ID when recorded; canonical remediation evidence is not copied into superseded groups. +- Each v1 report range is capped at 90 inclusive calendar days. +- A report displays its reliable event-coverage start and labels supplemental legacy activity as potentially incomplete. +- Disabling Security Agent or its integration does not hide authorized historical Security Agent Audit Reports. +- `security_audit_log` is the canonical ledger for Security Finding Activity Events; finding events are distinguished by stable finding identity. +- A reportable local Security Finding state transition and its Security Finding Activity Event are atomic. External side effects use a durable request event and terminal outcome event without keeping database transactions open across network calls. +- Security Agent Audit Reports include structured, sanitized analysis and remediation outcomes, not prompts, raw analysis markdown, transcripts, assistant messages, full execution logs, or recipient-level notification history. ## Agent Rules @@ -50,6 +69,8 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr - Keep notification eligibility and outbox transitions in **Security Sync**. Keep rendering and Mailgun access in **Security Agent Email Delivery**. - Keep notification config parsing and pure eligibility semantics in **Shared Security Notification Policy** so web and Worker cannot drift. - Do not call organization members or billing managers **Notification Recipients** unless they also hold current organization `owner` role. +- Treat "all activity" in a **Security Agent Audit Report** as all material actions and outcomes recorded by Kilo, not every internal processing step or an attestation that legacy history is exhaustive. Exclude reads, unchanged sync observations, queue claims, heartbeats, and retries with no new finding-level outcome. +- A rollout baseline event records current state at actual capture time for an existing Security Finding; it is not a synthetic creation event and must not be backdated. ## Ambiguities @@ -73,3 +94,4 @@ Kilo Code Cloud hosts Kilo Code agents, integrations, and automation. This contr - `.specs/security-agent.md` defines Security Agent Auto Remediation and notification guarantees. - `.plans/security-agent-notifications.md` records notification implementation and rollout design. +- `.plans/security-agent-audit-report.md` records Security Agent Audit Report implementation and evidence design. diff --git a/apps/web/src/lib/security-agent/core/schemas.ts b/apps/web/src/lib/security-agent/core/schemas.ts index 9a3515ea04..3d58aae693 100644 --- a/apps/web/src/lib/security-agent/core/schemas.ts +++ b/apps/web/src/lib/security-agent/core/schemas.ts @@ -24,6 +24,21 @@ export const AnalysisModeSchema = z.enum(['auto', 'shallow', 'deep']); export const AutoAnalysisMinSeveritySchema = z.enum(['critical', 'high', 'medium', 'all']); export const AutoRemediationMinSeveritySchema = z.enum(['critical', 'high', 'medium', 'all']); export const NotificationMinSeveritySchema = SecurityNotificationSeveritySchema; +export const SecurityAgentUiInteractionSchema = z.enum([ + 'finding_detail_opened', + 'finding_triage_viewed', + 'finding_analysis_viewed', + 'finding_remediation_viewed', + 'findings_filtered', + 'settings_config_viewed', + 'settings_automation_viewed', + 'settings_notifications_viewed', + 'settings_sla_viewed', +]); + +export const TrackSecurityAgentUiInteractionInputSchema = z.object({ + interaction: SecurityAgentUiInteractionSchema, +}); export const SaveSecurityConfigInputSchema = z.object({ slaCriticalDays: z.number().min(1).max(365).optional(), @@ -120,6 +135,7 @@ export const SandboxSuggestedActionSchema = z.enum([ export const SecurityFindingSandboxAnalysisSchema = z.object({ isExploitable: z.union([z.boolean(), z.literal('unknown')]), + extractionStatus: z.enum(['succeeded', 'failed']).optional(), exploitabilityReasoning: z.string(), usageLocations: z.array(z.string()), suggestedFix: z.string(), @@ -155,6 +171,7 @@ export const StartAnalysisInputSchema = z.object({ analysisModel: z.string().optional(), forceSandbox: z.boolean().optional(), retrySandboxOnly: z.boolean().optional(), + restartActive: z.boolean().optional(), }); export const StartRemediationInputSchema = z.object({ @@ -185,6 +202,10 @@ export const GetDashboardStatsInputSchema = z.object({ repoFullName: z.string().optional(), }); +export type SecurityAgentUiInteraction = z.infer; +export type TrackSecurityAgentUiInteractionInput = z.infer< + typeof TrackSecurityAgentUiInteractionInputSchema +>; export type SaveSecurityConfigInput = z.infer; export type ListFindingsInput = z.infer; export type TriggerSyncInput = z.infer; diff --git a/apps/web/src/lib/security-agent/db/dashboard-stats.test.ts b/apps/web/src/lib/security-agent/db/dashboard-stats.test.ts new file mode 100644 index 0000000000..8c67e30304 --- /dev/null +++ b/apps/web/src/lib/security-agent/db/dashboard-stats.test.ts @@ -0,0 +1,179 @@ +import { describe, expect, it } from '@jest/globals'; +import { db } from '@/lib/drizzle'; +import { security_findings } from '@kilocode/db/schema'; +import type { NewSecurityFinding } from '@kilocode/db/schema'; +import type { SecurityFindingAnalysis } from '@kilocode/db/schema-types'; +import { insertTestUser } from '@/tests/helpers/user.helper'; +import { getDashboardStats } from './dashboard-stats'; + +const slaConfig = { + slaCriticalDays: 15, + slaHighDays: 30, + slaMediumDays: 45, + slaLowDays: 90, +}; + +function finding( + userId: string, + sourceId: string, + overrides: Partial +): NewSecurityFinding { + return { + owned_by_user_id: userId, + repo_full_name: 'Kilo-Org/cloud', + source: 'dependabot', + source_id: sourceId, + severity: 'high', + package_name: `package-${sourceId}`, + package_ecosystem: 'npm', + title: `Security Finding ${sourceId}`, + ...overrides, + }; +} + +function analysis({ + isExploitable, + suggestedAction, +}: { + isExploitable: boolean | 'unknown'; + suggestedAction: 'dismiss' | 'open_pr' | 'manual_review' | 'monitor'; +}): SecurityFindingAnalysis { + return { + analyzedAt: '2026-06-17T00:00:00.000Z', + sandboxAnalysis: { + isExploitable, + exploitabilityReasoning: 'Test reasoning', + usageLocations: [], + suggestedFix: 'Update dependency', + suggestedAction, + summary: 'Test analysis', + rawMarkdown: 'Test analysis', + analysisAt: '2026-06-17T00:00:00.000Z', + }, + }; +} + +describe('getDashboardStats', () => { + it('returns guided urgency, priority, and repository action data within owner scope', async () => { + const user = await insertTestUser(); + const otherUser = await insertTestUser(); + const now = Date.now(); + const overdueAt = new Date(now - 2 * 24 * 60 * 60 * 1000).toISOString(); + const dueSoonAt = new Date(now + 3 * 24 * 60 * 60 * 1000).toISOString(); + + await db.insert(security_findings).values([ + finding(user.id, 'dashboard-overdue', { + severity: 'critical', + title: 'Critical overdue finding', + sla_due_at: overdueAt, + }), + finding(user.id, 'dashboard-due-soon', { + sla_due_at: dueSoonAt, + analysis_status: 'completed', + analysis: analysis({ isExploitable: true, suggestedAction: 'open_pr' }), + }), + finding(user.id, 'dashboard-review', { + repo_full_name: 'Kilo-Org/kilocode', + sla_due_at: null, + analysis_status: 'completed', + analysis: analysis({ isExploitable: 'unknown', suggestedAction: 'manual_review' }), + }), + finding(otherUser.id, 'dashboard-other-owner', { + severity: 'critical', + sla_due_at: overdueAt, + }), + ]); + + const result = await getDashboardStats({ + owner: { userId: user.id }, + slaEnabled: true, + slaConfig, + }); + + expect(result.sla.overall).toEqual({ total: 2, withinSla: 1, overdue: 1 }); + expect(result.sla.dueSoon).toEqual({ total: 1, exploitable: 1 }); + expect(result.sla.untrackedCount).toBe(1); + expect(result.severity).toEqual({ critical: 1, high: 2, medium: 0, low: 0 }); + expect(result.analysis).toMatchObject({ + total: 3, + analyzed: 2, + exploitable: 1, + needsReview: 1, + notAnalyzed: 1, + }); + expect(result.priorityFinding).toMatchObject({ + severity: 'critical', + title: 'Critical overdue finding', + repoFullName: 'Kilo-Org/cloud', + analysisStatus: null, + isExploitable: null, + slaDueAt: overdueAt, + daysOverdue: 2, + }); + expect(result.priorityFinding?.slaDueAt).toMatch(/T/); + expect(result.repositoryCount).toBe(2); + expect(result.repoHealth).toEqual([ + { + repoFullName: 'Kilo-Org/cloud', + open: 2, + critical: 1, + high: 1, + medium: 0, + low: 0, + overdue: 1, + exploitable: 1, + needsAction: 2, + slaCompliancePercent: 50, + }, + { + repoFullName: 'Kilo-Org/kilocode', + open: 1, + critical: 0, + high: 1, + medium: 0, + low: 0, + overdue: 0, + exploitable: 0, + needsAction: 1, + slaCompliancePercent: 100, + }, + ]); + }); + + it('applies the exact repository filter and risk-first ordering when SLA is disabled', async () => { + const user = await insertTestUser(); + const overdueAt = new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(); + + await db.insert(security_findings).values([ + finding(user.id, 'dashboard-filter-cloud', { repo_full_name: 'Kilo-Org/cloud' }), + finding(user.id, 'dashboard-filter-web-complete', { + repo_full_name: 'Kilo-Org/web', + title: 'Completed analysis with expired deadline', + sla_due_at: overdueAt, + analysis_status: 'completed', + analysis: analysis({ isExploitable: false, suggestedAction: 'monitor' }), + }), + finding(user.id, 'dashboard-filter-web-needs-analysis', { + repo_full_name: 'Kilo-Org/web', + title: 'Finding needing analysis', + }), + ]); + + const result = await getDashboardStats({ + owner: { userId: user.id }, + repoFullName: 'Kilo-Org/web', + slaEnabled: false, + slaConfig, + }); + + expect(result.analysis.total).toBe(2); + expect(result.repositoryCount).toBe(1); + expect(result.repoHealth).toHaveLength(1); + expect(result.repoHealth[0]?.repoFullName).toBe('Kilo-Org/web'); + expect(result.priorityFinding).toMatchObject({ + repoFullName: 'Kilo-Org/web', + title: 'Finding needing analysis', + analysisStatus: null, + }); + }); +}); diff --git a/apps/web/src/lib/security-agent/db/dashboard-stats.ts b/apps/web/src/lib/security-agent/db/dashboard-stats.ts index 18ee79c54a..be663fa92b 100644 --- a/apps/web/src/lib/security-agent/db/dashboard-stats.ts +++ b/apps/web/src/lib/security-agent/db/dashboard-stats.ts @@ -10,6 +10,7 @@ export type DashboardStats = { sla: { overall: { total: number; withinSla: number; overdue: number }; bySeverity: Record; + dueSoon: { total: number; exploitable: number }; untrackedCount: number; }; severity: Record; @@ -46,15 +47,30 @@ export type DashboardStats = { slaDueAt: string; daysOverdue: number; }>; + priorityFinding: { + id: string; + severity: string; + title: string; + repoFullName: string; + analysisStatus: string | null; + isExploitable: boolean | 'unknown' | null; + suggestedAction: string | null; + slaDueAt: string | null; + daysOverdue: number | null; + } | null; repoHealth: Array<{ repoFullName: string; + open: number; critical: number; high: number; medium: number; low: number; overdue: number; + exploitable: number; + needsAction: number; slaCompliancePercent: number; }>; + repositoryCount: number; }; type Owner = { type: 'org'; id: string } | { type: 'user'; id: string }; @@ -86,6 +102,8 @@ type SlaRow = { total: string; within_sla: string; overdue: string; + due_soon: string; + due_soon_exploitable: string; untracked: string; }; @@ -129,19 +147,36 @@ type OverdueRow = { days_overdue: string; }; +type PriorityFindingRow = { + id: string; + severity: string; + title: string; + repo_full_name: string; + analysis_status: string | null; + is_exploitable: string | null; + suggested_action: string | null; + sla_due_at: string | null; + days_overdue: string | null; +}; + type RepoHealthRow = { repo_full_name: string; + open: string; critical: string; high: string; medium: string; low: string; overdue: string; + exploitable: string; + needs_action: string; sla_compliance_percent: string; + repository_count: string; }; type GetDashboardStatsParams = { owner: SecurityReviewOwner; repoFullName?: string; + slaEnabled: boolean; slaConfig: { slaCriticalDays: number; slaHighDays: number; @@ -163,11 +198,34 @@ function emptySeverityRecord(defaultValue: () => T): Record { }; } +function parseExploitability(value: string | null): boolean | 'unknown' | null { + if (value === 'true') return true; + if (value === 'false') return false; + if (value === 'unknown') return 'unknown'; + return null; +} + +function toUtcIso(value: string): string { + return new Date(value).toISOString(); +} + export async function getDashboardStats(params: GetDashboardStatsParams): Promise { try { - const { owner, repoFullName, slaConfig } = params; + const { owner, repoFullName, slaEnabled, slaConfig } = params; const ownerConverted = toOwner(owner); const whereClause = buildWhereClause(ownerConverted, repoFullName); + const repositorySecondaryOrder = slaEnabled + ? sql`COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} <= now())` + : sql`COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'true')`; + const priorityDeadlineOrder = slaEnabled + ? sql`CASE + WHEN ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} <= now() THEN 0 + ELSE 1 + END` + : sql`CASE WHEN true THEN 0 END`; + const priorityDeadlineDateOrder = slaEnabled + ? sql`${security_findings.sla_due_at} ASC NULLS LAST` + : sql`${security_findings.first_detected_at} ASC`; const [ slaResult, @@ -176,6 +234,7 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis analysisResult, mttrResult, overdueResult, + priorityFindingResult, repoHealthResult, ] = await Promise.all([ // SLA query @@ -185,6 +244,8 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis COUNT(*) FILTER (WHERE ${security_findings.sla_due_at} IS NOT NULL) AS total, COUNT(*) FILTER (WHERE ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} > now()) AS within_sla, COUNT(*) FILTER (WHERE ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} <= now()) AS overdue, + COUNT(*) FILTER (WHERE ${security_findings.sla_due_at} > now() AND ${security_findings.sla_due_at} <= now() + interval '7 days') AS due_soon, + COUNT(*) FILTER (WHERE ${security_findings.sla_due_at} > now() AND ${security_findings.sla_due_at} <= now() + interval '7 days' AND ${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'true') AS due_soon_exploitable, COUNT(*) FILTER (WHERE ${security_findings.sla_due_at} IS NULL) AS untracked FROM ${security_findings} WHERE ${security_findings.status} = 'open' AND ${whereClause} @@ -216,7 +277,7 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis COUNT(*) FILTER (WHERE ${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'false') AS not_exploitable, COUNT(*) FILTER (WHERE ${security_findings.analysis_status} = 'completed' AND ((${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') IS NULL OR (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'unknown') AND (${security_findings.analysis}->'triage'->>'suggestedAction') = 'analyze_codebase') AS triage_complete, COUNT(*) FILTER (WHERE ${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'triage'->>'suggestedAction') = 'dismiss' AND ((${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') IS NULL OR (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'unknown')) AS safe_to_dismiss, - COUNT(*) FILTER (WHERE ${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'triage'->>'suggestedAction') = 'manual_review' AND ((${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') IS NULL OR (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'unknown')) AS needs_review, + COUNT(*) FILTER (WHERE ${security_findings.analysis_status} = 'completed' AND COALESCE(${security_findings.analysis}->'sandboxAnalysis'->>'suggestedAction', ${security_findings.analysis}->'triage'->>'suggestedAction') = 'manual_review' AND ((${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') IS NULL OR (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'unknown')) AS needs_review, COUNT(*) FILTER (WHERE ${security_findings.analysis_status} IN ('pending', 'running')) AS analyzing, COUNT(*) FILTER (WHERE ${security_findings.analysis_status} IS NULL) AS not_analyzed, COUNT(*) FILTER (WHERE ${security_findings.analysis_status} = 'failed') AS failed @@ -252,35 +313,100 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis FROM ${security_findings} WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL - AND ${security_findings.sla_due_at} < now() + AND ${security_findings.sla_due_at} <= now() AND ${whereClause} ORDER BY ${security_findings.sla_due_at} ASC LIMIT 10 `), + // Highest-priority open finding for guided next action + db.execute(sql` + SELECT + ${security_findings.id} AS id, + ${security_findings.severity} AS severity, + ${security_findings.title} AS title, + ${security_findings.repo_full_name} AS repo_full_name, + ${security_findings.analysis_status} AS analysis_status, + ${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable' AS is_exploitable, + COALESCE( + ${security_findings.analysis}->'sandboxAnalysis'->>'suggestedAction', + ${security_findings.analysis}->'triage'->>'suggestedAction' + ) AS suggested_action, + ${security_findings.sla_due_at} AS sla_due_at, + CASE + WHEN ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} <= now() + THEN EXTRACT(EPOCH FROM (now() - ${security_findings.sla_due_at})) / 86400 + ELSE NULL + END AS days_overdue + FROM ${security_findings} + WHERE ${security_findings.status} = 'open' AND ${whereClause} + ORDER BY + CASE ${security_findings.severity} + WHEN 'critical' THEN 0 + WHEN 'high' THEN 1 + WHEN 'medium' THEN 2 + WHEN 'low' THEN 3 + ELSE 4 + END, + ${priorityDeadlineOrder}, + CASE + WHEN ${security_findings.analysis_status} IS NULL OR ${security_findings.analysis_status} = 'failed' THEN 0 + WHEN ${security_findings.analysis_status} IN ('pending', 'running') THEN 1 + WHEN (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'true' THEN 2 + WHEN COALESCE(${security_findings.analysis}->'sandboxAnalysis'->>'suggestedAction', ${security_findings.analysis}->'triage'->>'suggestedAction') IN ('analyze_codebase', 'manual_review') THEN 3 + ELSE 4 + END, + ${priorityDeadlineDateOrder}, + ${security_findings.first_detected_at} ASC, + ${security_findings.id} ASC + LIMIT 1 + `), + // Repo health query db.execute(sql` SELECT ${security_findings.repo_full_name} AS repo_full_name, + COUNT(*) FILTER (WHERE ${security_findings.status} = 'open') AS open, COUNT(*) FILTER (WHERE ${security_findings.severity} = 'critical' AND ${security_findings.status} = 'open') AS critical, COUNT(*) FILTER (WHERE ${security_findings.severity} = 'high' AND ${security_findings.status} = 'open') AS high, COUNT(*) FILTER (WHERE ${security_findings.severity} = 'medium' AND ${security_findings.status} = 'open') AS medium, COUNT(*) FILTER (WHERE ${security_findings.severity} = 'low' AND ${security_findings.status} = 'open') AS low, - COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} < now()) AS overdue, + COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} <= now()) AS overdue, + COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'true') AS exploitable, + COUNT(*) FILTER ( + WHERE ${security_findings.status} = 'open' + AND ( + ${security_findings.analysis_status} IS NULL + OR ${security_findings.analysis_status} = 'failed' + OR (${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'true') + OR (${security_findings.analysis_status} = 'completed' AND COALESCE(${security_findings.analysis}->'sandboxAnalysis'->>'suggestedAction', ${security_findings.analysis}->'triage'->>'suggestedAction') IN ('analyze_codebase', 'manual_review')) + ) + ) AS needs_action, CASE WHEN COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL) = 0 THEN 100 ELSE ROUND( COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} > now()) * 100.0 / COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL), 1 ) - END AS sla_compliance_percent + END AS sla_compliance_percent, + COUNT(*) OVER() AS repository_count FROM ${security_findings} WHERE ${whereClause} GROUP BY ${security_findings.repo_full_name} HAVING COUNT(*) FILTER (WHERE ${security_findings.status} = 'open') > 0 ORDER BY COUNT(*) FILTER (WHERE ${security_findings.severity} = 'critical' AND ${security_findings.status} = 'open') DESC, - COUNT(*) FILTER (WHERE ${security_findings.status} = 'open' AND ${security_findings.sla_due_at} IS NOT NULL AND ${security_findings.sla_due_at} < now()) DESC + ${repositorySecondaryOrder} DESC, + COUNT(*) FILTER ( + WHERE ${security_findings.status} = 'open' + AND ( + ${security_findings.analysis_status} IS NULL + OR ${security_findings.analysis_status} = 'failed' + OR (${security_findings.analysis_status} = 'completed' AND (${security_findings.analysis}->'sandboxAnalysis'->>'isExploitable') = 'true') + OR (${security_findings.analysis_status} = 'completed' AND COALESCE(${security_findings.analysis}->'sandboxAnalysis'->>'suggestedAction', ${security_findings.analysis}->'triage'->>'suggestedAction') IN ('analyze_codebase', 'manual_review')) + ) + ) DESC, + ${security_findings.repo_full_name} ASC LIMIT 10 `), ]); @@ -290,6 +416,8 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis let slaOverallTotal = 0; let slaOverallWithinSla = 0; let slaOverallOverdue = 0; + let dueSoonCount = 0; + let dueSoonExploitableCount = 0; let untrackedCount = 0; for (const row of slaResult.rows) { @@ -303,6 +431,8 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis slaOverallWithinSla += withinSla; slaOverallOverdue += overdue; } + dueSoonCount += Number(row.due_soon); + dueSoonExploitableCount += Number(row.due_soon_exploitable); untrackedCount += Number(row.untracked); } @@ -387,20 +517,42 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis title: row.title, repoFullName: row.repo_full_name, packageName: row.package_name, - slaDueAt: row.sla_due_at, + slaDueAt: toUtcIso(row.sla_due_at), daysOverdue: Math.max(0, Math.floor(Number(row.days_overdue))), })); + const priorityRow = priorityFindingResult.rows[0]; + const priorityFinding = priorityRow + ? { + id: priorityRow.id, + severity: priorityRow.severity, + title: priorityRow.title, + repoFullName: priorityRow.repo_full_name, + analysisStatus: priorityRow.analysis_status, + isExploitable: parseExploitability(priorityRow.is_exploitable), + suggestedAction: priorityRow.suggested_action, + slaDueAt: priorityRow.sla_due_at ? toUtcIso(priorityRow.sla_due_at) : null, + daysOverdue: + priorityRow.days_overdue === null + ? null + : Math.max(0, Math.floor(Number(priorityRow.days_overdue))), + } + : null; + // Parse repo health results const repoHealth = repoHealthResult.rows.map(row => ({ repoFullName: row.repo_full_name, + open: Number(row.open), critical: Number(row.critical), high: Number(row.high), medium: Number(row.medium), low: Number(row.low), overdue: Number(row.overdue), + exploitable: Number(row.exploitable), + needsAction: Number(row.needs_action), slaCompliancePercent: Number(row.sla_compliance_percent), })); + const repositoryCount = Number(repoHealthResult.rows[0]?.repository_count ?? 0); return { sla: { @@ -410,6 +562,7 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis overdue: slaOverallOverdue, }, bySeverity: slaBySeverity, + dueSoon: { total: dueSoonCount, exploitable: dueSoonExploitableCount }, untrackedCount, }, severity: severityCounts, @@ -417,7 +570,9 @@ export async function getDashboardStats(params: GetDashboardStatsParams): Promis analysis, mttr: { bySeverity: mttrBySeverity }, overdue, + priorityFinding, repoHealth, + repositoryCount, }; } catch (error) { captureException(error, { diff --git a/apps/web/src/lib/security-agent/db/security-analysis.ts b/apps/web/src/lib/security-agent/db/security-analysis.ts index a59ee7d19d..2cbc26bee6 100644 --- a/apps/web/src/lib/security-agent/db/security-analysis.ts +++ b/apps/web/src/lib/security-agent/db/security-analysis.ts @@ -5,6 +5,17 @@ import { security_analysis_owner_state, type SecurityFinding, } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditEventFinding, + type SecurityFindingAuditOwner, +} from '@kilocode/worker-utils/security-finding-audit'; import { eq, and, sql, count, isNotNull, desc, or, isNull, not, like } from 'drizzle-orm'; import { captureException } from '@sentry/nextjs'; import type { @@ -28,6 +39,30 @@ function toOwner(owner: SecurityReviewOwner): Owner { throw new Error('Invalid owner: must have either organizationId or userId'); } +function toAuditOwner( + finding: Pick +): SecurityFindingAuditOwner { + if (finding.owned_by_organization_id) { + return { type: 'organization', organizationId: finding.owned_by_organization_id }; + } + if (finding.owned_by_user_id) { + return { type: 'user', userId: finding.owned_by_user_id }; + } + throw new Error('Security finding has no audit owner'); +} + +function ownerAuditKeyPart( + finding: Pick +): string { + if (finding.owned_by_organization_id) return `organization:${finding.owned_by_organization_id}`; + if (finding.owned_by_user_id) return `user:${finding.owned_by_user_id}`; + throw new Error('Security finding has no audit owner'); +} + +function toAuditFinding(finding: SecurityFinding): SecurityFindingAuditEventFinding { + return finding; +} + export type AutoAnalysisQueueStatus = 'queued' | 'pending' | 'running' | 'failed' | 'completed'; export type AutoAnalysisFailureCode = @@ -123,18 +158,20 @@ export async function updateAnalysisStatus( updateData.analysis_completed_at = sql`now()`; } + if (status === 'completed' || status === 'failed') { + const rows = await updateTerminalAnalysisStatusWithAuditEvent( + findingId, + status, + updateData, + updates + ); + return rows.length > 0; + } + const rows = await db .update(security_findings) .set(updateData) - .where( - and( - eq(security_findings.id, findingId), - or( - isNull(security_findings.ignored_reason), - not(like(security_findings.ignored_reason, 'superseded:%')) - ) - ) - ) + .where(analysisStatusUpdatePredicate(findingId)) .returning({ id: security_findings.id }); return rows.length > 0; @@ -147,6 +184,120 @@ export async function updateAnalysisStatus( } } +function analysisStatusUpdatePredicate(findingId: string) { + return and( + eq(security_findings.id, findingId), + or( + isNull(security_findings.ignored_reason), + not(like(security_findings.ignored_reason, 'superseded:%')) + ) + ); +} + +async function updateTerminalAnalysisStatusWithAuditEvent( + findingId: string, + status: 'completed' | 'failed', + updateData: Record, + updates: { + error?: string; + analysis?: SecurityFindingAnalysis; + } +): Promise> { + return db.transaction(async tx => { + const [previousFinding] = await tx + .select() + .from(security_findings) + .where(analysisStatusUpdatePredicate(findingId)) + .limit(1); + + if (!previousFinding) return []; + + const [updatedFinding] = await tx + .update(security_findings) + .set(updateData) + .where(analysisStatusUpdatePredicate(findingId)) + .returning(); + + if (!updatedFinding) return []; + + await insertAnalysisAuditEvent(tx, { + previousFinding, + updatedFinding, + status, + analysis: updates.analysis, + }); + + return [{ id: updatedFinding.id }]; + }); +} + +async function insertAnalysisAuditEvent( + db: Parameters[0], + params: { + previousFinding: SecurityFinding; + updatedFinding: SecurityFinding; + status: 'completed' | 'failed'; + analysis?: SecurityFindingAnalysis; + } +): Promise { + const { previousFinding, updatedFinding, status, analysis } = params; + const action = + status === 'completed' + ? SecurityAuditLogAction.FindingAnalysisCompleted + : SecurityAuditLogAction.FindingAnalysisFailed; + const occurredAt = updatedFinding.analysis_completed_at ?? new Date().toISOString(); + const correlationId = analysis?.correlationId ?? updatedFinding.session_id ?? 'none'; + const modelSlug = analysis?.analysisModel ?? analysis?.modelUsed ?? analysis?.triageModel ?? null; + const triage = analysis?.triage; + const sandbox = analysis?.sandboxAnalysis; + const suggestedAction = sandbox?.suggestedAction ?? triage?.suggestedAction; + + await insertSecurityFindingAuditEvent(db, { + owner: toAuditOwner(updatedFinding), + finding: toAuditFinding(updatedFinding), + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ownerAuditKeyPart(updatedFinding), + updatedFinding.id, + action, + correlationId, + ]), + sourceContext: SecurityFindingAuditSourceContext.AnalysisWorker, + beforeState: { analysis_status: previousFinding.analysis_status ?? 'unknown' }, + afterState: + status === 'completed' + ? { + analysis_status: 'completed', + ...(suggestedAction ? { suggested_action: suggestedAction } : {}), + ...(!sandbox && triage?.confidence ? { confidence: triage.confidence } : {}), + ...(sandbox?.extractionStatus + ? { structured_extraction_status: sandbox.extractionStatus } + : {}), + ...(sandbox?.isExploitable !== undefined && sandbox.extractionStatus !== 'failed' + ? { is_exploitable: sandbox.isExploitable } + : {}), + } + : { analysis_status: 'failed' }, + metadata: + status === 'completed' + ? { + ...(analysis?.correlationId ? { correlation_id: analysis.correlationId } : {}), + ...(modelSlug ? { model_slug: modelSlug } : {}), + ...(analysis?.triageModel ? { triage_model_slug: analysis.triageModel } : {}), + ...(analysis?.analysisModel ? { analysis_model_slug: analysis.analysisModel } : {}), + ...(triage?.needsSandboxAnalysis !== undefined + ? { needs_sandbox_analysis: triage.needsSandboxAnalysis } + : {}), + } + : { + failure_code: 'analysis_failed', + ...(updatedFinding.session_id ? { session_id: updatedFinding.session_id } : {}), + }, + }); +} + /** * Clear analysis_status so a superseded finding no longer counts against * the owner's concurrency cap in countRunningAnalyses(). diff --git a/apps/web/src/lib/security-agent/db/security-audit-report.test.ts b/apps/web/src/lib/security-agent/db/security-audit-report.test.ts new file mode 100644 index 0000000000..0de563fb6a --- /dev/null +++ b/apps/web/src/lib/security-agent/db/security-audit-report.test.ts @@ -0,0 +1,782 @@ +import { afterEach, describe, expect, it } from '@jest/globals'; +import { db, pool } from '@/lib/drizzle'; +import { insertTestUser } from '@/tests/helpers/user.helper'; +import { kilocode_users, security_audit_log } from '@kilocode/db/schema'; +import { SecurityAuditLogAction, SecurityAuditLogActorType } from '@kilocode/db/schema-types'; +import { inArray } from 'drizzle-orm'; +import { randomUUID } from 'node:crypto'; +import { + assertSecurityAgentAuditReportSerializedByteBudget, + buildSecurityAgentAuditReportFromRows, + defaultSecurityAgentAuditReportInput, + getSecurityAgentAuditReport, + normalizeSecurityAgentAuditReportPeriod, + resolveSecurityAgentAuditReliableCoverageStart, + SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS, + SECURITY_AGENT_AUDIT_REPORT_MAX_SERIALIZED_BYTES, + SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE, + securityAgentAuditReportSerializedByteLength, + securityAgentAuditReportEventCountBucket, + SecurityAgentAuditReportQueryError, + withSecurityAgentAuditReportTimeout, +} from './security-audit-report'; + +type AuditReportRow = Parameters[0]['rows'][number]; + +process.env.SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START = '2026-06-17T12:00:00.000Z'; + +const period = normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-06-01', endDate: '2026-06-12' }, + new Date('2026-06-12T15:00:00.000Z') +); +const integrationOwnerIds: string[] = []; + +async function createIntegrationOwner() { + const user = await insertTestUser(); + integrationOwnerIds.push(user.id); + return { + type: 'user' as const, + id: user.id, + displayName: user.google_user_name ?? 'Test User', + }; +} + +function dateOnly(value: string): string { + return value.slice(0, 10); +} + +function findingSnapshot(findingId: string): Record { + return { + finding_id: findingId, + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/snapshot-report-test', + title: 'Snapshot report pagination test', + severity: 'high', + status: 'open', + }; +} + +async function waitForAuditReportReadBlockedBy(lockingPid: number): Promise { + const deadline = Date.now() + 5_000; + while (Date.now() < deadline) { + const result = await pool.query<{ waiting: boolean }>( + `SELECT EXISTS ( + SELECT 1 + FROM pg_locks + WHERE relation = 'security_audit_log'::regclass + AND NOT granted + AND $1::integer = ANY(pg_blocking_pids(pid)) + ) AS waiting`, + [lockingPid] + ); + if (result.rows[0]?.waiting) return; + await new Promise(resolve => setTimeout(resolve, 10)); + } + throw new Error('Audit report read did not block on expected table lock'); +} + +afterEach(async () => { + if (integrationOwnerIds.length === 0) return; + await db.delete(kilocode_users).where(inArray(kilocode_users.id, integrationOwnerIds)); + integrationOwnerIds.length = 0; +}); + +function row(overrides: Partial): AuditReportRow { + return { + id: '00000000-0000-4000-8000-000000000001', + action: SecurityAuditLogAction.FindingCreated, + actor_id: null, + actor_email: null, + actor_name: null, + actor_type: SecurityAuditLogActorType.System, + before_state: null, + after_state: null, + metadata: null, + created_at: '2026-06-01T10:00:00.000Z', + finding_id: '11111111-1111-4111-8111-111111111111', + resource_type: 'security_finding', + resource_id: '11111111-1111-4111-8111-111111111111', + occurred_at: '2026-06-01T10:00:00.000Z', + source_occurred_at: null, + finding_snapshot: { + finding_id: '11111111-1111-4111-8111-111111111111', + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/repo', + title: 'Prototype Pollution in lodash', + severity: 'high', + status: 'open', + package_name: 'lodash', + package_ecosystem: 'npm', + first_detected_at: '2026-06-01T08:00:00.000Z', + sla_due_at: '2026-06-08T08:00:00.000Z', + }, + effective_at: '2026-06-01T10:00:00.000Z', + ...overrides, + }; +} + +describe('resolveSecurityAgentAuditReliableCoverageStart', () => { + it('requires and normalizes deployment-time coverage configuration', () => { + expect(resolveSecurityAgentAuditReliableCoverageStart('2026-06-17T14:00:00+02:00')).toBe( + '2026-06-17T12:00:00.000Z' + ); + expect(() => resolveSecurityAgentAuditReliableCoverageStart('')).toThrow( + 'SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START is required' + ); + expect(() => resolveSecurityAgentAuditReliableCoverageStart('2026-06-17')).toThrow( + 'SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START must be an ISO timestamp' + ); + }); +}); + +describe('defaultSecurityAgentAuditReportInput', () => { + it('defaults to 90 UTC calendar days ending on the current day', () => { + expect(defaultSecurityAgentAuditReportInput(new Date('2026-06-16T21:08:07+02:00'))).toEqual({ + startDate: '2026-03-19', + endDate: '2026-06-16', + }); + }); +}); + +describe('normalizeSecurityAgentAuditReportPeriod', () => { + it('uses inclusive UTC end date and exclusive next-day boundary', () => { + const normalized = normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-06-12', endDate: '2026-06-12' }, + new Date('2026-06-12T15:00:00.000Z') + ); + + expect(normalized).toEqual({ + start: '2026-06-12T00:00:00.000Z', + endExclusive: '2026-06-13T00:00:00.000Z', + displayEnd: '2026-06-12', + timeZone: 'UTC', + }); + }); + + it('accepts a range of exactly 90 inclusive UTC calendar days', () => { + const normalized = normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-03-15', endDate: '2026-06-12' }, + new Date('2026-06-12T15:00:00.000Z') + ); + + expect(normalized.start).toBe('2026-03-15T00:00:00.000Z'); + expect(normalized.endExclusive).toBe('2026-06-13T00:00:00.000Z'); + }); + + it('rejects reversed, future, invalid, and over-limit ranges', () => { + const now = new Date('2026-06-12T15:00:00.000Z'); + + expect(() => + normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-06-12', endDate: '2026-06-11' }, + now + ) + ).toThrow('start date'); + expect(() => + normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-06-12', endDate: '2026-06-13' }, + now + ) + ).toThrow('future'); + expect(() => + normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-02-30', endDate: '2026-03-01' }, + now + ) + ).toThrow('valid UTC calendar date'); + expect(() => + normalizeSecurityAgentAuditReportPeriod( + { startDate: '2026-03-14', endDate: '2026-06-12' }, + now + ) + ).toThrow('90 inclusive'); + }); +}); + +describe('getSecurityAgentAuditReport', () => { + it('scans more than one page of same-timestamp events without gaps or duplicates', async () => { + const owner = await createIntegrationOwner(); + const findingId = randomUUID(); + const occurredAt = new Date(Date.now() - 1_000).toISOString(); + const eventIds = Array.from({ length: SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE + 1 }, () => + randomUUID() + ); + + await db.insert(security_audit_log).values( + eventIds.map(id => ({ + id, + owned_by_user_id: owner.id, + actor_type: SecurityAuditLogActorType.System, + action: SecurityAuditLogAction.FindingCreated, + resource_type: 'security_finding', + resource_id: findingId, + finding_id: findingId, + occurred_at: occurredAt, + finding_snapshot: findingSnapshot(findingId), + })) + ); + + const report = await getSecurityAgentAuditReport({ + owner, + input: { startDate: dateOnly(occurredAt), endDate: dateOnly(occurredAt) }, + isRequestingUserKiloAdmin: false, + }); + const scannedEventIds = report.findings.flatMap(finding => + finding.events.map(event => event.id) + ); + + expect(report.summary.activityCount).toBe(SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE + 1); + expect(scannedEventIds).toEqual([...eventIds].sort()); + expect(new Set(scannedEventIds).size).toBe(SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE + 1); + }, 20_000); + + it('excludes an event committed after its snapshot and includes it in a fresh report', async () => { + const owner = await createIntegrationOwner(); + const findingId = randomUUID(); + const initialEventId = randomUUID(); + const lateEventId = randomUUID(); + const occurredAt = new Date(Date.now() - 1_000).toISOString(); + const input = { startDate: dateOnly(occurredAt), endDate: dateOnly(occurredAt) }; + + await db.insert(security_audit_log).values({ + id: initialEventId, + owned_by_user_id: owner.id, + actor_type: SecurityAuditLogActorType.System, + action: SecurityAuditLogAction.FindingCreated, + resource_type: 'security_finding', + resource_id: findingId, + finding_id: findingId, + occurred_at: occurredAt, + finding_snapshot: findingSnapshot(findingId), + }); + + const lockClient = await pool.connect(); + let firstReportPromise: ReturnType | null = null; + try { + await lockClient.query('BEGIN'); + await lockClient.query('LOCK TABLE security_audit_log IN ACCESS EXCLUSIVE MODE'); + const pidResult = await lockClient.query<{ pid: number }>('SELECT pg_backend_pid() AS pid'); + const lockingPid = pidResult.rows[0]?.pid; + if (lockingPid === undefined) throw new Error('Could not determine locking database process'); + + await lockClient.query( + `INSERT INTO security_audit_log ( + id, + owned_by_user_id, + actor_type, + action, + resource_type, + resource_id, + finding_id, + occurred_at, + finding_snapshot + ) VALUES ($1, $2, $3, $4, 'security_finding', $5::text, $5::uuid, $6, $7::jsonb)`, + [ + lateEventId, + owner.id, + SecurityAuditLogActorType.System, + SecurityAuditLogAction.FindingCreated, + findingId, + occurredAt, + JSON.stringify(findingSnapshot(findingId)), + ] + ); + + firstReportPromise = getSecurityAgentAuditReport({ + owner, + input, + isRequestingUserKiloAdmin: false, + }); + await waitForAuditReportReadBlockedBy(lockingPid); + await lockClient.query('COMMIT'); + + const firstReport = await firstReportPromise; + const freshReport = await getSecurityAgentAuditReport({ + owner, + input, + isRequestingUserKiloAdmin: false, + }); + const firstEventIds = firstReport.findings.flatMap(finding => + finding.events.map(event => event.id) + ); + const freshEventIds = freshReport.findings.flatMap(finding => + finding.events.map(event => event.id) + ); + + expect(firstEventIds).toEqual([initialEventId]); + expect(freshEventIds).toEqual([initialEventId, lateEventId].sort()); + } finally { + await lockClient.query('ROLLBACK').catch(() => undefined); + lockClient.release(); + if (firstReportPromise) await firstReportPromise.catch(() => undefined); + } + }, 20_000); +}); + +describe('buildSecurityAgentAuditReportFromRows', () => { + it('builds an empty report when no reportable activity exists', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'organization', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Acme', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [], + }); + + expect(report.summary).toEqual({ + findingCount: 0, + activityCount: 0, + bySeverity: { + critical: 0, + high: 0, + medium: 0, + low: 0, + }, + byAction: {}, + }); + expect(report.findings).toEqual([]); + }); + + it('groups events deterministically, masks internal actors, and labels legacy rows', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'organization', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Acme', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [ + row({ + id: '00000000-0000-4000-8000-000000000003', + action: SecurityAuditLogAction.FindingDismissed, + actor_id: 'admin-user', + actor_email: 'ops@kilocode.ai', + actor_name: 'Ops User', + actor_type: SecurityAuditLogActorType.KiloAdmin, + after_state: { status: 'ignored', token: 'secret-token' }, + metadata: { reason_code: 'not_used', actor_email: 'ops@kilocode.ai' }, + occurred_at: '2026-06-02T10:00:00.000Z', + effective_at: '2026-06-02T10:00:00.000Z', + finding_snapshot: { + finding_id: '11111111-1111-4111-8111-111111111111', + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/repo', + title: 'Prototype Pollution in lodash', + severity: 'high', + status: 'ignored', + fixed_at: '2026-06-07T08:00:00.000Z', + sla_due_at: '2026-06-08T08:00:00.000Z', + }, + }), + row({ + id: '00000000-0000-4000-8000-000000000002', + action: SecurityAuditLogAction.FindingCreated, + occurred_at: '2026-06-01T10:00:00.000Z', + effective_at: '2026-06-01T10:00:00.000Z', + }), + row({ + id: '00000000-0000-4000-8000-000000000004', + action: SecurityAuditLogAction.FindingDismissed, + finding_id: null, + resource_id: '22222222-2222-4222-8222-222222222222', + occurred_at: null, + effective_at: '2026-06-03T10:00:00.000Z', + finding_snapshot: null, + }), + ], + }); + + expect(report.summary).toMatchObject({ + findingCount: 2, + activityCount: 3, + }); + expect(report.findings[0].findingId).toBe('11111111-1111-4111-8111-111111111111'); + expect(report.findings[0].events.map(event => event.id)).toEqual([ + '00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000003', + ]); + expect(report.findings[0].events[1].actor).toEqual({ + type: 'user', + id: '00000000-0000-0000-0000-000000000000', + displayName: 'Kilo Admin', + masked: true, + }); + expect(report.findings[0].events[1].afterState).toEqual({ status: 'ignored' }); + expect(report.findings[0].events[1].metadata).toEqual({ reason_code: 'not_used' }); + expect(report.findings[0].sla.status).toBe('terminal_met'); + expect(report.findings[1]).toMatchObject({ + findingId: '22222222-2222-4222-8222-222222222222', + title: 'Legacy Security Finding', + hasLegacySupplementalActivity: true, + }); + expect(report.hasLegacySupplementalActivity).toBe(true); + }); + + it('masks actors from persisted classification without inferring from email', () => { + const rows = [ + row({ + id: '00000000-0000-4000-8000-000000000011', + actor_id: 'admin-user', + actor_email: 'operator@example.com', + actor_name: 'Internal Operator', + actor_type: SecurityAuditLogActorType.KiloAdmin, + }), + row({ + id: '00000000-0000-4000-8000-000000000012', + actor_id: 'customer-user', + actor_email: 'customer@kilocode.ai', + actor_name: 'Customer User', + actor_type: SecurityAuditLogActorType.CustomerUser, + }), + row({ + id: '00000000-0000-4000-8000-000000000013', + actor_id: 'legacy-user', + actor_email: 'legacy@example.com', + actor_name: 'Legacy User', + actor_type: null, + }), + ]; + const baseParams = { + owner: { + type: 'organization' as const, + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Acme', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + rows, + }; + + const customerReport = buildSecurityAgentAuditReportFromRows({ + ...baseParams, + isRequestingUserKiloAdmin: false, + }); + expect(customerReport.findings[0].events.map(event => event.actor)).toEqual([ + { + type: 'user', + id: '00000000-0000-0000-0000-000000000000', + displayName: 'Kilo Admin', + masked: true, + }, + { + type: 'user', + id: 'customer-user', + displayName: 'Customer User', + masked: false, + }, + { + type: 'user', + id: '00000000-0000-0000-0000-000000000000', + displayName: 'Masked user', + masked: true, + }, + ]); + + const adminReport = buildSecurityAgentAuditReportFromRows({ + ...baseParams, + isRequestingUserKiloAdmin: true, + }); + expect(adminReport.findings[0].events[0].actor).toEqual({ + type: 'user', + id: 'admin-user', + displayName: 'Internal Operator', + masked: false, + }); + expect(adminReport.findings[0].events[2].actor).toEqual({ + type: 'user', + id: 'legacy-user', + displayName: 'Legacy User', + masked: false, + }); + }); + + it('uses earlier recorded timeline evidence when a later legacy snapshot is sparse', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'user', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Ada', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [ + row({}), + row({ + id: '00000000-0000-4000-8000-000000000002', + action: SecurityAuditLogAction.RemediationPrOpened, + occurred_at: '2026-06-02T10:00:00.000Z', + effective_at: '2026-06-02T10:00:00.000Z', + finding_snapshot: { + finding_id: '11111111-1111-4111-8111-111111111111', + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/repo', + title: 'Prototype Pollution in lodash', + severity: 'high', + status: 'open', + }, + }), + ], + }); + + expect(report.findings[0].firstDetectedAt).toBe('2026-06-01T08:00:00.000Z'); + expect(report.findings[0].sla).toEqual({ + status: 'open_past_deadline', + deadline: '2026-06-08T08:00:00.000Z', + terminalAt: null, + }); + }); + + it('does not reuse an earlier SLA deadline after a later snapshot records no deadline', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'user', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Ada', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [ + row({}), + row({ + id: '00000000-0000-4000-8000-000000000002', + action: SecurityAuditLogAction.RemediationPrOpened, + occurred_at: '2026-06-02T10:00:00.000Z', + effective_at: '2026-06-02T10:00:00.000Z', + finding_snapshot: { + finding_id: '11111111-1111-4111-8111-111111111111', + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/repo', + title: 'Prototype Pollution in lodash', + severity: 'high', + status: 'open', + first_detected_at: '2026-06-01T08:00:00.000Z', + fixed_at: null, + sla_due_at: null, + }, + }), + ], + }); + + expect(report.findings[0].sla).toEqual({ + status: 'unknown', + deadline: null, + reason: 'missing_recorded_deadline', + }); + }); + + it('publishes structured extraction status without raw analysis content', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'user', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Ada', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [ + row({ + action: SecurityAuditLogAction.FindingAnalysisCompleted, + after_state: { + analysis_status: 'completed', + structured_extraction_status: 'failed', + suggested_action: 'manual_review', + raw_markdown: '# Sensitive raw analysis', + }, + metadata: { + model_slug: 'analysis/model', + }, + }), + ], + }); + + expect(report.findings[0].events[0]).toMatchObject({ + afterState: { + analysis_status: 'completed', + structured_extraction_status: 'failed', + suggested_action: 'manual_review', + }, + metadata: null, + }); + expect(report.findings[0].events[0].afterState).not.toHaveProperty('raw_markdown'); + }); + + it('publishes user-facing evidence while omitting internal remediation identifiers', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'user', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Ada', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [ + row({ + action: SecurityAuditLogAction.RemediationQueued, + actor_id: '4d857fd4-70b3-48a2-9130-45873d3051c4', + actor_type: SecurityAuditLogActorType.CustomerUser, + after_state: { + remediation_status: 'queued', + attempt_number: 1, + remediation_id: '3ade7a41-97de-4089-a331-2a6f3e5ad448', + }, + metadata: { + origin: 'manual', + attempt_id: '7b04b2bc-07c2-4252-bf9f-4dffe03cc7cb', + branch_name: 'security-remediation/internal-branch', + remediation_model_slug: 'kilo-auto/balanced', + }, + }), + ], + }); + + expect(report.findings[0].events[0]).toMatchObject({ + actor: { + type: 'user', + displayName: 'Kilo user', + }, + afterState: { + remediation_status: 'queued', + attempt_number: 1, + }, + metadata: { + origin: 'manual', + }, + }); + expect(report.findings[0].events[0].afterState).not.toHaveProperty('remediation_id'); + expect(report.findings[0].events[0].metadata).not.toHaveProperty('attempt_id'); + expect(report.findings[0].events[0].metadata).not.toHaveProperty('branch_name'); + expect(report.findings[0].events[0].metadata).not.toHaveProperty('remediation_model_slug'); + }); + + it('builds a max-event report under the serialized byte budget without truncation', () => { + const rows = Array.from({ length: SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS }, (_, index) => { + const occurredAt = new Date(Date.UTC(2026, 5, 1, 0, 0, index)).toISOString(); + return row({ + id: `00000000-0000-4000-8000-${String(index).padStart(12, '0')}`, + action: + index % 2 === 0 + ? SecurityAuditLogAction.FindingCreated + : SecurityAuditLogAction.FindingStatusChange, + before_state: index % 2 === 0 ? null : { status: 'open' }, + after_state: index % 2 === 0 ? { status: 'open' } : { status: 'fixed' }, + metadata: { reason_code: 'load_test' }, + occurred_at: occurredAt, + effective_at: occurredAt, + }); + }); + + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'organization', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Acme', + }, + period, + generatedAt: '2026-06-12T15:00:00.000Z', + dataThrough: '2026-06-12T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows, + }); + + expect(report.summary.activityCount).toBe(SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS); + expect(report.findings[0].events).toHaveLength(SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS); + expect(securityAgentAuditReportSerializedByteLength(report)).toBeLessThanOrEqual( + SECURITY_AGENT_AUDIT_REPORT_MAX_SERIALIZED_BYTES + ); + expect(() => assertSecurityAgentAuditReportSerializedByteBudget(report)).not.toThrow(); + }); + + it('does not classify ignored or superseded findings as open SLA states', () => { + const report = buildSecurityAgentAuditReportFromRows({ + owner: { + type: 'organization', + id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + displayName: 'Acme', + }, + period, + generatedAt: '2026-06-02T15:00:00.000Z', + dataThrough: '2026-06-02T15:00:00.000Z', + isRequestingUserKiloAdmin: false, + rows: [ + row({ + action: SecurityAuditLogAction.FindingSuperseded, + occurred_at: '2026-06-02T10:00:00.000Z', + effective_at: '2026-06-02T10:00:00.000Z', + finding_snapshot: { + finding_id: '11111111-1111-4111-8111-111111111111', + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/repo', + title: 'Prototype Pollution in lodash', + severity: 'high', + status: 'ignored', + sla_due_at: '2026-06-08T08:00:00.000Z', + canonical_finding_id: '22222222-2222-4222-8222-222222222222', + }, + }), + ], + }); + + expect(report.findings[0].sla).toEqual({ + status: 'unknown', + deadline: '2026-06-08T08:00:00.000Z', + reason: 'ignored_or_superseded_without_terminal_time', + }); + }); +}); + +describe('securityAgentAuditReportEventCountBucket', () => { + it('returns stable non-PII telemetry buckets', () => { + expect(securityAgentAuditReportEventCountBucket(null)).toBe('unknown'); + expect(securityAgentAuditReportEventCountBucket(0)).toBe('0'); + expect(securityAgentAuditReportEventCountBucket(99)).toBe('1-99'); + expect(securityAgentAuditReportEventCountBucket(999)).toBe('100-999'); + expect(securityAgentAuditReportEventCountBucket(4_999)).toBe('1000-4999'); + expect(securityAgentAuditReportEventCountBucket(10_000)).toBe('5000-10000'); + expect(securityAgentAuditReportEventCountBucket(10_001)).toBe('over-budget'); + }); +}); + +describe('withSecurityAgentAuditReportTimeout', () => { + it('fails with report query error and stage when budget expires', async () => { + await expect( + withSecurityAgentAuditReportTimeout(new Promise(() => {}), 1, 'scan') + ).rejects.toMatchObject({ + name: 'SecurityAgentAuditReportQueryError', + message: 'Report query did not finish', + stage: 'scan', + }); + }); + + it('returns resolved value before budget expires', async () => { + await expect( + withSecurityAgentAuditReportTimeout(Promise.resolve('ok'), 50, 'scan') + ).resolves.toBe('ok'); + }); + + it('preserves exported error type for timeout callers', () => { + expect(new SecurityAgentAuditReportQueryError('x', 'request').stage).toBe('request'); + }); +}); diff --git a/apps/web/src/lib/security-agent/db/security-audit-report.ts b/apps/web/src/lib/security-agent/db/security-audit-report.ts new file mode 100644 index 0000000000..6457472d4d --- /dev/null +++ b/apps/web/src/lib/security-agent/db/security-audit-report.ts @@ -0,0 +1,994 @@ +import 'server-only'; +import { captureException, startSpan } from '@sentry/nextjs'; +import { security_audit_log } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityAuditLogActorType, + SecuritySeverity, +} from '@kilocode/db/schema-types'; +import { REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS } from '@kilocode/worker-utils/security-finding-audit'; +import { and, asc, count, eq, gt, gte, inArray, isNotNull, lt, lte, or, sql } from 'drizzle-orm'; +import * as z from 'zod'; +import { db, type DrizzleTransaction } from '@/lib/drizzle'; + +export const SECURITY_AGENT_AUDIT_REPORT_VERSION = 1; +export const SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE = 1000; +export const SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS = 10_000; +export const SECURITY_AGENT_AUDIT_REPORT_MAX_SERIALIZED_BYTES = 8 * 1024 * 1024; +export const SECURITY_AGENT_AUDIT_REPORT_REQUEST_TIMEOUT_MS = 25_000; +export const SECURITY_AGENT_AUDIT_REPORT_QUERY_TIMEOUT_MS = 8_000; + +const DateOnlySchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/); + +export const SecurityAgentAuditReportInputSchema = z.object({ + startDate: DateOnlySchema.optional(), + endDate: DateOnlySchema.optional(), +}); + +const ReliableCoverageStartSchema = z.string().datetime({ offset: true }); + +export function resolveSecurityAgentAuditReliableCoverageStart( + value = process.env.SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START +): string { + if (!value) { + throw new Error('SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START is required'); + } + + const parsed = ReliableCoverageStartSchema.safeParse(value); + if (!parsed.success) { + throw new Error('SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START must be an ISO timestamp'); + } + + return new Date(parsed.data).toISOString(); +} + +export type SecurityAgentAuditReportInput = z.infer; + +export type SecurityAgentAuditReportOwner = + | { type: 'user'; id: string; displayName: string } + | { type: 'organization'; id: string; displayName: string }; + +export type SecurityAgentAuditReportActor = + | { + type: 'user'; + id: string | null; + displayName: string; + masked: boolean; + } + | { type: 'system'; displayName: string; masked: false }; + +export type SecurityAgentAuditReportEvent = { + id: string; + action: SecurityAuditLogAction; + label: string; + occurredAt: string; + sourceOccurredAt: string | null; + recordedAt: string; + actor: SecurityAgentAuditReportActor; + beforeState: Record | null; + afterState: Record | null; + metadata: Record | null; + legacySupplemental: boolean; +}; + +export type SecurityAgentAuditSlaEvidence = + | { status: 'unknown'; deadline: string | null; reason: string } + | { + status: 'terminal_met' | 'terminal_missed' | 'open_within_deadline' | 'open_past_deadline'; + deadline: string; + terminalAt: string | null; + }; + +export type SecurityFindingAuditSection = { + findingId: string; + source: string | null; + sourceId: string | null; + repository: string | null; + title: string; + severity: SecuritySeverity | 'unknown'; + status: string | null; + packageName: string | null; + packageEcosystem: string | null; + manifestPath: string | null; + patchedVersion: string | null; + ghsaId: string | null; + cveId: string | null; + cweIds: string[]; + cvssScore: string | number | null; + dependabotUrl: string | null; + firstDetectedAt: string | null; + canonicalFindingId: string | null; + deleted: boolean; + sla: SecurityAgentAuditSlaEvidence; + events: SecurityAgentAuditReportEvent[]; + hasLegacySupplementalActivity: boolean; +}; + +export type SecurityAgentAuditReport = { + reportVersion: typeof SECURITY_AGENT_AUDIT_REPORT_VERSION; + owner: SecurityAgentAuditReportOwner; + period: { + start: string; + endExclusive: string; + displayEnd: string; + timeZone: 'UTC'; + }; + generatedAt: string; + dataThrough: string; + reliableCoverageStart: string; + evidenceBasis: 'recorded_by_kilo'; + hasLegacySupplementalActivity: boolean; + summary: { + findingCount: number; + activityCount: number; + bySeverity: Record; + byAction: Record; + }; + findings: SecurityFindingAuditSection[]; +}; + +type NormalizedAuditReportPeriod = SecurityAgentAuditReport['period']; + +type AuditReportRow = { + id: string; + action: SecurityAuditLogAction; + actor_id: string | null; + actor_email: string | null; + actor_name: string | null; + actor_type: SecurityAuditLogActorType | null; + before_state: Record | null; + after_state: Record | null; + metadata: Record | null; + created_at: string; + finding_id: string | null; + resource_type: string; + resource_id: string; + occurred_at: string | null; + source_occurred_at: string | null; + finding_snapshot: Record | null; + effective_at: string; +}; + +type AuditReportCursor = { + effectiveAt: string; + id: string; +}; + +type SecurityAgentAuditReportFailureStage = + | 'data_through' + | 'count' + | 'budget' + | 'scan' + | 'request'; + +const ACTION_LABELS: Partial> = { + [SecurityAuditLogAction.FindingCreated]: 'Imported', + [SecurityAuditLogAction.FindingSeverityChanged]: 'Severity changed', + [SecurityAuditLogAction.FindingStatusChange]: 'Status changed', + [SecurityAuditLogAction.FindingDismissed]: 'Dismissed', + [SecurityAuditLogAction.FindingAutoDismissed]: 'Auto dismissed', + [SecurityAuditLogAction.FindingSuperseded]: 'Superseded', + [SecurityAuditLogAction.FindingAnalysisCompleted]: 'Analysis completed', + [SecurityAuditLogAction.FindingAnalysisFailed]: 'Analysis failed', + [SecurityAuditLogAction.RemediationQueued]: 'Remediation requested', + [SecurityAuditLogAction.RemediationPrOpened]: 'PR opened', + [SecurityAuditLogAction.RemediationFailed]: 'Remediation failed', + [SecurityAuditLogAction.RemediationBlocked]: 'Remediation blocked', + [SecurityAuditLogAction.RemediationNoChangesNeeded]: 'No changes needed', + [SecurityAuditLogAction.RemediationCancelled]: 'Cancelled', + [SecurityAuditLogAction.FindingDeleted]: 'Deleted', +}; + +const EMPTY_SEVERITY_COUNTS = { + [SecuritySeverity.CRITICAL]: 0, + [SecuritySeverity.HIGH]: 0, + [SecuritySeverity.MEDIUM]: 0, + [SecuritySeverity.LOW]: 0, +} satisfies Record; + +type AuditEventEvidenceFields = { + beforeState: readonly string[]; + afterState: readonly string[]; + metadata: readonly string[]; +}; + +const ACTION_EVIDENCE_FIELDS: Partial> = { + [SecurityAuditLogAction.FindingCreated]: { + beforeState: [], + afterState: ['status', 'severity'], + metadata: ['source_alert_number'], + }, + [SecurityAuditLogAction.FindingSeverityChanged]: { + beforeState: ['severity'], + afterState: ['severity'], + metadata: ['source_alert_number'], + }, + [SecurityAuditLogAction.FindingStatusChange]: { + beforeState: ['status'], + afterState: ['status', 'fixed_at', 'reason_code'], + metadata: ['source_alert_number', 'source_state'], + }, + [SecurityAuditLogAction.FindingDismissed]: { + beforeState: ['status'], + afterState: ['status', 'reason_code'], + metadata: ['reason_code', 'source_alert_number'], + }, + [SecurityAuditLogAction.FindingAutoDismissed]: { + beforeState: ['status'], + afterState: ['status', 'reason_code'], + metadata: ['reason_code', 'source_alert_number'], + }, + [SecurityAuditLogAction.FindingSuperseded]: { + beforeState: ['status'], + afterState: ['status', 'reason_code'], + metadata: ['source_alert_number'], + }, + [SecurityAuditLogAction.FindingAnalysisCompleted]: { + beforeState: ['analysis_status'], + afterState: [ + 'analysis_status', + 'structured_extraction_status', + 'suggested_action', + 'confidence', + 'is_exploitable', + ], + metadata: [], + }, + [SecurityAuditLogAction.FindingAnalysisFailed]: { + beforeState: ['analysis_status'], + afterState: ['analysis_status'], + metadata: ['failure_code'], + }, + [SecurityAuditLogAction.RemediationQueued]: { + beforeState: [], + afterState: ['remediation_status', 'attempt_number'], + metadata: ['origin'], + }, + [SecurityAuditLogAction.RemediationPrOpened]: { + beforeState: ['remediation_status'], + afterState: ['remediation_status', 'pr_number', 'pr_draft'], + metadata: ['origin', 'pr_url', 'validation_count'], + }, + [SecurityAuditLogAction.RemediationFailed]: { + beforeState: ['remediation_status'], + afterState: ['remediation_status', 'failure_code'], + metadata: ['origin', 'failure_code'], + }, + [SecurityAuditLogAction.RemediationBlocked]: { + beforeState: ['remediation_status'], + afterState: ['remediation_status', 'blocked_reason_code'], + metadata: ['origin', 'blocked_reason_code'], + }, + [SecurityAuditLogAction.RemediationNoChangesNeeded]: { + beforeState: ['remediation_status'], + afterState: ['remediation_status'], + metadata: ['origin'], + }, + [SecurityAuditLogAction.RemediationCancelled]: { + beforeState: ['remediation_status'], + afterState: ['remediation_status'], + metadata: ['origin'], + }, + [SecurityAuditLogAction.FindingDeleted]: { + beforeState: ['status'], + afterState: ['deleted'], + metadata: [], + }, +}; + +const EMPTY_EVIDENCE_FIELDS = { + beforeState: [], + afterState: [], + metadata: [], +} satisfies AuditEventEvidenceFields; + +const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +export class SecurityAgentAuditReportQueryError extends Error { + constructor( + message = 'Report query did not finish', + readonly stage: SecurityAgentAuditReportFailureStage = 'request' + ) { + super(message); + this.name = 'SecurityAgentAuditReportQueryError'; + } +} + +export function defaultSecurityAgentAuditReportInput( + now = new Date() +): Required { + const endDate = formatUtcDate(startOfUtcDay(now)); + const startDate = formatUtcDate(addUtcDays(parseUtcDateOnly(endDate), -89)); + return { startDate, endDate }; +} + +export function normalizeSecurityAgentAuditReportPeriod( + input: SecurityAgentAuditReportInput, + now = new Date() +): NormalizedAuditReportPeriod { + const defaults = defaultSecurityAgentAuditReportInput(now); + const startDate = input.startDate ?? defaults.startDate; + const endDate = input.endDate ?? defaults.endDate; + + DateOnlySchema.parse(startDate); + DateOnlySchema.parse(endDate); + + const start = parseUtcDateOnly(startDate); + const endInclusive = parseUtcDateOnly(endDate); + const today = startOfUtcDay(now); + + if (start.getTime() > endInclusive.getTime()) { + throw new Error('Report start date must be before or equal to end date'); + } + if (endInclusive.getTime() > today.getTime()) { + throw new Error('Report end date cannot be in the future'); + } + + const inclusiveDays = utcDayDiff(start, endInclusive) + 1; + if (inclusiveDays > 90) { + throw new Error('Report range cannot exceed 90 inclusive UTC calendar days'); + } + + const endExclusive = addUtcDays(endInclusive, 1); + return { + start: start.toISOString(), + endExclusive: endExclusive.toISOString(), + displayEnd: endDate, + timeZone: 'UTC', + }; +} + +export async function getSecurityAgentAuditReport(params: { + owner: SecurityAgentAuditReportOwner; + input: SecurityAgentAuditReportInput; + isRequestingUserKiloAdmin: boolean; +}): Promise { + const parsedInput = SecurityAgentAuditReportInputSchema.parse(params.input); + const period = normalizeSecurityAgentAuditReportPeriod(parsedInput); + const generatedAt = new Date().toISOString(); + const startedAt = Date.now(); + let eventCount: number | null = null; + + return await startSpan( + { + name: 'security-agent.audit-report', + op: 'security_agent.report', + }, + async span => { + span.setAttribute('security_agent.audit_report.owner_type', params.owner.type); + span.setAttribute( + 'security_agent.audit_report.report_version', + SECURITY_AGENT_AUDIT_REPORT_VERSION + ); + + try { + const report = await withSecurityAgentAuditReportTimeout( + assembleSecurityAgentAuditReport({ + ...params, + period, + generatedAt, + onEventCount: count => { + eventCount = count; + }, + }), + SECURITY_AGENT_AUDIT_REPORT_REQUEST_TIMEOUT_MS, + 'request' + ); + + span.setAttribute('security_agent.audit_report.duration_ms', Date.now() - startedAt); + span.setAttribute( + 'security_agent.audit_report.event_count_bucket', + securityAgentAuditReportEventCountBucket(report.summary.activityCount) + ); + span.setAttribute('security_agent.audit_report.failure_stage', 'none'); + return report; + } catch (error) { + const failureStage = + error instanceof SecurityAgentAuditReportQueryError ? error.stage : 'request'; + const eventCountBucket = securityAgentAuditReportEventCountBucket(eventCount); + span.setAttribute('security_agent.audit_report.duration_ms', Date.now() - startedAt); + span.setAttribute('security_agent.audit_report.event_count_bucket', eventCountBucket); + span.setAttribute('security_agent.audit_report.failure_stage', failureStage); + captureException(error, { + tags: { + operation: 'security_agent.audit_report', + owner_type: params.owner.type, + report_version: String(SECURITY_AGENT_AUDIT_REPORT_VERSION), + failure_stage: failureStage, + event_count_bucket: eventCountBucket, + }, + }); + throw error; + } + } + ); +} + +async function assembleSecurityAgentAuditReport(params: { + owner: SecurityAgentAuditReportOwner; + period: NormalizedAuditReportPeriod; + generatedAt: string; + isRequestingUserKiloAdmin: boolean; + onEventCount: (eventCount: number) => void; +}): Promise { + const { dataThrough, rows } = await db.transaction( + async tx => { + const dataThrough = await getDatabaseNow(tx); + const eventCount = await countReportEvents(tx, params.owner, params.period, dataThrough); + params.onEventCount(eventCount); + + if (eventCount > SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS) { + throw new SecurityAgentAuditReportQueryError( + 'Report event count exceeds v1 tested budget', + 'budget' + ); + } + + const rows = await scanReportRows(tx, params.owner, params.period, dataThrough); + if (rows.length !== eventCount) { + throw new SecurityAgentAuditReportQueryError( + 'Report scan row count does not match counted events', + 'scan' + ); + } + + return { dataThrough, rows }; + }, + { isolationLevel: 'repeatable read', accessMode: 'read only' } + ); + + const report = buildSecurityAgentAuditReportFromRows({ + owner: params.owner, + period: params.period, + generatedAt: params.generatedAt, + dataThrough, + rows, + isRequestingUserKiloAdmin: params.isRequestingUserKiloAdmin, + }); + assertSecurityAgentAuditReportSerializedByteBudget(report); + return report; +} + +export function buildSecurityAgentAuditReportFromRows(params: { + owner: SecurityAgentAuditReportOwner; + period: NormalizedAuditReportPeriod; + generatedAt: string; + dataThrough: string; + rows: AuditReportRow[]; + isRequestingUserKiloAdmin: boolean; +}): SecurityAgentAuditReport { + const groups = new Map(); + for (const row of params.rows) { + const findingId = getRowFindingId(row); + if (!findingId) continue; + const existing = groups.get(findingId) ?? []; + existing.push(row); + groups.set(findingId, existing); + } + + const findings = Array.from(groups.entries()).map(([findingId, rows]) => + buildFindingSection(findingId, rows, params) + ); + + findings.sort((left, right) => { + const leftFirst = left.events[0]?.occurredAt ?? ''; + const rightFirst = right.events[0]?.occurredAt ?? ''; + return ( + leftFirst.localeCompare(rightFirst) || + (left.repository ?? '').localeCompare(right.repository ?? '') || + left.title.localeCompare(right.title) || + left.findingId.localeCompare(right.findingId) + ); + }); + + const bySeverity = { ...EMPTY_SEVERITY_COUNTS }; + for (const finding of findings) { + if (finding.severity !== 'unknown') bySeverity[finding.severity] += 1; + } + + const byAction: Record = {}; + for (const row of params.rows) { + byAction[row.action] = (byAction[row.action] ?? 0) + 1; + } + + return { + reportVersion: SECURITY_AGENT_AUDIT_REPORT_VERSION, + owner: params.owner, + period: params.period, + generatedAt: params.generatedAt, + dataThrough: params.dataThrough, + reliableCoverageStart: resolveSecurityAgentAuditReliableCoverageStart(), + evidenceBasis: 'recorded_by_kilo', + hasLegacySupplementalActivity: findings.some(finding => finding.hasLegacySupplementalActivity), + summary: { + findingCount: findings.length, + activityCount: params.rows.length, + bySeverity, + byAction, + }, + findings, + }; +} + +async function getDatabaseNow(tx: DrizzleTransaction): Promise { + const { rows } = await withSecurityAgentAuditReportTimeout( + tx.execute<{ data_through: string }>(sql`SELECT now() AS data_through`), + SECURITY_AGENT_AUDIT_REPORT_QUERY_TIMEOUT_MS, + 'data_through' + ); + return new Date(rows[0].data_through).toISOString(); +} + +async function countReportEvents( + tx: DrizzleTransaction, + owner: SecurityAgentAuditReportOwner, + period: NormalizedAuditReportPeriod, + dataThrough: string +): Promise { + try { + const [row] = await withSecurityAgentAuditReportTimeout( + tx + .select({ eventCount: count(security_audit_log.id) }) + .from(security_audit_log) + .where(and(...baseReportConditions(owner, period, dataThrough))), + SECURITY_AGENT_AUDIT_REPORT_QUERY_TIMEOUT_MS, + 'count' + ); + return row.eventCount; + } catch (error) { + if (error instanceof SecurityAgentAuditReportQueryError) throw error; + throw new SecurityAgentAuditReportQueryError( + error instanceof Error ? error.message : 'Report query did not finish', + 'count' + ); + } +} + +async function scanReportRows( + tx: DrizzleTransaction, + owner: SecurityAgentAuditReportOwner, + period: NormalizedAuditReportPeriod, + dataThrough: string +): Promise { + const rows: AuditReportRow[] = []; + let cursor: AuditReportCursor | null = null; + + while (true) { + const page = await scanReportPage(tx, owner, period, dataThrough, cursor); + rows.push(...page); + + if (page.length < SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE) return rows; + const last = page[page.length - 1]; + cursor = { effectiveAt: last.effective_at, id: last.id }; + } +} + +async function scanReportPage( + tx: DrizzleTransaction, + owner: SecurityAgentAuditReportOwner, + period: NormalizedAuditReportPeriod, + dataThrough: string, + cursor: AuditReportCursor | null +): Promise { + const effectiveAt = reportEffectiveAtSql(); + const whereConditions = baseReportConditions(owner, period, dataThrough); + if (cursor) { + const cursorCondition = or( + gt(effectiveAt, cursor.effectiveAt), + and(eq(effectiveAt, cursor.effectiveAt), gt(security_audit_log.id, cursor.id)) + ); + if (cursorCondition) whereConditions.push(cursorCondition); + } + + try { + return await withSecurityAgentAuditReportTimeout( + tx + .select({ + id: security_audit_log.id, + action: security_audit_log.action, + actor_id: security_audit_log.actor_id, + actor_email: security_audit_log.actor_email, + actor_name: security_audit_log.actor_name, + actor_type: security_audit_log.actor_type, + before_state: security_audit_log.before_state, + after_state: security_audit_log.after_state, + metadata: security_audit_log.metadata, + created_at: security_audit_log.created_at, + finding_id: security_audit_log.finding_id, + resource_type: security_audit_log.resource_type, + resource_id: security_audit_log.resource_id, + occurred_at: security_audit_log.occurred_at, + source_occurred_at: security_audit_log.source_occurred_at, + finding_snapshot: security_audit_log.finding_snapshot, + effective_at: effectiveAt, + }) + .from(security_audit_log) + .where(and(...whereConditions)) + .orderBy(asc(effectiveAt), asc(security_audit_log.id)) + .limit(SECURITY_AGENT_AUDIT_REPORT_PAGE_SIZE), + SECURITY_AGENT_AUDIT_REPORT_QUERY_TIMEOUT_MS, + 'scan' + ); + } catch (error) { + if (error instanceof SecurityAgentAuditReportQueryError) throw error; + throw new SecurityAgentAuditReportQueryError( + error instanceof Error ? error.message : 'Report query did not finish', + 'scan' + ); + } +} + +function baseReportConditions( + owner: SecurityAgentAuditReportOwner, + period: NormalizedAuditReportPeriod, + dataThrough: string +) { + const effectiveAt = reportEffectiveAtSql(); + const findingIdentityCondition = or( + isNotNull(security_audit_log.finding_id), + and( + eq(security_audit_log.resource_type, 'security_finding'), + sql`${security_audit_log.resource_id}::text ~ ${UUID_PATTERN.source}::text` + ) + ); + if (!findingIdentityCondition) { + throw new SecurityAgentAuditReportQueryError('Report finding identity query failed', 'scan'); + } + + return [ + owner.type === 'user' + ? eq(security_audit_log.owned_by_user_id, owner.id) + : eq(security_audit_log.owned_by_organization_id, owner.id), + inArray(security_audit_log.action, [...REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS]), + gte(effectiveAt, period.start), + lt(effectiveAt, period.endExclusive), + lte(security_audit_log.created_at, dataThrough), + findingIdentityCondition, + ]; +} + +function reportEffectiveAtSql() { + return sql`COALESCE(${security_audit_log.occurred_at}, ${security_audit_log.created_at})`; +} + +export async function withSecurityAgentAuditReportTimeout( + promise: Promise, + timeoutMs: number, + stage: SecurityAgentAuditReportFailureStage +): Promise { + let timeoutId: ReturnType | null = null; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new SecurityAgentAuditReportQueryError('Report query did not finish', stage)); + }, timeoutMs); + }); + + try { + return await Promise.race([promise, timeoutPromise]); + } finally { + if (timeoutId !== null) clearTimeout(timeoutId); + } +} + +export function securityAgentAuditReportEventCountBucket(eventCount: number | null): string { + if (eventCount === null) return 'unknown'; + if (eventCount === 0) return '0'; + if (eventCount < 100) return '1-99'; + if (eventCount < 1_000) return '100-999'; + if (eventCount < 5_000) return '1000-4999'; + if (eventCount <= SECURITY_AGENT_AUDIT_REPORT_MAX_EVENTS) return '5000-10000'; + return 'over-budget'; +} + +export function securityAgentAuditReportSerializedByteLength( + report: SecurityAgentAuditReport +): number { + return Buffer.byteLength(JSON.stringify(report), 'utf8'); +} + +export function assertSecurityAgentAuditReportSerializedByteBudget( + report: SecurityAgentAuditReport +): void { + if ( + securityAgentAuditReportSerializedByteLength(report) > + SECURITY_AGENT_AUDIT_REPORT_MAX_SERIALIZED_BYTES + ) { + throw new SecurityAgentAuditReportQueryError( + 'Report serialized size exceeds v1 tested budget', + 'budget' + ); + } +} + +function buildFindingSection( + findingId: string, + rows: AuditReportRow[], + reportParams: { + dataThrough: string; + isRequestingUserKiloAdmin: boolean; + } +): SecurityFindingAuditSection { + rows.sort( + (left, right) => + left.effective_at.localeCompare(right.effective_at) || left.id.localeCompare(right.id) + ); + const latestSnapshot = latestFindingSnapshot(rows); + const evidenceSnapshot = withRecordedTimelineEvidence(rows, latestSnapshot); + const events = rows.map(row => buildReportEvent(row, reportParams.isRequestingUserKiloAdmin)); + const deleted = rows.some(row => row.action === SecurityAuditLogAction.FindingDeleted); + const legacySupplemental = rows.some(row => isLegacySupplementalRow(row)); + + return { + findingId, + source: stringFromSnapshot(latestSnapshot, 'source'), + sourceId: stringFromSnapshot(latestSnapshot, 'source_id'), + repository: stringFromSnapshot(latestSnapshot, 'repo_full_name'), + title: stringFromSnapshot(latestSnapshot, 'title') ?? 'Legacy Security Finding', + severity: severityFromSnapshot(latestSnapshot), + status: stringFromSnapshot(latestSnapshot, 'status'), + packageName: stringFromSnapshot(latestSnapshot, 'package_name'), + packageEcosystem: stringFromSnapshot(latestSnapshot, 'package_ecosystem'), + manifestPath: stringFromSnapshot(latestSnapshot, 'manifest_path'), + patchedVersion: stringFromSnapshot(latestSnapshot, 'patched_version'), + ghsaId: stringFromSnapshot(latestSnapshot, 'ghsa_id'), + cveId: stringFromSnapshot(latestSnapshot, 'cve_id'), + cweIds: stringArrayFromSnapshot(latestSnapshot, 'cwe_ids'), + cvssScore: cvssFromSnapshot(latestSnapshot), + dependabotUrl: safeUrl(stringFromSnapshot(latestSnapshot, 'dependabot_html_url')), + firstDetectedAt: stringFromSnapshot(evidenceSnapshot, 'first_detected_at'), + canonicalFindingId: stringFromSnapshot(latestSnapshot, 'canonical_finding_id'), + deleted, + sla: buildSlaEvidence(evidenceSnapshot, reportParams.dataThrough), + events, + hasLegacySupplementalActivity: legacySupplemental, + }; +} + +function buildReportEvent( + row: AuditReportRow, + isRequestingUserKiloAdmin: boolean +): SecurityAgentAuditReportEvent { + const evidenceFields = ACTION_EVIDENCE_FIELDS[row.action] ?? EMPTY_EVIDENCE_FIELDS; + return { + id: row.id, + action: row.action, + label: ACTION_LABELS[row.action] ?? row.action, + occurredAt: new Date(row.effective_at).toISOString(), + sourceOccurredAt: row.source_occurred_at + ? new Date(row.source_occurred_at).toISOString() + : null, + recordedAt: new Date(row.created_at).toISOString(), + actor: buildReportActor(row, isRequestingUserKiloAdmin), + beforeState: selectReportEvidence(row.before_state, evidenceFields.beforeState), + afterState: selectReportEvidence(row.after_state, evidenceFields.afterState), + metadata: selectReportEvidence(row.metadata, evidenceFields.metadata), + legacySupplemental: isLegacySupplementalRow(row), + }; +} + +function buildReportActor( + row: Pick, + isRequestingUserKiloAdmin: boolean +): SecurityAgentAuditReportActor { + if (row.actor_type === SecurityAuditLogActorType.System) { + return { type: 'system', displayName: 'Kilo system', masked: false }; + } + + const hasPersistedIdentity = Boolean(row.actor_id || row.actor_name); + if (row.actor_type === null && !hasPersistedIdentity) { + return { type: 'system', displayName: 'Kilo system', masked: false }; + } + + if ( + !isRequestingUserKiloAdmin && + (row.actor_type === SecurityAuditLogActorType.KiloAdmin || row.actor_type === null) + ) { + return { + type: 'user', + id: '00000000-0000-0000-0000-000000000000', + displayName: + row.actor_type === SecurityAuditLogActorType.KiloAdmin ? 'Kilo Admin' : 'Masked user', + masked: true, + }; + } + + return { + type: 'user', + id: row.actor_id, + displayName: row.actor_name ?? (row.actor_id ? 'Kilo user' : 'Unknown user'), + masked: false, + }; +} + +function latestFindingSnapshot(rows: AuditReportRow[]): Record | null { + for (let index = rows.length - 1; index >= 0; index -= 1) { + const snapshot = rows[index].finding_snapshot; + if (snapshot) return snapshot; + } + return null; +} + +function withRecordedTimelineEvidence( + rows: AuditReportRow[], + latestSnapshot: Record | null +): Record | null { + if (!latestSnapshot) return null; + + const evidenceSnapshot = { ...latestSnapshot }; + for (const key of ['first_detected_at', 'sla_due_at'] as const) { + if (Object.hasOwn(evidenceSnapshot, key)) continue; + + for (let index = rows.length - 1; index >= 0; index -= 1) { + const snapshot = rows[index].finding_snapshot; + if (!snapshot || !Object.hasOwn(snapshot, key)) continue; + evidenceSnapshot[key] = snapshot[key]; + break; + } + } + return evidenceSnapshot; +} + +function getRowFindingId(row: AuditReportRow): string | null { + if (row.finding_id) return row.finding_id; + if (row.resource_type === 'security_finding' && UUID_PATTERN.test(row.resource_id)) { + return row.resource_id; + } + return null; +} + +function isLegacySupplementalRow(row: AuditReportRow): boolean { + return !row.occurred_at || !row.finding_id; +} + +function buildSlaEvidence( + snapshot: Record | null, + dataThrough: string +): SecurityAgentAuditSlaEvidence { + const deadline = stringFromSnapshot(snapshot, 'sla_due_at'); + if (!deadline) return { status: 'unknown', deadline: null, reason: 'missing_recorded_deadline' }; + + const deadlineMs = Date.parse(deadline); + if (Number.isNaN(deadlineMs)) { + return { status: 'unknown', deadline, reason: 'invalid_recorded_deadline' }; + } + + const fixedAt = stringFromSnapshot(snapshot, 'fixed_at'); + if (fixedAt) { + const fixedAtMs = Date.parse(fixedAt); + if (Number.isNaN(fixedAtMs)) { + return { status: 'unknown', deadline, reason: 'invalid_terminal_timestamp' }; + } + return { + status: fixedAtMs <= deadlineMs ? 'terminal_met' : 'terminal_missed', + deadline, + terminalAt: fixedAt, + }; + } + + const status = stringFromSnapshot(snapshot, 'status'); + const canonicalFindingId = stringFromSnapshot(snapshot, 'canonical_finding_id'); + if (status === 'ignored' || canonicalFindingId) { + return { status: 'unknown', deadline, reason: 'ignored_or_superseded_without_terminal_time' }; + } + if (status === 'fixed') { + return { status: 'unknown', deadline, reason: 'missing_terminal_timestamp' }; + } + if (status !== 'open') { + return { status: 'unknown', deadline, reason: 'missing_open_status_evidence' }; + } + + const cutoffMs = Date.parse(dataThrough); + if (Number.isNaN(cutoffMs)) { + return { status: 'unknown', deadline, reason: 'invalid_report_cutoff' }; + } + + return { + status: cutoffMs <= deadlineMs ? 'open_within_deadline' : 'open_past_deadline', + deadline, + terminalAt: null, + }; +} + +function severityFromSnapshot( + snapshot: Record | null +): SecuritySeverity | 'unknown' { + const severity = stringFromSnapshot(snapshot, 'severity'); + if ( + severity === SecuritySeverity.CRITICAL || + severity === SecuritySeverity.HIGH || + severity === SecuritySeverity.MEDIUM || + severity === SecuritySeverity.LOW + ) { + return severity; + } + return 'unknown'; +} + +function stringFromSnapshot(snapshot: Record | null, key: string): string | null { + const value = snapshot?.[key]; + return typeof value === 'string' && value.length > 0 ? value : null; +} + +function stringArrayFromSnapshot(snapshot: Record | null, key: string): string[] { + const value = snapshot?.[key]; + if (!Array.isArray(value)) return []; + return value.filter((item): item is string => typeof item === 'string' && item.length > 0); +} + +function cvssFromSnapshot(snapshot: Record | null): string | number | null { + const value = snapshot?.cvss_score; + if (typeof value === 'string' || typeof value === 'number') return value; + return null; +} + +function safeUrl(value: string | null): string | null { + if (!value) return null; + try { + const url = new URL(value); + if (url.protocol !== 'https:' && url.protocol !== 'http:') return null; + return url.toString(); + } catch { + return null; + } +} + +const REDACTED_JSON_KEY_PATTERN = + /(^|_)(email|recipient|prompt|rawmarkdown|raw_markdown|transcript|assistant|provider_response|authorization|auth_header|cookie|token|secret|password|credential|headers|raw_error)(_|$)/i; + +function selectReportEvidence( + value: Record | null, + allowedFields: readonly string[] +): Record | null { + const sanitized = sanitizeJsonObject(value); + if (!sanitized) return null; + + const selected: Record = {}; + for (const field of allowedFields) { + if (Object.prototype.hasOwnProperty.call(sanitized, field)) selected[field] = sanitized[field]; + } + return Object.keys(selected).length > 0 ? selected : null; +} + +function sanitizeJsonObject(value: Record | null): Record | null { + if (!value) return null; + return sanitizeJson(value) as Record; +} + +function sanitizeJson(value: unknown): unknown { + if (Array.isArray(value)) return value.map(item => sanitizeJson(item)); + if (value && typeof value === 'object') { + const sanitized: Record = {}; + for (const [key, child] of Object.entries(value)) { + if (REDACTED_JSON_KEY_PATTERN.test(key)) continue; + sanitized[key] = sanitizeJson(child); + } + return sanitized; + } + return value; +} + +function parseUtcDateOnly(value: string): Date { + const date = new Date(`${value}T00:00:00.000Z`); + if (Number.isNaN(date.getTime()) || formatUtcDate(date) !== value) { + throw new Error('Report date must be a valid UTC calendar date'); + } + return date; +} + +function startOfUtcDay(value: Date): Date { + return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate())); +} + +function addUtcDays(value: Date, days: number): Date { + const next = new Date(value); + next.setUTCDate(next.getUTCDate() + days); + return next; +} + +function utcDayDiff(start: Date, end: Date): number { + return Math.floor((end.getTime() - start.getTime()) / 86_400_000); +} + +function formatUtcDate(value: Date): string { + return value.toISOString().slice(0, 10); +} diff --git a/apps/web/src/lib/security-agent/db/security-findings.test.ts b/apps/web/src/lib/security-agent/db/security-findings.test.ts index 21399d8a1d..eabb3f5e64 100644 --- a/apps/web/src/lib/security-agent/db/security-findings.test.ts +++ b/apps/web/src/lib/security-agent/db/security-findings.test.ts @@ -119,7 +119,14 @@ describe('upsertSecurityFinding', () => { expect(first.wasInserted).toBe(true); const second = await upsertSecurityFinding({ - ...makeFinding({ source_id: '10', status: 'fixed', severity: 'critical' }), + ...makeFinding({ + source_id: '10', + status: 'fixed', + severity: 'critical', + package_name: 'lodash-es', + package_ecosystem: 'npm-v2', + manifest_path: 'apps/web/package.json', + }), owner, repoFullName: repo, }); @@ -135,6 +142,9 @@ describe('upsertSecurityFinding', () => { expect(row.status).toBe('fixed'); expect(row.severity).toBe('critical'); + expect(row.package_name).toBe('lodash-es'); + expect(row.package_ecosystem).toBe('npm-v2'); + expect(row.manifest_path).toBe('apps/web/package.json'); }); it('returns the same row for concurrent first upserts on the same source key', async () => { diff --git a/apps/web/src/lib/security-agent/db/security-findings.ts b/apps/web/src/lib/security-agent/db/security-findings.ts index ba7b064acb..e4999afca5 100644 --- a/apps/web/src/lib/security-agent/db/security-findings.ts +++ b/apps/web/src/lib/security-agent/db/security-findings.ts @@ -4,6 +4,16 @@ import { security_findings, agent_configs } from '@kilocode/db/schema'; import { eq, and, desc, count, sql, max, or, type SQL } from 'drizzle-orm'; import { captureException } from '@sentry/nextjs'; import type { SecurityFinding, NewSecurityFinding } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditHumanActor, + type SecurityFindingAuditOwner, +} from '@kilocode/worker-utils/security-finding-audit'; import type { SecurityReviewOwner, SecurityFindingStatus, @@ -37,6 +47,16 @@ function ownerConflictTarget(owner: Owner): SQL { : sql`(${sql.identifier(security_findings.owned_by_user_id.name)}, ${sql.identifier(security_findings.repo_full_name.name)}, ${sql.identifier(security_findings.source.name)}, ${sql.identifier(security_findings.source_id.name)}) WHERE ${sql.identifier(security_findings.owned_by_user_id.name)} IS NOT NULL`; } +function toSecurityFindingAuditOwner(owner: Owner): SecurityFindingAuditOwner { + return owner.type === 'org' + ? { type: 'organization', organizationId: owner.id } + : { type: 'user', userId: owner.id }; +} + +function ownerAuditKeyPart(owner: Owner): string { + return owner.type === 'org' ? `organization:${owner.id}` : `user:${owner.id}`; +} + type CreateFindingParams = ParsedSecurityFinding & { owner: SecurityReviewOwner; platformIntegrationId?: string; @@ -190,8 +210,11 @@ export async function upsertSecurityFinding( ${sql.identifier(security_findings.severity.name)} = EXCLUDED.${sql.identifier(security_findings.severity.name)}, ${sql.identifier(security_findings.ghsa_id.name)} = EXCLUDED.${sql.identifier(security_findings.ghsa_id.name)}, ${sql.identifier(security_findings.cve_id.name)} = EXCLUDED.${sql.identifier(security_findings.cve_id.name)}, + ${sql.identifier(security_findings.package_name.name)} = EXCLUDED.${sql.identifier(security_findings.package_name.name)}, + ${sql.identifier(security_findings.package_ecosystem.name)} = EXCLUDED.${sql.identifier(security_findings.package_ecosystem.name)}, ${sql.identifier(security_findings.vulnerable_version_range.name)} = EXCLUDED.${sql.identifier(security_findings.vulnerable_version_range.name)}, ${sql.identifier(security_findings.patched_version.name)} = EXCLUDED.${sql.identifier(security_findings.patched_version.name)}, + ${sql.identifier(security_findings.manifest_path.name)} = EXCLUDED.${sql.identifier(security_findings.manifest_path.name)}, ${sql.identifier(security_findings.title.name)} = EXCLUDED.${sql.identifier(security_findings.title.name)}, ${sql.identifier(security_findings.description.name)} = EXCLUDED.${sql.identifier(security_findings.description.name)}, ${sql.identifier(security_findings.status.name)} = CASE @@ -413,7 +436,7 @@ export async function listSecurityFindings( } = params; const ownerConverted = toOwner(owner); - const conditions = []; + const conditions: Array = []; if (ownerConverted.type === 'org') { conditions.push(eq(security_findings.owned_by_organization_id, ownerConverted.id)); @@ -584,7 +607,7 @@ export async function countSecurityFindings(params: { const { owner, status, severity, repoFullName } = params; const ownerConverted = toOwner(owner); - const conditions = []; + const conditions: Array = []; // Owner condition if (ownerConverted.type === 'org') { @@ -874,7 +897,7 @@ export async function getOrphanedRepositoriesWithFindingCounts(params: { const { owner, accessibleRepoFullNames } = params; const ownerConverted = toOwner(owner); - const conditions = []; + const conditions: SQL[] = []; // Owner condition if (ownerConverted.type === 'org') { @@ -909,12 +932,13 @@ export async function getOrphanedRepositoriesWithFindingCounts(params: { export async function deleteFindingsByRepository(params: { owner: SecurityReviewOwner; repoFullName: string; + actor: SecurityFindingAuditHumanActor; }): Promise<{ deletedCount: number }> { try { const { owner, repoFullName } = params; const ownerConverted = toOwner(owner); - const conditions = []; + const conditions: SQL[] = []; // Owner condition if (ownerConverted.type === 'org') { @@ -926,13 +950,44 @@ export async function deleteFindingsByRepository(params: { // Repository condition conditions.push(eq(security_findings.repo_full_name, repoFullName)); - // Delete findings and get count - const result = await db - .delete(security_findings) - .where(and(...conditions)) - .returning({ id: security_findings.id }); + const ownerForAudit = toSecurityFindingAuditOwner(ownerConverted); + const ownerKeyPart = ownerAuditKeyPart(ownerConverted); + + const deletedCount = await db.transaction(async tx => { + const findings = await tx + .select() + .from(security_findings) + .where(and(...conditions)); + const occurredAt = new Date().toISOString(); + + for (const finding of findings) { + await insertSecurityFindingAuditEvent(tx, { + owner: ownerForAudit, + finding, + actor: params.actor, + action: SecurityAuditLogAction.FindingDeleted, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ownerKeyPart, + finding.id, + SecurityAuditLogAction.FindingDeleted, + ]), + sourceContext: SecurityFindingAuditSourceContext.Web, + beforeState: { status: finding.status }, + afterState: { deleted: true }, + metadata: { repo_full_name: repoFullName }, + }); + } + + const deleted = await tx + .delete(security_findings) + .where(and(...conditions)) + .returning({ id: security_findings.id }); + + return deleted.length; + }); - return { deletedCount: result.length }; + return { deletedCount }; } catch (error) { captureException(error, { tags: { operation: 'deleteFindingsByRepository' }, diff --git a/apps/web/src/lib/security-agent/posthog-tracking.test.ts b/apps/web/src/lib/security-agent/posthog-tracking.test.ts new file mode 100644 index 0000000000..d2f130f9cf --- /dev/null +++ b/apps/web/src/lib/security-agent/posthog-tracking.test.ts @@ -0,0 +1,145 @@ +import { beforeAll, beforeEach, describe, expect, it, jest } from '@jest/globals'; +import type { + trackSecurityAgentRemediationAction as trackSecurityAgentRemediationActionType, + trackSecurityAgentUiInteraction as trackSecurityAgentUiInteractionType, +} from './posthog-tracking'; + +jest.mock('@/lib/posthog', () => { + const mockCapture = jest.fn(); + + return { + __esModule: true, + default: jest.fn(() => ({ capture: mockCapture })), + mockCapture, + }; +}); + +jest.mock('@sentry/nextjs', () => { + const mockCaptureException = jest.fn(); + + return { + captureException: mockCaptureException, + mockCaptureException, + }; +}); + +let trackSecurityAgentRemediationAction: typeof trackSecurityAgentRemediationActionType; +let trackSecurityAgentUiInteraction: typeof trackSecurityAgentUiInteractionType; + +const posthogMock: { mockCapture: jest.Mock } = jest.requireMock('@/lib/posthog'); +const sentryMock: { mockCaptureException: jest.Mock } = jest.requireMock('@sentry/nextjs'); +const { mockCapture } = posthogMock; +const { mockCaptureException } = sentryMock; + +beforeAll(async () => { + ({ trackSecurityAgentRemediationAction, trackSecurityAgentUiInteraction } = + await import('./posthog-tracking')); +}); + +describe('Security Agent PostHog tracking', () => { + beforeEach(() => { + mockCapture.mockReset(); + mockCaptureException.mockReset(); + }); + + it('captures UI interactions with only fixed allowlisted properties', () => { + const input = { + distinctId: 'user-123', + userId: 'user-123', + interaction: 'finding_detail_opened', + findingId: 'must-not-leak', + } as const; + + trackSecurityAgentUiInteraction(input); + + expect(mockCapture).toHaveBeenCalledWith({ + distinctId: 'user-123', + event: 'security_agent_ui_interaction', + properties: { + interaction: 'finding_detail_opened', + feature: 'security-agent', + operation: 'ui_interaction', + userId: 'user-123', + }, + }); + }); + + it('captures remediation actions with trusted organization context only', () => { + const input = { + distinctId: 'user-123', + userId: 'user-123', + organizationId: 'organization-123', + action: 'retry', + attemptId: 'must-not-leak', + error: 'must-not-leak', + } as const; + + trackSecurityAgentRemediationAction(input); + + expect(mockCapture).toHaveBeenCalledWith({ + distinctId: 'user-123', + event: 'security_agent_remediation_action', + properties: { + action: 'retry', + phase: 'accepted', + feature: 'security-agent', + operation: 'remediation_action', + userId: 'user-123', + organizationId: 'organization-123', + }, + }); + }); + + it('reports UI capture failures without throwing or leaking arbitrary properties', () => { + const error = new Error('capture failed'); + mockCapture.mockImplementation(() => { + throw error; + }); + + expect(() => + trackSecurityAgentUiInteraction({ + distinctId: 'user-123', + userId: 'user-123', + interaction: 'findings_filtered', + }) + ).not.toThrow(); + expect(mockCaptureException).toHaveBeenCalledWith(error, { + tags: { source: 'posthog_security_agent_ui_interaction' }, + extra: { + properties: { + interaction: 'findings_filtered', + feature: 'security-agent', + operation: 'ui_interaction', + userId: 'user-123', + }, + }, + }); + }); + + it('reports remediation capture failures without throwing', () => { + const error = new Error('capture failed'); + mockCapture.mockImplementation(() => { + throw error; + }); + + expect(() => + trackSecurityAgentRemediationAction({ + distinctId: 'user-123', + userId: 'user-123', + action: 'cancel', + }) + ).not.toThrow(); + expect(mockCaptureException).toHaveBeenCalledWith(error, { + tags: { source: 'posthog_security_agent_remediation_action' }, + extra: { + properties: { + action: 'cancel', + phase: 'accepted', + feature: 'security-agent', + operation: 'remediation_action', + userId: 'user-123', + }, + }, + }); + }); +}); diff --git a/apps/web/src/lib/security-agent/posthog-tracking.ts b/apps/web/src/lib/security-agent/posthog-tracking.ts index f1c13710c0..6f27bd5bb9 100644 --- a/apps/web/src/lib/security-agent/posthog-tracking.ts +++ b/apps/web/src/lib/security-agent/posthog-tracking.ts @@ -7,6 +7,7 @@ import 'server-only'; import PostHogClient from '@/lib/posthog'; +import type { SecurityAgentUiInteraction } from '@/lib/security-agent/core/schemas'; import { captureException } from '@sentry/nextjs'; const posthogClient = PostHogClient(); @@ -99,6 +100,67 @@ type SecurityAgentFullSyncEvent = { durationMs: number; }; +type SecurityAgentUiInteractionEvent = BaseSecurityAgentEvent & { + interaction: SecurityAgentUiInteraction; +}; + +type SecurityAgentRemediationActionEvent = BaseSecurityAgentEvent & { + action: 'start' | 'retry' | 'cancel'; +}; + +export function trackSecurityAgentUiInteraction(properties: SecurityAgentUiInteractionEvent): void { + const eventProperties = { + interaction: properties.interaction, + feature: 'security-agent', + operation: 'ui_interaction', + userId: properties.userId, + ...(properties.organizationId !== undefined + ? { organizationId: properties.organizationId } + : {}), + }; + + try { + posthogClient.capture({ + distinctId: properties.distinctId, + event: 'security_agent_ui_interaction', + properties: eventProperties, + }); + } catch (error) { + captureException(error, { + tags: { source: 'posthog_security_agent_ui_interaction' }, + extra: { properties: eventProperties }, + }); + } +} + +export function trackSecurityAgentRemediationAction( + properties: SecurityAgentRemediationActionEvent +): void { + const eventProperties = { + action: properties.action, + phase: 'accepted', + feature: 'security-agent', + operation: 'remediation_action', + userId: properties.userId, + ...(properties.organizationId !== undefined + ? { organizationId: properties.organizationId } + : {}), + }; + + try { + posthogClient.capture({ + distinctId: properties.distinctId, + event: 'security_agent_remediation_action', + properties: eventProperties, + }); + } catch (error) { + captureException(error, { + tags: { source: 'posthog_security_agent_remediation_action' }, + extra: { properties: eventProperties }, + }); + } +} + /** * Track security agent enabled/disabled */ diff --git a/apps/web/src/lib/security-agent/router/shared-handlers.test.ts b/apps/web/src/lib/security-agent/router/shared-handlers.test.ts index 09810e6fa6..479d0fec67 100644 --- a/apps/web/src/lib/security-agent/router/shared-handlers.test.ts +++ b/apps/web/src/lib/security-agent/router/shared-handlers.test.ts @@ -3,6 +3,7 @@ import type { createSecurityAgentHandlers as createSecurityAgentHandlersType } f import type * as manualSyncClientModule from '../services/manual-sync-client'; import type * as manualDismissClientModule from '../services/manual-dismiss-client'; import type * as manualAnalysisClientModule from '../services/manual-analysis-client'; +import type * as manualRemediationClientModule from '../services/manual-remediation-client'; const commandId = 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee'; const mockSubmitManualSecuritySync = jest.fn() as jest.MockedFunction< @@ -14,12 +15,38 @@ const mockSubmitManualFindingDismissal = jest.fn() as jest.MockedFunction< const mockSubmitManualAnalysisStart = jest.fn() as jest.MockedFunction< typeof manualAnalysisClientModule.submitManualAnalysisStart >; +const mockSubmitApplyAutoRemediation = jest.fn() as jest.MockedFunction< + typeof manualRemediationClientModule.submitApplyAutoRemediation +>; +const mockSubmitManualRemediationStart = jest.fn() as jest.MockedFunction< + typeof manualRemediationClientModule.submitManualRemediationStart +>; +const mockSubmitRemediationCancellation = jest.fn() as jest.MockedFunction< + typeof manualRemediationClientModule.submitRemediationCancellation +>; const mockGetSecurityFindingById = jest.fn<() => Promise>(); -const mockCanStartAnalysis = jest.fn<() => Promise>(); +const mockCanStartAnalysis = jest.fn<(owner: unknown) => Promise>(); const mockEnqueueBacklogFindings = jest.fn<() => Promise>(); const mockGetSecurityAgentConfigWithStatus = jest.fn<() => Promise>(); +const mockDecorateFindingWithRemediation = jest.fn<() => Promise>(); +const mockDecorateFindingsWithRemediation = jest.fn<() => Promise>(); +const mockGetRemediationAttemptHistory = jest.fn<() => Promise>(); +const mockDeleteFindingsByRepository = + jest.fn<(params: unknown) => Promise<{ deletedCount: number }>>(); const mockTrackSecurityAgentSync = jest.fn(); +const mockTrackSecurityAgentUiInteraction = jest.fn(); +const mockTrackSecurityAgentRemediationAction = jest.fn(); const mockLogSecurityAudit = jest.fn(); +const mockCreateSecurityAuditLog = jest.fn(); +const mockUpsertSecurityAgentConfig = jest.fn(); +const mockSetSecurityAgentEnabled = jest.fn(); +const mockAutoDismissEligibleFindings = + jest.fn< + ( + owner: unknown, + actor: unknown + ) => Promise<{ dismissed: number; skipped: number; errors: number }> + >(); jest.mock('../services/manual-sync-client', () => ({ submitManualSecuritySync: mockSubmitManualSecuritySync, @@ -30,6 +57,11 @@ jest.mock('../services/manual-dismiss-client', () => ({ jest.mock('../services/manual-analysis-client', () => ({ submitManualAnalysisStart: mockSubmitManualAnalysisStart, })); +jest.mock('../services/manual-remediation-client', () => ({ + submitApplyAutoRemediation: mockSubmitApplyAutoRemediation, + submitManualRemediationStart: mockSubmitManualRemediationStart, + submitRemediationCancellation: mockSubmitRemediationCancellation, +})); jest.mock('../github/permissions', () => ({ hasSecurityReviewPermissions: () => true, getReauthorizeUrl: jest.fn(), @@ -39,8 +71,11 @@ jest.mock('../posthog-tracking', () => ({ trackSecurityAgentConfigSaved: jest.fn(), trackSecurityAgentSync: mockTrackSecurityAgentSync, trackSecurityAgentFindingDismissed: jest.fn(), + trackSecurityAgentUiInteraction: mockTrackSecurityAgentUiInteraction, + trackSecurityAgentRemediationAction: mockTrackSecurityAgentRemediationAction, })); jest.mock('../services/audit-log-service', () => ({ + createSecurityAuditLog: mockCreateSecurityAuditLog, logSecurityAudit: mockLogSecurityAudit, SecurityAuditLogAction: { ConfigEnabled: 'config_enabled', @@ -52,8 +87,8 @@ jest.mock('../services/audit-log-service', () => ({ })); jest.mock('../db/security-config', () => ({ getSecurityAgentConfigWithStatus: mockGetSecurityAgentConfigWithStatus, - upsertSecurityAgentConfig: jest.fn(), - setSecurityAgentEnabled: jest.fn(), + upsertSecurityAgentConfig: mockUpsertSecurityAgentConfig, + setSecurityAgentEnabled: mockSetSecurityAgentEnabled, })); jest.mock('../db/security-findings', () => ({ listSecurityFindings: jest.fn(), @@ -61,7 +96,12 @@ jest.mock('../db/security-findings', () => ({ getSecurityFindingsSummary: jest.fn(), getLastSyncTime: jest.fn(), getOrphanedRepositoriesWithFindingCounts: jest.fn(), - deleteFindingsByRepository: jest.fn(), + deleteFindingsByRepository: mockDeleteFindingsByRepository, +})); +jest.mock('../db/security-remediation', () => ({ + decorateFindingWithRemediation: mockDecorateFindingWithRemediation, + decorateFindingsWithRemediation: mockDecorateFindingsWithRemediation, + getRemediationAttemptHistory: mockGetRemediationAttemptHistory, })); jest.mock('../db/security-commands', () => ({ getSecurityAgentCommandStatus: jest.fn(), @@ -73,7 +113,7 @@ jest.mock('../db/security-analysis', () => ({ enqueueBacklogFindings: mockEnqueueBacklogFindings, })); jest.mock('../services/auto-dismiss-service', () => ({ - autoDismissEligibleFindings: jest.fn(), + autoDismissEligibleFindings: mockAutoDismissEligibleFindings, countEligibleForAutoDismiss: jest.fn(), })); jest.mock('@/lib/integrations/db/platform-integrations', () => ({ @@ -92,6 +132,7 @@ beforeAll(async () => { beforeEach(() => { jest.clearAllMocks(); mockGetSecurityAgentConfigWithStatus.mockResolvedValue(null); + mockGetRemediationAttemptHistory.mockResolvedValue([]); mockEnqueueBacklogFindings.mockResolvedValue(0); }); @@ -116,14 +157,159 @@ function createHandlers() { }); } +function createPersonalHandlers() { + return createSecurityAgentHandlers({ + resolveOwner: () => ({ type: 'user', id: 'user-123', userId: 'user-123' }), + resolveSecurityOwner: () => ({ userId: 'user-123' }), + resolveResourceId: () => 'user-123', + verifyFindingOwnership: () => true, + getIntegration: async () => + ({ + id: 'integration-123', + integration_status: 'active', + platform_installation_id: 'installation-123', + repositories: [], + }) as never, + trackingExtras: () => ({}), + }); +} + +function createOrganizationTrackingHandlers() { + return createSecurityAgentHandlers<{ organizationId: string }>({ + resolveOwner: (ctx, input) => ({ + type: 'org', + id: input.organizationId, + userId: ctx.user.id, + }), + resolveSecurityOwner: (_ctx, input) => ({ organizationId: input.organizationId }), + resolveResourceId: (_ctx, input) => input.organizationId, + verifyFindingOwnership: (finding, _ctx, input) => + finding.owned_by_organization_id === input.organizationId, + getIntegration: async () => + ({ + id: 'integration-123', + integration_status: 'active', + platform_installation_id: 'installation-123', + repositories: [], + }) as never, + trackingExtras: (_ctx, input) => ({ organizationId: input.organizationId }), + }); +} + const context = { user: { id: 'user-123', google_user_email: 'owner@example.com', google_user_name: 'Owner Example', + is_admin: false, }, } as never; +describe('trackUiInteraction', () => { + it('tracks an allowlisted interaction with authenticated personal identity', async () => { + await expect( + createPersonalHandlers().trackUiInteraction.handler({ + ctx: context, + input: { interaction: 'finding_detail_opened' }, + }) + ).resolves.toEqual({ success: true }); + + expect(mockTrackSecurityAgentUiInteraction).toHaveBeenCalledWith({ + distinctId: 'user-123', + userId: 'user-123', + organizationId: undefined, + interaction: 'finding_detail_opened', + }); + }); + + it('uses trusted organization context from the router input', async () => { + await createOrganizationTrackingHandlers().trackUiInteraction.handler({ + ctx: context, + input: { + organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + interaction: 'settings_automation_viewed', + }, + }); + + expect(mockTrackSecurityAgentUiInteraction).toHaveBeenCalledWith({ + distinctId: 'user-123', + userId: 'user-123', + organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + interaction: 'settings_automation_viewed', + }); + }); + + it('rejects unsupported interaction values at the schema boundary', () => { + expect( + createPersonalHandlers().trackUiInteraction.inputSchema.safeParse({ + interaction: 'finding_exported', + }).success + ).toBe(false); + }); + + it('does not write UI interactions to database or audit storage', async () => { + await createPersonalHandlers().trackUiInteraction.handler({ + ctx: context, + input: { interaction: 'findings_filtered' }, + }); + + expect(mockUpsertSecurityAgentConfig).not.toHaveBeenCalled(); + expect(mockSetSecurityAgentEnabled).not.toHaveBeenCalled(); + expect(mockCreateSecurityAuditLog).not.toHaveBeenCalled(); + expect(mockLogSecurityAudit).not.toHaveBeenCalled(); + }); +}); + +describe('getConfig', () => { + it('marks new owners without config as setup state', async () => { + await expect(createHandlers().getConfig({ ctx: context, input: {} })).resolves.toMatchObject({ + hasConfig: false, + isEnabled: false, + }); + }); + + it('marks existing disabled config as configured', async () => { + mockGetSecurityAgentConfigWithStatus.mockResolvedValue({ + isEnabled: false, + storedConfig: {}, + config: { + sla_critical_days: 15, + sla_high_days: 30, + sla_medium_days: 45, + sla_low_days: 90, + sla_enabled: true, + auto_sync_enabled: true, + repository_selection_mode: 'selected', + selected_repository_ids: [], + model_slug: 'analysis-model', + triage_model_slug: 'triage-model', + analysis_model_slug: 'analysis-model', + analysis_mode: 'auto', + auto_dismiss_enabled: false, + auto_dismiss_confidence_threshold: 'high', + auto_analysis_enabled: false, + auto_analysis_min_severity: 'high', + auto_analysis_include_existing: false, + auto_remediation_enabled: false, + auto_remediation_min_severity: 'high', + auto_remediation_include_existing: false, + auto_remediation_enabled_at: null, + remediation_model_slug: 'remediation-model', + sla_notifications_enabled: false, + sla_notification_min_severity: 'high', + sla_notification_warning_days: 3, + new_finding_notifications_enabled: false, + new_finding_notification_min_severity: 'high', + }, + }); + + await expect(createHandlers().getConfig({ ctx: context, input: {} })).resolves.toMatchObject({ + hasConfig: true, + isEnabled: false, + }); + }); +}); + describe('setEnabled', () => { it('returns initial sync command correlation after enable', async () => { mockSubmitManualSecuritySync.mockResolvedValue({ @@ -196,6 +382,106 @@ describe('saveConfig', () => { }); }); +describe('autoDismissEligible', () => { + it('attributes per-finding bulk dismissal events without writing aggregate finding activity', async () => { + mockAutoDismissEligibleFindings.mockResolvedValue({ dismissed: 2, skipped: 1, errors: 0 }); + + await expect( + createHandlers().autoDismissEligible({ ctx: context, input: {} }) + ).resolves.toEqual({ dismissed: 2, skipped: 1, errors: 0 }); + + expect(mockAutoDismissEligibleFindings).toHaveBeenCalledWith( + { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, + { + type: 'customer_user', + id: 'user-123', + email: 'owner@example.com', + name: 'Owner Example', + } + ); + expect(mockLogSecurityAudit).not.toHaveBeenCalled(); + }); +}); + +describe('deleteFindingsByRepository', () => { + it('propagates authoritative admin classification to deletion events', async () => { + mockDeleteFindingsByRepository.mockResolvedValue({ deletedCount: 2 }); + const adminContext = { + user: { + id: 'user-123', + google_user_email: 'operator@example.com', + google_user_name: 'Owner Example', + is_admin: true, + }, + } as never; + + await expect( + createHandlers().deleteFindingsByRepository.handler({ + ctx: adminContext, + input: { repoFullName: 'kilo/repo' }, + }) + ).resolves.toEqual({ success: true, deletedCount: 2 }); + + expect(mockDeleteFindingsByRepository).toHaveBeenCalledWith({ + owner: { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, + repoFullName: 'kilo/repo', + actor: { + type: 'kilo_admin', + id: 'user-123', + email: 'operator@example.com', + name: 'Owner Example', + }, + }); + }); +}); + +describe('getAnalysis', () => { + it('returns current finding state with analysis and remediation data', async () => { + const findingId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; + const finding = { + id: findingId, + status: 'ignored', + ignored_reason: 'not_used', + ignored_by: 'auto-sandbox', + updated_at: '2026-06-17T11:45:00.000Z', + analysis_status: 'completed', + analysis_started_at: '2026-06-17T11:40:00.000Z', + analysis_completed_at: '2026-06-17T11:44:59.000Z', + analysis_error: null, + analysis: { analyzedAt: '2026-06-17T11:44:59.000Z' }, + session_id: 'session-123', + cli_session_id: 'cli-session-123', + }; + const decoratedFinding = { + ...finding, + remediationSummary: null, + remediationCapability: { + canStart: false, + startReason: 'finding_not_open', + canRetry: false, + retryReason: 'finding_not_open', + canCancel: false, + cancelAttemptId: null, + }, + }; + mockGetSecurityFindingById.mockResolvedValue(finding); + mockDecorateFindingWithRemediation.mockResolvedValue(decoratedFinding); + + await expect( + createHandlers().getAnalysis.handler({ ctx: context, input: { findingId } }) + ).resolves.toMatchObject({ + findingState: { + status: 'ignored', + ignoredReason: 'not_used', + ignoredBy: 'auto-sandbox', + updatedAt: '2026-06-17T11:45:00.000Z', + }, + status: 'completed', + remediationCapability: { startReason: 'finding_not_open' }, + }); + }); +}); + describe('queue-backed handlers', () => { it('returns sync command correlation', async () => { mockSubmitManualSecuritySync.mockResolvedValue({ @@ -228,6 +514,9 @@ describe('queue-backed handlers', () => { }, }) ).resolves.toMatchObject({ success: true, accepted: true, commandId }); + expect(mockSubmitManualFindingDismissal).toHaveBeenCalledWith( + expect.objectContaining({ actor: { id: 'user-123' } }) + ); }); it('returns manual analysis command correlation', async () => { @@ -241,5 +530,133 @@ describe('queue-backed handlers', () => { input: { findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb' }, }) ).resolves.toEqual({ success: true, queued: true, commandId }); + + expect(mockCanStartAnalysis).toHaveBeenCalledWith({ + organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + }); + }); + + it('bypasses owner capacity only for a validated active restart', async () => { + mockGetSecurityFindingById.mockResolvedValue({ + id: 'finding-id', + analysis_status: 'running', + }); + mockCanStartAnalysis.mockResolvedValue({ allowed: false, currentCount: 3, limit: 3 }); + mockSubmitManualAnalysisStart.mockResolvedValue({ queued: true, commandId }); + + await expect( + createHandlers().startAnalysis.handler({ + ctx: context, + input: { + findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + restartActive: true, + }, + }) + ).resolves.toEqual({ success: true, queued: true, commandId }); + + expect(mockCanStartAnalysis).not.toHaveBeenCalled(); + expect(mockSubmitManualAnalysisStart).toHaveBeenCalledWith( + expect.objectContaining({ restartActive: true }) + ); + }); + + it('rejects active restart requests after finding is no longer running', async () => { + mockGetSecurityFindingById.mockResolvedValue({ + id: 'finding-id', + analysis_status: 'completed', + }); + + await expect( + createHandlers().startAnalysis.handler({ + ctx: context, + input: { + findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + restartActive: true, + }, + }) + ).rejects.toMatchObject({ + code: 'PRECONDITION_FAILED', + message: 'Only a running Sandbox Analysis can be restarted', + }); + + expect(mockCanStartAnalysis).not.toHaveBeenCalled(); + expect(mockSubmitManualAnalysisStart).not.toHaveBeenCalled(); + }); +}); + +describe('remediation action tracking', () => { + const findingId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; + const attemptId = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'; + + it('tracks accepted start, retry, and cancel actions', async () => { + mockGetSecurityFindingById.mockResolvedValue({ id: findingId }); + mockSubmitManualRemediationStart.mockResolvedValue({ + queued: true, + remediationId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', + attemptId, + attemptNumber: 1, + }); + mockSubmitRemediationCancellation.mockResolvedValue({ + success: true, + status: 'cancellation_requested', + }); + const handlers = createHandlers(); + + await handlers.startRemediation.handler({ ctx: context, input: { findingId } }); + await handlers.retryRemediation.handler({ ctx: context, input: { findingId } }); + await handlers.cancelRemediation.handler({ ctx: context, input: { attemptId } }); + + expect(mockTrackSecurityAgentRemediationAction).toHaveBeenNthCalledWith(1, { + distinctId: 'user-123', + userId: 'user-123', + organizationId: undefined, + action: 'start', + }); + expect(mockTrackSecurityAgentRemediationAction).toHaveBeenNthCalledWith(2, { + distinctId: 'user-123', + userId: 'user-123', + organizationId: undefined, + action: 'retry', + }); + expect(mockTrackSecurityAgentRemediationAction).toHaveBeenNthCalledWith(3, { + distinctId: 'user-123', + userId: 'user-123', + organizationId: undefined, + action: 'cancel', + }); + }); + + it('returns typed policy rejections without tracking accepted remediation', async () => { + mockGetSecurityFindingById.mockResolvedValue({ id: findingId }); + mockSubmitManualRemediationStart.mockResolvedValue({ + queued: false, + reason: 'analysis_required', + }); + const handlers = createHandlers(); + + await expect( + handlers.startRemediation.handler({ ctx: context, input: { findingId } }) + ).resolves.toEqual({ success: false, queued: false, reason: 'analysis_required' }); + + expect(mockTrackSecurityAgentRemediationAction).not.toHaveBeenCalled(); + }); + + it('does not track remediation actions rejected by admission handlers', async () => { + mockGetSecurityFindingById.mockResolvedValue({ id: findingId }); + mockSubmitManualRemediationStart.mockRejectedValue(new Error('not admitted')); + mockSubmitRemediationCancellation.mockRejectedValue(new Error('not cancellable')); + const handlers = createHandlers(); + + await expect( + handlers.startRemediation.handler({ ctx: context, input: { findingId } }) + ).rejects.toThrow('not admitted'); + await expect( + handlers.retryRemediation.handler({ ctx: context, input: { findingId } }) + ).rejects.toThrow('not admitted'); + await expect( + handlers.cancelRemediation.handler({ ctx: context, input: { attemptId } }) + ).rejects.toThrow('not cancellable'); + + expect(mockTrackSecurityAgentRemediationAction).not.toHaveBeenCalled(); }); }); diff --git a/apps/web/src/lib/security-agent/router/shared-handlers.ts b/apps/web/src/lib/security-agent/router/shared-handlers.ts index 1ff4d1f06d..7ccb758316 100644 --- a/apps/web/src/lib/security-agent/router/shared-handlers.ts +++ b/apps/web/src/lib/security-agent/router/shared-handlers.ts @@ -34,6 +34,13 @@ import { decorateFindingsWithRemediation, getRemediationAttemptHistory, } from '@/lib/security-agent/db/security-remediation'; +import { + SecurityAgentAuditReportInputSchema, + SecurityAgentAuditReportQueryError, + getSecurityAgentAuditReport, + type SecurityAgentAuditReportInput, + type SecurityAgentAuditReportOwner, +} from '@/lib/security-agent/db/security-audit-report'; import { hasSecurityReviewPermissions, getReauthorizeUrl, @@ -51,7 +58,10 @@ import { countEligibleForAutoDismiss, } from '@/lib/security-agent/services/auto-dismiss-service'; import type { SecurityReviewOwner } from '@/lib/security-agent/core/types'; -import type { SecurityFinding } from '@kilocode/db/schema'; +import { organizations, type SecurityFinding } from '@kilocode/db/schema'; +import { buildSecurityFindingAuditHumanActor } from '@kilocode/worker-utils/security-finding-audit'; +import { db } from '@/lib/drizzle'; +import { eq } from 'drizzle-orm'; import { SaveSecurityConfigInputSchema, ListFindingsInputSchema, @@ -67,6 +77,7 @@ import { GetCommandStatusInputSchema, DeleteFindingsByRepoInputSchema, GetDashboardStatsInputSchema, + TrackSecurityAgentUiInteractionInputSchema, type SaveSecurityConfigInput, type ListFindingsInput, type TriggerSyncInput, @@ -81,6 +92,7 @@ import { type GetCommandStatusInput, type DeleteFindingsByRepoInput, type GetDashboardStatsInput, + type TrackSecurityAgentUiInteractionInput, } from '@/lib/security-agent/core/schemas'; import { DEFAULT_SECURITY_AGENT_TRIAGE_MODEL, @@ -93,8 +105,11 @@ import { trackSecurityAgentConfigSaved, trackSecurityAgentSync, trackSecurityAgentFindingDismissed, + trackSecurityAgentUiInteraction, + trackSecurityAgentRemediationAction, } from '@/lib/security-agent/posthog-tracking'; import { + createSecurityAuditLog, logSecurityAudit, SecurityAuditLogAction, } from '@/lib/security-agent/services/audit-log-service'; @@ -118,7 +133,7 @@ type SecurityAgentDeps = { resolveResourceId: (ctx: TRPCContext, input: TExtra) => string; verifyFindingOwnership: (finding: SecurityFinding, ctx: TRPCContext, input: TExtra) => boolean; getIntegration: (ctx: TRPCContext, input: TExtra) => Promise; - trackingExtras: (ctx: TRPCContext, input: TExtra) => Record; + trackingExtras: (ctx: TRPCContext, input: TExtra) => { organizationId?: string }; }; function getRepoFullNamesInScope( @@ -136,6 +151,99 @@ function getRepoFullNamesInScope( .filter((name): name is string => !!name); } +async function resolveAuditReportOwner( + ctx: TRPCContext, + owner: Owner +): Promise { + if (owner.type === 'user') { + return { + type: 'user', + id: ctx.user.id, + displayName: ctx.user.google_user_name || ctx.user.google_user_email || 'Personal owner', + }; + } + + const [organization] = await db + .select({ name: organizations.name }) + .from(organizations) + .where(eq(organizations.id, owner.id)) + .limit(1); + + if (!organization) { + throw new TRPCError({ code: 'NOT_FOUND', message: 'Organization not found' }); + } + + return { + type: 'organization', + id: owner.id, + displayName: organization.name, + }; +} + +async function logPlatformAdminAuditReportAccess(params: { + ctx: TRPCContext; + owner: SecurityAgentAuditReportOwner; + periodStart: string; + periodEndExclusive: string; +}): Promise { + if (!params.ctx.user.is_admin) return; + + const securityOwner = + params.owner.type === 'organization' + ? { organizationId: params.owner.id } + : { userId: params.owner.id }; + + await createSecurityAuditLog({ + owner: securityOwner, + actor_id: params.ctx.user.id, + actor_email: params.ctx.user.google_user_email, + actor_name: params.ctx.user.google_user_name, + action: SecurityAuditLogAction.AuditReportGenerated, + resource_type: 'security_agent_audit_report', + resource_id: `${params.owner.type}:${params.owner.id}`, + metadata: { + owner_type: params.owner.type, + period_start: params.periodStart, + period_end_exclusive: params.periodEndExclusive, + report_version: 1, + }, + }); +} + +async function assembleAuditReportResponse(params: { + ctx: TRPCContext; + input: SecurityAgentAuditReportInput & TExtra; + deps: SecurityAgentDeps; +}): Promise< + | { status: 'ok'; report: Awaited> } + | { status: 'query_failed'; message: 'Report query did not finish' } +> { + const owner = await resolveAuditReportOwner( + params.ctx, + params.deps.resolveOwner(params.ctx, params.input) + ); + + try { + const report = await getSecurityAgentAuditReport({ + owner, + input: params.input, + isRequestingUserKiloAdmin: params.ctx.user.is_admin, + }); + await logPlatformAdminAuditReportAccess({ + ctx: params.ctx, + owner, + periodStart: report.period.start, + periodEndExclusive: report.period.endExclusive, + }); + return { status: 'ok', report }; + } catch (error) { + if (error instanceof SecurityAgentAuditReportQueryError) { + return { status: 'query_failed', message: 'Report query did not finish' }; + } + throw error; + } +} + // --------------------------------------------------------------------------- // Factory // --------------------------------------------------------------------------- @@ -150,6 +258,26 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps const toExtra = (input: unknown): TExtra => (input ?? {}) as any; return { + trackUiInteraction: { + inputSchema: TrackSecurityAgentUiInteractionInputSchema, + handler: async ({ + ctx, + input, + }: { + ctx: TRPCContext; + input: TrackSecurityAgentUiInteractionInput & TExtra; + }) => { + trackSecurityAgentUiInteraction({ + distinctId: ctx.user.id, + userId: ctx.user.id, + organizationId: deps.trackingExtras(ctx, input).organizationId, + interaction: input.interaction, + }); + + return { success: true }; + }, + }, + // ----------------------------------------------------------------------- // 1. getPermissionStatus // ----------------------------------------------------------------------- @@ -195,6 +323,7 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps if (!result) { return { + hasConfig: false, isEnabled: false, slaCriticalDays: 15, slaHighDays: 30, @@ -245,6 +374,7 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps DEFAULT_SECURITY_AGENT_REMEDIATION_MODEL; return { + hasConfig: true, isEnabled: result.isEnabled, slaCriticalDays: result.config.sla_critical_days, slaHighDays: result.config.sla_high_days, @@ -1065,11 +1195,7 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps const accepted = await submitManualFindingDismissal({ owner: securityOwner, - actor: { - id: ctx.user.id, - email: ctx.user.google_user_email, - name: ctx.user.google_user_name, - }, + actor: { id: ctx.user.id }, findingId: input.findingId, installationId, reason: input.reason, @@ -1122,16 +1248,24 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps }); } - // Check concurrency limit - const concurrencyCheck = await canStartAnalysis(securityOwner); - - if (!concurrencyCheck.allowed) { + if (input.restartActive && finding.analysis_status !== 'running') { throw new TRPCError({ - code: 'TOO_MANY_REQUESTS', - message: `Maximum concurrent analyses reached (${concurrencyCheck.currentCount}/${concurrencyCheck.limit}). Please wait for existing analyses to complete.`, + code: 'PRECONDITION_FAILED', + message: 'Only a running Sandbox Analysis can be restarted', }); } + if (!input.restartActive) { + const concurrencyCheck = await canStartAnalysis(securityOwner); + + if (!concurrencyCheck.allowed) { + throw new TRPCError({ + code: 'TOO_MANY_REQUESTS', + message: `Maximum concurrent analyses reached (${concurrencyCheck.currentCount}/${concurrencyCheck.limit}). Please wait for existing analyses to complete.`, + }); + } + } + const queued = await submitManualAnalysisStart({ findingId: input.findingId, owner: securityOwner, @@ -1143,6 +1277,7 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps }, forceSandbox: input.forceSandbox, retrySandboxOnly: input.retrySandboxOnly, + restartActive: input.restartActive, }); return { success: true, ...queued }; @@ -1184,6 +1319,14 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps owner: securityOwner, actorUserId: ctx.user.id, }); + if (!queued.queued) return { success: false, ...queued }; + + trackSecurityAgentRemediationAction({ + distinctId: ctx.user.id, + userId: ctx.user.id, + organizationId: deps.trackingExtras(ctx, input).organizationId, + action: 'start', + }); return { success: true, ...queued }; }, @@ -1225,6 +1368,14 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps actorUserId: ctx.user.id, retry: true, }); + if (!queued.queued) return { success: false, ...queued }; + + trackSecurityAgentRemediationAction({ + distinctId: ctx.user.id, + userId: ctx.user.id, + organizationId: deps.trackingExtras(ctx, input).organizationId, + action: 'retry', + }); return { success: true, ...queued }; }, @@ -1250,6 +1401,13 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps actorUserId: ctx.user.id, }); + trackSecurityAgentRemediationAction({ + distinctId: ctx.user.id, + userId: ctx.user.id, + organizationId: deps.trackingExtras(ctx, input).organizationId, + action: 'cancel', + }); + return result; }, }, @@ -1299,6 +1457,13 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps }); return { + findingState: { + status: finding.status, + ignoredReason: finding.ignored_reason, + ignoredBy: finding.ignored_by, + fixedAt: finding.fixed_at, + updatedAt: finding.updated_at, + }, status: finding.analysis_status, startedAt: finding.analysis_started_at, completedAt: finding.analysis_completed_at, @@ -1391,17 +1556,12 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps const result = await deleteFindingsByRepositoryDb({ owner: securityOwner, repoFullName: input.repoFullName, - }); - - logSecurityAudit({ - owner: securityOwner, - actor_id: ctx.user.id, - actor_email: ctx.user.google_user_email, - actor_name: ctx.user.google_user_name, - action: SecurityAuditLogAction.FindingDeleted, - resource_type: 'security_finding', - resource_id: input.repoFullName, - metadata: { repoFullName: input.repoFullName, deletedCount: result.deletedCount }, + actor: buildSecurityFindingAuditHumanActor({ + id: ctx.user.id, + email: ctx.user.google_user_email, + name: ctx.user.google_user_name, + isAdmin: ctx.user.is_admin, + }), }); return { @@ -1431,23 +1591,15 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps autoDismissEligible: async ({ ctx, input }: { ctx: TRPCContext; input: unknown }) => { const extra = toExtra(input); const securityOwner = deps.resolveSecurityOwner(ctx, extra); - const result = await autoDismissEligibleFindings(securityOwner, ctx.user.id); - - logSecurityAudit({ - owner: securityOwner, - actor_id: ctx.user.id, - actor_email: ctx.user.google_user_email, - actor_name: ctx.user.google_user_name, - action: SecurityAuditLogAction.FindingAutoDismissed, - resource_type: 'security_finding', - resource_id: 'bulk', - metadata: { - source: 'bulk', - dismissed: result.dismissed, - skipped: result.skipped, - errors: result.errors, - }, - }); + const result = await autoDismissEligibleFindings( + securityOwner, + buildSecurityFindingAuditHumanActor({ + id: ctx.user.id, + email: ctx.user.google_user_email, + name: ctx.user.google_user_name, + isAdmin: ctx.user.is_admin, + }) + ); return { dismissed: result.dismissed, @@ -1457,7 +1609,27 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps }, // ----------------------------------------------------------------------- - // 18. getDashboardStats + // 18. getAuditReport + // ----------------------------------------------------------------------- + getAuditReport: { + inputSchema: SecurityAgentAuditReportInputSchema, + handler: async ({ + ctx, + input: rawInput, + }: { + ctx: TRPCContext; + input: SecurityAgentAuditReportInput & TExtra; + }) => { + return assembleAuditReportResponse({ + ctx, + input: rawInput, + deps, + }); + }, + }, + + // ----------------------------------------------------------------------- + // 19. getDashboardStats // ----------------------------------------------------------------------- getDashboardStats: { inputSchema: GetDashboardStatsInputSchema, @@ -1484,6 +1656,7 @@ export function createSecurityAgentHandlers(deps: SecurityAgentDeps return getDashboardStats({ owner: securityOwner, repoFullName: input.repoFullName, + slaEnabled: config?.config.sla_enabled ?? true, slaConfig, }); }, diff --git a/apps/web/src/lib/security-agent/services/analysis-service.test.ts b/apps/web/src/lib/security-agent/services/analysis-service.test.ts index 2f977ba1ec..49217642ad 100644 --- a/apps/web/src/lib/security-agent/services/analysis-service.test.ts +++ b/apps/web/src/lib/security-agent/services/analysis-service.test.ts @@ -160,6 +160,9 @@ describe('analysis-service', () => { const mockFinding = { id: findingId, + source: 'dependabot', + source_id: '42', + status: 'open', analysis_status: 'new', repo_full_name: 'acme/repo', package_name: 'lodash', @@ -173,6 +176,9 @@ describe('analysis-service', () => { vulnerable_version_range: '< 4.17.21', patched_version: '4.17.21', manifest_path: 'package.json', + cwe_ids: ['CWE-1321'], + cvss_score: '7.5', + raw_data: { updated_at: '2026-01-15T00:00:00.000Z' }, }; mockGetSecurityFindingById.mockResolvedValue( @@ -231,6 +237,20 @@ describe('analysis-service', () => { expect(mockTriageSecurityFinding).toHaveBeenCalledWith( expect.objectContaining({ model: 'anthropic/claude-sonnet-4' }) ); + expect(mockUpdateAnalysisStatus).toHaveBeenCalledWith( + findingId, + 'pending', + expect.objectContaining({ + analysis: expect.objectContaining({ + findingDataSnapshot: expect.objectContaining({ + schemaVersion: 1, + sourceId: '42', + sourceUpdatedAt: '2026-01-15T00:00:00.000Z', + packageName: 'lodash', + }), + }), + }) + ); expect(mockInitiateFromPreparedSession).toHaveBeenCalledWith({ cloudAgentSessionId: 'ses-agent-123', }); diff --git a/apps/web/src/lib/security-agent/services/analysis-service.ts b/apps/web/src/lib/security-agent/services/analysis-service.ts index 1668ce880a..1e93692827 100644 --- a/apps/web/src/lib/security-agent/services/analysis-service.ts +++ b/apps/web/src/lib/security-agent/services/analysis-service.ts @@ -21,6 +21,7 @@ import type { AnalysisErrorCode } from '../core/error-classification'; import { classifyAnalysisError, isUserActionableError } from '../core/error-classification'; import type { User, SecurityFinding } from '@kilocode/db/schema'; import { deriveCallbackToken } from '@kilocode/worker-utils/callback-token'; +import { buildSecurityFindingAnalysisInput } from '@kilocode/worker-utils/security-remediation-policy'; import { trackSecurityAgentAnalysisStarted, trackSecurityAgentAnalysisCompleted, @@ -169,6 +170,7 @@ export async function finalizeAnalysis( const analysis: SecurityFindingAnalysis = { triage: existingAnalysis?.triage, sandboxAnalysis, + findingDataSnapshot: existingAnalysis?.findingDataSnapshot, rawMarkdown: existingAnalysis?.rawMarkdown, analyzedAt: new Date().toISOString(), modelUsed: model, @@ -251,6 +253,7 @@ export async function startSecurityAnalysis(params: { if (!finding) { return { started: false, error: `Finding not found: ${findingId}` }; } + const findingDataSnapshot = buildSecurityFindingAnalysisInput(finding); const leaseAcquired = await tryAcquireAnalysisStartLease(findingId); if (!leaseAcquired) { @@ -370,6 +373,7 @@ export async function startSecurityAnalysis(params: { const analysis: SecurityFindingAnalysis = { triage, + findingDataSnapshot, analyzedAt: new Date().toISOString(), modelUsed: triageModel, triageModel, @@ -423,6 +427,7 @@ export async function startSecurityAnalysis(params: { const partialAnalysis: SecurityFindingAnalysis = { triage, + findingDataSnapshot, analyzedAt: new Date().toISOString(), modelUsed: analysisModel, triageModel, diff --git a/apps/web/src/lib/security-agent/services/audit-log-service.ts b/apps/web/src/lib/security-agent/services/audit-log-service.ts index 2104a2194f..f4bae59ad2 100644 --- a/apps/web/src/lib/security-agent/services/audit-log-service.ts +++ b/apps/web/src/lib/security-agent/services/audit-log-service.ts @@ -13,15 +13,27 @@ import { db } from '@/lib/drizzle'; import { SecurityAuditLogAction } from '../core/enums'; import type { SecurityReviewOwner } from '../core/types'; import { captureException } from '@sentry/nextjs'; +import { REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS } from '@kilocode/worker-utils/security-finding-audit'; export { SecurityAuditLogAction }; +type ReportableSecurityFindingAuditAction = + (typeof REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS)[number]; +type NonReportableSecurityAuditLogAction = Exclude< + SecurityAuditLogAction, + ReportableSecurityFindingAuditAction +>; + +const reportableSecurityFindingAuditActions = new Set( + REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS +); + type CreateSecurityAuditLogParams = { owner: SecurityReviewOwner; actor_id: string | null; actor_email: string | null; actor_name: string | null; - action: SecurityAuditLogAction; + action: NonReportableSecurityAuditLogAction; resource_type: string; resource_id: string; before_state?: Record; @@ -33,6 +45,10 @@ type CreateSecurityAuditLogParams = { export async function createSecurityAuditLog( params: CreateSecurityAuditLogParams ): Promise { + if (reportableSecurityFindingAuditActions.has(params.action)) { + throw new Error('Reportable Security Finding activity requires canonical audit writer'); + } + const { owner, actor_id, diff --git a/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts b/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts index 832eef0f1e..408a60a45c 100644 --- a/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts +++ b/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts @@ -7,8 +7,11 @@ import type * as posthogModule from '@/lib/security-agent/posthog-tracking'; import type { writebackDependabotDismissal as writebackDependabotDismissalType, maybeAutoDismissAnalysis as maybeAutoDismissAnalysisType, + autoDismissEligibleFindings as autoDismissEligibleFindingsType, + countEligibleForAutoDismiss as countEligibleForAutoDismissType, } from './auto-dismiss-service'; import type { SecurityFinding } from '@kilocode/db/schema'; +import { SecurityAuditLogActorType } from '@kilocode/db/schema-types'; import type { SecurityFindingAnalysis } from '../core/types'; // ── Mocks ────────────────────────────────────────────────────────────────── @@ -31,6 +34,10 @@ const mockDismissDependabotAlert = jest.fn() as jest.MockedFunction< const mockTrackAutoDismiss = jest.fn() as jest.MockedFunction< typeof posthogModule.trackSecurityAgentAutoDismiss >; +let mockTransactionFinding: SecurityFinding | null = null; +const mockAuditRows: unknown[] = []; +const mockUpdatedRows: unknown[] = []; +const mockBulkFindings: Array<{ id: string; analysis: SecurityFindingAnalysis | null }> = []; jest.mock('@/lib/security-agent/db/security-findings', () => ({ getSecurityFindingById: mockGetSecurityFindingById, @@ -57,9 +64,48 @@ jest.mock('@/lib/drizzle', () => ({ db: { select: jest.fn(() => ({ from: jest.fn(() => ({ - where: jest.fn(() => []), + where: jest.fn(() => mockBulkFindings), })), })), + transaction: jest.fn(async (callback: (tx: unknown) => Promise) => { + const tx = { + select: jest.fn(() => ({ + from: jest.fn(() => ({ + where: jest.fn(() => ({ + for: jest.fn(() => ({ + limit: jest.fn(async () => + mockTransactionFinding ? [mockTransactionFinding] : [] + ), + })), + })), + })), + })), + update: jest.fn(() => ({ + set: jest.fn((values: Record) => ({ + where: jest.fn(() => ({ + returning: jest.fn(async () => { + if (!mockTransactionFinding) return []; + const updated = { ...mockTransactionFinding, ...values }; + mockTransactionFinding = updated as SecurityFinding; + mockUpdatedRows.push(updated); + return [updated]; + }), + })), + })), + })), + insert: jest.fn(() => ({ + values: jest.fn((values: unknown) => ({ + onConflictDoNothing: jest.fn(() => ({ + returning: jest.fn(async () => { + mockAuditRows.push(values); + return [{ id: 'audit-row-1' }]; + }), + })), + })), + })), + }; + return callback(tx); + }), }, })); @@ -67,17 +113,23 @@ jest.mock('@/lib/drizzle', () => ({ let writebackDependabotDismissal: typeof writebackDependabotDismissalType; let maybeAutoDismissAnalysis: typeof maybeAutoDismissAnalysisType; +let autoDismissEligibleFindings: typeof autoDismissEligibleFindingsType; +let countEligibleForAutoDismiss: typeof countEligibleForAutoDismissType; beforeAll(async () => { - ({ writebackDependabotDismissal, maybeAutoDismissAnalysis } = - await import('./auto-dismiss-service')); + ({ + writebackDependabotDismissal, + maybeAutoDismissAnalysis, + autoDismissEligibleFindings, + countEligibleForAutoDismiss, + } = await import('./auto-dismiss-service')); }); // ── Helpers ──────────────────────────────────────────────────────────────── function makeFinding(overrides: Partial = {}): SecurityFinding { return { - id: 'finding-1', + id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', owned_by_organization_id: null, owned_by_user_id: 'user-1', platform_integration_id: 'integration-1', @@ -131,6 +183,10 @@ const userOwner = { type: 'user' as const, id: 'user-1', userId: 'user-1' }; beforeEach(() => { jest.clearAllMocks(); + mockTransactionFinding = makeFinding(); + mockAuditRows.length = 0; + mockUpdatedRows.length = 0; + mockBulkFindings.length = 0; }); describe('writebackDependabotDismissal', () => { @@ -218,28 +274,31 @@ describe('writebackDependabotDismissal', () => { }); describe('maybeAutoDismissAnalysis', () => { + const sandboxResult: NonNullable = { + isExploitable: false, + exploitabilityReasoning: + 'The dependency is installed, but the vulnerable template function is never called.', + usageLocations: ['package.json:17'], + suggestedFix: 'Upgrade to latest version', + suggestedAction: 'dismiss', + summary: 'The vulnerable code path is not reachable.', + rawMarkdown: 'raw', + analysisAt: '2024-01-01T00:00:00Z', + }; const sandboxAnalysis: SecurityFindingAnalysis = { - sandboxAnalysis: { - isExploitable: false, - exploitabilityReasoning: 'Not exploitable because dev dependency', - usageLocations: [], - suggestedFix: 'Upgrade to latest version', - suggestedAction: 'dismiss', - summary: 'Dev dependency, not exploitable', - rawMarkdown: 'raw', - analysisAt: '2024-01-01T00:00:00Z', - }, + sandboxAnalysis: sandboxResult, analyzedAt: '2024-01-01T00:00:00Z', }; + const triageResult: NonNullable = { + suggestedAction: 'dismiss', + confidence: 'high', + needsSandboxAnalysis: false, + needsSandboxReasoning: 'Dev dependency, not exploitable', + triageAt: '2024-01-01T00:00:00Z', + }; const triageAnalysis: SecurityFindingAnalysis = { - triage: { - suggestedAction: 'dismiss', - confidence: 'high', - needsSandboxAnalysis: false, - needsSandboxReasoning: 'Dev dependency, not exploitable', - triageAt: '2024-01-01T00:00:00Z', - }, + triage: triageResult, analyzedAt: '2024-01-01T00:00:00Z', }; @@ -299,6 +358,65 @@ describe('maybeAutoDismissAnalysis', () => { ); }); + it.each([ + ['exploitable', true, 'open_pr'], + ['unknown exploitability', 'unknown', 'manual_review'], + ['inconsistent not-exploitable result', false, 'manual_review'], + ] as const)( + 'keeps findings open when authoritative sandbox result is %s', + async (_label, isExploitable, suggestedAction) => { + mockGetSecurityAgentConfig.mockResolvedValue({ + auto_dismiss_enabled: true, + auto_dismiss_confidence_threshold: 'high', + } as Awaited>); + + const result = await maybeAutoDismissAnalysis({ + findingId: 'finding-1', + analysis: { + analyzedAt: '2024-01-01T00:00:00Z', + triage: triageResult, + sandboxAnalysis: { + ...sandboxResult, + isExploitable, + suggestedAction, + }, + }, + owner: { userId: 'user-1' }, + userId: 'user-1', + }); + + expect(result).toEqual({ dismissed: false }); + expect(mockUpdatedRows).toEqual([]); + expect(mockAuditRows).toEqual([]); + expect(mockDismissDependabotAlert).not.toHaveBeenCalled(); + } + ); + + it('keeps triage findings open when triage says sandbox analysis is needed', async () => { + mockGetSecurityAgentConfig.mockResolvedValue({ + auto_dismiss_enabled: true, + auto_dismiss_confidence_threshold: 'high', + } as Awaited>); + + const result = await maybeAutoDismissAnalysis({ + findingId: 'finding-1', + analysis: { + analyzedAt: '2024-01-01T00:00:00Z', + triage: { + ...triageResult, + needsSandboxAnalysis: true, + }, + }, + owner: { userId: 'user-1' }, + userId: 'user-1', + }); + + expect(result).toEqual({ dismissed: false }); + expect(mockUpdatedRows).toEqual([]); + expect(mockAuditRows).toEqual([]); + expect(mockDismissDependabotAlert).not.toHaveBeenCalled(); + }); + it('does not write back when auto-dismiss is disabled', async () => { mockGetSecurityAgentConfig.mockResolvedValue({ auto_dismiss_enabled: false, @@ -334,6 +452,129 @@ describe('maybeAutoDismissAnalysis', () => { // Should still succeed — writeback failure is non-fatal expect(result).toEqual({ dismissed: true, source: 'sandbox' }); - expect(mockUpdateSecurityFindingStatus).toHaveBeenCalled(); + expect(mockUpdatedRows).toHaveLength(1); + expect(mockUpdatedRows[0]).toMatchObject({ status: 'ignored', ignored_reason: 'not_used' }); + expect(mockAuditRows[0]).toMatchObject({ + actor_type: 'system', + action: 'security.finding.auto_dismissed', + finding_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + source_context: 'web', + schema_version: 1, + }); + }); + + describe('autoDismissEligibleFindings', () => { + it('records event-time actor and unique operation identity for bulk dismissals', async () => { + const analysis: SecurityFindingAnalysis = { + triage: { + suggestedAction: 'dismiss', + confidence: 'high', + needsSandboxAnalysis: false, + needsSandboxReasoning: 'No runtime path', + triageAt: '2026-06-16T10:00:00.000Z', + }, + analyzedAt: '2026-06-16T10:00:00.000Z', + }; + mockBulkFindings.push({ id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', analysis }); + mockGetSecurityAgentConfig.mockResolvedValue({ + auto_dismiss_enabled: true, + auto_dismiss_confidence_threshold: 'high', + } as Awaited>); + mockGetSecurityFindingById.mockResolvedValue(makeFinding()); + mockGetIntegrationForOwner.mockResolvedValue(makeIntegration(undefined as unknown as string)); + + await expect( + autoDismissEligibleFindings( + { userId: 'user-1' }, + { + type: SecurityAuditLogActorType.CustomerUser, + id: 'user-1', + email: 'owner@example.com', + name: 'Owner Example', + } + ) + ).resolves.toEqual({ dismissed: 1, skipped: 0, errors: 0 }); + + expect(mockAuditRows).toHaveLength(1); + expect(mockAuditRows[0]).toMatchObject({ + actor_id: 'user-1', + actor_email: 'owner@example.com', + actor_name: 'Owner Example', + actor_type: 'customer_user', + action: 'security.finding.auto_dismissed', + finding_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + source_context: 'web', + schema_version: 1, + metadata: expect.objectContaining({ + trigger: 'auto_dismiss_policy', + dismiss_source: 'bulk', + correlation_id: expect.any(String), + }), + }); + }); + + it('does not bulk-dismiss from triage when a sandbox result exists', async () => { + mockBulkFindings.push({ + id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + analysis: { + analyzedAt: '2026-06-16T10:00:00.000Z', + triage: triageResult, + sandboxAnalysis: { + ...sandboxResult, + isExploitable: true, + suggestedAction: 'open_pr', + }, + }, + }); + mockGetSecurityAgentConfig.mockResolvedValue({ + auto_dismiss_enabled: true, + auto_dismiss_confidence_threshold: 'high', + } as Awaited>); + + await expect( + autoDismissEligibleFindings( + { userId: 'user-1' }, + { + type: SecurityAuditLogActorType.CustomerUser, + id: 'user-1', + email: 'owner@example.com', + name: 'Owner Example', + } + ) + ).resolves.toEqual({ dismissed: 0, skipped: 1, errors: 0 }); + + expect(mockUpdatedRows).toEqual([]); + expect(mockAuditRows).toEqual([]); + expect(mockDismissDependabotAlert).not.toHaveBeenCalled(); + }); + + it('excludes sandbox-analyzed findings from triage-only eligibility counts', async () => { + mockBulkFindings.push( + { + id: 'triage-only', + analysis: { + analyzedAt: '2026-06-16T10:00:00.000Z', + triage: triageResult, + }, + }, + { + id: 'sandbox-completed', + analysis: { + analyzedAt: '2026-06-16T10:00:00.000Z', + triage: triageResult, + sandboxAnalysis: { + ...sandboxResult, + isExploitable: true, + suggestedAction: 'open_pr', + }, + }, + } + ); + + await expect(countEligibleForAutoDismiss({ userId: 'user-1' }, 'user-1')).resolves.toEqual({ + eligible: 1, + byConfidence: { high: 1, medium: 0, low: 0 }, + }); + }); }); }); diff --git a/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts b/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts index 37435c1558..f58cef9aaa 100644 --- a/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts +++ b/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts @@ -5,8 +5,8 @@ * Auto-dismiss is OFF by default and must be explicitly enabled per-organization. * * Unified auto-dismiss logic: - * - After Tier 1 triage: if triage.suggestedAction === 'dismiss' (with confidence threshold) - * - After Tier 2 sandbox: if sandboxAnalysis.isExploitable === false (no confidence threshold) + * - After Tier 1 triage: dismiss only when no sandbox is needed and confidence meets policy + * - After Tier 2 sandbox: dismiss only when analysis says not exploitable and recommends dismissal */ import 'server-only'; @@ -22,7 +22,19 @@ import { dismissDependabotAlert } from '../github/dependabot-api'; import type { Owner } from '@/lib/code-reviews/core'; import type { SecurityFindingAnalysis, SecurityReviewOwner } from '../core/types'; import { sentryLogger } from '@/lib/utils.server'; -import { logSecurityAudit, SecurityAuditLogAction } from './audit-log-service'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditActor, + type SecurityFindingAuditEventFinding, + type SecurityFindingAuditHumanActor, + type SecurityFindingAuditOwner, +} from '@kilocode/worker-utils/security-finding-audit'; import { parseDependabotDismissalTarget } from '@kilocode/worker-utils/dependabot-dismissal-target'; const log = sentryLogger('security-agent:auto-dismiss', 'info'); @@ -42,6 +54,39 @@ function toOwner(securityOwner: SecurityReviewOwner, userId: string): Owner { throw new Error('Invalid owner: must have either organizationId or userId'); } +function toAuditOwner(owner: SecurityReviewOwner): SecurityFindingAuditOwner { + if ('organizationId' in owner && owner.organizationId) { + return { type: 'organization', organizationId: owner.organizationId }; + } + if ('userId' in owner && owner.userId) { + return { type: 'user', userId: owner.userId }; + } + throw new Error('Invalid owner: must have either organizationId or userId'); +} + +function ownerAuditKeyPart(owner: SecurityReviewOwner): string { + if ('organizationId' in owner && owner.organizationId) + return `organization:${owner.organizationId}`; + if ('userId' in owner && owner.userId) return `user:${owner.userId}`; + throw new Error('Invalid owner: must have either organizationId or userId'); +} + +function ownerFindingCondition(owner: SecurityReviewOwner) { + if ('organizationId' in owner && owner.organizationId) { + return eq(security_findings.owned_by_organization_id, owner.organizationId); + } + if ('userId' in owner && owner.userId) { + return eq(security_findings.owned_by_user_id, owner.userId); + } + throw new Error('Invalid owner: must have either organizationId or userId'); +} + +function toAuditFinding( + finding: SecurityFindingAuditEventFinding +): SecurityFindingAuditEventFinding { + return finding; +} + /** * Dismiss a security finding with the given reason */ @@ -59,6 +104,86 @@ export async function dismissFinding( }); } +async function dismissFindingWithAuditEvent( + findingId: string, + params: { + owner: SecurityReviewOwner; + reason: string; + comment: string; + dismissedBy: string; + dismissSource: AutoDismissSource | 'bulk'; + confidence?: string | null; + correlationId?: string; + actor: SecurityFindingAuditActor; + } +): Promise { + const occurredAt = new Date().toISOString(); + return db.transaction(async tx => { + const [finding] = await tx + .select() + .from(security_findings) + .where(and(eq(security_findings.id, findingId), ownerFindingCondition(params.owner))) + .for('update') + .limit(1); + + if (!finding) { + throw new Error('Security finding not found for owner'); + } + + if (finding.status !== 'open') { + return false; + } + + const ignoredBy = params.dismissedBy || `auto-dismiss: ${params.comment}`; + const [updatedFinding] = await tx + .update(security_findings) + .set({ + status: 'ignored', + ignored_reason: params.reason, + ignored_by: ignoredBy, + updated_at: occurredAt, + }) + .where( + and( + eq(security_findings.id, findingId), + ownerFindingCondition(params.owner), + eq(security_findings.status, 'open') + ) + ) + .returning(); + + if (!updatedFinding) { + throw new Error('Security finding status update failed'); + } + + await insertSecurityFindingAuditEvent(tx, { + owner: toAuditOwner(params.owner), + finding: toAuditFinding(updatedFinding), + actor: params.actor, + action: SecurityAuditLogAction.FindingAutoDismissed, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ownerAuditKeyPart(params.owner), + findingId, + SecurityAuditLogAction.FindingAutoDismissed, + params.dismissSource, + params.correlationId || 'none', + ]), + sourceContext: SecurityFindingAuditSourceContext.Web, + beforeState: { status: finding.status }, + afterState: { status: 'ignored', reason_code: params.reason }, + metadata: { + trigger: 'auto_dismiss_policy', + dismiss_source: params.dismissSource, + ...(params.confidence ? { confidence: params.confidence } : {}), + ...(params.correlationId ? { correlation_id: params.correlationId } : {}), + }, + }); + + return true; + }); +} + /** * Write back a dismissal to Dependabot on GitHub. * Fetches the finding and integration data, then calls the Dependabot API. @@ -130,8 +255,8 @@ type AutoDismissSource = 'triage' | 'sandbox'; * Only runs if auto-dismiss is enabled in config. * * Priority: - * 1. If sandboxAnalysis exists and isExploitable === false -> dismiss (no confidence threshold) - * 2. If triage.suggestedAction === 'dismiss' -> dismiss (with confidence threshold) + * 1. Treat any sandbox analysis as authoritative and dismiss only a coherent not-exploitable result + * 2. Without sandbox analysis, dismiss a triage result only when no sandbox is needed * * @param options.findingId - The ID of the finding to potentially dismiss * @param options.analysis - The full analysis result (triage + optional sandbox) @@ -156,24 +281,33 @@ export async function maybeAutoDismissAnalysis(options: { return { dismissed: false }; } - // Priority 1: Check sandbox analysis (no confidence threshold - sandbox is definitive) - if (analysis.sandboxAnalysis?.isExploitable === false) { - await dismissFinding(findingId, { + const sandbox = analysis.sandboxAnalysis; + if (sandbox) { + if (sandbox.isExploitable !== false || sandbox.suggestedAction !== 'dismiss') { + return { dismissed: false }; + } + + const dismissed = await dismissFindingWithAuditEvent(findingId, { + owner, reason: 'not_used', - comment: analysis.sandboxAnalysis.exploitabilityReasoning, + comment: sandbox.exploitabilityReasoning, dismissedBy: 'auto-sandbox', + dismissSource: 'sandbox', + correlationId, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, }); + if (!dismissed) return { dismissed: false }; await safeWritebackDependabotDismissal( findingId, ownerConverted, - analysis.sandboxAnalysis.exploitabilityReasoning + sandbox.exploitabilityReasoning ); log('Auto-dismissed finding (sandbox)', { correlationId, findingId, - reasoning: analysis.sandboxAnalysis.exploitabilityReasoning.slice(0, 100), + reasoning: sandbox.exploitabilityReasoning.slice(0, 100), }); trackSecurityAgentAutoDismiss({ @@ -184,29 +318,11 @@ export async function maybeAutoDismissAnalysis(options: { source: 'sandbox', }); - logSecurityAudit({ - owner, - actor_id: null, - actor_email: null, - actor_name: null, - action: SecurityAuditLogAction.FindingAutoDismissed, - resource_type: 'security_finding', - resource_id: findingId, - after_state: { status: 'ignored' }, - metadata: { - source: 'system', - trigger: 'auto_dismiss_policy', - dismissSource: 'sandbox', - correlationId, - }, - }); - return { dismissed: true, source: 'sandbox' }; } - // Priority 2: Check triage (with confidence threshold) const triage = analysis.triage; - if (triage?.suggestedAction === 'dismiss') { + if (triage?.needsSandboxAnalysis === false && triage.suggestedAction === 'dismiss') { const threshold = config.auto_dismiss_confidence_threshold ?? 'high'; // Check confidence threshold @@ -216,11 +332,17 @@ export async function maybeAutoDismissAnalysis(options: { (threshold === 'high' && triage.confidence === 'high'); if (meetsThreshold) { - await dismissFinding(findingId, { + const dismissed = await dismissFindingWithAuditEvent(findingId, { + owner, reason: 'not_used', comment: triage.needsSandboxReasoning, dismissedBy: 'auto-triage', + dismissSource: 'triage', + confidence: triage.confidence, + correlationId, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, }); + if (!dismissed) return { dismissed: false }; await safeWritebackDependabotDismissal( findingId, @@ -244,24 +366,6 @@ export async function maybeAutoDismissAnalysis(options: { confidence: triage.confidence, }); - logSecurityAudit({ - owner, - actor_id: null, - actor_email: null, - actor_name: null, - action: SecurityAuditLogAction.FindingAutoDismissed, - resource_type: 'security_finding', - resource_id: findingId, - after_state: { status: 'ignored' }, - metadata: { - source: 'system', - trigger: 'auto_dismiss_policy', - dismissSource: 'triage', - confidence: triage.confidence, - correlationId, - }, - }); - return { dismissed: true, source: 'triage' }; } } @@ -285,13 +389,15 @@ export type AutoDismissResult = { * This is useful for processing findings that were triaged before auto-dismiss was enabled. * * @param owner - The security review owner (org or user) - * @param userId - The user performing the action (for audit/permissions) + * @param actor - The user performing the bulk action */ export async function autoDismissEligibleFindings( owner: SecurityReviewOwner, - userId: string + actor: SecurityFindingAuditHumanActor ): Promise { + const userId = actor.id; const ownerConverted = toOwner(owner, userId); + const operationId = crypto.randomUUID(); const config = await getSecurityAgentConfig(ownerConverted); if (!config.auto_dismiss_enabled) { @@ -331,7 +437,12 @@ export async function autoDismissEligibleFindings( const analysis = finding.analysis; const triage = analysis?.triage; - if (!triage) { + if ( + !triage || + analysis?.sandboxAnalysis !== undefined || + triage.needsSandboxAnalysis !== false || + triage.suggestedAction !== 'dismiss' + ) { skipped++; continue; } @@ -346,11 +457,20 @@ export async function autoDismissEligibleFindings( continue; } - await dismissFinding(finding.id, { + const didDismiss = await dismissFindingWithAuditEvent(finding.id, { + owner, reason: 'not_used', comment: triage.needsSandboxReasoning, dismissedBy: 'auto-triage-bulk', + dismissSource: 'bulk', + confidence: triage.confidence, + correlationId: operationId, + actor, }); + if (!didDismiss) { + skipped++; + continue; + } await safeWritebackDependabotDismissal( finding.id, ownerConverted, @@ -420,17 +540,26 @@ export async function countEligibleForAutoDismiss( ); const byConfidence = { high: 0, medium: 0, low: 0 }; + let eligible = 0; for (const finding of findings) { const analysis = finding.analysis; - const confidence = analysis?.triage?.confidence; - if (confidence && confidence in byConfidence) { - byConfidence[confidence]++; + const triage = analysis?.triage; + if ( + !triage || + analysis?.sandboxAnalysis !== undefined || + triage.needsSandboxAnalysis !== false || + triage.suggestedAction !== 'dismiss' + ) { + continue; } + + eligible++; + byConfidence[triage.confidence]++; } return { - eligible: findings.length, + eligible, byConfidence, }; } diff --git a/apps/web/src/lib/security-agent/services/extraction-service.ts b/apps/web/src/lib/security-agent/services/extraction-service.ts index 7b6cec5ab4..54ddd9518e 100644 --- a/apps/web/src/lib/security-agent/services/extraction-service.ts +++ b/apps/web/src/lib/security-agent/services/extraction-service.ts @@ -152,7 +152,16 @@ ${rawMarkdown} --- -Please extract the structured analysis from the report above and call the submit_analysis_extraction tool with your findings.`; +Please extract the structured analysis from the report above and call the submit_analysis_extraction tool with your findings. If tool calls are unavailable, return only a JSON object matching the tool parameters, without markdown or prose.`; +} + +function extractJsonContent(content: string | null): string | null { + const trimmed = content?.trim(); + if (!trimmed) return null; + if (trimmed.startsWith('{') && trimmed.endsWith('}')) return trimmed; + + const fencedJson = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```$/i); + return fencedJson?.[1]?.trim() || null; } /** @@ -213,6 +222,7 @@ function parseExtractionResult( return { isExploitable, + extractionStatus: 'succeeded', exploitabilityReasoning: parsed.exploitabilityReasoning, usageLocations: parsed.usageLocations.map(String), suggestedFix: parsed.suggestedFix, @@ -236,6 +246,7 @@ function createFallbackExtraction( ): SecurityFindingSandboxAnalysis { return { isExploitable: 'unknown', + extractionStatus: 'failed', exploitabilityReasoning: `Extraction failed: ${reason}. Please review the raw analysis.`, usageLocations: [], suggestedFix: 'Review the raw analysis for fix recommendations.', @@ -307,10 +318,7 @@ export async function extractSandboxAnalysis(options: { model, messages, tools: [SUBMIT_EXTRACTION_TOOL], - tool_choice: { - type: 'function', - function: { name: 'submit_analysis_extraction' }, - }, + tool_choice: 'auto', }, organizationId, feature: 'security-agent', @@ -394,28 +402,30 @@ export async function extractSandboxAnalysis(options: { } const message = choice.message; - const toolCall = message.tool_calls?.[0]; - - if (!toolCall || toolCall.type !== 'function') { - logError('No tool call in response', { correlationId, findingId: finding.id }); - span.setAttribute('security_agent.is_fallback', true); - return createFallbackExtraction(rawMarkdown, 'LLM did not call the extraction tool'); - } - - if (toolCall.function.name !== 'submit_analysis_extraction') { - logError('Unexpected tool call', { + const toolCall = message.tool_calls?.find( + candidate => + candidate.type === 'function' && + candidate.function.name === 'submit_analysis_extraction' + ); + const toolArguments = + toolCall?.type === 'function' ? toolCall.function.arguments : undefined; + const structuredJson = + toolArguments ?? + extractJsonContent(typeof message.content === 'string' ? message.content : null); + + if (!structuredJson) { + logError('No structured result in response', { correlationId, findingId: finding.id, - tool: toolCall.function.name, }); span.setAttribute('security_agent.is_fallback', true); return createFallbackExtraction( rawMarkdown, - `Unexpected tool: ${toolCall.function.name}` + 'LLM did not return a structured extraction result' ); } - const parsed = parseExtractionResult(toolCall.function.arguments, rawMarkdown); + const parsed = parseExtractionResult(structuredJson, rawMarkdown); if (!parsed) { span.setAttribute('security_agent.is_fallback', true); return createFallbackExtraction(rawMarkdown, 'Failed to parse extraction result'); diff --git a/apps/web/src/lib/security-agent/services/manual-analysis-client.test.ts b/apps/web/src/lib/security-agent/services/manual-analysis-client.test.ts index 5dfa967a05..6a97fa01ae 100644 --- a/apps/web/src/lib/security-agent/services/manual-analysis-client.test.ts +++ b/apps/web/src/lib/security-agent/services/manual-analysis-client.test.ts @@ -33,6 +33,7 @@ describe('submitManualAnalysisStart', () => { requestedModels: { analysisModel: 'analysis/model' }, forceSandbox: true, retrySandboxOnly: true, + restartActive: true, }) ).resolves.toEqual({ queued: true, @@ -55,6 +56,7 @@ describe('submitManualAnalysisStart', () => { requestedModels: { analysisModel: 'analysis/model' }, forceSandbox: true, retrySandboxOnly: true, + restartActive: true, }), }) ); diff --git a/apps/web/src/lib/security-agent/services/manual-analysis-client.ts b/apps/web/src/lib/security-agent/services/manual-analysis-client.ts index eeaf57ee25..ef27fdbb80 100644 --- a/apps/web/src/lib/security-agent/services/manual-analysis-client.ts +++ b/apps/web/src/lib/security-agent/services/manual-analysis-client.ts @@ -16,6 +16,7 @@ type ManualAnalysisStartParams = { }; forceSandbox?: boolean; retrySandboxOnly?: boolean; + restartActive?: boolean; }; type ManualAnalysisResponse = { @@ -51,6 +52,7 @@ export async function submitManualAnalysisStart( requestedModels: params.requestedModels, forceSandbox: params.forceSandbox, retrySandboxOnly: params.retrySandboxOnly, + restartActive: params.restartActive, }), } ); diff --git a/apps/web/src/lib/security-agent/services/manual-dismiss-client.test.ts b/apps/web/src/lib/security-agent/services/manual-dismiss-client.test.ts index 0dcb542c0a..38c2f2e449 100644 --- a/apps/web/src/lib/security-agent/services/manual-dismiss-client.test.ts +++ b/apps/web/src/lib/security-agent/services/manual-dismiss-client.test.ts @@ -13,7 +13,7 @@ describe('submitManualFindingDismissal', () => { mockFetch.mockReset(); }); - it('submits dismissal actor context and returns accepted correlation ids', async () => { + it('submits only stable dismissal actor identity and returns accepted correlation ids', async () => { mockFetch.mockResolvedValue({ ok: true, status: 202, @@ -30,7 +30,7 @@ describe('submitManualFindingDismissal', () => { await expect( submitManualFindingDismissal({ owner: { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, - actor: { id: 'user-123', email: 'owner@example.com', name: 'Owner Example' }, + actor: { id: 'user-123' }, findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', installationId: 'installation-123', reason: 'not_used', @@ -53,5 +53,10 @@ describe('submitManualFindingDismissal', () => { }, }) ); + const request = mockFetch.mock.calls[0]?.[1] as RequestInit; + expect(JSON.parse(String(request.body))).toMatchObject({ + actor: { id: 'user-123' }, + }); + expect(JSON.parse(String(request.body)).actor).toEqual({ id: 'user-123' }); }); }); diff --git a/apps/web/src/lib/security-agent/services/manual-dismiss-client.ts b/apps/web/src/lib/security-agent/services/manual-dismiss-client.ts index 9b353928ba..e869306e96 100644 --- a/apps/web/src/lib/security-agent/services/manual-dismiss-client.ts +++ b/apps/web/src/lib/security-agent/services/manual-dismiss-client.ts @@ -7,8 +7,6 @@ type ManualFindingDismissalOwner = type ManualFindingDismissalActor = { id: string; - email?: string | null; - name?: string | null; }; type DismissReason = 'fix_started' | 'no_bandwidth' | 'tolerable_risk' | 'inaccurate' | 'not_used'; diff --git a/apps/web/src/lib/security-agent/services/manual-remediation-client.test.ts b/apps/web/src/lib/security-agent/services/manual-remediation-client.test.ts new file mode 100644 index 0000000000..72c1da4ea5 --- /dev/null +++ b/apps/web/src/lib/security-agent/services/manual-remediation-client.test.ts @@ -0,0 +1,88 @@ +import { submitManualRemediationStart } from './manual-remediation-client'; + +jest.mock('@/lib/config.server', () => ({ + INTERNAL_API_SECRET: 'test-internal-secret', + SECURITY_AUTO_ANALYSIS_WORKER_URL: 'https://security-auto-analysis.test', +})); + +const mockFetch = jest.fn(); +global.fetch = mockFetch; + +describe('submitManualRemediationStart', () => { + beforeEach(() => { + mockFetch.mockReset(); + }); + + it('returns queued attempt correlation for accepted admission', async () => { + mockFetch.mockResolvedValue({ + ok: true, + status: 202, + json: () => + Promise.resolve({ + success: true, + accepted: true, + admitted: true, + remediationId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', + attemptId: 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', + attemptNumber: 1, + }), + }); + + await expect( + submitManualRemediationStart({ + findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + owner: { userId: 'user-123' }, + actorUserId: 'user-123', + }) + ).resolves.toEqual({ + queued: true, + remediationId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', + attemptId: 'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', + attemptNumber: 1, + }); + }); + + it('returns a typed policy rejection for analysis_required', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 409, + json: () => + Promise.resolve({ + success: false, + accepted: false, + admitted: false, + reason: 'analysis_required', + }), + }); + + await expect( + submitManualRemediationStart({ + findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + owner: { userId: 'user-123' }, + actorUserId: 'user-123', + }) + ).resolves.toEqual({ queued: false, reason: 'analysis_required' }); + }); + + it('returns a typed not-found rejection when the finding disappears', async () => { + mockFetch.mockResolvedValue({ + ok: false, + status: 404, + json: () => + Promise.resolve({ + success: false, + accepted: false, + admitted: false, + reason: 'finding_not_found', + }), + }); + + await expect( + submitManualRemediationStart({ + findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + owner: { userId: 'user-123' }, + actorUserId: 'user-123', + }) + ).resolves.toEqual({ queued: false, reason: 'finding_not_found' }); + }); +}); diff --git a/apps/web/src/lib/security-agent/services/manual-remediation-client.ts b/apps/web/src/lib/security-agent/services/manual-remediation-client.ts index 4ba74042d6..c618c43fed 100644 --- a/apps/web/src/lib/security-agent/services/manual-remediation-client.ts +++ b/apps/web/src/lib/security-agent/services/manual-remediation-client.ts @@ -1,5 +1,10 @@ import 'server-only'; import { INTERNAL_API_SECRET, SECURITY_AUTO_ANALYSIS_WORKER_URL } from '@/lib/config.server'; +import { + SECURITY_REMEDIATION_ADMISSION_REJECTION_REASONS, + type SecurityRemediationAdmissionRejectionReason, +} from '@kilocode/worker-utils/security-remediation-policy'; +import { z } from 'zod'; type RemediationOwner = | { organizationId: string; userId?: never } @@ -24,16 +29,36 @@ type ApplyAutoRemediationParams = { actorUserId: string; }; -type ManualRemediationStartResponse = { - success?: boolean; - accepted?: boolean; - admitted?: boolean; - remediationId?: string; - attemptId?: string; - attemptNumber?: number; - reason?: string; - error?: string; -}; +const ManualRemediationStartResponseSchema = z.discriminatedUnion('admitted', [ + z.object({ + success: z.literal(true), + accepted: z.literal(true), + admitted: z.literal(true), + remediationId: z.string(), + attemptId: z.string(), + attemptNumber: z.number(), + }), + z.object({ + success: z.literal(false), + accepted: z.literal(false), + admitted: z.literal(false), + reason: z.enum(SECURITY_REMEDIATION_ADMISSION_REJECTION_REASONS), + }), +]); + +const WorkerErrorResponseSchema = z.object({ error: z.string() }); + +export type ManualRemediationStartResult = + | { + queued: true; + remediationId: string; + attemptId: string; + attemptNumber: number; + } + | { + queued: false; + reason: SecurityRemediationAdmissionRejectionReason; + }; type RemediationCancellationResponse = { success?: boolean; @@ -61,12 +86,9 @@ async function parseJsonResponse(response: Response): Promise { return (await response.json()) as T; } -export async function submitManualRemediationStart(params: ManualRemediationStartParams): Promise<{ - queued: true; - remediationId: string; - attemptId: string; - attemptNumber: number; -}> { +export async function submitManualRemediationStart( + params: ManualRemediationStartParams +): Promise { requireWorkerConfig(); const response = await fetch(`${SECURITY_AUTO_ANALYSIS_WORKER_URL}/internal/remediation/start`, { @@ -83,27 +105,30 @@ export async function submitManualRemediationStart(params: ManualRemediationStar retry: params.retry, }), }); - const body = await parseJsonResponse(response); + const body: unknown = await response.json(); + const parsedBody = ManualRemediationStartResponseSchema.safeParse(body); if (!response.ok) { + if (parsedBody.success && !parsedBody.data.admitted) { + const expectedStatus = parsedBody.data.reason === 'finding_not_found' ? 404 : 409; + if (response.status === expectedStatus) { + return { queued: false, reason: parsedBody.data.reason }; + } + } + const parsedError = WorkerErrorResponseSchema.safeParse(body); throw new Error( - body.reason ?? body.error ?? `Remediation request failed with ${response.status}` + parsedError.success + ? parsedError.data.error + : `Remediation request failed with ${response.status}` ); } - if ( - body.success !== true || - body.accepted !== true || - body.admitted !== true || - typeof body.remediationId !== 'string' || - typeof body.attemptId !== 'string' || - typeof body.attemptNumber !== 'number' - ) { + if (!parsedBody.success || !parsedBody.data.admitted) { throw new Error('Security remediation Worker returned an invalid accepted response'); } return { queued: true, - remediationId: body.remediationId, - attemptId: body.attemptId, - attemptNumber: body.attemptNumber, + remediationId: parsedBody.data.remediationId, + attemptId: parsedBody.data.attemptId, + attemptNumber: parsedBody.data.attemptNumber, }; } diff --git a/apps/web/src/lib/security-agent/services/triage-service.ts b/apps/web/src/lib/security-agent/services/triage-service.ts index 17adc7baf9..6b22954c3c 100644 --- a/apps/web/src/lib/security-agent/services/triage-service.ts +++ b/apps/web/src/lib/security-agent/services/triage-service.ts @@ -126,7 +126,16 @@ function buildTriagePrompt(finding: SecurityFinding): string { ${rawData ? `**Additional Context**: ${JSON.stringify(rawData, null, 2).slice(0, 1000)}` : ''} -Please analyze this vulnerability and call the submit_triage_result tool with your assessment.`; +Please analyze this vulnerability and call the submit_triage_result tool with your assessment. If tool calls are unavailable, return only a JSON object matching the tool parameters, without markdown or prose.`; +} + +function extractJsonContent(content: string | null): string | null { + const trimmed = content?.trim(); + if (!trimmed) return null; + if (trimmed.startsWith('{') && trimmed.endsWith('}')) return trimmed; + + const fencedJson = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```$/i); + return fencedJson?.[1]?.trim() || null; } /** @@ -243,10 +252,7 @@ export async function triageSecurityFinding(options: { model, messages, tools: [SUBMIT_TRIAGE_TOOL], - tool_choice: { - type: 'function', - function: { name: 'submit_triage_result' }, - }, + tool_choice: 'auto', }, organizationId, feature: 'security-agent', @@ -371,25 +377,26 @@ export async function triageSecurityFinding(options: { } const message = choice.message; - const toolCall = message.tool_calls?.[0]; - - if (!toolCall || toolCall.type !== 'function') { - logError('No tool call in response', { correlationId, findingId: finding.id }); - span.setAttribute('security_agent.is_fallback', true); - return createFallbackTriage('LLM did not call the triage tool'); - } - - if (toolCall.function.name !== 'submit_triage_result') { - logError('Unexpected tool call', { + const toolCall = message.tool_calls?.find( + candidate => + candidate.type === 'function' && candidate.function.name === 'submit_triage_result' + ); + const toolArguments = + toolCall?.type === 'function' ? toolCall.function.arguments : undefined; + const structuredJson = + toolArguments ?? + extractJsonContent(typeof message.content === 'string' ? message.content : null); + + if (!structuredJson) { + logError('No structured result in response', { correlationId, findingId: finding.id, - tool: toolCall.function.name, }); span.setAttribute('security_agent.is_fallback', true); - return createFallbackTriage(`Unexpected tool: ${toolCall.function.name}`); + return createFallbackTriage('LLM did not return a structured triage result'); } - const parsed = parseTriageResult(toolCall.function.arguments); + const parsed = parseTriageResult(structuredJson); if (!parsed) { span.setAttribute('security_agent.is_fallback', true); return createFallbackTriage('Failed to parse triage result'); diff --git a/apps/web/src/lib/user/index.test.ts b/apps/web/src/lib/user/index.test.ts index 0daff18a09..ffa6c60dc5 100644 --- a/apps/web/src/lib/user/index.test.ts +++ b/apps/web/src/lib/user/index.test.ts @@ -126,6 +126,7 @@ import { import { SecurityAuditLogAction } from '@/lib/security-agent/core/enums'; import { recordAffiliateAttributionAndQueueParentEvent } from '@/lib/impact/affiliate-events'; import { + SecurityAuditLogActorType, SecurityFindingNotificationKind, SecurityFindingNotificationStatus, } from '@kilocode/db/schema-types'; @@ -1632,6 +1633,7 @@ describe('User', () => { actor_id: user.id, actor_email: user.google_user_email, actor_name: user.google_user_name, + actor_type: SecurityAuditLogActorType.CustomerUser, action: SecurityAuditLogAction.FindingDismissed, resource_type: 'security_finding', resource_id: randomUUID(), @@ -1647,6 +1649,7 @@ describe('User', () => { expect(logs[0].actor_email).toBeNull(); expect(logs[0].actor_name).toBeNull(); expect(logs[0].actor_id).toBe(user.id); // actor_id preserved + expect(logs[0].actor_type).toBe(SecurityAuditLogActorType.CustomerUser); expect(logs[0].action).toBe(SecurityAuditLogAction.FindingDismissed); // action preserved }); diff --git a/apps/web/src/routers/organizations/organization-security-agent-router.ts b/apps/web/src/routers/organizations/organization-security-agent-router.ts index 0911f416fc..61e5ac2544 100644 --- a/apps/web/src/routers/organizations/organization-security-agent-router.ts +++ b/apps/web/src/routers/organizations/organization-security-agent-router.ts @@ -2,6 +2,7 @@ import { createTRPCRouter } from '@/lib/trpc/init'; import { organizationMemberProcedure, organizationMemberMutationProcedure, + organizationBillingProcedure, organizationBillingMutationProcedure, OrganizationIdInputSchema, } from './utils'; @@ -29,6 +30,9 @@ const handlers = createSecurityAgentHandlers<{ organizationId: string }>({ }); export const organizationSecurityAgentRouter = createTRPCRouter({ + trackUiInteraction: organizationMemberProcedure + .input(OrganizationIdInputSchema.merge(handlers.trackUiInteraction.inputSchema)) + .mutation(handlers.trackUiInteraction.handler), getPermissionStatus: organizationMemberProcedure.query(handlers.getPermissionStatus), getConfig: organizationMemberProcedure.query(handlers.getConfig), saveConfig: organizationBillingMutationProcedure @@ -82,4 +86,7 @@ export const organizationSecurityAgentRouter = createTRPCRouter({ .mutation(handlers.deleteFindingsByRepository.handler), getAutoDismissEligible: organizationMemberProcedure.query(handlers.getAutoDismissEligible), autoDismissEligible: organizationBillingMutationProcedure.mutation(handlers.autoDismissEligible), + getAuditReport: organizationBillingProcedure + .input(OrganizationIdInputSchema.merge(handlers.getAuditReport.inputSchema)) + .query(handlers.getAuditReport.handler), }); diff --git a/apps/web/src/routers/security-agent-router.ts b/apps/web/src/routers/security-agent-router.ts index 90ada69387..ec7ce67e39 100644 --- a/apps/web/src/routers/security-agent-router.ts +++ b/apps/web/src/routers/security-agent-router.ts @@ -21,6 +21,9 @@ const handlers = createSecurityAgentHandlers({ }); export const securityAgentRouter = createTRPCRouter({ + trackUiInteraction: baseProcedure + .input(handlers.trackUiInteraction.inputSchema) + .mutation(handlers.trackUiInteraction.handler), getPermissionStatus: baseProcedure.query(handlers.getPermissionStatus), getConfig: baseProcedure.query(handlers.getConfig), saveConfig: baseProcedure @@ -74,4 +77,7 @@ export const securityAgentRouter = createTRPCRouter({ .mutation(handlers.deleteFindingsByRepository.handler), getAutoDismissEligible: baseProcedure.query(handlers.getAutoDismissEligible), autoDismissEligible: baseProcedure.mutation(handlers.autoDismissEligible), + getAuditReport: baseProcedure + .input(handlers.getAuditReport.inputSchema) + .query(handlers.getAuditReport.handler), }); diff --git a/packages/db/src/migrations/0166_curved_praxagora.sql b/packages/db/src/migrations/0166_curved_praxagora.sql new file mode 100644 index 0000000000..cb8cf7b4bf --- /dev/null +++ b/packages/db/src/migrations/0166_curved_praxagora.sql @@ -0,0 +1,16 @@ +ALTER TABLE "security_audit_log" DROP CONSTRAINT "security_audit_log_action_check";--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "actor_type" text;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "finding_id" uuid;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "occurred_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "source_occurred_at" timestamp with time zone;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "event_key" text;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "schema_version" smallint;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "finding_snapshot" jsonb;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD COLUMN "source_context" text;--> statement-breakpoint +CREATE UNIQUE INDEX "UQ_security_audit_log_org_event_key" ON "security_audit_log" USING btree ("owned_by_organization_id","event_key") WHERE "security_audit_log"."owned_by_organization_id" IS NOT NULL AND "security_audit_log"."event_key" IS NOT NULL;--> statement-breakpoint +CREATE UNIQUE INDEX "UQ_security_audit_log_user_event_key" ON "security_audit_log" USING btree ("owned_by_user_id","event_key") WHERE "security_audit_log"."owned_by_user_id" IS NOT NULL AND "security_audit_log"."event_key" IS NOT NULL;--> statement-breakpoint +CREATE INDEX "IDX_security_audit_log_org_occurred" ON "security_audit_log" USING btree ("owned_by_organization_id","occurred_at","id") WHERE "security_audit_log"."owned_by_organization_id" IS NOT NULL AND "security_audit_log"."occurred_at" IS NOT NULL;--> statement-breakpoint +CREATE INDEX "IDX_security_audit_log_user_occurred" ON "security_audit_log" USING btree ("owned_by_user_id","occurred_at","id") WHERE "security_audit_log"."owned_by_user_id" IS NOT NULL AND "security_audit_log"."occurred_at" IS NOT NULL;--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD CONSTRAINT "security_audit_log_actor_type_check" CHECK ("security_audit_log"."actor_type" IN ('customer_user', 'kilo_admin', 'system'));--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD CONSTRAINT "security_audit_log_source_context_check" CHECK ("security_audit_log"."source_context" IN ('security_sync', 'web', 'analysis_worker', 'remediation_callback', 'rollout_baseline'));--> statement-breakpoint +ALTER TABLE "security_audit_log" ADD CONSTRAINT "security_audit_log_action_check" CHECK ("security_audit_log"."action" IN ('security.finding.created', 'security.finding.severity_changed', 'security.finding.status_change', 'security.finding.dismissed', 'security.finding.auto_dismissed', 'security.finding.superseded', 'security.finding.analysis_started', 'security.finding.analysis_completed', 'security.finding.analysis_failed', 'security.remediation.queued', 'security.remediation.started', 'security.remediation.pr_opened', 'security.remediation.failed', 'security.remediation.blocked', 'security.remediation.no_changes_needed', 'security.remediation.cancelled', 'security.remediation.retried', 'security.finding.deleted', 'security.config.enabled', 'security.config.disabled', 'security.config.updated', 'security.sync.triggered', 'security.sync.completed', 'security.audit_log.exported', 'security.audit_report.generated')); \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0166_snapshot.json b/packages/db/src/migrations/meta/0166_snapshot.json new file mode 100644 index 0000000000..91fed1db5d --- /dev/null +++ b/packages/db/src/migrations/meta/0166_snapshot.json @@ -0,0 +1,31219 @@ +{ + "id": "b7f2a811-fee2-47a4-83ab-eced6a28adba", + "prevId": "1b09f3ca-b736-46b2-b352-3e9ffe7c04a2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_configs": { + "name": "agent_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_type": { + "name": "agent_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "runtime_state": { + "name": "runtime_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_configs_org_id": { + "name": "IDX_agent_configs_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_owned_by_user_id": { + "name": "IDX_agent_configs_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_agent_type": { + "name": "IDX_agent_configs_agent_type", + "columns": [ + { + "expression": "agent_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_platform": { + "name": "IDX_agent_configs_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_configs_owned_by_organization_id_organizations_id_fk": { + "name": "agent_configs_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_configs", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_configs_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_configs_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_configs_org_agent_platform": { + "name": "UQ_agent_configs_org_agent_platform", + "nullsNotDistinct": false, + "columns": [ + "owned_by_organization_id", + "agent_type", + "platform" + ] + }, + "UQ_agent_configs_user_agent_platform": { + "name": "UQ_agent_configs_user_agent_platform", + "nullsNotDistinct": false, + "columns": [ + "owned_by_user_id", + "agent_type", + "platform" + ] + } + }, + "policies": {}, + "checkConstraints": { + "agent_configs_owner_check": { + "name": "agent_configs_owner_check", + "value": "(\n (\"agent_configs\".\"owned_by_user_id\" IS NOT NULL AND \"agent_configs\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_configs\".\"owned_by_user_id\" IS NULL AND \"agent_configs\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "agent_configs_agent_type_check": { + "name": "agent_configs_agent_type_check", + "value": "\"agent_configs\".\"agent_type\" IN ('code_review', 'auto_triage', 'auto_fix', 'security_scan')" + } + }, + "isRLSEnabled": false + }, + "public.agent_environment_profile_agents": { + "name": "agent_environment_profile_agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_agents_profile_id": { + "name": "IDX_agent_env_profile_agents_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_agents_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_agents_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_agents", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_agents_profile_slug": { + "name": "UQ_agent_env_profile_agents_profile_slug", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_commands": { + "name": "agent_environment_profile_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_commands_profile_id": { + "name": "IDX_agent_env_profile_commands_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_commands_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_commands_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_commands", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_commands_profile_sequence": { + "name": "UQ_agent_env_profile_commands_profile_sequence", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "sequence" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_kilo_commands": { + "name": "agent_environment_profile_kilo_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subtask": { + "name": "subtask", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_kilo_cmds_profile_id": { + "name": "IDX_agent_env_profile_kilo_cmds_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_kilo_commands_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_kilo_commands_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_kilo_commands", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_kilo_cmds_profile_name": { + "name": "UQ_agent_env_profile_kilo_cmds_profile_name", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_mcp_servers": { + "name": "agent_environment_profile_mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_mcp_servers_profile_id": { + "name": "IDX_agent_env_profile_mcp_servers_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_mcp_servers_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_mcp_servers_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_mcp_servers", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_mcp_servers_profile_name": { + "name": "UQ_agent_env_profile_mcp_servers_profile_name", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_repo_bindings": { + "name": "agent_environment_profile_repo_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_agent_env_profile_repo_bindings_user": { + "name": "UQ_agent_env_profile_repo_bindings_user", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profile_repo_bindings_org": { + "name": "UQ_agent_env_profile_repo_bindings_org", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_repo_bindings_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_repo_bindings_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profile_repo_bindings_owned_by_organization_id_organizations_id_fk": { + "name": "agent_environment_profile_repo_bindings_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profile_repo_bindings_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_environment_profile_repo_bindings_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_env_profile_repo_bindings_owner_check": { + "name": "agent_env_profile_repo_bindings_owner_check", + "value": "(\n (\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" IS NOT NULL AND \"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" IS NULL AND \"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.agent_environment_profile_skills": { + "name": "agent_environment_profile_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "raw_markdown": { + "name": "raw_markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_skills_profile_id": { + "name": "IDX_agent_env_profile_skills_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_skills_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_skills_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_skills", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_skills_profile_name": { + "name": "UQ_agent_env_profile_skills_profile_name", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_vars": { + "name": "agent_environment_profile_vars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_secret": { + "name": "is_secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_vars_profile_id": { + "name": "IDX_agent_env_profile_vars_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_vars_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_vars_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_vars", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_vars_profile_key": { + "name": "UQ_agent_env_profile_vars_profile_key", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profiles": { + "name": "agent_environment_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_agent_env_profiles_org_name": { + "name": "UQ_agent_env_profiles_org_name", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_user_name": { + "name": "UQ_agent_env_profiles_user_name", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_org_default": { + "name": "UQ_agent_env_profiles_org_default", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"is_default\" = true AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_user_default": { + "name": "UQ_agent_env_profiles_user_default", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"is_default\" = true AND \"agent_environment_profiles\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_org_id": { + "name": "IDX_agent_env_profiles_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_user_id": { + "name": "IDX_agent_env_profiles_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_created_by_user_id": { + "name": "IDX_agent_env_profiles_created_by_user_id", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profiles_owned_by_organization_id_organizations_id_fk": { + "name": "agent_environment_profiles_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_environment_profiles", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profiles_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_environment_profiles_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_environment_profiles", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_env_profiles_owner_check": { + "name": "agent_env_profiles_owner_check", + "value": "(\n (\"agent_environment_profiles\".\"owned_by_user_id\" IS NOT NULL AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_environment_profiles\".\"owned_by_user_id\" IS NULL AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.api_kind": { + "name": "api_kind", + "schema": "", + "columns": { + "api_kind_id": { + "name": "api_kind_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "api_kind": { + "name": "api_kind", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_api_kind": { + "name": "UQ_api_kind", + "columns": [ + { + "expression": "api_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_request_log": { + "name": "api_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_api_request_log_created_at": { + "name": "idx_api_request_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_feedback": { + "name": "app_builder_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_status": { + "name": "preview_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_streaming": { + "name": "is_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recent_messages": { + "name": "recent_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_app_builder_feedback_created_at": { + "name": "IDX_app_builder_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_feedback_kilo_user_id": { + "name": "IDX_app_builder_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_feedback_project_id": { + "name": "IDX_app_builder_feedback_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "app_builder_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "app_builder_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "app_builder_feedback_project_id_app_builder_projects_id_fk": { + "name": "app_builder_feedback_project_id_app_builder_projects_id_fk", + "tableFrom": "app_builder_feedback", + "tableTo": "app_builder_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_project_sessions": { + "name": "app_builder_project_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "worker_version": { + "name": "worker_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'v2'" + } + }, + "indexes": { + "IDX_app_builder_project_sessions_project_id": { + "name": "IDX_app_builder_project_sessions_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_project_sessions_project_id_app_builder_projects_id_fk": { + "name": "app_builder_project_sessions_project_id_app_builder_projects_id_fk", + "tableFrom": "app_builder_project_sessions", + "tableTo": "app_builder_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_app_builder_project_sessions_cloud_agent_session_id": { + "name": "UQ_app_builder_project_sessions_cloud_agent_session_id", + "nullsNotDistinct": false, + "columns": [ + "cloud_agent_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_projects": { + "name": "app_builder_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "git_repo_full_name": { + "name": "git_repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_platform_integration_id": { + "name": "git_platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "migrated_at": { + "name": "migrated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_app_builder_projects_created_by_user_id": { + "name": "IDX_app_builder_projects_created_by_user_id", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_owned_by_user_id": { + "name": "IDX_app_builder_projects_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_owned_by_organization_id": { + "name": "IDX_app_builder_projects_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_created_at": { + "name": "IDX_app_builder_projects_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_last_message_at": { + "name": "IDX_app_builder_projects_last_message_at", + "columns": [ + { + "expression": "last_message_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_git_repo_integration": { + "name": "IDX_app_builder_projects_git_repo_integration", + "columns": [ + { + "expression": "git_repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"app_builder_projects\".\"git_repo_full_name\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_projects_owned_by_user_id_kilocode_users_id_fk": { + "name": "app_builder_projects_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "app_builder_projects_owned_by_organization_id_organizations_id_fk": { + "name": "app_builder_projects_owned_by_organization_id_organizations_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "app_builder_projects_deployment_id_deployments_id_fk": { + "name": "app_builder_projects_deployment_id_deployments_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "app_builder_projects_git_platform_integration_id_platform_integrations_id_fk": { + "name": "app_builder_projects_git_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "platform_integrations", + "columnsFrom": [ + "git_platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "app_builder_projects_owner_check": { + "name": "app_builder_projects_owner_check", + "value": "(\n (\"app_builder_projects\".\"owned_by_user_id\" IS NOT NULL AND \"app_builder_projects\".\"owned_by_organization_id\" IS NULL) OR\n (\"app_builder_projects\".\"owned_by_user_id\" IS NULL AND \"app_builder_projects\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.app_min_versions": { + "name": "app_min_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ios_min_version": { + "name": "ios_min_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "android_min_version": { + "name": "android_min_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_reported_messages": { + "name": "app_reported_messages", + "schema": "", + "columns": { + "report_id": { + "name": "report_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signature": { + "name": "signature", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "app_reported_messages_cli_session_id_cli_sessions_session_id_fk": { + "name": "app_reported_messages_cli_session_id_cli_sessions_session_id_fk", + "tableFrom": "app_reported_messages", + "tableTo": "cli_sessions", + "columnsFrom": [ + "cli_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_fix_tickets": { + "name": "auto_fix_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "triage_ticket_id": { + "name": "triage_ticket_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_url": { + "name": "issue_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_body": { + "name": "issue_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_author": { + "name": "issue_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_labels": { + "name": "issue_labels", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "trigger_source": { + "name": "trigger_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'label'" + }, + "review_comment_id": { + "name": "review_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "review_comment_body": { + "name": "review_comment_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "diff_hunk": { + "name": "diff_hunk", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_head_ref": { + "name": "pr_head_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "intent_summary": { + "name": "intent_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_files": { + "name": "related_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_branch": { + "name": "pr_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_fix_tickets_repo_issue": { + "name": "UQ_auto_fix_tickets_repo_issue", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_fix_tickets\".\"trigger_source\" = 'label'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_auto_fix_tickets_repo_review_comment": { + "name": "UQ_auto_fix_tickets_repo_review_comment", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "review_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_fix_tickets\".\"review_comment_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_owned_by_org": { + "name": "IDX_auto_fix_tickets_owned_by_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_owned_by_user": { + "name": "IDX_auto_fix_tickets_owned_by_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_status": { + "name": "IDX_auto_fix_tickets_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_created_at": { + "name": "IDX_auto_fix_tickets_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_triage_ticket_id": { + "name": "IDX_auto_fix_tickets_triage_ticket_id", + "columns": [ + { + "expression": "triage_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_session_id": { + "name": "IDX_auto_fix_tickets_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_fix_tickets_owned_by_organization_id_organizations_id_fk": { + "name": "auto_fix_tickets_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_fix_tickets_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_fix_tickets_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_fix_tickets_platform_integration_id_platform_integrations_id_fk": { + "name": "auto_fix_tickets_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_fix_tickets_triage_ticket_id_auto_triage_tickets_id_fk": { + "name": "auto_fix_tickets_triage_ticket_id_auto_triage_tickets_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "auto_triage_tickets", + "columnsFrom": [ + "triage_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_fix_tickets_cli_session_id_cli_sessions_session_id_fk": { + "name": "auto_fix_tickets_cli_session_id_cli_sessions_session_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "cli_sessions", + "columnsFrom": [ + "cli_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_fix_tickets_owner_check": { + "name": "auto_fix_tickets_owner_check", + "value": "(\n (\"auto_fix_tickets\".\"owned_by_user_id\" IS NOT NULL AND \"auto_fix_tickets\".\"owned_by_organization_id\" IS NULL) OR\n (\"auto_fix_tickets\".\"owned_by_user_id\" IS NULL AND \"auto_fix_tickets\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "auto_fix_tickets_status_check": { + "name": "auto_fix_tickets_status_check", + "value": "\"auto_fix_tickets\".\"status\" IN ('pending', 'running', 'completed', 'failed', 'cancelled')" + }, + "auto_fix_tickets_classification_check": { + "name": "auto_fix_tickets_classification_check", + "value": "\"auto_fix_tickets\".\"classification\" IN ('bug', 'feature', 'question', 'unclear')" + }, + "auto_fix_tickets_confidence_check": { + "name": "auto_fix_tickets_confidence_check", + "value": "\"auto_fix_tickets\".\"confidence\" >= 0 AND \"auto_fix_tickets\".\"confidence\" <= 1" + }, + "auto_fix_tickets_trigger_source_check": { + "name": "auto_fix_tickets_trigger_source_check", + "value": "\"auto_fix_tickets\".\"trigger_source\" IN ('label', 'review_comment')" + } + }, + "isRLSEnabled": false + }, + "public.auto_model": { + "name": "auto_model", + "schema": "", + "columns": { + "auto_model_id": { + "name": "auto_model_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "auto_model": { + "name": "auto_model", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_auto_model": { + "name": "UQ_auto_model", + "columns": [ + { + "expression": "auto_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_top_up_configs": { + "name": "auto_top_up_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_method_id": { + "name": "stripe_payment_method_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5000 + }, + "last_auto_top_up_at": { + "name": "last_auto_top_up_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempt_started_at": { + "name": "attempt_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_top_up_configs_owned_by_user_id": { + "name": "UQ_auto_top_up_configs_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_top_up_configs\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_auto_top_up_configs_owned_by_organization_id": { + "name": "UQ_auto_top_up_configs_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_top_up_configs\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_top_up_configs_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_top_up_configs_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_top_up_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "auto_top_up_configs_owned_by_organization_id_organizations_id_fk": { + "name": "auto_top_up_configs_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_top_up_configs", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_top_up_configs_exactly_one_owner": { + "name": "auto_top_up_configs_exactly_one_owner", + "value": "(\"auto_top_up_configs\".\"owned_by_user_id\" IS NOT NULL AND \"auto_top_up_configs\".\"owned_by_organization_id\" IS NULL) OR (\"auto_top_up_configs\".\"owned_by_user_id\" IS NULL AND \"auto_top_up_configs\".\"owned_by_organization_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.auto_triage_tickets": { + "name": "auto_triage_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_url": { + "name": "issue_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_body": { + "name": "issue_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_author": { + "name": "issue_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_type": { + "name": "issue_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_labels": { + "name": "issue_labels", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "intent_summary": { + "name": "intent_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_files": { + "name": "related_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_duplicate": { + "name": "is_duplicate", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duplicate_of_ticket_id": { + "name": "duplicate_of_ticket_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "similarity_score": { + "name": "similarity_score", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "qdrant_point_id": { + "name": "qdrant_point_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "should_auto_fix": { + "name": "should_auto_fix", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "action_taken": { + "name": "action_taken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_metadata": { + "name": "action_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_triage_tickets_repo_issue": { + "name": "UQ_auto_triage_tickets_repo_issue", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owned_by_org": { + "name": "IDX_auto_triage_tickets_owned_by_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owned_by_user": { + "name": "IDX_auto_triage_tickets_owned_by_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_status": { + "name": "IDX_auto_triage_tickets_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_created_at": { + "name": "IDX_auto_triage_tickets_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_qdrant_point_id": { + "name": "IDX_auto_triage_tickets_qdrant_point_id", + "columns": [ + { + "expression": "qdrant_point_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owner_status_created": { + "name": "IDX_auto_triage_tickets_owner_status_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_user_status_created": { + "name": "IDX_auto_triage_tickets_user_status_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_repo_classification": { + "name": "IDX_auto_triage_tickets_repo_classification", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "classification", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_triage_tickets_owned_by_organization_id_organizations_id_fk": { + "name": "auto_triage_tickets_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_triage_tickets_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_triage_tickets_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_triage_tickets_platform_integration_id_platform_integrations_id_fk": { + "name": "auto_triage_tickets_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_triage_tickets_duplicate_of_ticket_id_auto_triage_tickets_id_fk": { + "name": "auto_triage_tickets_duplicate_of_ticket_id_auto_triage_tickets_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "auto_triage_tickets", + "columnsFrom": [ + "duplicate_of_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_triage_tickets_owner_check": { + "name": "auto_triage_tickets_owner_check", + "value": "(\n (\"auto_triage_tickets\".\"owned_by_user_id\" IS NOT NULL AND \"auto_triage_tickets\".\"owned_by_organization_id\" IS NULL) OR\n (\"auto_triage_tickets\".\"owned_by_user_id\" IS NULL AND \"auto_triage_tickets\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "auto_triage_tickets_issue_type_check": { + "name": "auto_triage_tickets_issue_type_check", + "value": "\"auto_triage_tickets\".\"issue_type\" IN ('issue', 'pull_request')" + }, + "auto_triage_tickets_classification_check": { + "name": "auto_triage_tickets_classification_check", + "value": "\"auto_triage_tickets\".\"classification\" IN ('bug', 'feature', 'question', 'duplicate', 'unclear')" + }, + "auto_triage_tickets_confidence_check": { + "name": "auto_triage_tickets_confidence_check", + "value": "\"auto_triage_tickets\".\"confidence\" >= 0 AND \"auto_triage_tickets\".\"confidence\" <= 1" + }, + "auto_triage_tickets_similarity_score_check": { + "name": "auto_triage_tickets_similarity_score_check", + "value": "\"auto_triage_tickets\".\"similarity_score\" >= 0 AND \"auto_triage_tickets\".\"similarity_score\" <= 1" + }, + "auto_triage_tickets_status_check": { + "name": "auto_triage_tickets_status_check", + "value": "\"auto_triage_tickets\".\"status\" IN ('pending', 'analyzing', 'actioned', 'failed', 'skipped')" + }, + "auto_triage_tickets_action_taken_check": { + "name": "auto_triage_tickets_action_taken_check", + "value": "\"auto_triage_tickets\".\"action_taken\" IN ('pr_created', 'comment_posted', 'closed_duplicate', 'needs_clarification')" + } + }, + "isRLSEnabled": false + }, + "public.bot_request_cloud_agent_sessions": { + "name": "bot_request_cloud_agent_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "bot_request_id": { + "name": "bot_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "spawn_group_id": { + "name": "spawn_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repo": { + "name": "github_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlab_project": { + "name": "gitlab_project", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "callback_step": { + "name": "callback_step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "final_message": { + "name": "final_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "final_message_fetched_at": { + "name": "final_message_fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "final_message_error": { + "name": "final_message_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "continuation_started_at": { + "name": "continuation_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_bot_request_cas_cloud_agent_session_id": { + "name": "UQ_bot_request_cas_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id": { + "name": "IDX_bot_request_cas_bot_request_id", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id_spawn_group_id": { + "name": "IDX_bot_request_cas_bot_request_id_spawn_group_id", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spawn_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id_spawn_group_id_status": { + "name": "IDX_bot_request_cas_bot_request_id_spawn_group_id_status", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spawn_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_request_cloud_agent_sessions_bot_request_id_bot_requests_id_fk": { + "name": "bot_request_cloud_agent_sessions_bot_request_id_bot_requests_id_fk", + "tableFrom": "bot_request_cloud_agent_sessions", + "tableTo": "bot_requests", + "columnsFrom": [ + "bot_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_requests": { + "name": "bot_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_thread_id": { + "name": "platform_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_message_id": { + "name": "platform_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message": { + "name": "user_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steps": { + "name": "steps", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_bot_requests_created_at": { + "name": "IDX_bot_requests_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_created_by": { + "name": "IDX_bot_requests_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_organization_id": { + "name": "IDX_bot_requests_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_platform_integration_id": { + "name": "IDX_bot_requests_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_status": { + "name": "IDX_bot_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_requests_created_by_kilocode_users_id_fk": { + "name": "bot_requests_created_by_kilocode_users_id_fk", + "tableFrom": "bot_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_requests_organization_id_organizations_id_fk": { + "name": "bot_requests_organization_id_organizations_id_fk", + "tableFrom": "bot_requests", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_requests_platform_integration_id_platform_integrations_id_fk": { + "name": "bot_requests_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "bot_requests", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.byok_api_keys": { + "name": "byok_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "management_source": { + "name": "management_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_byok_api_keys_organization_id": { + "name": "IDX_byok_api_keys_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_byok_api_keys_kilo_user_id": { + "name": "IDX_byok_api_keys_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_byok_api_keys_provider_id": { + "name": "IDX_byok_api_keys_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "byok_api_keys_organization_id_organizations_id_fk": { + "name": "byok_api_keys_organization_id_organizations_id_fk", + "tableFrom": "byok_api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "byok_api_keys_kilo_user_id_kilocode_users_id_fk": { + "name": "byok_api_keys_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "byok_api_keys", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_byok_api_keys_org_provider": { + "name": "UQ_byok_api_keys_org_provider", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider_id" + ] + }, + "UQ_byok_api_keys_user_provider": { + "name": "UQ_byok_api_keys_user_provider", + "nullsNotDistinct": false, + "columns": [ + "kilo_user_id", + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "byok_api_keys_management_source_check": { + "name": "byok_api_keys_management_source_check", + "value": "\"byok_api_keys\".\"management_source\" IN ('user', 'coding_plan')" + }, + "byok_api_keys_owner_check": { + "name": "byok_api_keys_owner_check", + "value": "(\n (\"byok_api_keys\".\"kilo_user_id\" IS NOT NULL AND \"byok_api_keys\".\"organization_id\" IS NULL) OR\n (\"byok_api_keys\".\"kilo_user_id\" IS NULL AND \"byok_api_keys\".\"organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.cli_sessions": { + "name": "cli_sessions", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_on_platform": { + "name": "created_on_platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "api_conversation_history_blob_url": { + "name": "api_conversation_history_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_metadata_blob_url": { + "name": "task_metadata_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ui_messages_blob_url": { + "name": "ui_messages_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_state_blob_url": { + "name": "git_state_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from": { + "name": "forked_from", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_mode": { + "name": "last_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cli_sessions_kilo_user_id": { + "name": "IDX_cli_sessions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_created_at": { + "name": "IDX_cli_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_updated_at": { + "name": "IDX_cli_sessions_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_organization_id": { + "name": "IDX_cli_sessions_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_user_updated": { + "name": "IDX_cli_sessions_user_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "cli_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cli_sessions_forked_from_cli_sessions_session_id_fk": { + "name": "cli_sessions_forked_from_cli_sessions_session_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "forked_from" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_parent_session_id_cli_sessions_session_id_fk": { + "name": "cli_sessions_parent_session_id_cli_sessions_session_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_organization_id_organizations_id_fk": { + "name": "cli_sessions_organization_id_organizations_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cli_sessions_cloud_agent_session_id_unique": { + "name": "cli_sessions_cloud_agent_session_id_unique", + "nullsNotDistinct": false, + "columns": [ + "cloud_agent_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_sessions_v2": { + "name": "cli_sessions_v2", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_on_platform": { + "name": "created_on_platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_updated_at": { + "name": "status_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cli_sessions_v2_parent_session_id_kilo_user_id": { + "name": "IDX_cli_sessions_v2_parent_session_id_kilo_user_id", + "columns": [ + { + "expression": "parent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cli_sessions_v2_public_id": { + "name": "UQ_cli_sessions_v2_public_id", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_sessions_v2\".\"public_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cli_sessions_v2_cloud_agent_session_id": { + "name": "UQ_cli_sessions_v2_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_sessions_v2\".\"cloud_agent_session_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_organization_id": { + "name": "IDX_cli_sessions_v2_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_kilo_user_id": { + "name": "IDX_cli_sessions_v2_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_created_at": { + "name": "IDX_cli_sessions_v2_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_user_updated": { + "name": "IDX_cli_sessions_v2_user_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "cli_sessions_v2_git_url_branch_idx": { + "name": "cli_sessions_v2_git_url_branch_idx", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_sessions_v2_kilo_user_id_kilocode_users_id_fk": { + "name": "cli_sessions_v2_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cli_sessions_v2_organization_id_organizations_id_fk": { + "name": "cli_sessions_v2_organization_id_organizations_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_v2_parent_session_id_kilo_user_id_fk": { + "name": "cli_sessions_v2_parent_session_id_kilo_user_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "cli_sessions_v2", + "columnsFrom": [ + "parent_session_id", + "kilo_user_id" + ], + "columnsTo": [ + "session_id", + "kilo_user_id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cli_sessions_v2_session_id_kilo_user_id_pk": { + "name": "cli_sessions_v2_session_id_kilo_user_id_pk", + "columns": [ + "session_id", + "kilo_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_agent_code_review_attempts": { + "name": "cloud_agent_code_review_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code_review_id": { + "name": "code_review_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attempt_number": { + "name": "attempt_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retry_of_attempt_id": { + "name": "retry_of_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_reason": { + "name": "retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_reason": { + "name": "terminal_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_code_review_attempts_review_attempt_number": { + "name": "UQ_cloud_agent_code_review_attempts_review_attempt_number", + "columns": [ + { + "expression": "code_review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_code_review_id": { + "name": "idx_cloud_agent_code_review_attempts_code_review_id", + "columns": [ + { + "expression": "code_review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_session_id": { + "name": "idx_cloud_agent_code_review_attempts_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_cli_session_id": { + "name": "idx_cloud_agent_code_review_attempts_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_status": { + "name": "idx_cloud_agent_code_review_attempts_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_retry_reason": { + "name": "idx_cloud_agent_code_review_attempts_retry_reason", + "columns": [ + { + "expression": "retry_reason", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_code_review_attempts_code_review_id_cloud_agent_code_reviews_id_fk": { + "name": "cloud_agent_code_review_attempts_code_review_id_cloud_agent_code_reviews_id_fk", + "tableFrom": "cloud_agent_code_review_attempts", + "tableTo": "cloud_agent_code_reviews", + "columnsFrom": [ + "code_review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_review_attempts_retry_of_attempt_id_cloud_agent_code_review_attempts_id_fk": { + "name": "cloud_agent_code_review_attempts_retry_of_attempt_id_cloud_agent_code_review_attempts_id_fk", + "tableFrom": "cloud_agent_code_review_attempts", + "tableTo": "cloud_agent_code_review_attempts", + "columnsFrom": [ + "retry_of_attempt_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_code_review_attempts_attempt_number_check": { + "name": "cloud_agent_code_review_attempts_attempt_number_check", + "value": "\"cloud_agent_code_review_attempts\".\"attempt_number\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_code_reviews": { + "name": "cloud_agent_code_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_title": { + "name": "pr_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author": { + "name": "pr_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author_github_id": { + "name": "pr_author_github_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_ref": { + "name": "head_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "platform_project_id": { + "name": "platform_project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "dispatch_reservation_id": { + "name": "dispatch_reservation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_reason": { + "name": "terminal_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'v1'" + }, + "check_run_id": { + "name": "check_run_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "repository_review_instructions_used": { + "name": "repository_review_instructions_used", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "repository_review_instructions_ref": { + "name": "repository_review_instructions_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_review_instructions_truncated": { + "name": "repository_review_instructions_truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previous_summary_body": { + "name": "previous_summary_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_summary_head_sha": { + "name": "previous_summary_head_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_tokens_in": { + "name": "total_tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_tokens_out": { + "name": "total_tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_cost_musd": { + "name": "total_cost_musd", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_code_reviews_repo_pr_sha": { + "name": "UQ_cloud_agent_code_reviews_repo_pr_sha", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "head_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_owned_by_org_id": { + "name": "idx_cloud_agent_code_reviews_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_owned_by_user_id": { + "name": "idx_cloud_agent_code_reviews_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_session_id": { + "name": "idx_cloud_agent_code_reviews_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_cli_session_id": { + "name": "idx_cloud_agent_code_reviews_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_status": { + "name": "idx_cloud_agent_code_reviews_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_repo": { + "name": "idx_cloud_agent_code_reviews_repo", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_pr_number": { + "name": "idx_cloud_agent_code_reviews_pr_number", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_created_at": { + "name": "idx_cloud_agent_code_reviews_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_pr_author_github_id": { + "name": "idx_cloud_agent_code_reviews_pr_author_github_id", + "columns": [ + { + "expression": "pr_author_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_code_reviews_owned_by_organization_id_organizations_id_fk": { + "name": "cloud_agent_code_reviews_owned_by_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_reviews_owned_by_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_code_reviews_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_reviews_platform_integration_id_platform_integrations_id_fk": { + "name": "cloud_agent_code_reviews_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_code_reviews_owner_check": { + "name": "cloud_agent_code_reviews_owner_check", + "value": "(\n (\"cloud_agent_code_reviews\".\"owned_by_user_id\" IS NOT NULL AND \"cloud_agent_code_reviews\".\"owned_by_organization_id\" IS NULL) OR\n (\"cloud_agent_code_reviews\".\"owned_by_user_id\" IS NULL AND \"cloud_agent_code_reviews\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_feedback": { + "name": "cloud_agent_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_streaming": { + "name": "is_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recent_messages": { + "name": "recent_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cloud_agent_feedback_created_at": { + "name": "IDX_cloud_agent_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_feedback_kilo_user_id": { + "name": "IDX_cloud_agent_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_feedback_cloud_agent_session_id": { + "name": "IDX_cloud_agent_feedback_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "cloud_agent_feedback_organization_id_organizations_id_fk": { + "name": "cloud_agent_feedback_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_feedback", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_agent_session_runs": { + "name": "cloud_agent_session_runs", + "schema": "", + "columns": { + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wrapper_run_id": { + "name": "wrapper_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dispatch_accepted_at": { + "name": "dispatch_accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "agent_activity_observed_at": { + "name": "agent_activity_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_stage": { + "name": "failure_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message_redacted": { + "name": "error_message_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_expires_at": { + "name": "error_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_cloud_agent_session_runs_wrapper_run_id": { + "name": "IDX_cloud_agent_session_runs_wrapper_run_id", + "columns": [ + { + "expression": "wrapper_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_session_runs\".\"wrapper_run_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_session_queued": { + "name": "IDX_cloud_agent_session_runs_session_queued", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_queued_at": { + "name": "IDX_cloud_agent_session_runs_queued_at", + "columns": [ + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_terminal_at": { + "name": "IDX_cloud_agent_session_runs_terminal_at", + "columns": [ + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_status_terminal": { + "name": "IDX_cloud_agent_session_runs_status_terminal", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_failure_terminal": { + "name": "IDX_cloud_agent_session_runs_failure_terminal", + "columns": [ + { + "expression": "failure_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_error_expires_at": { + "name": "IDX_cloud_agent_session_runs_error_expires_at", + "columns": [ + { + "expression": "error_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_session_runs\".\"error_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_session_runs_cloud_agent_session_id_cloud_agent_sessions_cloud_agent_session_id_fk": { + "name": "cloud_agent_session_runs_cloud_agent_session_id_cloud_agent_sessions_cloud_agent_session_id_fk", + "tableFrom": "cloud_agent_session_runs", + "tableTo": "cloud_agent_sessions", + "columnsFrom": [ + "cloud_agent_session_id" + ], + "columnsTo": [ + "cloud_agent_session_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cloud_agent_session_runs_cloud_agent_session_id_message_id_pk": { + "name": "cloud_agent_session_runs_cloud_agent_session_id_message_id_pk", + "columns": [ + "cloud_agent_session_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_session_runs_status_check": { + "name": "cloud_agent_session_runs_status_check", + "value": "\"cloud_agent_session_runs\".\"status\" IN ('queued', 'accepted', 'completed', 'failed', 'interrupted')" + }, + "cloud_agent_session_runs_failure_classification_check": { + "name": "cloud_agent_session_runs_failure_classification_check", + "value": "(\"cloud_agent_session_runs\".\"failure_stage\" IS NULL AND \"cloud_agent_session_runs\".\"failure_code\" IS NULL) OR\n (\"cloud_agent_session_runs\".\"failure_stage\" = 'pre_dispatch' AND \"cloud_agent_session_runs\".\"failure_code\" IN ('sandbox_connect_failed', 'workspace_setup_failed', 'kilo_server_failed', 'wrapper_start_failed', 'invalid_delivery_request', 'session_metadata_missing', 'model_missing', 'delivery_failure_unknown')) OR\n (\"cloud_agent_session_runs\".\"failure_stage\" = 'post_dispatch_no_activity' AND \"cloud_agent_session_runs\".\"failure_code\" IN ('wrapper_disconnected', 'wrapper_no_output', 'wrapper_ping_timeout', 'wrapper_error_before_activity', 'missing_assistant_reply')) OR\n (\"cloud_agent_session_runs\".\"failure_stage\" = 'agent_activity' AND \"cloud_agent_session_runs\".\"failure_code\" IN ('assistant_error', 'wrapper_error_after_activity')) OR\n (\"cloud_agent_session_runs\".\"failure_stage\" = 'interruption' AND \"cloud_agent_session_runs\".\"failure_code\" IN ('user_interrupt', 'container_shutdown', 'system_interrupt')) OR\n (\"cloud_agent_session_runs\".\"failure_stage\" = 'unknown' AND \"cloud_agent_session_runs\".\"failure_code\" = 'unclassified')" + }, + "cloud_agent_session_runs_error_message_bounded_check": { + "name": "cloud_agent_session_runs_error_message_bounded_check", + "value": "\"cloud_agent_session_runs\".\"error_message_redacted\" IS NULL OR char_length(\"cloud_agent_session_runs\".\"error_message_redacted\") <= 4096" + }, + "cloud_agent_session_runs_error_expiry_check": { + "name": "cloud_agent_session_runs_error_expiry_check", + "value": "(\"cloud_agent_session_runs\".\"error_message_redacted\" IS NULL AND \"cloud_agent_session_runs\".\"error_expires_at\" IS NULL) OR\n (\"cloud_agent_session_runs\".\"error_message_redacted\" IS NOT NULL AND \"cloud_agent_session_runs\".\"error_expires_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_sessions": { + "name": "cloud_agent_sessions", + "schema": "", + "columns": { + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "initial_message_id": { + "name": "initial_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_id": { + "name": "sandbox_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "failure_at": { + "name": "failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_stage": { + "name": "failure_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message_redacted": { + "name": "error_message_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_expires_at": { + "name": "error_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_cloud_agent_sessions_kilo_session_id": { + "name": "UQ_cloud_agent_sessions_kilo_session_id", + "columns": [ + { + "expression": "kilo_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cloud_agent_sessions_initial_message_id": { + "name": "UQ_cloud_agent_sessions_initial_message_id", + "columns": [ + { + "expression": "initial_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_sandbox_id": { + "name": "IDX_cloud_agent_sessions_sandbox_id", + "columns": [ + { + "expression": "sandbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"sandbox_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_created_at": { + "name": "IDX_cloud_agent_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_failure_created": { + "name": "IDX_cloud_agent_sessions_failure_created", + "columns": [ + { + "expression": "failure_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_failure_at": { + "name": "IDX_cloud_agent_sessions_failure_at", + "columns": [ + { + "expression": "failure_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"failure_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_failure_classification_at": { + "name": "IDX_cloud_agent_sessions_failure_classification_at", + "columns": [ + { + "expression": "failure_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"failure_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_error_expires_at": { + "name": "IDX_cloud_agent_sessions_error_expires_at", + "columns": [ + { + "expression": "error_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"error_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_sessions_failure_classification_check": { + "name": "cloud_agent_sessions_failure_classification_check", + "value": "(\"cloud_agent_sessions\".\"failure_at\" IS NULL AND \"cloud_agent_sessions\".\"failure_stage\" IS NULL AND \"cloud_agent_sessions\".\"failure_code\" IS NULL) OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'sandbox_identity' AND \"cloud_agent_sessions\".\"failure_code\" = 'sandbox_id_derivation_failed') OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'registration' AND \"cloud_agent_sessions\".\"failure_code\" = 'do_registration_rejected') OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'initial_admission' AND \"cloud_agent_sessions\".\"failure_code\" IN ('initial_admission_rejected', 'initial_queue_full', 'invalid_initial_intent')) OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'transport' AND \"cloud_agent_sessions\".\"failure_code\" = 'do_rpc_outcome_unknown')" + }, + "cloud_agent_sessions_error_message_bounded_check": { + "name": "cloud_agent_sessions_error_message_bounded_check", + "value": "\"cloud_agent_sessions\".\"error_message_redacted\" IS NULL OR char_length(\"cloud_agent_sessions\".\"error_message_redacted\") <= 4096" + }, + "cloud_agent_sessions_error_expiry_check": { + "name": "cloud_agent_sessions_error_expiry_check", + "value": "(\"cloud_agent_sessions\".\"error_message_redacted\" IS NULL AND \"cloud_agent_sessions\".\"error_expires_at\" IS NULL) OR\n (\"cloud_agent_sessions\".\"error_message_redacted\" IS NOT NULL AND \"cloud_agent_sessions\".\"error_expires_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_webhook_triggers": { + "name": "cloud_agent_webhook_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "trigger_id": { + "name": "trigger_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cloud_agent'" + }, + "kiloclaw_instance_id": { + "name": "kiloclaw_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "activation_mode": { + "name": "activation_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'webhook'" + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_timezone": { + "name": "cron_timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'UTC'" + }, + "github_repo": { + "name": "github_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_webhook_triggers_user_trigger": { + "name": "UQ_cloud_agent_webhook_triggers_user_trigger", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_webhook_triggers\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cloud_agent_webhook_triggers_org_trigger": { + "name": "UQ_cloud_agent_webhook_triggers_org_trigger", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_webhook_triggers\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_user": { + "name": "IDX_cloud_agent_webhook_triggers_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_org": { + "name": "IDX_cloud_agent_webhook_triggers_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_active": { + "name": "IDX_cloud_agent_webhook_triggers_active", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_profile": { + "name": "IDX_cloud_agent_webhook_triggers_profile", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_webhook_triggers_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_webhook_triggers_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_organization_id_organizations_id_fk": { + "name": "cloud_agent_webhook_triggers_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_kiloclaw_instance_id_kiloclaw_instances_id_fk": { + "name": "cloud_agent_webhook_triggers_kiloclaw_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "kiloclaw_instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_profile_id_agent_environment_profiles_id_fk": { + "name": "cloud_agent_webhook_triggers_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_cloud_agent_webhook_triggers_owner": { + "name": "CHK_cloud_agent_webhook_triggers_owner", + "value": "(\n (\"cloud_agent_webhook_triggers\".\"user_id\" IS NOT NULL AND \"cloud_agent_webhook_triggers\".\"organization_id\" IS NULL) OR\n (\"cloud_agent_webhook_triggers\".\"user_id\" IS NULL AND \"cloud_agent_webhook_triggers\".\"organization_id\" IS NOT NULL)\n )" + }, + "CHK_cloud_agent_webhook_triggers_cloud_agent_fields": { + "name": "CHK_cloud_agent_webhook_triggers_cloud_agent_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"target_type\" != 'cloud_agent' OR\n (\"cloud_agent_webhook_triggers\".\"github_repo\" IS NOT NULL AND \"cloud_agent_webhook_triggers\".\"profile_id\" IS NOT NULL)\n )" + }, + "CHK_cloud_agent_webhook_triggers_kiloclaw_fields": { + "name": "CHK_cloud_agent_webhook_triggers_kiloclaw_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"target_type\" != 'kiloclaw_chat' OR\n \"cloud_agent_webhook_triggers\".\"kiloclaw_instance_id\" IS NOT NULL\n )" + }, + "CHK_cloud_agent_webhook_triggers_scheduled_fields": { + "name": "CHK_cloud_agent_webhook_triggers_scheduled_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"activation_mode\" != 'scheduled' OR\n \"cloud_agent_webhook_triggers\".\"cron_expression\" IS NOT NULL\n )" + } + }, + "isRLSEnabled": false + }, + "public.code_indexing_manifest": { + "name": "code_indexing_manifest", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_lines": { + "name": "total_lines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_ai_lines": { + "name": "total_ai_lines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_code_indexing_manifest_organization_id": { + "name": "IDX_code_indexing_manifest_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_kilo_user_id": { + "name": "IDX_code_indexing_manifest_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_project_id": { + "name": "IDX_code_indexing_manifest_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_git_branch": { + "name": "IDX_code_indexing_manifest_git_branch", + "columns": [ + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_created_at": { + "name": "IDX_code_indexing_manifest_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_indexing_manifest_kilo_user_id_kilocode_users_id_fk": { + "name": "code_indexing_manifest_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "code_indexing_manifest", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_indexing_manifest_org_user_project_hash_branch": { + "name": "UQ_code_indexing_manifest_org_user_project_hash_branch", + "nullsNotDistinct": true, + "columns": [ + "organization_id", + "kilo_user_id", + "project_id", + "file_path", + "git_branch" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.code_indexing_search": { + "name": "code_indexing_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "query": { + "name": "query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_code_indexing_search_organization_id": { + "name": "IDX_code_indexing_search_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_kilo_user_id": { + "name": "IDX_code_indexing_search_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_project_id": { + "name": "IDX_code_indexing_search_project_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_created_at": { + "name": "IDX_code_indexing_search_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_indexing_search_kilo_user_id_kilocode_users_id_fk": { + "name": "code_indexing_search_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "code_indexing_search", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.code_review_feedback_events": { + "name": "code_review_feedback_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "kilo_comment_id": { + "name": "kilo_comment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reply_excerpt": { + "name": "reply_excerpt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_comment_excerpt": { + "name": "kilo_comment_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dedupe_hash": { + "name": "dedupe_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_code_review_feedback_events_owned_by_org_id": { + "name": "idx_code_review_feedback_events_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_feedback_events_owned_by_user_id": { + "name": "idx_code_review_feedback_events_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_feedback_events_platform_repo": { + "name": "idx_code_review_feedback_events_platform_repo", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_feedback_events_created_at": { + "name": "idx_code_review_feedback_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_review_feedback_events_owned_by_organization_id_organizations_id_fk": { + "name": "code_review_feedback_events_owned_by_organization_id_organizations_id_fk", + "tableFrom": "code_review_feedback_events", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_review_feedback_events_owned_by_user_id_kilocode_users_id_fk": { + "name": "code_review_feedback_events_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "code_review_feedback_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_review_feedback_events_dedupe_hash": { + "name": "UQ_code_review_feedback_events_dedupe_hash", + "nullsNotDistinct": false, + "columns": [ + "dedupe_hash" + ] + } + }, + "policies": {}, + "checkConstraints": { + "code_review_feedback_events_owner_check": { + "name": "code_review_feedback_events_owner_check", + "value": "(\n (\"code_review_feedback_events\".\"owned_by_user_id\" IS NOT NULL AND \"code_review_feedback_events\".\"owned_by_organization_id\" IS NULL) OR\n (\"code_review_feedback_events\".\"owned_by_user_id\" IS NULL AND \"code_review_feedback_events\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.code_review_memory_proposals": { + "name": "code_review_memory_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rationale": { + "name": "rationale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposed_markdown": { + "name": "proposed_markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "positive_count": { + "name": "positive_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "negative_count": { + "name": "negative_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "neutral_count": { + "name": "neutral_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "change_request_url": { + "name": "change_request_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_code_review_memory_proposals_owned_by_org_id": { + "name": "idx_code_review_memory_proposals_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_memory_proposals_owned_by_user_id": { + "name": "idx_code_review_memory_proposals_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_memory_proposals_platform_repo_status": { + "name": "idx_code_review_memory_proposals_platform_repo_status", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_memory_proposals_updated_at": { + "name": "idx_code_review_memory_proposals_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_code_review_memory_proposals_org_active_scope": { + "name": "UQ_code_review_memory_proposals_org_active_scope", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"code_review_memory_proposals\".\"owned_by_organization_id\" IS NOT NULL AND \"code_review_memory_proposals\".\"status\" IN ('open', 'edited', 'opening_change_request')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_code_review_memory_proposals_user_active_scope": { + "name": "UQ_code_review_memory_proposals_user_active_scope", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"code_review_memory_proposals\".\"owned_by_user_id\" IS NOT NULL AND \"code_review_memory_proposals\".\"status\" IN ('open', 'edited', 'opening_change_request')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_review_memory_proposals_owned_by_organization_id_organizations_id_fk": { + "name": "code_review_memory_proposals_owned_by_organization_id_organizations_id_fk", + "tableFrom": "code_review_memory_proposals", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_review_memory_proposals_owned_by_user_id_kilocode_users_id_fk": { + "name": "code_review_memory_proposals_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "code_review_memory_proposals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "code_review_memory_proposals_owner_check": { + "name": "code_review_memory_proposals_owner_check", + "value": "(\n (\"code_review_memory_proposals\".\"owned_by_user_id\" IS NOT NULL AND \"code_review_memory_proposals\".\"owned_by_organization_id\" IS NULL) OR\n (\"code_review_memory_proposals\".\"owned_by_user_id\" IS NULL AND \"code_review_memory_proposals\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.coding_plan_availability_intents": { + "name": "coding_plan_availability_intents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_availability_intents_user_plan": { + "name": "UQ_coding_plan_availability_intents_user_plan", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_availability_intents_plan": { + "name": "IDX_coding_plan_availability_intents_plan", + "columns": [ + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_availability_intents_user_id_kilocode_users_id_fk": { + "name": "coding_plan_availability_intents_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_availability_intents", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.coding_plan_key_inventory": { + "name": "coding_plan_key_inventory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "upstream_plan_id": { + "name": "upstream_plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "assigned_to_user_id": { + "name": "assigned_to_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_requested_at": { + "name": "revocation_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_attempt_count": { + "name": "revocation_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_revocation_error": { + "name": "last_revocation_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_key_inv_fingerprint": { + "name": "UQ_coding_plan_key_inv_fingerprint", + "columns": [ + { + "expression": "credential_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_key_inv_plan_status": { + "name": "IDX_coding_plan_key_inv_plan_status", + "columns": [ + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_key_inv_available": { + "name": "IDX_coding_plan_key_inv_available", + "columns": [ + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"coding_plan_key_inventory\".\"status\" = 'available'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_key_inventory_assigned_to_user_id_kilocode_users_id_fk": { + "name": "coding_plan_key_inventory_assigned_to_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_key_inventory", + "tableTo": "kilocode_users", + "columnsFrom": [ + "assigned_to_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "coding_plan_key_inventory_status_check": { + "name": "coding_plan_key_inventory_status_check", + "value": "\"coding_plan_key_inventory\".\"status\" IN ('available', 'assigned', 'revocation_pending', 'revoked', 'revocation_failed')" + } + }, + "isRLSEnabled": false + }, + "public.coding_plan_subscriptions": { + "name": "coding_plan_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_inventory_id": { + "name": "key_inventory_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "installed_byok_key_id": { + "name": "installed_byok_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "billing_period_days": { + "name": "billing_period_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "credit_renewal_at": { + "name": "credit_renewal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "past_due_started_at": { + "name": "past_due_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payment_grace_expires_at": { + "name": "payment_grace_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_attempted_for_due": { + "name": "auto_top_up_attempted_for_due", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancellation_reason": { + "name": "cancellation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_sub_live_user_plan": { + "name": "UQ_coding_plan_sub_live_user_plan", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"coding_plan_subscriptions\".\"status\" IN ('active', 'past_due')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_sub_status": { + "name": "IDX_coding_plan_sub_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_sub_renewal": { + "name": "IDX_coding_plan_sub_renewal", + "columns": [ + { + "expression": "credit_renewal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_sub_inventory": { + "name": "IDX_coding_plan_sub_inventory", + "columns": [ + { + "expression": "key_inventory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_subscriptions_user_id_kilocode_users_id_fk": { + "name": "coding_plan_subscriptions_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "coding_plan_subscriptions_key_inventory_id_coding_plan_key_inventory_id_fk": { + "name": "coding_plan_subscriptions_key_inventory_id_coding_plan_key_inventory_id_fk", + "tableFrom": "coding_plan_subscriptions", + "tableTo": "coding_plan_key_inventory", + "columnsFrom": [ + "key_inventory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "coding_plan_subscriptions_installed_byok_key_id_byok_api_keys_id_fk": { + "name": "coding_plan_subscriptions_installed_byok_key_id_byok_api_keys_id_fk", + "tableFrom": "coding_plan_subscriptions", + "tableTo": "byok_api_keys", + "columnsFrom": [ + "installed_byok_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "coding_plan_subscriptions_status_check": { + "name": "coding_plan_subscriptions_status_check", + "value": "\"coding_plan_subscriptions\".\"status\" IN ('active', 'past_due', 'canceled')" + }, + "coding_plan_subscriptions_live_access_check": { + "name": "coding_plan_subscriptions_live_access_check", + "value": "\"coding_plan_subscriptions\".\"status\" = 'canceled' OR \"coding_plan_subscriptions\".\"key_inventory_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.coding_plan_terms": { + "name": "coding_plan_terms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_start": { + "name": "period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "credit_transaction_id": { + "name": "credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_terms_request": { + "name": "UQ_coding_plan_terms_request", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_terms_subscription": { + "name": "IDX_coding_plan_terms_subscription", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_terms_subscription_id_coding_plan_subscriptions_id_fk": { + "name": "coding_plan_terms_subscription_id_coding_plan_subscriptions_id_fk", + "tableFrom": "coding_plan_terms", + "tableTo": "coding_plan_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "coding_plan_terms_user_id_kilocode_users_id_fk": { + "name": "coding_plan_terms_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_terms", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "coding_plan_terms_credit_transaction_id_credit_transactions_id_fk": { + "name": "coding_plan_terms_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "coding_plan_terms", + "tableTo": "credit_transactions", + "columnsFrom": [ + "credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "coding_plan_terms_kind_check": { + "name": "coding_plan_terms_kind_check", + "value": "\"coding_plan_terms\".\"kind\" IN ('activation', 'extension', 'renewal')" + } + }, + "isRLSEnabled": false + }, + "public.contributor_champion_contributors": { + "name": "contributor_champion_contributors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "first_contribution_at": { + "name": "first_contribution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_contribution_at": { + "name": "last_contribution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "all_time_contributions": { + "name": "all_time_contributions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "manual_email": { + "name": "manual_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_contributors_last_contribution_at": { + "name": "IDX_contributor_champion_contributors_last_contribution_at", + "columns": [ + { + "expression": "last_contribution_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_contributors_manual_email": { + "name": "IDX_contributor_champion_contributors_manual_email", + "columns": [ + { + "expression": "manual_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_contributors_github_login": { + "name": "UQ_contributor_champion_contributors_github_login", + "nullsNotDistinct": false, + "columns": [ + "github_login" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_events": { + "name": "contributor_champion_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "contributor_id": { + "name": "contributor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_pr_number": { + "name": "github_pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "github_pr_url": { + "name": "github_pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_pr_title": { + "name": "github_pr_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_author_login": { + "name": "github_author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_author_email": { + "name": "github_author_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_events_contributor_id": { + "name": "IDX_contributor_champion_events_contributor_id", + "columns": [ + { + "expression": "contributor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_events_merged_at": { + "name": "IDX_contributor_champion_events_merged_at", + "columns": [ + { + "expression": "merged_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_events_author_email": { + "name": "IDX_contributor_champion_events_author_email", + "columns": [ + { + "expression": "github_author_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contributor_champion_events_contributor_id_contributor_champion_contributors_id_fk": { + "name": "contributor_champion_events_contributor_id_contributor_champion_contributors_id_fk", + "tableFrom": "contributor_champion_events", + "tableTo": "contributor_champion_contributors", + "columnsFrom": [ + "contributor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_events_repo_pr": { + "name": "UQ_contributor_champion_events_repo_pr", + "nullsNotDistinct": false, + "columns": [ + "repo_full_name", + "github_pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_memberships": { + "name": "contributor_champion_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "contributor_id": { + "name": "contributor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selected_tier": { + "name": "selected_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrolled_tier": { + "name": "enrolled_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credit_amount_microdollars": { + "name": "credit_amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credits_last_granted_at": { + "name": "credits_last_granted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "linked_kilo_user_id": { + "name": "linked_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_memberships_credits_due": { + "name": "IDX_contributor_champion_memberships_credits_due", + "columns": [ + { + "expression": "credits_last_granted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"contributor_champion_memberships\".\"enrolled_tier\" IS NOT NULL AND \"contributor_champion_memberships\".\"credit_amount_microdollars\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_memberships_linked_kilo_user_id": { + "name": "IDX_contributor_champion_memberships_linked_kilo_user_id", + "columns": [ + { + "expression": "linked_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contributor_champion_memberships_contributor_id_contributor_champion_contributors_id_fk": { + "name": "contributor_champion_memberships_contributor_id_contributor_champion_contributors_id_fk", + "tableFrom": "contributor_champion_memberships", + "tableTo": "contributor_champion_contributors", + "columnsFrom": [ + "contributor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "contributor_champion_memberships_linked_kilo_user_id_kilocode_users_id_fk": { + "name": "contributor_champion_memberships_linked_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "contributor_champion_memberships", + "tableTo": "kilocode_users", + "columnsFrom": [ + "linked_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_memberships_contributor_id": { + "name": "UQ_contributor_champion_memberships_contributor_id", + "nullsNotDistinct": false, + "columns": [ + "contributor_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "contributor_champion_memberships_selected_tier_check": { + "name": "contributor_champion_memberships_selected_tier_check", + "value": "\"contributor_champion_memberships\".\"selected_tier\" IS NULL OR \"contributor_champion_memberships\".\"selected_tier\" IN ('contributor', 'ambassador', 'champion')" + }, + "contributor_champion_memberships_enrolled_tier_check": { + "name": "contributor_champion_memberships_enrolled_tier_check", + "value": "\"contributor_champion_memberships\".\"enrolled_tier\" IS NULL OR \"contributor_champion_memberships\".\"enrolled_tier\" IN ('contributor', 'ambassador', 'champion')" + } + }, + "isRLSEnabled": false + }, + "public.contributor_champion_sync_state": { + "name": "contributor_champion_sync_state", + "schema": "", + "columns": { + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "last_merged_at": { + "name": "last_merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credit_campaigns": { + "name": "credit_campaigns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit_category": { + "name": "credit_category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credit_expiry_hours": { + "name": "credit_expiry_hours", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "campaign_ends_at": { + "name": "campaign_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_redemptions_allowed": { + "name": "total_redemptions_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_credit_campaigns_slug": { + "name": "UQ_credit_campaigns_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_credit_campaigns_credit_category": { + "name": "UQ_credit_campaigns_credit_category", + "columns": [ + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credit_campaigns_slug_format_check": { + "name": "credit_campaigns_slug_format_check", + "value": "\"credit_campaigns\".\"slug\" ~ '^[a-z0-9-]{5,40}$'" + }, + "credit_campaigns_amount_positive_check": { + "name": "credit_campaigns_amount_positive_check", + "value": "\"credit_campaigns\".\"amount_microdollars\" > 0" + }, + "credit_campaigns_credit_expiry_hours_positive_check": { + "name": "credit_campaigns_credit_expiry_hours_positive_check", + "value": "\"credit_campaigns\".\"credit_expiry_hours\" IS NULL OR \"credit_campaigns\".\"credit_expiry_hours\" > 0" + }, + "credit_campaigns_total_redemptions_allowed_positive_check": { + "name": "credit_campaigns_total_redemptions_allowed_positive_check", + "value": "\"credit_campaigns\".\"total_redemptions_allowed\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.credit_transactions": { + "name": "credit_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expiration_baseline_microdollars_used": { + "name": "expiration_baseline_microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "original_baseline_microdollars_used": { + "name": "original_baseline_microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_transaction_id": { + "name": "original_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_id": { + "name": "stripe_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coinbase_credit_block_id": { + "name": "coinbase_credit_block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credit_category": { + "name": "credit_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_date": { + "name": "expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_category_uniqueness": { + "name": "check_category_uniqueness", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_credit_transactions_created_at": { + "name": "IDX_credit_transactions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_is_free": { + "name": "IDX_credit_transactions_is_free", + "columns": [ + { + "expression": "is_free", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_kilo_user_id": { + "name": "IDX_credit_transactions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_credit_category": { + "name": "IDX_credit_transactions_credit_category", + "columns": [ + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_stripe_payment_id": { + "name": "IDX_credit_transactions_stripe_payment_id", + "columns": [ + { + "expression": "stripe_payment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_original_transaction_id": { + "name": "IDX_credit_transactions_original_transaction_id", + "columns": [ + { + "expression": "original_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_coinbase_credit_block_id": { + "name": "IDX_credit_transactions_coinbase_credit_block_id", + "columns": [ + { + "expression": "coinbase_credit_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_organization_id": { + "name": "IDX_credit_transactions_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_unique_category": { + "name": "IDX_credit_transactions_unique_category", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credit_transactions\".\"check_category_uniqueness\" = TRUE", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credit_transactions_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "credit_transactions_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "credit_transactions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_llm2": { + "name": "custom_llm2", + "schema": "", + "columns": { + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deleted_user_email_tombstones": { + "name": "deleted_user_email_tombstones", + "schema": "", + "columns": { + "normalized_email_hash": { + "name": "normalized_email_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_builds": { + "name": "deployment_builds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_builds_deployment_id": { + "name": "idx_deployment_builds_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_builds_status": { + "name": "idx_deployment_builds_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_builds_deployment_id_deployments_id_fk": { + "name": "deployment_builds_deployment_id_deployments_id_fk", + "tableFrom": "deployment_builds", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_env_vars": { + "name": "deployment_env_vars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_secret": { + "name": "is_secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_env_vars_deployment_id": { + "name": "idx_deployment_env_vars_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_env_vars_deployment_id_deployments_id_fk": { + "name": "deployment_env_vars_deployment_id_deployments_id_fk", + "tableFrom": "deployment_env_vars", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployment_env_vars_deployment_key": { + "name": "UQ_deployment_env_vars_deployment_key", + "nullsNotDistinct": false, + "columns": [ + "deployment_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_events": { + "name": "deployment_events", + "schema": "", + "columns": { + "build_id": { + "name": "build_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'log'" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_deployment_events_build_id": { + "name": "idx_deployment_events_build_id", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_events_timestamp": { + "name": "idx_deployment_events_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_events_type": { + "name": "idx_deployment_events_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_events_build_id_deployment_builds_id_fk": { + "name": "deployment_events_build_id_deployment_builds_id_fk", + "tableFrom": "deployment_events", + "tableTo": "deployment_builds", + "columnsFrom": [ + "build_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "deployment_events_build_id_event_id_pk": { + "name": "deployment_events_build_id_event_id_pk", + "columns": [ + "build_id", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_threat_detections": { + "name": "deployment_threat_detections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "build_id": { + "name": "build_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "threat_type": { + "name": "threat_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_threat_detections_deployment_id": { + "name": "idx_deployment_threat_detections_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_threat_detections_created_at": { + "name": "idx_deployment_threat_detections_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_threat_detections_deployment_id_deployments_id_fk": { + "name": "deployment_threat_detections_deployment_id_deployments_id_fk", + "tableFrom": "deployment_threat_detections", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_threat_detections_build_id_deployment_builds_id_fk": { + "name": "deployment_threat_detections_build_id_deployment_builds_id_fk", + "tableFrom": "deployment_threat_detections", + "tableTo": "deployment_builds", + "columnsFrom": [ + "build_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployments": { + "name": "deployments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deployment_slug": { + "name": "deployment_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_worker_name": { + "name": "internal_worker_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_source": { + "name": "repository_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_url": { + "name": "deployment_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "git_auth_token": { + "name": "git_auth_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_deployed_at": { + "name": "last_deployed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_build_id": { + "name": "last_build_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "threat_status": { + "name": "threat_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_from": { + "name": "created_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_deployments_owned_by_user_id": { + "name": "idx_deployments_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_owned_by_organization_id": { + "name": "idx_deployments_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_platform_integration_id": { + "name": "idx_deployments_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_repository_source_branch": { + "name": "idx_deployments_repository_source_branch", + "columns": [ + { + "expression": "repository_source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_threat_status_pending": { + "name": "idx_deployments_threat_status_pending", + "columns": [ + { + "expression": "threat_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"deployments\".\"threat_status\" = 'pending_scan'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployments_owned_by_user_id_kilocode_users_id_fk": { + "name": "deployments_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "deployments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployments_owned_by_organization_id_organizations_id_fk": { + "name": "deployments_owned_by_organization_id_organizations_id_fk", + "tableFrom": "deployments", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployments_deployment_slug": { + "name": "UQ_deployments_deployment_slug", + "nullsNotDistinct": false, + "columns": [ + "deployment_slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "deployments_owner_check": { + "name": "deployments_owner_check", + "value": "(\n (\"deployments\".\"owned_by_user_id\" IS NOT NULL AND \"deployments\".\"owned_by_organization_id\" IS NULL) OR\n (\"deployments\".\"owned_by_user_id\" IS NULL AND \"deployments\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "deployments_source_type_check": { + "name": "deployments_source_type_check", + "value": "\"deployments\".\"source_type\" IN ('github', 'git', 'app-builder')" + } + }, + "isRLSEnabled": false + }, + "public.deployments_ephemeral": { + "name": "deployments_ephemeral", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_worker_name": { + "name": "internal_worker_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_slug": { + "name": "deployment_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_cleanup_at": { + "name": "next_cleanup_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cleanup_claim_token": { + "name": "cleanup_claim_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cleanup_claimed_until": { + "name": "cleanup_claimed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployments_ephemeral_owned_by_user_id": { + "name": "idx_deployments_ephemeral_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_ephemeral_next_cleanup_at": { + "name": "idx_deployments_ephemeral_next_cleanup_at", + "columns": [ + { + "expression": "next_cleanup_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployments_ephemeral_owned_by_user_id_kilocode_users_id_fk": { + "name": "deployments_ephemeral_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "deployments_ephemeral", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployments_ephemeral_internal_worker_name": { + "name": "UQ_deployments_ephemeral_internal_worker_name", + "nullsNotDistinct": false, + "columns": [ + "internal_worker_name" + ] + }, + "UQ_deployments_ephemeral_deployment_slug": { + "name": "UQ_deployments_ephemeral_deployment_slug", + "nullsNotDistinct": false, + "columns": [ + "deployment_slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "deployments_ephemeral_source_type_check": { + "name": "deployments_ephemeral_source_type_check", + "value": "\"deployments_ephemeral\".\"source_type\" IN ('html')" + }, + "deployments_ephemeral_status_check": { + "name": "deployments_ephemeral_status_check", + "value": "\"deployments_ephemeral\".\"status\" IN ('pending', 'active', 'cleanup_retry')" + }, + "deployments_ephemeral_claim_fields_check": { + "name": "deployments_ephemeral_claim_fields_check", + "value": "(\"deployments_ephemeral\".\"cleanup_claim_token\" IS NULL) = (\"deployments_ephemeral\".\"cleanup_claimed_until\" IS NULL)" + }, + "deployments_ephemeral_active_fields_check": { + "name": "deployments_ephemeral_active_fields_check", + "value": "\"deployments_ephemeral\".\"status\" <> 'active' OR (\"deployments_ephemeral\".\"deployment_slug\" IS NOT NULL AND \"deployments_ephemeral\".\"expires_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.device_auth_requests": { + "name": "device_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_device_auth_requests_code": { + "name": "UQ_device_auth_requests_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_status": { + "name": "IDX_device_auth_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_expires_at": { + "name": "IDX_device_auth_requests_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_kilo_user_id": { + "name": "IDX_device_auth_requests_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_auth_requests_kilo_user_id_kilocode_users_id_fk": { + "name": "device_auth_requests_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "device_auth_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord_gateway_listener": { + "name": "discord_gateway_listener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "listener_id": { + "name": "listener_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.editor_name": { + "name": "editor_name", + "schema": "", + "columns": { + "editor_name_id": { + "name": "editor_name_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "editor_name": { + "name": "editor_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_editor_name": { + "name": "UQ_editor_name", + "columns": [ + { + "expression": "editor_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.enrichment_data": { + "name": "enrichment_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_enrichment_data": { + "name": "github_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linkedin_enrichment_data": { + "name": "linkedin_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "clay_enrichment_data": { + "name": "clay_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_enrichment_data_user_id": { + "name": "IDX_enrichment_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "enrichment_data_user_id_kilocode_users_id_fk": { + "name": "enrichment_data_user_id_kilocode_users_id_fk", + "tableFrom": "enrichment_data", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_enrichment_data_user_id": { + "name": "UQ_enrichment_data_user_id", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exa_monthly_usage": { + "name": "exa_monthly_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "month": { + "name": "month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_charged_microdollars": { + "name": "total_charged_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "free_allowance_microdollars": { + "name": "free_allowance_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 10000000 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_exa_monthly_usage_personal": { + "name": "idx_exa_monthly_usage_personal", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"exa_monthly_usage\".\"organization_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_exa_monthly_usage_org": { + "name": "idx_exa_monthly_usage_org", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"exa_monthly_usage\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exa_usage_log": { + "name": "exa_usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "charged_to_balance": { + "name": "charged_to_balance", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_exa_usage_log_user_created": { + "name": "idx_exa_usage_log_user_created", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "exa_usage_log_id_created_at_pk": { + "name": "exa_usage_log_id_created_at_pk", + "columns": [ + "id", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feature": { + "name": "feature", + "schema": "", + "columns": { + "feature_id": { + "name": "feature_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_feature": { + "name": "UQ_feature", + "columns": [ + { + "expression": "feature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finish_reason": { + "name": "finish_reason", + "schema": "", + "columns": { + "finish_reason_id": { + "name": "finish_reason_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_finish_reason": { + "name": "UQ_finish_reason", + "columns": [ + { + "expression": "finish_reason", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_model_usage": { + "name": "free_model_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_free_model_usage_ip_created_at": { + "name": "idx_free_model_usage_ip_created_at", + "columns": [ + { + "expression": "ip_address", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_free_model_usage_created_at": { + "name": "idx_free_model_usage_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_branch_pull_requests": { + "name": "github_branch_pull_requests", + "schema": "", + "columns": { + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_state": { + "name": "pr_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_title": { + "name": "pr_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_head_sha": { + "name": "pr_head_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_review_decision": { + "name": "pr_review_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_decision_pending": { + "name": "review_decision_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "review_decision_fetching_at": { + "name": "review_decision_fetching_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pr_last_synced_at": { + "name": "pr_last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_github_branch_prs_org": { + "name": "UQ_github_branch_prs_org", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_branch_pull_requests\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_github_branch_prs_user": { + "name": "UQ_github_branch_prs_user", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_branch_pull_requests\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_branch_pull_requests_owned_by_organization_id_organizations_id_fk": { + "name": "github_branch_pull_requests_owned_by_organization_id_organizations_id_fk", + "tableFrom": "github_branch_pull_requests", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_branch_pull_requests_owned_by_user_id_kilocode_users_id_fk": { + "name": "github_branch_pull_requests_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "github_branch_pull_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_branch_pull_requests_owner_check": { + "name": "github_branch_pull_requests_owner_check", + "value": "(\n (\"github_branch_pull_requests\".\"owned_by_organization_id\" IS NOT NULL AND \"github_branch_pull_requests\".\"owned_by_user_id\" IS NULL) OR\n (\"github_branch_pull_requests\".\"owned_by_organization_id\" IS NULL AND \"github_branch_pull_requests\".\"owned_by_user_id\" IS NOT NULL)\n )" + }, + "github_branch_pull_requests_review_decision_check": { + "name": "github_branch_pull_requests_review_decision_check", + "value": "\"github_branch_pull_requests\".\"pr_review_decision\" IS NULL OR \"github_branch_pull_requests\".\"pr_review_decision\" IN ('approved', 'changes_requested', 'review_required')" + } + }, + "isRLSEnabled": false + }, + "public.http_ip": { + "name": "http_ip", + "schema": "", + "columns": { + "http_ip_id": { + "name": "http_ip_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "http_ip": { + "name": "http_ip", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_http_ip": { + "name": "UQ_http_ip", + "columns": [ + { + "expression": "http_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.http_user_agent": { + "name": "http_user_agent", + "schema": "", + "columns": { + "http_user_agent_id": { + "name": "http_user_agent_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_http_user_agent": { + "name": "UQ_http_user_agent", + "columns": [ + { + "expression": "http_user_agent", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.impact_advocate_participants": { + "name": "impact_advocate_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "program_key": { + "name": "program_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "advocate_id": { + "name": "advocate_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "advocate_account_id": { + "name": "advocate_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opaque_referral_identifier": { + "name": "opaque_referral_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contact_email": { + "name": "contact_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_state": { + "name": "registration_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_registration_attempt_at": { + "name": "last_registration_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_impact_advocate_participants_program_referral_identifier": { + "name": "UQ_impact_advocate_participants_program_referral_identifier", + "columns": [ + { + "expression": "program_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opaque_referral_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"impact_advocate_participants\".\"opaque_referral_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_advocate_participants_registration_state": { + "name": "IDX_impact_advocate_participants_registration_state", + "columns": [ + { + "expression": "registration_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_advocate_participants_user_id_kilocode_users_id_fk": { + "name": "impact_advocate_participants_user_id_kilocode_users_id_fk", + "tableFrom": "impact_advocate_participants", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_advocate_participants_program_user": { + "name": "UQ_impact_advocate_participants_program_user", + "nullsNotDistinct": false, + "columns": [ + "program_key", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_advocate_participants_program_key_check": { + "name": "impact_advocate_participants_program_key_check", + "value": "\"impact_advocate_participants\".\"program_key\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_advocate_participants_registration_state_check": { + "name": "impact_advocate_participants_registration_state_check", + "value": "\"impact_advocate_participants\".\"registration_state\" IN ('pending', 'retrying', 'registered', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.impact_advocate_registration_attempts": { + "name": "impact_advocate_registration_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "program_key": { + "name": "program_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opaque_cookie_value": { + "name": "opaque_cookie_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cookie_value_length": { + "name": "cookie_value_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "request_payload": { + "name": "request_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_payload": { + "name": "response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_status_code": { + "name": "response_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_advocate_registration_attempts_participant_id": { + "name": "IDX_impact_advocate_registration_attempts_participant_id", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_advocate_registration_attempts_delivery_state": { + "name": "IDX_impact_advocate_registration_attempts_delivery_state", + "columns": [ + { + "expression": "delivery_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_advocate_registration_attempts_participant_id_impact_advocate_participants_id_fk": { + "name": "impact_advocate_registration_attempts_participant_id_impact_advocate_participants_id_fk", + "tableFrom": "impact_advocate_registration_attempts", + "tableTo": "impact_advocate_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_advocate_registration_attempts_dedupe_key": { + "name": "UQ_impact_advocate_registration_attempts_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_advocate_registration_attempts_program_key_check": { + "name": "impact_advocate_registration_attempts_program_key_check", + "value": "\"impact_advocate_registration_attempts\".\"program_key\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_advocate_registration_attempts_delivery_state_check": { + "name": "impact_advocate_registration_attempts_delivery_state_check", + "value": "\"impact_advocate_registration_attempts\".\"delivery_state\" IN ('queued', 'sending', 'succeeded', 'failed')" + }, + "impact_advocate_registration_attempts_cookie_value_length_non_negative_check": { + "name": "impact_advocate_registration_attempts_cookie_value_length_non_negative_check", + "value": "\"impact_advocate_registration_attempts\".\"cookie_value_length\" >= 0" + }, + "impact_advocate_registration_attempts_attempt_count_non_negative_check": { + "name": "impact_advocate_registration_attempts_attempt_count_non_negative_check", + "value": "\"impact_advocate_registration_attempts\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_advocate_reward_redemptions": { + "name": "impact_advocate_reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "reward_id": { + "name": "reward_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "impact_reward_id": { + "name": "impact_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_payload": { + "name": "request_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lookup_response_payload": { + "name": "lookup_response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "redeem_response_payload": { + "name": "redeem_response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_status_code": { + "name": "response_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_advocate_reward_redemptions_beneficiary_user_id": { + "name": "IDX_impact_advocate_reward_redemptions_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_advocate_reward_redemptions_state": { + "name": "IDX_impact_advocate_reward_redemptions_state", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_advocate_reward_redemptions_reward_id_impact_referral_rewards_id_fk": { + "name": "impact_advocate_reward_redemptions_reward_id_impact_referral_rewards_id_fk", + "tableFrom": "impact_advocate_reward_redemptions", + "tableTo": "impact_referral_rewards", + "columnsFrom": [ + "reward_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_advocate_reward_redemptions_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_advocate_reward_redemptions_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_advocate_reward_redemptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_advocate_reward_redemptions_reward_id": { + "name": "UQ_impact_advocate_reward_redemptions_reward_id", + "nullsNotDistinct": false, + "columns": [ + "reward_id" + ] + }, + "UQ_impact_advocate_reward_redemptions_dedupe_key": { + "name": "UQ_impact_advocate_reward_redemptions_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_advocate_reward_redemptions_state_check": { + "name": "impact_advocate_reward_redemptions_state_check", + "value": "\"impact_advocate_reward_redemptions\".\"state\" IN ('queued', 'retrying', 'redeemed', 'failed')" + }, + "impact_advocate_reward_redemptions_attempt_count_non_negative_check": { + "name": "impact_advocate_reward_redemptions_attempt_count_non_negative_check", + "value": "\"impact_advocate_reward_redemptions\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_attribution_touches": { + "name": "impact_attribution_touches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "program_key": { + "name": "program_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'kiloclaw'" + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anonymous_id": { + "name": "anonymous_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "touch_type": { + "name": "touch_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opaque_tracking_value": { + "name": "opaque_tracking_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tracking_value_length": { + "name": "tracking_value_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_tracking_value_accepted": { + "name": "is_tracking_value_accepted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "rs_code": { + "name": "rs_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rs_share_medium": { + "name": "rs_share_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rs_engagement_medium": { + "name": "rs_engagement_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "im_ref": { + "name": "im_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "landing_path": { + "name": "landing_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_source": { + "name": "utm_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_medium": { + "name": "utm_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_campaign": { + "name": "utm_campaign", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_term": { + "name": "utm_term", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_content": { + "name": "utm_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "touched_at": { + "name": "touched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sale_attributed_at": { + "name": "sale_attributed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_attribution_touches_product_user_id": { + "name": "IDX_impact_attribution_touches_product_user_id", + "columns": [ + { + "expression": "product", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_user_id": { + "name": "IDX_impact_attribution_touches_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_anonymous_id": { + "name": "IDX_impact_attribution_touches_anonymous_id", + "columns": [ + { + "expression": "anonymous_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_expires_at": { + "name": "IDX_impact_attribution_touches_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_sale_attributed_at": { + "name": "IDX_impact_attribution_touches_sale_attributed_at", + "columns": [ + { + "expression": "sale_attributed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_attribution_touches_user_id_kilocode_users_id_fk": { + "name": "impact_attribution_touches_user_id_kilocode_users_id_fk", + "tableFrom": "impact_attribution_touches", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_attribution_touches_dedupe_key": { + "name": "UQ_impact_attribution_touches_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_attribution_touches_product_check": { + "name": "impact_attribution_touches_product_check", + "value": "\"impact_attribution_touches\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_attribution_touches_program_key_check": { + "name": "impact_attribution_touches_program_key_check", + "value": "\"impact_attribution_touches\".\"program_key\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_attribution_touches_touch_type_check": { + "name": "impact_attribution_touches_touch_type_check", + "value": "\"impact_attribution_touches\".\"touch_type\" IN ('affiliate', 'referral')" + }, + "impact_attribution_touches_provider_check": { + "name": "impact_attribution_touches_provider_check", + "value": "\"impact_attribution_touches\".\"provider\" IN ('impact_performance', 'impact_advocate')" + }, + "impact_attribution_touches_tracking_value_length_non_negative_check": { + "name": "impact_attribution_touches_tracking_value_length_non_negative_check", + "value": "\"impact_attribution_touches\".\"tracking_value_length\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_conversion_reports": { + "name": "impact_conversion_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "conversion_id": { + "name": "conversion_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_tracker_id": { + "name": "action_tracker_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "request_payload": { + "name": "request_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_payload": { + "name": "response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_status_code": { + "name": "response_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_conversion_reports_conversion_id": { + "name": "IDX_impact_conversion_reports_conversion_id", + "columns": [ + { + "expression": "conversion_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_conversion_reports_state": { + "name": "IDX_impact_conversion_reports_state", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_conversion_reports_conversion_id_impact_referral_conversions_id_fk": { + "name": "impact_conversion_reports_conversion_id_impact_referral_conversions_id_fk", + "tableFrom": "impact_conversion_reports", + "tableTo": "impact_referral_conversions", + "columnsFrom": [ + "conversion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_conversion_reports_dedupe_key": { + "name": "UQ_impact_conversion_reports_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_conversion_reports_state_check": { + "name": "impact_conversion_reports_state_check", + "value": "\"impact_conversion_reports\".\"state\" IN ('queued', 'retrying', 'delivered', 'failed')" + }, + "impact_conversion_reports_attempt_count_non_negative_check": { + "name": "impact_conversion_reports_attempt_count_non_negative_check", + "value": "\"impact_conversion_reports\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_conversions": { + "name": "impact_referral_conversions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "referee_user_id": { + "name": "referee_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "referrer_user_id": { + "name": "referrer_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_touch_id": { + "name": "source_touch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winning_touch_type": { + "name": "winning_touch_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credits'" + }, + "source_payment_id": { + "name": "source_payment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "qualified": { + "name": "qualified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disqualification_reason": { + "name": "disqualification_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "converted_at": { + "name": "converted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_conversions_referee_user_id": { + "name": "IDX_impact_referral_conversions_referee_user_id", + "columns": [ + { + "expression": "referee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referral_conversions_referrer_user_id": { + "name": "IDX_impact_referral_conversions_referrer_user_id", + "columns": [ + { + "expression": "referrer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_conversions_referee_user_id_kilocode_users_id_fk": { + "name": "impact_referral_conversions_referee_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_conversions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referee_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_conversions_referrer_user_id_kilocode_users_id_fk": { + "name": "impact_referral_conversions_referrer_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_conversions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referrer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "impact_referral_conversions_source_touch_id_impact_attribution_touches_id_fk": { + "name": "impact_referral_conversions_source_touch_id_impact_attribution_touches_id_fk", + "tableFrom": "impact_referral_conversions", + "tableTo": "impact_attribution_touches", + "columnsFrom": [ + "source_touch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referral_conversions_product_payment_source": { + "name": "UQ_impact_referral_conversions_product_payment_source", + "nullsNotDistinct": false, + "columns": [ + "product", + "payment_provider", + "source_payment_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referral_conversions_product_check": { + "name": "impact_referral_conversions_product_check", + "value": "\"impact_referral_conversions\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_referral_conversions_winning_touch_type_check": { + "name": "impact_referral_conversions_winning_touch_type_check", + "value": "\"impact_referral_conversions\".\"winning_touch_type\" IN ('referral', 'affiliate', 'none')" + }, + "impact_referral_conversions_payment_provider_check": { + "name": "impact_referral_conversions_payment_provider_check", + "value": "\"impact_referral_conversions\".\"payment_provider\" IN ('stripe', 'credits', 'app_store', 'google_play')" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_reward_applications": { + "name": "impact_referral_reward_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "reward_id": { + "name": "reward_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "previous_renewal_boundary": { + "name": "previous_renewal_boundary", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "new_renewal_boundary": { + "name": "new_renewal_boundary", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "local_operation_id": { + "name": "local_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_operation_id": { + "name": "stripe_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_idempotency_key": { + "name": "stripe_idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_reward_applications_reward_id": { + "name": "IDX_impact_referral_reward_applications_reward_id", + "columns": [ + { + "expression": "reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referral_reward_applications_beneficiary_user_id": { + "name": "IDX_impact_referral_reward_applications_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_reward_applications_reward_id_impact_referral_rewards_id_fk": { + "name": "impact_referral_reward_applications_reward_id_impact_referral_rewards_id_fk", + "tableFrom": "impact_referral_reward_applications", + "tableTo": "impact_referral_rewards", + "columnsFrom": [ + "reward_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_reward_applications_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_referral_reward_applications_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_reward_applications", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "impact_referral_reward_applications_product_check": { + "name": "impact_referral_reward_applications_product_check", + "value": "\"impact_referral_reward_applications\".\"product\" IN ('kiloclaw', 'kilo_pass')" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_reward_decisions": { + "name": "impact_referral_reward_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "conversion_id": { + "name": "conversion_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beneficiary_role": { + "name": "beneficiary_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_kind": { + "name": "reward_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw_free_month'" + }, + "months_granted": { + "name": "months_granted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reward_percent": { + "name": "reward_percent", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "source_tier": { + "name": "source_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_amount_usd": { + "name": "reward_amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_reward_decisions_beneficiary_user_id": { + "name": "IDX_impact_referral_reward_decisions_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_reward_decisions_conversion_id_impact_referral_conversions_id_fk": { + "name": "impact_referral_reward_decisions_conversion_id_impact_referral_conversions_id_fk", + "tableFrom": "impact_referral_reward_decisions", + "tableTo": "impact_referral_conversions", + "columnsFrom": [ + "conversion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_reward_decisions_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_referral_reward_decisions_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_reward_decisions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referral_reward_decisions_conversion_role": { + "name": "UQ_impact_referral_reward_decisions_conversion_role", + "nullsNotDistinct": false, + "columns": [ + "conversion_id", + "beneficiary_role" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referral_reward_decisions_product_check": { + "name": "impact_referral_reward_decisions_product_check", + "value": "\"impact_referral_reward_decisions\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_referral_reward_decisions_beneficiary_role_check": { + "name": "impact_referral_reward_decisions_beneficiary_role_check", + "value": "\"impact_referral_reward_decisions\".\"beneficiary_role\" IN ('referrer', 'referee')" + }, + "impact_referral_reward_decisions_outcome_check": { + "name": "impact_referral_reward_decisions_outcome_check", + "value": "\"impact_referral_reward_decisions\".\"outcome\" IN ('granted', 'cap_limited', 'disqualified')" + }, + "impact_referral_reward_decisions_reward_kind_check": { + "name": "impact_referral_reward_decisions_reward_kind_check", + "value": "\"impact_referral_reward_decisions\".\"reward_kind\" IN ('kiloclaw_free_month', 'kilo_pass_bonus')" + }, + "impact_referral_reward_decisions_months_granted_non_negative_check": { + "name": "impact_referral_reward_decisions_months_granted_non_negative_check", + "value": "\"impact_referral_reward_decisions\".\"months_granted\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_rewards": { + "name": "impact_referral_rewards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "conversion_id": { + "name": "conversion_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beneficiary_role": { + "name": "beneficiary_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reward_kind": { + "name": "reward_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw_free_month'" + }, + "months_granted": { + "name": "months_granted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "reward_percent": { + "name": "reward_percent", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "source_tier": { + "name": "source_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_amount_usd": { + "name": "reward_amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "applies_to_subscription_id": { + "name": "applies_to_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applies_to_kilo_pass_subscription_id": { + "name": "applies_to_kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "consumed_kilo_pass_issuance_id": { + "name": "consumed_kilo_pass_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "consumed_kilo_pass_issuance_item_id": { + "name": "consumed_kilo_pass_issuance_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "earned_at": { + "name": "earned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reversed_at": { + "name": "reversed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_reason": { + "name": "review_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_rewards_beneficiary_user_id": { + "name": "IDX_impact_referral_rewards_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referral_rewards_status": { + "name": "IDX_impact_referral_rewards_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_rewards_conversion_id_impact_referral_conversions_id_fk": { + "name": "impact_referral_rewards_conversion_id_impact_referral_conversions_id_fk", + "tableFrom": "impact_referral_rewards", + "tableTo": "impact_referral_conversions", + "columnsFrom": [ + "conversion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_rewards_decision_id_impact_referral_reward_decisions_id_fk": { + "name": "impact_referral_rewards_decision_id_impact_referral_reward_decisions_id_fk", + "tableFrom": "impact_referral_rewards", + "tableTo": "impact_referral_reward_decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_rewards_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_referral_rewards_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "FK_impact_referral_rewards_kilo_pass_subscription": { + "name": "FK_impact_referral_rewards_kilo_pass_subscription", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "applies_to_kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "FK_impact_referral_rewards_kilo_pass_issuance": { + "name": "FK_impact_referral_rewards_kilo_pass_issuance", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "consumed_kilo_pass_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "FK_impact_referral_rewards_kilo_pass_issuance_item": { + "name": "FK_impact_referral_rewards_kilo_pass_issuance_item", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilo_pass_issuance_items", + "columnsFrom": [ + "consumed_kilo_pass_issuance_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referral_rewards_conversion_role": { + "name": "UQ_impact_referral_rewards_conversion_role", + "nullsNotDistinct": false, + "columns": [ + "conversion_id", + "beneficiary_role" + ] + }, + "UQ_impact_referral_rewards_decision_id": { + "name": "UQ_impact_referral_rewards_decision_id", + "nullsNotDistinct": false, + "columns": [ + "decision_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referral_rewards_product_check": { + "name": "impact_referral_rewards_product_check", + "value": "\"impact_referral_rewards\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_referral_rewards_beneficiary_role_check": { + "name": "impact_referral_rewards_beneficiary_role_check", + "value": "\"impact_referral_rewards\".\"beneficiary_role\" IN ('referrer', 'referee')" + }, + "impact_referral_rewards_reward_kind_check": { + "name": "impact_referral_rewards_reward_kind_check", + "value": "\"impact_referral_rewards\".\"reward_kind\" IN ('kiloclaw_free_month', 'kilo_pass_bonus')" + }, + "impact_referral_rewards_status_check": { + "name": "impact_referral_rewards_status_check", + "value": "\"impact_referral_rewards\".\"status\" IN ('pending', 'earned', 'applied', 'reversed', 'expired', 'canceled', 'review_required')" + }, + "impact_referral_rewards_months_granted_non_negative_check": { + "name": "impact_referral_rewards_months_granted_non_negative_check", + "value": "\"impact_referral_rewards\".\"months_granted\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_referrals": { + "name": "impact_referrals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "referee_user_id": { + "name": "referee_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "referrer_user_id": { + "name": "referrer_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_touch_id": { + "name": "source_touch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "impact_referral_id": { + "name": "impact_referral_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referrals_referrer_user_id": { + "name": "IDX_impact_referrals_referrer_user_id", + "columns": [ + { + "expression": "referrer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referrals_source_touch_id": { + "name": "IDX_impact_referrals_source_touch_id", + "columns": [ + { + "expression": "source_touch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referrals_referee_user_id_kilocode_users_id_fk": { + "name": "impact_referrals_referee_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referrals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referee_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referrals_referrer_user_id_kilocode_users_id_fk": { + "name": "impact_referrals_referrer_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referrals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referrer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "impact_referrals_source_touch_id_impact_attribution_touches_id_fk": { + "name": "impact_referrals_source_touch_id_impact_attribution_touches_id_fk", + "tableFrom": "impact_referrals", + "tableTo": "impact_attribution_touches", + "columnsFrom": [ + "source_touch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referrals_product_referee_user_id": { + "name": "UQ_impact_referrals_product_referee_user_id", + "nullsNotDistinct": false, + "columns": [ + "product", + "referee_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referrals_product_check": { + "name": "impact_referrals_product_check", + "value": "\"impact_referrals\".\"product\" IN ('kiloclaw', 'kilo_pass')" + } + }, + "isRLSEnabled": false + }, + "public.ja4_digest": { + "name": "ja4_digest", + "schema": "", + "columns": { + "ja4_digest_id": { + "name": "ja4_digest_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "ja4_digest": { + "name": "ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_ja4_digest": { + "name": "UQ_ja4_digest", + "columns": [ + { + "expression": "ja4_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilo_pass_audit_log": { + "name": "kilo_pass_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_credit_transaction_id": { + "name": "related_credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_monthly_issuance_id": { + "name": "related_monthly_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "IDX_kilo_pass_audit_log_created_at": { + "name": "IDX_kilo_pass_audit_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_kilo_user_id": { + "name": "IDX_kilo_pass_audit_log_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_kilo_pass_subscription_id": { + "name": "IDX_kilo_pass_audit_log_kilo_pass_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_action": { + "name": "IDX_kilo_pass_audit_log_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_result": { + "name": "IDX_kilo_pass_audit_log_result", + "columns": [ + { + "expression": "result", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_idempotency_key": { + "name": "IDX_kilo_pass_audit_log_idempotency_key", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_event_id": { + "name": "IDX_kilo_pass_audit_log_stripe_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_invoice_id": { + "name": "IDX_kilo_pass_audit_log_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_subscription_id": { + "name": "IDX_kilo_pass_audit_log_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_related_credit_transaction_id": { + "name": "IDX_kilo_pass_audit_log_related_credit_transaction_id", + "columns": [ + { + "expression": "related_credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_related_monthly_issuance_id": { + "name": "IDX_kilo_pass_audit_log_related_monthly_issuance_id", + "columns": [ + { + "expression": "related_monthly_issuance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_audit_log_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_audit_log_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_audit_log_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_related_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_audit_log_related_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "credit_transactions", + "columnsFrom": [ + "related_credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_related_monthly_issuance_id_kilo_pass_issuances_id_fk": { + "name": "kilo_pass_audit_log_related_monthly_issuance_id_kilo_pass_issuances_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "related_monthly_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_audit_log_action_check": { + "name": "kilo_pass_audit_log_action_check", + "value": "\"kilo_pass_audit_log\".\"action\" IN ('stripe_webhook_received', 'kilo_pass_invoice_paid_handled', 'store_purchase_completed', 'store_notification_received', 'store_subscription_renewed', 'store_subscription_canceled', 'store_subscription_expired', 'store_subscription_refunded', 'base_credits_issued', 'bonus_credits_issued', 'bonus_credits_skipped_idempotent', 'first_month_50pct_promo_issued', 'yearly_monthly_base_cron_started', 'yearly_monthly_base_cron_completed', 'issue_yearly_remaining_credits', 'duplicate_card_subscription_canceled', 'yearly_monthly_bonus_cron_started', 'yearly_monthly_bonus_cron_completed')" + }, + "kilo_pass_audit_log_result_check": { + "name": "kilo_pass_audit_log_result_check", + "value": "\"kilo_pass_audit_log\".\"result\" IN ('success', 'skipped_idempotent', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_issuance_items": { + "name": "kilo_pass_issuance_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_issuance_id": { + "name": "kilo_pass_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit_transaction_id": { + "name": "credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "bonus_percent_applied": { + "name": "bonus_percent_applied", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_issuance_items_issuance_id": { + "name": "IDX_kilo_pass_issuance_items_issuance_id", + "columns": [ + { + "expression": "kilo_pass_issuance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuance_items_credit_transaction_id": { + "name": "IDX_kilo_pass_issuance_items_credit_transaction_id", + "columns": [ + { + "expression": "credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_issuance_items_kilo_pass_issuance_id_kilo_pass_issuances_id_fk": { + "name": "kilo_pass_issuance_items_kilo_pass_issuance_id_kilo_pass_issuances_id_fk", + "tableFrom": "kilo_pass_issuance_items", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "kilo_pass_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_issuance_items_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_issuance_items_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_issuance_items", + "tableTo": "credit_transactions", + "columnsFrom": [ + "credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilo_pass_issuance_items_credit_transaction_id_unique": { + "name": "kilo_pass_issuance_items_credit_transaction_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credit_transaction_id" + ] + }, + "UQ_kilo_pass_issuance_items_issuance_kind": { + "name": "UQ_kilo_pass_issuance_items_issuance_kind", + "nullsNotDistinct": false, + "columns": [ + "kilo_pass_issuance_id", + "kind" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_issuance_items_bonus_percent_applied_range_check": { + "name": "kilo_pass_issuance_items_bonus_percent_applied_range_check", + "value": "\"kilo_pass_issuance_items\".\"bonus_percent_applied\" IS NULL OR (\"kilo_pass_issuance_items\".\"bonus_percent_applied\" >= 0 AND \"kilo_pass_issuance_items\".\"bonus_percent_applied\" <= 1)" + }, + "kilo_pass_issuance_items_amount_usd_non_negative_check": { + "name": "kilo_pass_issuance_items_amount_usd_non_negative_check", + "value": "\"kilo_pass_issuance_items\".\"amount_usd\" >= 0" + }, + "kilo_pass_issuance_items_kind_check": { + "name": "kilo_pass_issuance_items_kind_check", + "value": "\"kilo_pass_issuance_items\".\"kind\" IN ('base', 'bonus', 'promo_first_month_50pct', 'referral_bonus')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_issuances": { + "name": "kilo_pass_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_month": { + "name": "issue_month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initial_welcome_promo_eligibility_reason": { + "name": "initial_welcome_promo_eligibility_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_issuances_stripe_invoice_id": { + "name": "UQ_kilo_pass_issuances_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_issuances\".\"stripe_invoice_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuances_subscription_id": { + "name": "IDX_kilo_pass_issuances_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuances_issue_month": { + "name": "IDX_kilo_pass_issuances_issue_month", + "columns": [ + { + "expression": "issue_month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_issuances_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_issuances_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_issuances", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_issuances_subscription_issue_month": { + "name": "UQ_kilo_pass_issuances_subscription_issue_month", + "nullsNotDistinct": false, + "columns": [ + "kilo_pass_subscription_id", + "issue_month" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_issuances_issue_month_day_one_check": { + "name": "kilo_pass_issuances_issue_month_day_one_check", + "value": "EXTRACT(DAY FROM \"kilo_pass_issuances\".\"issue_month\") = 1" + }, + "kilo_pass_issuances_source_check": { + "name": "kilo_pass_issuances_source_check", + "value": "\"kilo_pass_issuances\".\"source\" IN ('stripe_invoice', 'app_store_transaction', 'google_play_transaction', 'cron')" + }, + "kilo_pass_issuances_initial_welcome_promo_reason_check": { + "name": "kilo_pass_issuances_initial_welcome_promo_reason_check", + "value": "\"kilo_pass_issuances\".\"initial_welcome_promo_eligibility_reason\" IN ('first_payment_fingerprint_claim', 'fingerprint_previously_claimed', 'missing_fingerprint', 'no_supported_fingerprint', 'no_positive_settlement', 'settlement_unresolved')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_pause_events": { + "name": "kilo_pass_pause_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resumes_at": { + "name": "resumes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resumed_at": { + "name": "resumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_pause_events_subscription_id": { + "name": "IDX_kilo_pass_pause_events_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_pause_events_one_open_per_sub": { + "name": "UQ_kilo_pass_pause_events_one_open_per_sub", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_pause_events\".\"resumed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_pause_events_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_pause_events_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_pause_events", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_pause_events_resumed_at_after_paused_at_check": { + "name": "kilo_pass_pause_events_resumed_at_after_paused_at_check", + "value": "\"kilo_pass_pause_events\".\"resumed_at\" IS NULL OR \"kilo_pass_pause_events\".\"resumed_at\" >= \"kilo_pass_pause_events\".\"paused_at\"" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_scheduled_changes": { + "name": "kilo_pass_scheduled_changes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_tier": { + "name": "from_tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_cadence": { + "name": "from_cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_tier": { + "name": "to_tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_cadence": { + "name": "to_cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_scheduled_changes_kilo_user_id": { + "name": "IDX_kilo_pass_scheduled_changes_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_status": { + "name": "IDX_kilo_pass_scheduled_changes_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_stripe_subscription_id": { + "name": "IDX_kilo_pass_scheduled_changes_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_scheduled_changes_active_stripe_subscription_id": { + "name": "UQ_kilo_pass_scheduled_changes_active_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_scheduled_changes\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_effective_at": { + "name": "IDX_kilo_pass_scheduled_changes_effective_at", + "columns": [ + { + "expression": "effective_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_deleted_at": { + "name": "IDX_kilo_pass_scheduled_changes_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_scheduled_changes_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_scheduled_changes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_scheduled_changes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_scheduled_changes_stripe_subscription_id_kilo_pass_subscriptions_stripe_subscription_id_fk": { + "name": "kilo_pass_scheduled_changes_stripe_subscription_id_kilo_pass_subscriptions_stripe_subscription_id_fk", + "tableFrom": "kilo_pass_scheduled_changes", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "stripe_subscription_id" + ], + "columnsTo": [ + "stripe_subscription_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_scheduled_changes_from_tier_check": { + "name": "kilo_pass_scheduled_changes_from_tier_check", + "value": "\"kilo_pass_scheduled_changes\".\"from_tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_scheduled_changes_from_cadence_check": { + "name": "kilo_pass_scheduled_changes_from_cadence_check", + "value": "\"kilo_pass_scheduled_changes\".\"from_cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_scheduled_changes_to_tier_check": { + "name": "kilo_pass_scheduled_changes_to_tier_check", + "value": "\"kilo_pass_scheduled_changes\".\"to_tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_scheduled_changes_to_cadence_check": { + "name": "kilo_pass_scheduled_changes_to_cadence_check", + "value": "\"kilo_pass_scheduled_changes\".\"to_cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_scheduled_changes_status_check": { + "name": "kilo_pass_scheduled_changes_status_check", + "value": "\"kilo_pass_scheduled_changes\".\"status\" IN ('not_started', 'active', 'completed', 'released', 'canceled')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_store_events": { + "name": "kilo_pass_store_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_transaction_id": { + "name": "provider_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_account_token": { + "name": "app_account_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment": { + "name": "environment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_store_events_provider_event": { + "name": "UQ_kilo_pass_store_events_provider_event", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_events_provider_subscription": { + "name": "IDX_kilo_pass_store_events_provider_subscription", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_events_app_account_token": { + "name": "IDX_kilo_pass_store_events_app_account_token", + "columns": [ + { + "expression": "app_account_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_store_events_payment_provider_check": { + "name": "kilo_pass_store_events_payment_provider_check", + "value": "\"kilo_pass_store_events\".\"payment_provider\" IN ('stripe', 'app_store', 'google_play')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_store_purchases": { + "name": "kilo_pass_store_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_transaction_id": { + "name": "provider_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_original_transaction_id": { + "name": "provider_original_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_account_token": { + "name": "app_account_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchase_token": { + "name": "purchase_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment": { + "name": "environment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchased_at": { + "name": "purchased_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "raw_payload_json": { + "name": "raw_payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_store_purchases_provider_transaction": { + "name": "UQ_kilo_pass_store_purchases_provider_transaction", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_subscription_id": { + "name": "IDX_kilo_pass_store_purchases_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_user_id": { + "name": "IDX_kilo_pass_store_purchases_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_app_account_token": { + "name": "IDX_kilo_pass_store_purchases_app_account_token", + "columns": [ + { + "expression": "app_account_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_latest_subscription_purchase": { + "name": "IDX_kilo_pass_store_purchases_latest_subscription_purchase", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "purchased_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_store_purchases_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_store_purchases_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_store_purchases", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_store_purchases_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_store_purchases_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_store_purchases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "FK_kilo_pass_store_purchases_subscription_owner_provider": { + "name": "FK_kilo_pass_store_purchases_subscription_owner_provider", + "tableFrom": "kilo_pass_store_purchases", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id", + "kilo_user_id", + "payment_provider", + "provider_subscription_id" + ], + "columnsTo": [ + "id", + "kilo_user_id", + "payment_provider", + "provider_subscription_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_store_purchases_store_provider_check": { + "name": "kilo_pass_store_purchases_store_provider_check", + "value": "\"kilo_pass_store_purchases\".\"payment_provider\" IN ('app_store', 'google_play')" + }, + "kilo_pass_store_purchases_payment_provider_check": { + "name": "kilo_pass_store_purchases_payment_provider_check", + "value": "\"kilo_pass_store_purchases\".\"payment_provider\" IN ('stripe', 'app_store', 'google_play')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_subscriptions": { + "name": "kilo_pass_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stripe'" + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_streak_months": { + "name": "current_streak_months", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_yearly_issue_at": { + "name": "next_yearly_issue_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_subscriptions_kilo_user_id": { + "name": "IDX_kilo_pass_subscriptions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_payment_provider": { + "name": "IDX_kilo_pass_subscriptions_payment_provider", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_status": { + "name": "IDX_kilo_pass_subscriptions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_cadence": { + "name": "IDX_kilo_pass_subscriptions_cadence", + "columns": [ + { + "expression": "cadence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_subscriptions_provider_subscription": { + "name": "UQ_kilo_pass_subscriptions_provider_subscription", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_subscriptions\".\"provider_subscription_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_subscriptions_store_purchase_reference": { + "name": "UQ_kilo_pass_subscriptions_store_purchase_reference", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_subscriptions_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_subscriptions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilo_pass_subscriptions_stripe_subscription_id_unique": { + "name": "kilo_pass_subscriptions_stripe_subscription_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_subscriptions_current_streak_months_non_negative_check": { + "name": "kilo_pass_subscriptions_current_streak_months_non_negative_check", + "value": "\"kilo_pass_subscriptions\".\"current_streak_months\" >= 0" + }, + "kilo_pass_subscriptions_provider_ids_check": { + "name": "kilo_pass_subscriptions_provider_ids_check", + "value": "(\n \"kilo_pass_subscriptions\".\"payment_provider\" = 'stripe'\n AND \"kilo_pass_subscriptions\".\"provider_subscription_id\" IS NOT NULL\n AND \"kilo_pass_subscriptions\".\"stripe_subscription_id\" IS NOT NULL\n AND \"kilo_pass_subscriptions\".\"provider_subscription_id\" = \"kilo_pass_subscriptions\".\"stripe_subscription_id\"\n ) OR (\n \"kilo_pass_subscriptions\".\"payment_provider\" IN ('app_store', 'google_play')\n AND \"kilo_pass_subscriptions\".\"provider_subscription_id\" IS NOT NULL\n AND \"kilo_pass_subscriptions\".\"stripe_subscription_id\" IS NULL\n )" + }, + "kilo_pass_subscriptions_payment_provider_check": { + "name": "kilo_pass_subscriptions_payment_provider_check", + "value": "\"kilo_pass_subscriptions\".\"payment_provider\" IN ('stripe', 'app_store', 'google_play')" + }, + "kilo_pass_subscriptions_tier_check": { + "name": "kilo_pass_subscriptions_tier_check", + "value": "\"kilo_pass_subscriptions\".\"tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_subscriptions_cadence_check": { + "name": "kilo_pass_subscriptions_cadence_check", + "value": "\"kilo_pass_subscriptions\".\"cadence\" IN ('monthly', 'yearly')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_welcome_promo_payment_fingerprint_claims": { + "name": "kilo_pass_welcome_promo_payment_fingerprint_claims", + "schema": "", + "columns": { + "stripe_payment_method_type": { + "name": "stripe_payment_method_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_fingerprint": { + "name": "stripe_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_stripe_invoice_id": { + "name": "source_stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "kilo_pass_welcome_promo_payment_fingerprint_claims_stripe_payment_method_type_stripe_fingerprint_pk": { + "name": "kilo_pass_welcome_promo_payment_fingerprint_claims_stripe_payment_method_type_stripe_fingerprint_pk", + "columns": [ + "stripe_payment_method_type", + "stripe_fingerprint" + ] + } + }, + "uniqueConstraints": { + "UQ_kilo_pass_welcome_promo_payment_fingerprint_claims_source_invoice_id": { + "name": "UQ_kilo_pass_welcome_promo_payment_fingerprint_claims_source_invoice_id", + "nullsNotDistinct": false, + "columns": [ + "source_stripe_invoice_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_welcome_promo_payment_fingerprint_claims_type_check": { + "name": "kilo_pass_welcome_promo_payment_fingerprint_claims_type_check", + "value": "\"kilo_pass_welcome_promo_payment_fingerprint_claims\".\"stripe_payment_method_type\" IN ('card', 'sepa_debit', 'us_bank_account', 'bacs_debit', 'au_becs_debit')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_access_codes": { + "name": "kiloclaw_access_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_access_codes_code": { + "name": "UQ_kiloclaw_access_codes_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_access_codes_user_status": { + "name": "IDX_kiloclaw_access_codes_user_status", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_access_codes_one_active_per_user": { + "name": "UQ_kiloclaw_access_codes_one_active_per_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_access_codes_kilo_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_access_codes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_access_codes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_admin_audit_logs": { + "name": "kiloclaw_admin_audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_admin_audit_logs_target_user_id": { + "name": "IDX_kiloclaw_admin_audit_logs_target_user_id", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_admin_audit_logs_action": { + "name": "IDX_kiloclaw_admin_audit_logs_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_admin_audit_logs_created_at": { + "name": "IDX_kiloclaw_admin_audit_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_cli_runs": { + "name": "kiloclaw_cli_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "initiated_by_admin_id": { + "name": "initiated_by_admin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_cli_runs_user_id": { + "name": "IDX_kiloclaw_cli_runs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_cli_runs_started_at": { + "name": "IDX_kiloclaw_cli_runs_started_at", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_cli_runs_instance_id": { + "name": "IDX_kiloclaw_cli_runs_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_cli_runs_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_cli_runs_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_cli_runs_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_cli_runs_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_cli_runs_initiated_by_admin_id_kilocode_users_id_fk": { + "name": "kiloclaw_cli_runs_initiated_by_admin_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "initiated_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_earlybird_purchases": { + "name": "kiloclaw_earlybird_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manual_payment_id": { + "name": "manual_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kiloclaw_earlybird_purchases_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_earlybird_purchases_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_earlybird_purchases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_earlybird_purchases_user_id_unique": { + "name": "kiloclaw_earlybird_purchases_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "kiloclaw_earlybird_purchases_stripe_charge_id_unique": { + "name": "kiloclaw_earlybird_purchases_stripe_charge_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_charge_id" + ] + }, + "kiloclaw_earlybird_purchases_manual_payment_id_unique": { + "name": "kiloclaw_earlybird_purchases_manual_payment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "manual_payment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_email_log": { + "name": "kiloclaw_email_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email_type": { + "name": "email_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_start": { + "name": "period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "'epoch'" + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_email_log_user_type_global": { + "name": "UQ_kiloclaw_email_log_user_type_global", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_email_log\".\"instance_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_email_log_user_instance_type_period": { + "name": "UQ_kiloclaw_email_log_user_instance_type_period", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_email_log\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_email_log_type_sent_instance": { + "name": "IDX_kiloclaw_email_log_type_sent_instance", + "columns": [ + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_email_log\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_email_log_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_email_log_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_email_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_email_log_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_email_log_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_email_log", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_google_oauth_connections": { + "name": "kiloclaw_google_oauth_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'google'" + }, + "account_email": { + "name": "account_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_subject": { + "name": "account_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_secret_encrypted": { + "name": "oauth_client_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_profile": { + "name": "credential_profile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kilo_owned'" + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "grants_by_source": { + "name": "grants_by_source", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_google_oauth_connections_instance": { + "name": "UQ_kiloclaw_google_oauth_connections_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_google_oauth_connections_status": { + "name": "IDX_kiloclaw_google_oauth_connections_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_google_oauth_connections_provider": { + "name": "IDX_kiloclaw_google_oauth_connections_provider", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_google_oauth_connections_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_google_oauth_connections_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_google_oauth_connections", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_google_oauth_connections_status_check": { + "name": "kiloclaw_google_oauth_connections_status_check", + "value": "\"kiloclaw_google_oauth_connections\".\"status\" IN ('active', 'action_required', 'disconnected')" + }, + "kiloclaw_google_oauth_connections_credential_profile_check": { + "name": "kiloclaw_google_oauth_connections_credential_profile_check", + "value": "\"kiloclaw_google_oauth_connections\".\"credential_profile\" IN ('legacy', 'kilo_owned')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_image_catalog": { + "name": "kiloclaw_image_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "openclaw_version": { + "name": "openclaw_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_digest": { + "name": "image_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "rollout_percent": { + "name": "rollout_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_latest": { + "name": "is_latest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_kiloclaw_image_catalog_status": { + "name": "IDX_kiloclaw_image_catalog_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_image_catalog_variant": { + "name": "IDX_kiloclaw_image_catalog_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_image_catalog_one_latest_per_variant": { + "name": "UQ_kiloclaw_image_catalog_one_latest_per_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_image_catalog\".\"is_latest\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_image_catalog_one_candidate_per_variant": { + "name": "UQ_kiloclaw_image_catalog_one_candidate_per_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_image_catalog\".\"is_latest\" = false AND \"kiloclaw_image_catalog\".\"rollout_percent\" > 0 AND \"kiloclaw_image_catalog\".\"status\" = 'available'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_image_catalog_image_tag_unique": { + "name": "kiloclaw_image_catalog_image_tag_unique", + "nullsNotDistinct": false, + "columns": [ + "image_tag" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_inbound_email_aliases": { + "name": "kiloclaw_inbound_email_aliases", + "schema": "", + "columns": { + "alias": { + "name": "alias", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_inbound_email_aliases_instance_id": { + "name": "IDX_kiloclaw_inbound_email_aliases_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_inbound_email_aliases_active_instance": { + "name": "UQ_kiloclaw_inbound_email_aliases_active_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_inbound_email_aliases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_inbound_email_aliases_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_inbound_email_aliases_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_inbound_email_aliases", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_inbound_email_reserved_aliases": { + "name": "kiloclaw_inbound_email_reserved_aliases", + "schema": "", + "columns": { + "alias": { + "name": "alias", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_instances": { + "name": "kiloclaw_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_id": { + "name": "sandbox_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'fly'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbound_email_enabled": { + "name": "inbound_email_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inactive_trial_stopped_at": { + "name": "inactive_trial_stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tracked_image_tag": { + "name": "tracked_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instance_type": { + "name": "instance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "admin_size_override": { + "name": "admin_size_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_instances_active": { + "name": "UQ_kiloclaw_instances_active", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sandbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_personal_by_user": { + "name": "IDX_kiloclaw_instances_active_personal_by_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_org_by_user_org": { + "name": "IDX_kiloclaw_instances_active_org_by_user_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_org_by_org_created": { + "name": "IDX_kiloclaw_instances_active_org_by_org_created", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_user_id_created_at": { + "name": "IDX_kiloclaw_instances_user_id_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_tracked_image_tag": { + "name": "IDX_kiloclaw_instances_tracked_image_tag", + "columns": [ + { + "expression": "tracked_image_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_instance_type": { + "name": "IDX_kiloclaw_instances_instance_type", + "columns": [ + { + "expression": "instance_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_admin_size_override": { + "name": "IDX_kiloclaw_instances_admin_size_override", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"admin_size_override\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_instances_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_instances_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_instances", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_instances_organization_id_organizations_id_fk": { + "name": "kiloclaw_instances_organization_id_organizations_id_fk", + "tableFrom": "kiloclaw_instances", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_kiloclaw_instances_instance_type": { + "name": "CHK_kiloclaw_instances_instance_type", + "value": "\"kiloclaw_instances\".\"instance_type\" IS NULL OR \"kiloclaw_instances\".\"instance_type\" IN ('perf-1-3', 'perf-4-8', 'perf-4-16', 'shared-2-3', 'shared-2-4', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_morning_briefing_configs": { + "name": "kiloclaw_morning_briefing_configs", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0 7 * * *'" + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "interest_topics": { + "name": "interest_topics", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_morning_briefing_configs_enabled": { + "name": "IDX_kiloclaw_morning_briefing_configs_enabled", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_morning_briefing_configs\".\"enabled\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_morning_briefing_configs_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_morning_briefing_configs_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_morning_briefing_configs", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_notifications": { + "name": "kiloclaw_scheduled_action_notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'notice'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_notifications_target_kind_channel": { + "name": "UQ_kiloclaw_scheduled_action_notifications_target_kind_channel", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_notifications_pending": { + "name": "IDX_kiloclaw_scheduled_action_notifications_pending", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_notifications\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_notifications_target_id_kiloclaw_scheduled_action_targets_id_fk": { + "name": "kiloclaw_scheduled_action_notifications_target_id_kiloclaw_scheduled_action_targets_id_fk", + "tableFrom": "kiloclaw_scheduled_action_notifications", + "tableTo": "kiloclaw_scheduled_action_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_stages": { + "name": "kiloclaw_scheduled_action_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scheduled_action_id": { + "name": "scheduled_action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_index": { + "name": "stage_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "notice_sent_at": { + "name": "notice_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "applied_count": { + "name": "applied_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_stages_parent_index": { + "name": "UQ_kiloclaw_scheduled_action_stages_parent_index", + "columns": [ + { + "expression": "scheduled_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_stages_notice_due": { + "name": "IDX_kiloclaw_scheduled_action_stages_notice_due", + "columns": [ + { + "expression": "scheduled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_stages\".\"notice_sent_at\" IS NULL AND \"kiloclaw_scheduled_action_stages\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_stages_scheduled_action_id_kiloclaw_scheduled_actions_id_fk": { + "name": "kiloclaw_scheduled_action_stages_scheduled_action_id_kiloclaw_scheduled_actions_id_fk", + "tableFrom": "kiloclaw_scheduled_action_stages", + "tableTo": "kiloclaw_scheduled_actions", + "columnsFrom": [ + "scheduled_action_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_targets": { + "name": "kiloclaw_scheduled_action_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scheduled_action_id": { + "name": "scheduled_action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_image_tag": { + "name": "source_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_image_tag": { + "name": "target_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_targets_parent_instance": { + "name": "UQ_kiloclaw_scheduled_action_targets_parent_instance", + "columns": [ + { + "expression": "scheduled_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_targets_stage": { + "name": "IDX_kiloclaw_scheduled_action_targets_stage", + "columns": [ + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_targets_pending_by_instance": { + "name": "IDX_kiloclaw_scheduled_action_targets_pending_by_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_targets\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_targets_scheduled_action_id_kiloclaw_scheduled_actions_id_fk": { + "name": "kiloclaw_scheduled_action_targets_scheduled_action_id_kiloclaw_scheduled_actions_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_scheduled_actions", + "columnsFrom": [ + "scheduled_action_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_stage_id_kiloclaw_scheduled_action_stages_id_fk": { + "name": "kiloclaw_scheduled_action_targets_stage_id_kiloclaw_scheduled_action_stages_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_scheduled_action_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_scheduled_action_targets_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_scheduled_action_targets_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_actions": { + "name": "kiloclaw_scheduled_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_image_tag": { + "name": "target_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_pins": { + "name": "override_pins", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notice_lead_hours": { + "name": "notice_lead_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "notice_subject": { + "name": "notice_subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notice_body": { + "name": "notice_body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_count": { + "name": "total_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "applied_count": { + "name": "applied_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "IDX_kiloclaw_scheduled_actions_status": { + "name": "IDX_kiloclaw_scheduled_actions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_actions_action_type": { + "name": "IDX_kiloclaw_scheduled_actions_action_type", + "columns": [ + { + "expression": "action_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_actions_created_by": { + "name": "IDX_kiloclaw_scheduled_actions_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_actions_target_image_tag_kiloclaw_image_catalog_image_tag_fk": { + "name": "kiloclaw_scheduled_actions_target_image_tag_kiloclaw_image_catalog_image_tag_fk", + "tableFrom": "kiloclaw_scheduled_actions", + "tableTo": "kiloclaw_image_catalog", + "columnsFrom": [ + "target_image_tag" + ], + "columnsTo": [ + "image_tag" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_actions_created_by_kilocode_users_id_fk": { + "name": "kiloclaw_scheduled_actions_created_by_kilocode_users_id_fk", + "tableFrom": "kiloclaw_scheduled_actions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_subscription_change_log": { + "name": "kiloclaw_subscription_change_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "before_state": { + "name": "before_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_state": { + "name": "after_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_subscription_change_log_subscription_created_at": { + "name": "IDX_kiloclaw_subscription_change_log_subscription_created_at", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscription_change_log_created_at": { + "name": "IDX_kiloclaw_subscription_change_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_subscription_change_log_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_subscription_change_log_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_subscription_change_log", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_subscription_change_log_actor_type_check": { + "name": "kiloclaw_subscription_change_log_actor_type_check", + "value": "\"kiloclaw_subscription_change_log\".\"actor_type\" IN ('user', 'system')" + }, + "kiloclaw_subscription_change_log_action_check": { + "name": "kiloclaw_subscription_change_log_action_check", + "value": "\"kiloclaw_subscription_change_log\".\"action\" IN ('created', 'status_changed', 'plan_switched', 'period_advanced', 'canceled', 'reactivated', 'suspended', 'destruction_scheduled', 'reassigned', 'backfilled', 'payment_source_changed', 'schedule_changed', 'admin_override')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_subscriptions": { + "name": "kiloclaw_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transferred_to_subscription_id": { + "name": "transferred_to_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "access_origin": { + "name": "access_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_source": { + "name": "payment_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kiloclaw_price_version": { + "name": "kiloclaw_price_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_plan": { + "name": "scheduled_plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduled_by": { + "name": "scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_conversion": { + "name": "pending_conversion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trial_started_at": { + "name": "trial_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credit_renewal_at": { + "name": "credit_renewal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "commit_ends_at": { + "name": "commit_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "past_due_since": { + "name": "past_due_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "destruction_deadline": { + "name": "destruction_deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_requested_at": { + "name": "auto_resume_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_retry_after": { + "name": "auto_resume_retry_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_attempt_count": { + "name": "auto_resume_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "auto_top_up_triggered_for_period": { + "name": "auto_top_up_triggered_for_period", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_subscriptions_status": { + "name": "IDX_kiloclaw_subscriptions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_user_id": { + "name": "IDX_kiloclaw_subscriptions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_user_status": { + "name": "IDX_kiloclaw_subscriptions_user_status", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_price_version": { + "name": "IDX_kiloclaw_subscriptions_price_version", + "columns": [ + { + "expression": "kiloclaw_price_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_transferred_to": { + "name": "IDX_kiloclaw_subscriptions_transferred_to", + "columns": [ + { + "expression": "transferred_to_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_stripe_schedule_id": { + "name": "IDX_kiloclaw_subscriptions_stripe_schedule_id", + "columns": [ + { + "expression": "stripe_schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_auto_resume_retry_after": { + "name": "IDX_kiloclaw_subscriptions_auto_resume_retry_after", + "columns": [ + { + "expression": "auto_resume_retry_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_subscriptions_instance": { + "name": "UQ_kiloclaw_subscriptions_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_subscriptions\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_subscriptions_transferred_to": { + "name": "UQ_kiloclaw_subscriptions_transferred_to", + "columns": [ + { + "expression": "transferred_to_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_subscriptions\".\"transferred_to_subscription_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_earlybird_origin": { + "name": "IDX_kiloclaw_subscriptions_earlybird_origin", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "access_origin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_subscriptions\".\"access_origin\" = 'earlybird'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_subscriptions_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_subscriptions_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_subscriptions_transferred_to_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_subscriptions_transferred_to_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "transferred_to_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_subscriptions_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_subscriptions_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_subscriptions_stripe_subscription_id_unique": { + "name": "kiloclaw_subscriptions_stripe_subscription_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kiloclaw_subscriptions_price_version_check": { + "name": "kiloclaw_subscriptions_price_version_check", + "value": "\"kiloclaw_subscriptions\".\"kiloclaw_price_version\" IN ('2026-03-19', '2026-05-10')" + }, + "kiloclaw_subscriptions_plan_check": { + "name": "kiloclaw_subscriptions_plan_check", + "value": "\"kiloclaw_subscriptions\".\"plan\" IN ('trial', 'commit', 'standard')" + }, + "kiloclaw_subscriptions_scheduled_plan_check": { + "name": "kiloclaw_subscriptions_scheduled_plan_check", + "value": "\"kiloclaw_subscriptions\".\"scheduled_plan\" IN ('commit', 'standard')" + }, + "kiloclaw_subscriptions_scheduled_by_check": { + "name": "kiloclaw_subscriptions_scheduled_by_check", + "value": "\"kiloclaw_subscriptions\".\"scheduled_by\" IN ('auto', 'user')" + }, + "kiloclaw_subscriptions_status_check": { + "name": "kiloclaw_subscriptions_status_check", + "value": "\"kiloclaw_subscriptions\".\"status\" IN ('trialing', 'active', 'past_due', 'canceled', 'unpaid')" + }, + "kiloclaw_subscriptions_access_origin_check": { + "name": "kiloclaw_subscriptions_access_origin_check", + "value": "\"kiloclaw_subscriptions\".\"access_origin\" IN ('earlybird')" + }, + "kiloclaw_subscriptions_payment_source_check": { + "name": "kiloclaw_subscriptions_payment_source_check", + "value": "\"kiloclaw_subscriptions\".\"payment_source\" IN ('stripe', 'credits')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_terminal_renewal_failures": { + "name": "kiloclaw_terminal_renewal_failures", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "renewal_boundary": { + "name": "renewal_boundary", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unresolved'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "first_failure_at": { + "name": "first_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_failure_at": { + "name": "last_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_failure_code": { + "name": "last_failure_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_failure_message": { + "name": "last_failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_actor_type": { + "name": "resolution_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_actor_id": { + "name": "resolution_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_at": { + "name": "resolution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_terminal_renewal_failures_subscription_boundary": { + "name": "UQ_kiloclaw_terminal_renewal_failures_subscription_boundary", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "renewal_boundary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_terminal_renewal_failures_unresolved": { + "name": "IDX_kiloclaw_terminal_renewal_failures_unresolved", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "renewal_boundary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_terminal_renewal_failures\".\"status\" = 'unresolved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_terminal_renewal_failures_status_last_failure_at": { + "name": "IDX_kiloclaw_terminal_renewal_failures_status_last_failure_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_failure_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_terminal_renewal_failures_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_terminal_renewal_failures_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_terminal_renewal_failures", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_terminal_renewal_failures_status_check": { + "name": "kiloclaw_terminal_renewal_failures_status_check", + "value": "\"kiloclaw_terminal_renewal_failures\".\"status\" IN ('unresolved', 'resolved', 'waived', 'superseded')" + }, + "kiloclaw_terminal_renewal_failures_last_failure_code_check": { + "name": "kiloclaw_terminal_renewal_failures_last_failure_code_check", + "value": "\"kiloclaw_terminal_renewal_failures\".\"last_failure_code\" IN ('credit_balance_read_failed', 'renewal_transaction_failed', 'auto_top_up_marker_write_failed', 'worker_timeout', 'poison_payload', 'queue_delivery_exhausted')" + }, + "kiloclaw_terminal_renewal_failures_resolution_actor_type_check": { + "name": "kiloclaw_terminal_renewal_failures_resolution_actor_type_check", + "value": "\"kiloclaw_terminal_renewal_failures\".\"resolution_actor_type\" IN ('operator', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_version_pins": { + "name": "kiloclaw_version_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_by": { + "name": "pinned_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kiloclaw_version_pins_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_version_pins_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_version_pins_image_tag_kiloclaw_image_catalog_image_tag_fk": { + "name": "kiloclaw_version_pins_image_tag_kiloclaw_image_catalog_image_tag_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kiloclaw_image_catalog", + "columnsFrom": [ + "image_tag" + ], + "columnsTo": [ + "image_tag" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "kiloclaw_version_pins_pinned_by_kilocode_users_id_fk": { + "name": "kiloclaw_version_pins_pinned_by_kilocode_users_id_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kilocode_users", + "columnsFrom": [ + "pinned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_version_pins_instance_id_unique": { + "name": "kiloclaw_version_pins_instance_id_unique", + "nullsNotDistinct": false, + "columns": [ + "instance_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilocode_users": { + "name": "kilocode_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "google_user_email": { + "name": "google_user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_user_name": { + "name": "google_user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_user_image_url": { + "name": "google_user_image_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "hosted_domain": { + "name": "hosted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "kilo_pass_threshold": { + "name": "kilo_pass_threshold", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_store_account_token": { + "name": "app_store_account_token", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_manage_credits": { + "name": "can_manage_credits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "total_microdollars_acquired": { + "name": "total_microdollars_acquired", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "next_credit_expiration_at": { + "name": "next_credit_expiration_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "has_validation_stytch": { + "name": "has_validation_stytch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_validation_novel_card_with_hold": { + "name": "has_validation_novel_card_with_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_at": { + "name": "blocked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_by_kilo_user_id": { + "name": "blocked_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_token_pepper": { + "name": "api_token_pepper", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "web_session_pepper": { + "name": "web_session_pepper", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_enabled": { + "name": "auto_top_up_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "kiloclaw_early_access": { + "name": "kiloclaw_early_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cohorts": { + "name": "cohorts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "completed_welcome_form": { + "name": "completed_welcome_form", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "linkedin_url": { + "name": "linkedin_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_url": { + "name": "github_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_server_membership_verified_at": { + "name": "discord_server_membership_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "openrouter_upstream_safety_identifier": { + "name": "openrouter_upstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vercel_downstream_safety_identifier": { + "name": "vercel_downstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_source": { + "name": "customer_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signup_ip": { + "name": "signup_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_deletion_requested_at": { + "name": "account_deletion_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_domain": { + "name": "email_domain", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kilocode_users_signup_ip_created_at": { + "name": "IDX_kilocode_users_signup_ip_created_at", + "columns": [ + { + "expression": "signup_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_blocked_at": { + "name": "IDX_kilocode_users_blocked_at", + "columns": [ + { + "expression": "blocked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_blocked_by_kilo_user_id": { + "name": "IDX_kilocode_users_blocked_by_kilo_user_id", + "columns": [ + { + "expression": "blocked_by_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_openrouter_upstream_safety_identifier": { + "name": "UQ_kilocode_users_openrouter_upstream_safety_identifier", + "columns": [ + { + "expression": "openrouter_upstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"openrouter_upstream_safety_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_vercel_downstream_safety_identifier": { + "name": "UQ_kilocode_users_vercel_downstream_safety_identifier", + "columns": [ + { + "expression": "vercel_downstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"vercel_downstream_safety_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_normalized_email": { + "name": "IDX_kilocode_users_normalized_email", + "columns": [ + { + "expression": "normalized_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_email_domain": { + "name": "IDX_kilocode_users_email_domain", + "columns": [ + { + "expression": "email_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilocode_users_app_store_account_token_unique": { + "name": "kilocode_users_app_store_account_token_unique", + "nullsNotDistinct": false, + "columns": [ + "app_store_account_token" + ] + }, + "UQ_b1afacbcf43f2c7c4cb9f7e7faa": { + "name": "UQ_b1afacbcf43f2c7c4cb9f7e7faa", + "nullsNotDistinct": false, + "columns": [ + "google_user_email" + ] + } + }, + "policies": {}, + "checkConstraints": { + "blocked_reason_not_empty": { + "name": "blocked_reason_not_empty", + "value": "length(blocked_reason) > 0" + }, + "kilocode_users_can_manage_credits_requires_admin_check": { + "name": "kilocode_users_can_manage_credits_requires_admin_check", + "value": "NOT \"kilocode_users\".\"can_manage_credits\" OR \"kilocode_users\".\"is_admin\"" + } + }, + "isRLSEnabled": false + }, + "public.magic_link_tokens": { + "name": "magic_link_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_magic_link_tokens_email": { + "name": "idx_magic_link_tokens_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_magic_link_tokens_expires_at": { + "name": "idx_magic_link_tokens_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_expires_at_future": { + "name": "check_expires_at_future", + "value": "\"magic_link_tokens\".\"expires_at\" > \"magic_link_tokens\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_assignments": { + "name": "mcp_gateway_assignments", + "schema": "", + "columns": { + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by_kilo_user_id": { + "name": "assigned_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "single_user_slot": { + "name": "single_user_slot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_assignments_active": { + "name": "UQ_mcp_gateway_assignments_active", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_assignments\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_mcp_gateway_assignments_single_user_slot": { + "name": "UQ_mcp_gateway_assignments_single_user_slot", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "single_user_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_assignments\".\"revoked_at\" is null and \"mcp_gateway_assignments\".\"single_user_slot\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_assignments_config": { + "name": "IDX_mcp_gateway_assignments_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_assignments_user": { + "name": "IDX_mcp_gateway_assignments_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_assignments_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_assignments_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_assignments", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_assignments_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_assignments_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_assignments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_assignments_assigned_by_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_assignments_assigned_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_assignments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "assigned_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_gateway_audit_events": { + "name": "mcp_gateway_audit_events", + "schema": "", + "columns": { + "audit_event_id": { + "name": "audit_event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "actor_kilo_user_id": { + "name": "actor_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connect_resource_id": { + "name": "connect_resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_metadata": { + "name": "correlation_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_mcp_gateway_audit_events_config": { + "name": "IDX_mcp_gateway_audit_events_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_audit_events_owner": { + "name": "IDX_mcp_gateway_audit_events_owner", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_audit_events_created_at": { + "name": "IDX_mcp_gateway_audit_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_audit_events_actor_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_audit_events_actor_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "actor_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_audit_events_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_connect_resource_id_mcp_gateway_connect_resources_connect_resource_id_fk": { + "name": "mcp_gateway_audit_events_connect_resource_id_mcp_gateway_connect_resources_connect_resource_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_connect_resources", + "columnsFrom": [ + "connect_resource_id" + ], + "columnsTo": [ + "connect_resource_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_audit_events_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_audit_events_owner_scope": { + "name": "mcp_gateway_audit_events_owner_scope", + "value": "\"mcp_gateway_audit_events\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_audit_events_outcome": { + "name": "mcp_gateway_audit_events_outcome", + "value": "\"mcp_gateway_audit_events\".\"outcome\" IN ('success', 'failure', 'blocked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_authorization_codes": { + "name": "mcp_gateway_authorization_codes", + "schema": "", + "columns": { + "authorization_code_id": { + "name": "authorization_code_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authorization_request_id": { + "name": "authorization_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'S256'" + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_authorization_codes_code_hash": { + "name": "UQ_mcp_gateway_authorization_codes_code_hash", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_codes_expires_at": { + "name": "IDX_mcp_gateway_authorization_codes_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_codes_client": { + "name": "IDX_mcp_gateway_authorization_codes_client", + "columns": [ + { + "expression": "oauth_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_authorization_codes_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk": { + "name": "mcp_gateway_authorization_codes_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_authorization_requests", + "columnsFrom": [ + "authorization_request_id" + ], + "columnsTo": [ + "authorization_request_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_authorization_codes_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_authorization_codes_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_authorization_codes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_authorization_codes_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_authorization_codes_owner_scope": { + "name": "mcp_gateway_authorization_codes_owner_scope", + "value": "\"mcp_gateway_authorization_codes\".\"owner_scope\" IN ('personal', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_authorization_requests": { + "name": "mcp_gateway_authorization_requests", + "schema": "", + "columns": { + "authorization_request_id": { + "name": "authorization_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "request_state_hash": { + "name": "request_state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "oauth_state": { + "name": "oauth_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'S256'" + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_status": { + "name": "request_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_authorization_requests_state_hash": { + "name": "UQ_mcp_gateway_authorization_requests_state_hash", + "columns": [ + { + "expression": "request_state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_config": { + "name": "IDX_mcp_gateway_authorization_requests_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_user": { + "name": "IDX_mcp_gateway_authorization_requests_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_expires_at": { + "name": "IDX_mcp_gateway_authorization_requests_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_authorization_requests_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_authorization_requests_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_authorization_requests_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_authorization_requests_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_authorization_requests_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_authorization_requests_owner_scope": { + "name": "mcp_gateway_authorization_requests_owner_scope", + "value": "\"mcp_gateway_authorization_requests\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_authorization_requests_status": { + "name": "mcp_gateway_authorization_requests_status", + "value": "\"mcp_gateway_authorization_requests\".\"request_status\" IN ('pending', 'completed', 'error')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_config_secrets": { + "name": "mcp_gateway_config_secrets", + "schema": "", + "columns": { + "config_secret_id": { + "name": "config_secret_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_kind": { + "name": "secret_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_secret": { + "name": "encrypted_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_version": { + "name": "secret_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_config_secrets_active_kind": { + "name": "UQ_mcp_gateway_config_secrets_active_kind", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_config_secrets\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_config_secrets_config": { + "name": "IDX_mcp_gateway_config_secrets_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_config_secrets_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_config_secrets_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_config_secrets", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_config_secrets_version_positive": { + "name": "mcp_gateway_config_secrets_version_positive", + "value": "\"mcp_gateway_config_secrets\".\"secret_version\" > 0" + }, + "mcp_gateway_config_secrets_kind": { + "name": "mcp_gateway_config_secrets_kind", + "value": "\"mcp_gateway_config_secrets\".\"secret_kind\" IN ('static_provider_credentials', 'dynamic_registration', 'static_headers')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_configs": { + "name": "mcp_gateway_configs", + "schema": "", + "columns": { + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "remote_url": { + "name": "remote_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_mode": { + "name": "auth_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sharing_mode": { + "name": "sharing_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_scopes": { + "name": "provider_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_scope_source": { + "name": "provider_scope_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "provider_resource": { + "name": "provider_resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "path_passthrough": { + "name": "path_passthrough", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config_version": { + "name": "config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "discovered_provider_metadata": { + "name": "discovered_provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "registry_metadata": { + "name": "registry_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "auxiliary_headers": { + "name": "auxiliary_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_mcp_gateway_configs_owner": { + "name": "IDX_mcp_gateway_configs_owner", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_configs_enabled": { + "name": "IDX_mcp_gateway_configs_enabled", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_configs_remote_url": { + "name": "IDX_mcp_gateway_configs_remote_url", + "columns": [ + { + "expression": "remote_url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_configs_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_configs_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_configs_name_not_empty": { + "name": "mcp_gateway_configs_name_not_empty", + "value": "length(trim(\"mcp_gateway_configs\".\"name\")) > 0" + }, + "mcp_gateway_configs_config_version_positive": { + "name": "mcp_gateway_configs_config_version_positive", + "value": "\"mcp_gateway_configs\".\"config_version\" > 0" + }, + "mcp_gateway_configs_personal_single_user": { + "name": "mcp_gateway_configs_personal_single_user", + "value": "\"mcp_gateway_configs\".\"owner_scope\" <> 'personal' OR \"mcp_gateway_configs\".\"sharing_mode\" = 'single_user'" + }, + "mcp_gateway_configs_owner_scope": { + "name": "mcp_gateway_configs_owner_scope", + "value": "\"mcp_gateway_configs\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_configs_auth_mode": { + "name": "mcp_gateway_configs_auth_mode", + "value": "\"mcp_gateway_configs\".\"auth_mode\" IN ('none', 'static_headers', 'oauth_dynamic', 'oauth_static')" + }, + "mcp_gateway_configs_sharing_mode": { + "name": "mcp_gateway_configs_sharing_mode", + "value": "\"mcp_gateway_configs\".\"sharing_mode\" IN ('single_user', 'multi_user')" + }, + "mcp_gateway_configs_provider_scope_source": { + "name": "mcp_gateway_configs_provider_scope_source", + "value": "\"mcp_gateway_configs\".\"provider_scope_source\" IN ('none', 'discovered', 'override')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_connect_resources": { + "name": "mcp_gateway_connect_resources", + "schema": "", + "columns": { + "connect_resource_id": { + "name": "connect_resource_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_url": { + "name": "canonical_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route_status": { + "name": "route_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "route_version": { + "name": "route_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_connect_resources_route_key": { + "name": "UQ_mcp_gateway_connect_resources_route_key", + "columns": [ + { + "expression": "route_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_mcp_gateway_connect_resources_active_config": { + "name": "UQ_mcp_gateway_connect_resources_active_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_connect_resources\".\"route_status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connect_resources_config": { + "name": "IDX_mcp_gateway_connect_resources_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connect_resources_canonical_url": { + "name": "IDX_mcp_gateway_connect_resources_canonical_url", + "columns": [ + { + "expression": "canonical_url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_connect_resources_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_connect_resources_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_connect_resources", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_connect_resources_route_key_format": { + "name": "mcp_gateway_connect_resources_route_key_format", + "value": "\"mcp_gateway_connect_resources\".\"route_key\" ~ '^[A-Za-z0-9_-]{32,}$'" + }, + "mcp_gateway_connect_resources_route_version_positive": { + "name": "mcp_gateway_connect_resources_route_version_positive", + "value": "\"mcp_gateway_connect_resources\".\"route_version\" > 0" + }, + "mcp_gateway_connect_resources_owner_scope": { + "name": "mcp_gateway_connect_resources_owner_scope", + "value": "\"mcp_gateway_connect_resources\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_connect_resources_route_status": { + "name": "mcp_gateway_connect_resources_route_status", + "value": "\"mcp_gateway_connect_resources\".\"route_status\" IN ('active', 'rotated', 'revoked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_connection_instances": { + "name": "mcp_gateway_connection_instances", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_status": { + "name": "instance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "instance_version": { + "name": "instance_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_connection_instances_non_terminal": { + "name": "UQ_mcp_gateway_connection_instances_non_terminal", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_connection_instances\".\"instance_status\" IN ('active', 'needs_reauth')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connection_instances_config": { + "name": "IDX_mcp_gateway_connection_instances_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connection_instances_user": { + "name": "IDX_mcp_gateway_connection_instances_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_connection_instances_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_connection_instances_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_connection_instances", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_connection_instances_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_connection_instances_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_connection_instances", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_connection_instances_version_positive": { + "name": "mcp_gateway_connection_instances_version_positive", + "value": "\"mcp_gateway_connection_instances\".\"instance_version\" > 0" + }, + "mcp_gateway_connection_instances_owner_scope": { + "name": "mcp_gateway_connection_instances_owner_scope", + "value": "\"mcp_gateway_connection_instances\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_connection_instances_status": { + "name": "mcp_gateway_connection_instances_status", + "value": "\"mcp_gateway_connection_instances\".\"instance_status\" IN ('active', 'needs_reauth', 'revoked', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_oauth_clients": { + "name": "mcp_gateway_oauth_clients", + "schema": "", + "columns": { + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_token_hash": { + "name": "registration_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret_hash": { + "name": "client_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "declared_scopes": { + "name": "declared_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "registration_access_token_expires_at": { + "name": "registration_access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_oauth_clients_client_id": { + "name": "UQ_mcp_gateway_oauth_clients_client_id", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_mcp_gateway_oauth_clients_registration_token_hash": { + "name": "UQ_mcp_gateway_oauth_clients_registration_token_hash", + "columns": [ + { + "expression": "registration_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_clients_deleted_at": { + "name": "IDX_mcp_gateway_oauth_clients_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_oauth_clients_client_id_format": { + "name": "mcp_gateway_oauth_clients_client_id_format", + "value": "\"mcp_gateway_oauth_clients\".\"client_id\" ~ '^[A-Za-z0-9._-]+:[A-Za-z0-9._-]+$'" + }, + "mcp_gateway_oauth_clients_auth_method": { + "name": "mcp_gateway_oauth_clients_auth_method", + "value": "\"mcp_gateway_oauth_clients\".\"token_endpoint_auth_method\" IN ('none', 'client_secret_post', 'client_secret_basic')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_pending_provider_authorizations": { + "name": "mcp_gateway_pending_provider_authorizations", + "schema": "", + "columns": { + "pending_provider_authorization_id": { + "name": "pending_provider_authorization_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authorization_request_id": { + "name": "authorization_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "remote_url": { + "name": "remote_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_mode": { + "name": "auth_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_authorization_endpoint": { + "name": "provider_authorization_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_token_endpoint": { + "name": "provider_token_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_state": { + "name": "encrypted_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "config_version": { + "name": "config_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pending_status": { + "name": "pending_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_pending_provider_authorizations_state_hash": { + "name": "UQ_mcp_gateway_pending_provider_authorizations_state_hash", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_pending_provider_authorizations_config": { + "name": "IDX_mcp_gateway_pending_provider_authorizations_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_pending_provider_authorizations_expires_at": { + "name": "IDX_mcp_gateway_pending_provider_authorizations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_pending_provider_authorizations_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_authorization_requests", + "columnsFrom": [ + "authorization_request_id" + ], + "columnsTo": [ + "authorization_request_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_pending_provider_authorizations_config_version_positive": { + "name": "mcp_gateway_pending_provider_authorizations_config_version_positive", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"config_version\" > 0" + }, + "mcp_gateway_pending_provider_authorizations_owner_scope": { + "name": "mcp_gateway_pending_provider_authorizations_owner_scope", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_pending_provider_authorizations_auth_mode": { + "name": "mcp_gateway_pending_provider_authorizations_auth_mode", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"auth_mode\" IN ('none', 'static_headers', 'oauth_dynamic', 'oauth_static')" + }, + "mcp_gateway_pending_provider_authorizations_status": { + "name": "mcp_gateway_pending_provider_authorizations_status", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"pending_status\" IN ('pending', 'completed', 'error')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_provider_grants": { + "name": "mcp_gateway_provider_grants", + "schema": "", + "columns": { + "provider_grant_id": { + "name": "provider_grant_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "encrypted_grant": { + "name": "encrypted_grant", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subject": { + "name": "provider_subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_scope": { + "name": "grant_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "grant_status": { + "name": "grant_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "grant_version": { + "name": "grant_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_provider_grants_active_instance": { + "name": "UQ_mcp_gateway_provider_grants_active_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_provider_grants\".\"grant_status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_provider_grants_instance": { + "name": "IDX_mcp_gateway_provider_grants_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_provider_grants_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_provider_grants_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_provider_grants", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_provider_grants_version_positive": { + "name": "mcp_gateway_provider_grants_version_positive", + "value": "\"mcp_gateway_provider_grants\".\"grant_version\" > 0" + }, + "mcp_gateway_provider_grants_status": { + "name": "mcp_gateway_provider_grants_status", + "value": "\"mcp_gateway_provider_grants\".\"grant_status\" IN ('active', 'revoked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_rate_limit_windows": { + "name": "mcp_gateway_rate_limit_windows", + "schema": "", + "columns": { + "rate_limit_window_id": { + "name": "rate_limit_window_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_started_at": { + "name": "window_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_rate_limit_windows_ip_window": { + "name": "UQ_mcp_gateway_rate_limit_windows_ip_window", + "columns": [ + { + "expression": "ip_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_rate_limit_windows_window": { + "name": "IDX_mcp_gateway_rate_limit_windows_window", + "columns": [ + { + "expression": "window_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_rate_limit_windows_attempt_count_non_negative": { + "name": "mcp_gateway_rate_limit_windows_attempt_count_non_negative", + "value": "\"mcp_gateway_rate_limit_windows\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_refresh_tokens": { + "name": "mcp_gateway_refresh_tokens", + "schema": "", + "columns": { + "refresh_token_id": { + "name": "refresh_token_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotated_from_refresh_token_id": { + "name": "rotated_from_refresh_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_refresh_tokens_token_hash": { + "name": "UQ_mcp_gateway_refresh_tokens_token_hash", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_user": { + "name": "IDX_mcp_gateway_refresh_tokens_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_config": { + "name": "IDX_mcp_gateway_refresh_tokens_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_consumed_at": { + "name": "IDX_mcp_gateway_refresh_tokens_consumed_at", + "columns": [ + { + "expression": "consumed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_refresh_tokens_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_refresh_tokens_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_refresh_tokens_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_refresh_tokens_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_refresh_tokens_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_refresh_tokens_owner_scope": { + "name": "mcp_gateway_refresh_tokens_owner_scope", + "value": "\"mcp_gateway_refresh_tokens\".\"owner_scope\" IN ('personal', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.microdollar_usage": { + "name": "microdollar_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_hit_tokens": { + "name": "cache_hit_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_model": { + "name": "requested_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_discount": { + "name": "cache_discount", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_error": { + "name": "has_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "abuse_classification": { + "name": "abuse_classification", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "inference_provider": { + "name": "inference_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_created_at": { + "name": "idx_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_abuse_classification": { + "name": "idx_abuse_classification", + "columns": [ + { + "expression": "abuse_classification", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_kilo_user_id_created_at2": { + "name": "idx_kilo_user_id_created_at2", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_organization_id": { + "name": "idx_microdollar_usage_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"microdollar_usage\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.microdollar_usage_daily": { + "name": "microdollar_usage_daily", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_microdollar_usage_daily_personal": { + "name": "idx_microdollar_usage_daily_personal", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"microdollar_usage_daily\".\"organization_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_daily_org": { + "name": "idx_microdollar_usage_daily_org", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"microdollar_usage_daily\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.microdollar_usage_metadata": { + "name": "microdollar_usage_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "http_user_agent_id": { + "name": "http_user_agent_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "http_ip_id": { + "name": "http_ip_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_city_id": { + "name": "vercel_ip_city_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_country_id": { + "name": "vercel_ip_country_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_latitude": { + "name": "vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_longitude": { + "name": "vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "ja4_digest_id": { + "name": "ja4_digest_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_prompt_prefix": { + "name": "user_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_prefix_id": { + "name": "system_prompt_prefix_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "system_prompt_length": { + "name": "system_prompt_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_middle_out_transform": { + "name": "has_middle_out_transform", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "upstream_id": { + "name": "upstream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finish_reason_id": { + "name": "finish_reason_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency": { + "name": "latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moderation_latency": { + "name": "moderation_latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "generation_time": { + "name": "generation_time", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "is_byok": { + "name": "is_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_user_byok": { + "name": "is_user_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "streamed": { + "name": "streamed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "editor_name_id": { + "name": "editor_name_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_kind_id": { + "name": "api_kind_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_tools": { + "name": "has_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode_id": { + "name": "mode_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auto_model_id": { + "name": "auto_model_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "market_cost": { + "name": "market_cost", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "abuse_delay": { + "name": "abuse_delay", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "abuse_downgraded_from": { + "name": "abuse_downgraded_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_microdollar_usage_metadata_created_at": { + "name": "idx_microdollar_usage_metadata_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_metadata_session_id": { + "name": "idx_microdollar_usage_metadata_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"microdollar_usage_metadata\".\"session_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "microdollar_usage_metadata_http_user_agent_id_http_user_agent_http_user_agent_id_fk": { + "name": "microdollar_usage_metadata_http_user_agent_id_http_user_agent_http_user_agent_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "http_user_agent", + "columnsFrom": [ + "http_user_agent_id" + ], + "columnsTo": [ + "http_user_agent_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_http_ip_id_http_ip_http_ip_id_fk": { + "name": "microdollar_usage_metadata_http_ip_id_http_ip_http_ip_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "http_ip", + "columnsFrom": [ + "http_ip_id" + ], + "columnsTo": [ + "http_ip_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_vercel_ip_city_id_vercel_ip_city_vercel_ip_city_id_fk": { + "name": "microdollar_usage_metadata_vercel_ip_city_id_vercel_ip_city_vercel_ip_city_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "vercel_ip_city", + "columnsFrom": [ + "vercel_ip_city_id" + ], + "columnsTo": [ + "vercel_ip_city_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_vercel_ip_country_id_vercel_ip_country_vercel_ip_country_id_fk": { + "name": "microdollar_usage_metadata_vercel_ip_country_id_vercel_ip_country_vercel_ip_country_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "vercel_ip_country", + "columnsFrom": [ + "vercel_ip_country_id" + ], + "columnsTo": [ + "vercel_ip_country_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_ja4_digest_id_ja4_digest_ja4_digest_id_fk": { + "name": "microdollar_usage_metadata_ja4_digest_id_ja4_digest_ja4_digest_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "ja4_digest", + "columnsFrom": [ + "ja4_digest_id" + ], + "columnsTo": [ + "ja4_digest_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_system_prompt_prefix_id_system_prompt_prefix_system_prompt_prefix_id_fk": { + "name": "microdollar_usage_metadata_system_prompt_prefix_id_system_prompt_prefix_system_prompt_prefix_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "system_prompt_prefix", + "columnsFrom": [ + "system_prompt_prefix_id" + ], + "columnsTo": [ + "system_prompt_prefix_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mode": { + "name": "mode", + "schema": "", + "columns": { + "mode_id": { + "name": "mode_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_mode": { + "name": "UQ_mode", + "columns": [ + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_stats": { + "name": "model_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "is_featured": { + "name": "is_featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_stealth": { + "name": "is_stealth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_recommended": { + "name": "is_recommended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "openrouter_id": { + "name": "openrouter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aa_slug": { + "name": "aa_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_creator": { + "name": "model_creator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_slug": { + "name": "creator_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_date": { + "name": "release_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "price_input": { + "name": "price_input", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "price_output": { + "name": "price_output", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "coding_index": { + "name": "coding_index", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false + }, + "speed_tokens_per_sec": { + "name": "speed_tokens_per_sec", + "type": "numeric(8, 2)", + "primaryKey": false, + "notNull": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_output_tokens": { + "name": "max_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_modalities": { + "name": "input_modalities", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "openrouter_data": { + "name": "openrouter_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "benchmarks": { + "name": "benchmarks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chart_data": { + "name": "chart_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_stats_openrouter_id": { + "name": "IDX_model_stats_openrouter_id", + "columns": [ + { + "expression": "openrouter_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_slug": { + "name": "IDX_model_stats_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_is_active": { + "name": "IDX_model_stats_is_active", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_creator_slug": { + "name": "IDX_model_stats_creator_slug", + "columns": [ + { + "expression": "creator_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_price_input": { + "name": "IDX_model_stats_price_input", + "columns": [ + { + "expression": "price_input", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_coding_index": { + "name": "IDX_model_stats_coding_index", + "columns": [ + { + "expression": "coding_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_context_length": { + "name": "IDX_model_stats_context_length", + "columns": [ + { + "expression": "context_length", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_stats_openrouter_id_unique": { + "name": "model_stats_openrouter_id_unique", + "nullsNotDistinct": false, + "columns": [ + "openrouter_id" + ] + }, + "model_stats_slug_unique": { + "name": "model_stats_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_eval_ingestions": { + "name": "model_eval_ingestions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "bench_eval_name": { + "name": "bench_eval_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bench_eval_url": { + "name": "bench_eval_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_stats_id": { + "name": "model_stats_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_source": { + "name": "task_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "n_total_trials": { + "name": "n_total_trials", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "n_attempts": { + "name": "n_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_score": { + "name": "total_score", + "type": "numeric(14, 6)", + "primaryKey": false, + "notNull": true + }, + "overall_score": { + "name": "overall_score", + "type": "numeric(12, 8)", + "primaryKey": false, + "notNull": true + }, + "n_errored": { + "name": "n_errored", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "avg_cost_microdollars": { + "name": "avg_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_input_tokens": { + "name": "avg_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_output_tokens": { + "name": "avg_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_cache_read_tokens": { + "name": "avg_cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_cache_read_tokens": { + "name": "total_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_execution_ms": { + "name": "avg_execution_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "promoted_at": { + "name": "promoted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "promoted_by_email": { + "name": "promoted_by_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "promotion_note": { + "name": "promotion_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_eval_ingestions_lookup": { + "name": "IDX_model_eval_ingestions_lookup", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "promoted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_eval_ingestions_model_stats": { + "name": "IDX_model_eval_ingestions_model_stats", + "columns": [ + { + "expression": "model_stats_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_eval_ingestions_promoted_by_email_lower": { + "name": "IDX_model_eval_ingestions_promoted_by_email_lower", + "columns": [ + { + "expression": "LOWER(\"promoted_by_email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_eval_ingestions_model_stats_id_model_stats_id_fk": { + "name": "model_eval_ingestions_model_stats_id_model_stats_id_fk", + "tableFrom": "model_eval_ingestions", + "tableTo": "model_stats", + "columnsFrom": [ + "model_stats_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_eval_ingestions_bench_eval_name_unique": { + "name": "model_eval_ingestions_bench_eval_name_unique", + "nullsNotDistinct": false, + "columns": [ + "bench_eval_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_experiment": { + "name": "model_experiment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "public_model_id": { + "name": "public_model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_model_experiment_public_model_id_routing": { + "name": "UQ_model_experiment_public_model_id_routing", + "columns": [ + { + "expression": "public_model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"model_experiment\".\"status\" IN ('active', 'paused')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_experiment_status": { + "name": "IDX_model_experiment_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_created_by_user_id_kilocode_users_id_fk": { + "name": "model_experiment_created_by_user_id_kilocode_users_id_fk", + "tableFrom": "model_experiment", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "model_experiment_status_valid": { + "name": "model_experiment_status_valid", + "value": "\"model_experiment\".\"status\" IN ('draft', 'active', 'paused', 'completed')" + }, + "model_experiment_active_not_archived": { + "name": "model_experiment_active_not_archived", + "value": "\"model_experiment\".\"status\" <> 'active' OR \"model_experiment\".\"is_archived\" = false" + } + }, + "isRLSEnabled": false + }, + "public.model_experiment_request": { + "name": "model_experiment_request", + "schema": "", + "columns": { + "usage_id": { + "name": "usage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "variant_version_id": { + "name": "variant_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allocation_subject": { + "name": "allocation_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_kind": { + "name": "request_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_body_sha256": { + "name": "request_body_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "was_truncated": { + "name": "was_truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_experiment_request_variant_version_created_at": { + "name": "IDX_model_experiment_request_variant_version_created_at", + "columns": [ + { + "expression": "variant_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_experiment_request_client_request_id": { + "name": "IDX_model_experiment_request_client_request_id", + "columns": [ + { + "expression": "client_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"model_experiment_request\".\"client_request_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_request_usage_id_microdollar_usage_id_fk": { + "name": "model_experiment_request_usage_id_microdollar_usage_id_fk", + "tableFrom": "model_experiment_request", + "tableTo": "microdollar_usage", + "columnsFrom": [ + "usage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_experiment_request_variant_version_id_model_experiment_variant_version_id_fk": { + "name": "model_experiment_request_variant_version_id_model_experiment_variant_version_id_fk", + "tableFrom": "model_experiment_request", + "tableTo": "model_experiment_variant_version", + "columnsFrom": [ + "variant_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "model_experiment_request_usage_id_created_at_pk": { + "name": "model_experiment_request_usage_id_created_at_pk", + "columns": [ + "usage_id", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "model_experiment_request_allocation_subject_valid": { + "name": "model_experiment_request_allocation_subject_valid", + "value": "\"model_experiment_request\".\"allocation_subject\" IN ('user', 'machine', 'ip')" + }, + "model_experiment_request_request_kind_valid": { + "name": "model_experiment_request_request_kind_valid", + "value": "\"model_experiment_request\".\"request_kind\" IN ('chat_completions', 'messages', 'responses')" + }, + "model_experiment_request_request_body_sha256_format": { + "name": "model_experiment_request_request_body_sha256_format", + "value": "\"model_experiment_request\".\"request_body_sha256\" ~ '^[0-9a-f]{64}$' OR \"model_experiment_request\".\"request_body_sha256\" IN ('__failed__', '__deleted__')" + } + }, + "isRLSEnabled": false + }, + "public.model_experiment_variant": { + "name": "model_experiment_variant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "experiment_id": { + "name": "experiment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_experiment_variant_experiment_id": { + "name": "IDX_model_experiment_variant_experiment_id", + "columns": [ + { + "expression": "experiment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_variant_experiment_id_model_experiment_id_fk": { + "name": "model_experiment_variant_experiment_id_model_experiment_id_fk", + "tableFrom": "model_experiment_variant", + "tableTo": "model_experiment", + "columnsFrom": [ + "experiment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_model_experiment_variant_experiment_label": { + "name": "UQ_model_experiment_variant_experiment_label", + "nullsNotDistinct": false, + "columns": [ + "experiment_id", + "label" + ] + } + }, + "policies": {}, + "checkConstraints": { + "model_experiment_variant_weight_positive": { + "name": "model_experiment_variant_weight_positive", + "value": "\"model_experiment_variant\".\"weight\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.model_experiment_variant_version": { + "name": "model_experiment_variant_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "variant_id": { + "name": "variant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "upstream": { + "name": "upstream", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_experiment_variant_version_variant_effective": { + "name": "IDX_model_experiment_variant_version_variant_effective", + "columns": [ + { + "expression": "variant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effective_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_variant_version_variant_id_model_experiment_variant_id_fk": { + "name": "model_experiment_variant_version_variant_id_model_experiment_variant_id_fk", + "tableFrom": "model_experiment_variant_version", + "tableTo": "model_experiment_variant", + "columnsFrom": [ + "variant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_experiment_variant_version_created_by_kilocode_users_id_fk": { + "name": "model_experiment_variant_version_created_by_kilocode_users_id_fk", + "tableFrom": "model_experiment_variant_version", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.models_by_provider": { + "name": "models_by_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "openrouter": { + "name": "openrouter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "vercel": { + "name": "vercel", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_audit_logs": { + "name": "organization_audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_audit_logs_organization_id": { + "name": "IDX_organization_audit_logs_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_action": { + "name": "IDX_organization_audit_logs_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_actor_id": { + "name": "IDX_organization_audit_logs_actor_id", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_created_at": { + "name": "IDX_organization_audit_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_invitations": { + "name": "organization_invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_organization_invitations_token": { + "name": "UQ_organization_invitations_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_org_id": { + "name": "IDX_organization_invitations_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_email": { + "name": "IDX_organization_invitations_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_expires_at": { + "name": "IDX_organization_invitations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_membership_removals": { + "name": "organization_membership_removals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_by": { + "name": "removed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_role": { + "name": "previous_role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_org_membership_removals_org_id": { + "name": "IDX_org_membership_removals_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_org_membership_removals_user_id": { + "name": "IDX_org_membership_removals_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_org_membership_removals_org_user": { + "name": "UQ_org_membership_removals_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_memberships": { + "name": "organization_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_memberships_org_id": { + "name": "IDX_organization_memberships_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_memberships_user_id": { + "name": "IDX_organization_memberships_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_memberships_org_user": { + "name": "UQ_organization_memberships_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_seats_purchases": { + "name": "organization_seats_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_stripe_id": { + "name": "subscription_stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seat_count": { + "name": "seat_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "billing_cycle": { + "name": "billing_cycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'monthly'" + } + }, + "indexes": { + "IDX_organization_seats_org_id": { + "name": "IDX_organization_seats_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_expires_at": { + "name": "IDX_organization_seats_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_created_at": { + "name": "IDX_organization_seats_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_updated_at": { + "name": "IDX_organization_seats_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_starts_at": { + "name": "IDX_organization_seats_starts_at", + "columns": [ + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_seats_idempotency_key": { + "name": "UQ_organization_seats_idempotency_key", + "nullsNotDistinct": false, + "columns": [ + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_user_limits": { + "name": "organization_user_limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microdollar_limit": { + "name": "microdollar_limit", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_user_limits_org_id": { + "name": "IDX_organization_user_limits_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_user_limits_user_id": { + "name": "IDX_organization_user_limits_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_user_limits_org_user": { + "name": "UQ_organization_user_limits_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id", + "limit_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_user_usage": { + "name": "organization_user_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microdollar_usage": { + "name": "microdollar_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_user_daily_usage_org_id": { + "name": "IDX_organization_user_daily_usage_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_user_daily_usage_user_id": { + "name": "IDX_organization_user_daily_usage_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_user_daily_usage_org_user_date": { + "name": "UQ_organization_user_daily_usage_org_user_date", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id", + "limit_type", + "usage_date" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "microdollars_balance": { + "name": "microdollars_balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_microdollars_acquired": { + "name": "total_microdollars_acquired", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "next_credit_expiration_at": { + "name": "next_credit_expiration_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_enabled": { + "name": "auto_top_up_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "seat_count": { + "name": "seat_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_seats": { + "name": "require_seats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sso_domain": { + "name": "sso_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'teams'" + }, + "free_trial_end_at": { + "name": "free_trial_end_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "company_domain": { + "name": "company_domain", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_organizations_sso_domain": { + "name": "IDX_organizations_sso_domain", + "columns": [ + { + "expression": "sso_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organizations_name_not_empty_check": { + "name": "organizations_name_not_empty_check", + "value": "length(trim(\"organizations\".\"name\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.organization_modes": { + "name": "organization_modes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "IDX_organization_modes_organization_id": { + "name": "IDX_organization_modes_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_modes_org_id_slug": { + "name": "UQ_organization_modes_org_id_slug", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payment_methods": { + "name": "payment_methods", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_fingerprint": { + "name": "stripe_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last4": { + "name": "last4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line1": { + "name": "address_line1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line2": { + "name": "address_line2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "three_d_secure_supported": { + "name": "three_d_secure_supported", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "funding": { + "name": "funding", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "regulated_status": { + "name": "regulated_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line1_check_status": { + "name": "address_line1_check_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code_check_status": { + "name": "postal_code_check_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eligible_for_free_credits": { + "name": "eligible_for_free_credits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_data": { + "name": "stripe_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_d7d7fb15569674aaadcfbc0428": { + "name": "IDX_d7d7fb15569674aaadcfbc0428", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_e1feb919d0ab8a36381d5d5138": { + "name": "IDX_e1feb919d0ab8a36381d5d5138", + "columns": [ + { + "expression": "stripe_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_payment_methods_organization_id": { + "name": "IDX_payment_methods_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_29df1b0403df5792c96bbbfdbe6": { + "name": "UQ_29df1b0403df5792c96bbbfdbe6", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_impact_sale_reversals": { + "name": "pending_impact_sale_reversals", + "schema": "", + "columns": { + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "dispute_id": { + "name": "dispute_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_impact_sale_reversals_attempt_count_non_negative_check": { + "name": "pending_impact_sale_reversals_attempt_count_non_negative_check", + "value": "\"pending_impact_sale_reversals\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.platform_integrations": { + "name": "platform_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "integration_type": { + "name": "integration_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_installation_id": { + "name": "platform_installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_account_id": { + "name": "platform_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_account_login": { + "name": "platform_account_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "repository_access": { + "name": "repository_access", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repositories": { + "name": "repositories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "repositories_synced_at": { + "name": "repositories_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_invalid_at": { + "name": "auth_invalid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_invalid_reason": { + "name": "auth_invalid_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "kilo_requester_user_id": { + "name": "kilo_requester_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_requester_account_id": { + "name": "platform_requester_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "integration_status": { + "name": "integration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "suspended_by": { + "name": "suspended_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'standard'" + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_platform_integrations_owned_by_org_platform_inst": { + "name": "UQ_platform_integrations_owned_by_org_platform_inst", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_owned_by_user_platform_inst": { + "name": "UQ_platform_integrations_owned_by_user_platform_inst", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_slack_platform_inst": { + "name": "UQ_platform_integrations_slack_platform_inst", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'slack' AND \"platform_integrations\".\"platform_installation_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_linear_platform_inst": { + "name": "UQ_platform_integrations_linear_platform_inst", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'linear' AND \"platform_integrations\".\"platform_installation_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_org_id": { + "name": "IDX_platform_integrations_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_user_id": { + "name": "IDX_platform_integrations_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform_inst_id": { + "name": "IDX_platform_integrations_platform_inst_id", + "columns": [ + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform": { + "name": "IDX_platform_integrations_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_org_platform": { + "name": "IDX_platform_integrations_owned_by_org_platform", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_user_platform": { + "name": "IDX_platform_integrations_owned_by_user_platform", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_integration_status": { + "name": "IDX_platform_integrations_integration_status", + "columns": [ + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_kilo_requester": { + "name": "IDX_platform_integrations_kilo_requester", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_requester_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform_requester": { + "name": "IDX_platform_integrations_platform_requester", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_requester_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "platform_integrations_owned_by_organization_id_organizations_id_fk": { + "name": "platform_integrations_owned_by_organization_id_organizations_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "platform_integrations_owned_by_user_id_kilocode_users_id_fk": { + "name": "platform_integrations_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "platform_integrations_owner_check": { + "name": "platform_integrations_owner_check", + "value": "(\n (\"platform_integrations\".\"owned_by_user_id\" IS NOT NULL AND \"platform_integrations\".\"owned_by_organization_id\" IS NULL) OR\n (\"platform_integrations\".\"owned_by_user_id\" IS NULL AND \"platform_integrations\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.referral_code_usages": { + "name": "referral_code_usages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "referring_kilo_user_id": { + "name": "referring_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redeeming_kilo_user_id": { + "name": "redeeming_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_referral_code_usages_redeeming_kilo_user_id": { + "name": "IDX_referral_code_usages_redeeming_kilo_user_id", + "columns": [ + { + "expression": "redeeming_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_referral_code_usages_redeeming_user_id_code": { + "name": "UQ_referral_code_usages_redeeming_user_id_code", + "nullsNotDistinct": false, + "columns": [ + "redeeming_kilo_user_id", + "referring_kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_referral_codes_kilo_user_id": { + "name": "UQ_referral_codes_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_referral_codes_code": { + "name": "IDX_referral_codes_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_check_catalog": { + "name": "security_advisor_check_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "explanation": { + "name": "explanation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_check_catalog_check_id_unique": { + "name": "security_advisor_check_catalog_check_id_unique", + "nullsNotDistinct": false, + "columns": [ + "check_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "security_advisor_check_catalog_severity_check": { + "name": "security_advisor_check_catalog_severity_check", + "value": "\"security_advisor_check_catalog\".\"severity\" in ('critical', 'warn', 'info')" + } + }, + "isRLSEnabled": false + }, + "public.security_advisor_content": { + "name": "security_advisor_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_content_key_unique": { + "name": "security_advisor_content_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_kiloclaw_coverage": { + "name": "security_advisor_kiloclaw_coverage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "area": { + "name": "area", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_check_ids": { + "name": "match_check_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_kiloclaw_coverage_area_unique": { + "name": "security_advisor_kiloclaw_coverage_area_unique", + "nullsNotDistinct": false, + "columns": [ + "area" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_scans": { + "name": "security_advisor_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_platform": { + "name": "source_platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_method": { + "name": "source_method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "openclaw_version": { + "name": "openclaw_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_ip": { + "name": "public_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "findings_critical": { + "name": "findings_critical", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "findings_warn": { + "name": "findings_warn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "findings_info": { + "name": "findings_info", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_security_advisor_scans_user_created_at": { + "name": "idx_security_advisor_scans_user_created_at", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_advisor_scans_created_at": { + "name": "idx_security_advisor_scans_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_advisor_scans_platform": { + "name": "idx_security_advisor_scans_platform", + "columns": [ + { + "expression": "source_platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_agent_commands": { + "name": "security_agent_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "command_type": { + "name": "command_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "result_code": { + "name": "result_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_metadata": { + "name": "result_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_security_agent_commands_org_created": { + "name": "idx_security_agent_commands_org_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_agent_commands_user_created": { + "name": "idx_security_agent_commands_user_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_agent_commands_status_updated": { + "name": "idx_security_agent_commands_status_updated", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_agent_commands_finding_created": { + "name": "idx_security_agent_commands_finding_created", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_agent_commands_owned_by_organization_id_organizations_id_fk": { + "name": "security_agent_commands_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_agent_commands", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_agent_commands_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_agent_commands_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_agent_commands", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_agent_commands_finding_id_security_findings_id_fk": { + "name": "security_agent_commands_finding_id_security_findings_id_fk", + "tableFrom": "security_agent_commands", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_agent_commands_owner_check": { + "name": "security_agent_commands_owner_check", + "value": "(\n (\"security_agent_commands\".\"owned_by_user_id\" IS NOT NULL AND \"security_agent_commands\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_agent_commands\".\"owned_by_user_id\" IS NULL AND \"security_agent_commands\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_agent_commands_type_check": { + "name": "security_agent_commands_type_check", + "value": "\"security_agent_commands\".\"command_type\" IN ('sync', 'dismiss_finding', 'start_analysis', 'apply_auto_remediation')" + }, + "security_agent_commands_origin_check": { + "name": "security_agent_commands_origin_check", + "value": "\"security_agent_commands\".\"origin\" IN ('manual', 'dashboard_refresh', 'enable_initial_sync', 'settings_include_existing')" + }, + "security_agent_commands_status_check": { + "name": "security_agent_commands_status_check", + "value": "\"security_agent_commands\".\"status\" IN ('accepted', 'running', 'succeeded', 'failed', 'no_op')" + } + }, + "isRLSEnabled": false + }, + "public.security_agent_repository_sync_state": { + "name": "security_agent_repository_sync_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_succeeded_at": { + "name": "last_succeeded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_failure_code": { + "name": "last_failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_agent_repository_sync_state_org_repo": { + "name": "UQ_security_agent_repository_sync_state_org_repo", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_agent_repository_sync_state\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_agent_repository_sync_state_user_repo": { + "name": "UQ_security_agent_repository_sync_state_user_repo", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_agent_repository_sync_state\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_agent_repository_sync_state_owned_by_organization_id_organizations_id_fk": { + "name": "security_agent_repository_sync_state_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_agent_repository_sync_state", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_agent_repository_sync_state_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_agent_repository_sync_state_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_agent_repository_sync_state", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_agent_repository_sync_state_owner_check": { + "name": "security_agent_repository_sync_state_owner_check", + "value": "(\n (\"security_agent_repository_sync_state\".\"owned_by_user_id\" IS NOT NULL AND \"security_agent_repository_sync_state\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_agent_repository_sync_state\".\"owned_by_user_id\" IS NULL AND \"security_agent_repository_sync_state\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_analysis_owner_state": { + "name": "security_analysis_owner_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_analysis_enabled_at": { + "name": "auto_analysis_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "block_reason": { + "name": "block_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consecutive_actor_resolution_failures": { + "name": "consecutive_actor_resolution_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_actor_resolution_failure_at": { + "name": "last_actor_resolution_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_analysis_owner_state_org_owner": { + "name": "UQ_security_analysis_owner_state_org_owner", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_analysis_owner_state\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_analysis_owner_state_user_owner": { + "name": "UQ_security_analysis_owner_state_user_owner", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_analysis_owner_state\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_analysis_owner_state_owned_by_organization_id_organizations_id_fk": { + "name": "security_analysis_owner_state_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_analysis_owner_state", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_owner_state_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_analysis_owner_state_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_analysis_owner_state", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_analysis_owner_state_owner_check": { + "name": "security_analysis_owner_state_owner_check", + "value": "(\n (\"security_analysis_owner_state\".\"owned_by_user_id\" IS NOT NULL AND \"security_analysis_owner_state\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_analysis_owner_state\".\"owned_by_user_id\" IS NULL AND \"security_analysis_owner_state\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_analysis_owner_state_block_reason_check": { + "name": "security_analysis_owner_state_block_reason_check", + "value": "\"security_analysis_owner_state\".\"block_reason\" IS NULL OR \"security_analysis_owner_state\".\"block_reason\" IN ('INSUFFICIENT_CREDITS', 'ACTOR_RESOLUTION_FAILED', 'OPERATOR_PAUSE')" + } + }, + "isRLSEnabled": false + }, + "public.security_analysis_queue": { + "name": "security_analysis_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "queue_status": { + "name": "queue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity_rank": { + "name": "severity_rank", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_by_job_id": { + "name": "claimed_by_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reopen_requeue_count": { + "name": "reopen_requeue_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_analysis_queue_finding_id": { + "name": "UQ_security_analysis_queue_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_claim_path_org": { + "name": "idx_security_analysis_queue_claim_path_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "severity_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_claim_path_user": { + "name": "idx_security_analysis_queue_claim_path_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "severity_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_in_flight_org": { + "name": "idx_security_analysis_queue_in_flight_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queue_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_in_flight_user": { + "name": "idx_security_analysis_queue_in_flight_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queue_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_lag_dashboards": { + "name": "idx_security_analysis_queue_lag_dashboards", + "columns": [ + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_pending_reconciliation": { + "name": "idx_security_analysis_queue_pending_reconciliation", + "columns": [ + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_running_reconciliation": { + "name": "idx_security_analysis_queue_running_reconciliation", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_failure_trend": { + "name": "idx_security_analysis_queue_failure_trend", + "columns": [ + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"failure_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_analysis_queue_finding_id_security_findings_id_fk": { + "name": "security_analysis_queue_finding_id_security_findings_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_queue_owned_by_organization_id_organizations_id_fk": { + "name": "security_analysis_queue_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_queue_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_analysis_queue_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_analysis_queue_owner_check": { + "name": "security_analysis_queue_owner_check", + "value": "(\n (\"security_analysis_queue\".\"owned_by_user_id\" IS NOT NULL AND \"security_analysis_queue\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_analysis_queue\".\"owned_by_user_id\" IS NULL AND \"security_analysis_queue\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_analysis_queue_status_check": { + "name": "security_analysis_queue_status_check", + "value": "\"security_analysis_queue\".\"queue_status\" IN ('queued', 'pending', 'running', 'failed', 'completed')" + }, + "security_analysis_queue_claim_token_required_check": { + "name": "security_analysis_queue_claim_token_required_check", + "value": "\"security_analysis_queue\".\"queue_status\" NOT IN ('pending', 'running') OR \"security_analysis_queue\".\"claim_token\" IS NOT NULL" + }, + "security_analysis_queue_attempt_count_non_negative_check": { + "name": "security_analysis_queue_attempt_count_non_negative_check", + "value": "\"security_analysis_queue\".\"attempt_count\" >= 0" + }, + "security_analysis_queue_reopen_requeue_count_non_negative_check": { + "name": "security_analysis_queue_reopen_requeue_count_non_negative_check", + "value": "\"security_analysis_queue\".\"reopen_requeue_count\" >= 0" + }, + "security_analysis_queue_severity_rank_check": { + "name": "security_analysis_queue_severity_rank_check", + "value": "\"security_analysis_queue\".\"severity_rank\" IN (0, 1, 2, 3)" + }, + "security_analysis_queue_failure_code_check": { + "name": "security_analysis_queue_failure_code_check", + "value": "\"security_analysis_queue\".\"failure_code\" IS NULL OR \"security_analysis_queue\".\"failure_code\" IN (\n 'NETWORK_TIMEOUT',\n 'UPSTREAM_5XX',\n 'TEMP_TOKEN_FAILURE',\n 'START_CALL_AMBIGUOUS',\n 'REQUEUE_TEMPORARY_PRECONDITION',\n 'ACTOR_RESOLUTION_FAILED',\n 'GITHUB_TOKEN_UNAVAILABLE',\n 'INVALID_CONFIG',\n 'MISSING_OWNERSHIP',\n 'PERMISSION_DENIED_PERMANENT',\n 'UNSUPPORTED_SEVERITY',\n 'INSUFFICIENT_CREDITS',\n 'STATE_GUARD_REJECTED',\n 'SKIPPED_ALREADY_IN_PROGRESS',\n 'SKIPPED_NO_LONGER_ELIGIBLE',\n 'REOPEN_LOOP_GUARD',\n 'RUN_LOST'\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_audit_log": { + "name": "security_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "before_state": { + "name": "before_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_state": { + "name": "after_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "source_occurred_at": { + "name": "source_occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "finding_snapshot": { + "name": "finding_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_context": { + "name": "source_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_security_audit_log_org_created": { + "name": "IDX_security_audit_log_org_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_user_created": { + "name": "IDX_security_audit_log_user_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_resource": { + "name": "IDX_security_audit_log_resource", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_actor": { + "name": "IDX_security_audit_log_actor", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_action": { + "name": "IDX_security_audit_log_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_audit_log_org_event_key": { + "name": "UQ_security_audit_log_org_event_key", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL AND \"security_audit_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_audit_log_user_event_key": { + "name": "UQ_security_audit_log_user_event_key", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_org_occurred": { + "name": "IDX_security_audit_log_org_occurred", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL AND \"security_audit_log\".\"occurred_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_user_occurred": { + "name": "IDX_security_audit_log_user_occurred", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"occurred_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_audit_log_owned_by_organization_id_organizations_id_fk": { + "name": "security_audit_log_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_audit_log", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_audit_log_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_audit_log_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_audit_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_audit_log_owner_check": { + "name": "security_audit_log_owner_check", + "value": "(\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"owned_by_organization_id\" IS NULL) OR (\"security_audit_log\".\"owned_by_user_id\" IS NULL AND \"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL)" + }, + "security_audit_log_action_check": { + "name": "security_audit_log_action_check", + "value": "\"security_audit_log\".\"action\" IN ('security.finding.created', 'security.finding.severity_changed', 'security.finding.status_change', 'security.finding.dismissed', 'security.finding.auto_dismissed', 'security.finding.superseded', 'security.finding.analysis_started', 'security.finding.analysis_completed', 'security.finding.analysis_failed', 'security.remediation.queued', 'security.remediation.started', 'security.remediation.pr_opened', 'security.remediation.failed', 'security.remediation.blocked', 'security.remediation.no_changes_needed', 'security.remediation.cancelled', 'security.remediation.retried', 'security.finding.deleted', 'security.config.enabled', 'security.config.disabled', 'security.config.updated', 'security.sync.triggered', 'security.sync.completed', 'security.audit_log.exported', 'security.audit_report.generated')" + }, + "security_audit_log_actor_type_check": { + "name": "security_audit_log_actor_type_check", + "value": "\"security_audit_log\".\"actor_type\" IN ('customer_user', 'kilo_admin', 'system')" + }, + "security_audit_log_source_context_check": { + "name": "security_audit_log_source_context_check", + "value": "\"security_audit_log\".\"source_context\" IN ('security_sync', 'web', 'analysis_worker', 'remediation_callback', 'rollout_baseline')" + } + }, + "isRLSEnabled": false + }, + "public.security_finding_notifications": { + "name": "security_finding_notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staged'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_security_finding_notifications_finding_recipient_kind": { + "name": "uq_security_finding_notifications_finding_recipient_kind", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recipient_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_pending": { + "name": "idx_security_finding_notifications_pending", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_finding_notifications\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_staged": { + "name": "idx_security_finding_notifications_staged", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_finding_notifications\".\"status\" = 'staged'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_finding_id": { + "name": "idx_security_finding_notifications_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_recipient_user_id": { + "name": "idx_security_finding_notifications_recipient_user_id", + "columns": [ + { + "expression": "recipient_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_finding_notifications_finding_fk": { + "name": "security_finding_notifications_finding_fk", + "tableFrom": "security_finding_notifications", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_finding_notifications_recipient_fk": { + "name": "security_finding_notifications_recipient_fk", + "tableFrom": "security_finding_notifications", + "tableTo": "kilocode_users", + "columnsFrom": [ + "recipient_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_finding_notifications_kind_check": { + "name": "security_finding_notifications_kind_check", + "value": "\"security_finding_notifications\".\"kind\" IN ('new_finding', 'sla_warning', 'sla_breach')" + }, + "security_finding_notifications_status_check": { + "name": "security_finding_notifications_status_check", + "value": "\"security_finding_notifications\".\"status\" IN ('staged', 'pending', 'sending', 'sent', 'failed', 'cancelled')" + }, + "security_finding_notifications_attempt_count_check": { + "name": "security_finding_notifications_attempt_count_check", + "value": "\"security_finding_notifications\".\"attempt_count\" >= 0" + }, + "security_finding_notifications_claimed_at_check": { + "name": "security_finding_notifications_claimed_at_check", + "value": "(\n (\"security_finding_notifications\".\"status\" = 'sending' AND \"security_finding_notifications\".\"claimed_at\" IS NOT NULL) OR\n (\"security_finding_notifications\".\"status\" <> 'sending' AND \"security_finding_notifications\".\"claimed_at\" IS NULL)\n )" + }, + "security_finding_notifications_sent_at_check": { + "name": "security_finding_notifications_sent_at_check", + "value": "(\n (\"security_finding_notifications\".\"status\" = 'sent' AND \"security_finding_notifications\".\"sent_at\" IS NOT NULL) OR\n (\"security_finding_notifications\".\"status\" <> 'sent' AND \"security_finding_notifications\".\"sent_at\" IS NULL)\n )" + }, + "security_finding_notifications_error_message_length_check": { + "name": "security_finding_notifications_error_message_length_check", + "value": "\"security_finding_notifications\".\"error_message\" IS NULL OR length(\"security_finding_notifications\".\"error_message\") <= 500" + } + }, + "isRLSEnabled": false + }, + "public.security_findings": { + "name": "security_findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ghsa_id": { + "name": "ghsa_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_ecosystem": { + "name": "package_ecosystem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vulnerable_version_range": { + "name": "vulnerable_version_range", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "patched_version": { + "name": "patched_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest_path": { + "name": "manifest_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "ignored_reason": { + "name": "ignored_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ignored_by": { + "name": "ignored_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fixed_at": { + "name": "fixed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sla_due_at": { + "name": "sla_due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dependabot_html_url": { + "name": "dependabot_html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwe_ids": { + "name": "cwe_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cvss_score": { + "name": "cvss_score", + "type": "numeric(3, 1)", + "primaryKey": false, + "notNull": false + }, + "dependency_scope": { + "name": "dependency_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_status": { + "name": "analysis_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_started_at": { + "name": "analysis_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "analysis_completed_at": { + "name": "analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "analysis_error": { + "name": "analysis_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis": { + "name": "analysis", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "raw_data": { + "name": "raw_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_detected_at": { + "name": "first_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_security_findings_user_source": { + "name": "uq_security_findings_user_source", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_findings\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_security_findings_org_source": { + "name": "uq_security_findings_org_source", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_findings\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_org_id": { + "name": "idx_security_findings_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_user_id": { + "name": "idx_security_findings_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_repo": { + "name": "idx_security_findings_repo", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_severity": { + "name": "idx_security_findings_severity", + "columns": [ + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_status": { + "name": "idx_security_findings_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_package": { + "name": "idx_security_findings_package", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_sla_due_at": { + "name": "idx_security_findings_sla_due_at", + "columns": [ + { + "expression": "sla_due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_session_id": { + "name": "idx_security_findings_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_cli_session_id": { + "name": "idx_security_findings_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_analysis_status": { + "name": "idx_security_findings_analysis_status", + "columns": [ + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_org_analysis_in_flight": { + "name": "idx_security_findings_org_analysis_in_flight", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_findings\".\"analysis_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_user_analysis_in_flight": { + "name": "idx_security_findings_user_analysis_in_flight", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_findings\".\"analysis_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_findings_owned_by_organization_id_organizations_id_fk": { + "name": "security_findings_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_findings", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_findings_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_findings_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_findings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_findings_platform_integration_id_platform_integrations_id_fk": { + "name": "security_findings_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "security_findings", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_findings_owner_check": { + "name": "security_findings_owner_check", + "value": "(\n (\"security_findings\".\"owned_by_user_id\" IS NOT NULL AND \"security_findings\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_findings\".\"owned_by_user_id\" IS NULL AND \"security_findings\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_remediation_attempts": { + "name": "security_remediation_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "remediation_id": { + "name": "remediation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempt_number": { + "name": "attempt_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retry_of_attempt_id": { + "name": "retry_of_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_fingerprint": { + "name": "analysis_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "analysis_completed_at": { + "name": "analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "remediation_model_slug": { + "name": "remediation_model_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_by_job_id": { + "name": "claimed_by_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "launch_attempt_count": { + "name": "launch_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "callback_attempt_token_hash": { + "name": "callback_attempt_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "structured_result": { + "name": "structured_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "final_assistant_message": { + "name": "final_assistant_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "validation_evidence": { + "name": "validation_evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "risk_notes": { + "name": "risk_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "draft_reason": { + "name": "draft_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_draft": { + "name": "pr_draft", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pr_head_branch": { + "name": "pr_head_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_base_branch": { + "name": "pr_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cancellation_requested_at": { + "name": "cancellation_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancellation_requested_by_user_id": { + "name": "cancellation_requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "launched_at": { + "name": "launched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_remediation_attempts_number": { + "name": "UQ_security_remediation_attempts_number", + "columns": [ + { + "expression": "remediation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_remediation_attempts_active_finding": { + "name": "UQ_security_remediation_attempts_active_finding", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_remediation_attempts_active_remediation": { + "name": "UQ_security_remediation_attempts_active_remediation", + "columns": [ + { + "expression": "remediation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_remediation_attempts_finding_fingerprint_terminal": { + "name": "UQ_security_remediation_attempts_finding_fingerprint_terminal", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running', 'pr_opened')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_org_claim": { + "name": "idx_security_remediation_attempts_org_claim", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_user_claim": { + "name": "idx_security_remediation_attempts_user_claim", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_repo_claim": { + "name": "idx_security_remediation_attempts_repo_claim", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_org_inflight": { + "name": "idx_security_remediation_attempts_org_inflight", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" IN ('launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_user_inflight": { + "name": "idx_security_remediation_attempts_user_inflight", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" IN ('launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_repo_inflight": { + "name": "idx_security_remediation_attempts_repo_inflight", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" IN ('launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_cloud_agent_session": { + "name": "idx_security_remediation_attempts_cloud_agent_session", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_finding_fingerprint": { + "name": "idx_security_remediation_attempts_finding_fingerprint", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_remediation_attempts_remediation_id_security_remediations_id_fk": { + "name": "security_remediation_attempts_remediation_id_security_remediations_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "security_remediations", + "columnsFrom": [ + "remediation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_finding_id_security_findings_id_fk": { + "name": "security_remediation_attempts_finding_id_security_findings_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_owned_by_organization_id_organizations_id_fk": { + "name": "security_remediation_attempts_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_remediation_attempts_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_requested_by_user_id_kilocode_users_id_fk": { + "name": "security_remediation_attempts_requested_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "requested_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "security_remediation_attempts_cancellation_requested_by_user_id_kilocode_users_id_fk": { + "name": "security_remediation_attempts_cancellation_requested_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "cancellation_requested_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_remediation_attempts_owner_check": { + "name": "security_remediation_attempts_owner_check", + "value": "(\n (\"security_remediation_attempts\".\"owned_by_user_id\" IS NOT NULL AND \"security_remediation_attempts\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_remediation_attempts\".\"owned_by_user_id\" IS NULL AND \"security_remediation_attempts\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_remediation_attempts_status_check": { + "name": "security_remediation_attempts_status_check", + "value": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running', 'pr_opened', 'failed', 'blocked', 'no_changes_needed', 'cancelled')" + }, + "security_remediation_attempts_origin_check": { + "name": "security_remediation_attempts_origin_check", + "value": "\"security_remediation_attempts\".\"origin\" IN ('auto_policy', 'bulk_existing', 'manual')" + }, + "security_remediation_attempts_attempt_number_check": { + "name": "security_remediation_attempts_attempt_number_check", + "value": "\"security_remediation_attempts\".\"attempt_number\" >= 1" + }, + "security_remediation_attempts_launch_attempt_count_check": { + "name": "security_remediation_attempts_launch_attempt_count_check", + "value": "\"security_remediation_attempts\".\"launch_attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.security_remediations": { + "name": "security_remediations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "latest_attempt_id": { + "name": "latest_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_analysis_fingerprint": { + "name": "latest_analysis_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_analysis_completed_at": { + "name": "latest_analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_draft": { + "name": "pr_draft", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pr_head_branch": { + "name": "pr_head_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_base_branch": { + "name": "pr_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome_summary": { + "name": "outcome_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_remediations_finding_id": { + "name": "UQ_security_remediations_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_org_status": { + "name": "idx_security_remediations_org_status", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_user_status": { + "name": "idx_security_remediations_user_status", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_repo_status": { + "name": "idx_security_remediations_repo_status", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_latest_attempt": { + "name": "idx_security_remediations_latest_attempt", + "columns": [ + { + "expression": "latest_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_remediations_owned_by_organization_id_organizations_id_fk": { + "name": "security_remediations_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_remediations", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediations_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_remediations_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediations_finding_id_security_findings_id_fk": { + "name": "security_remediations_finding_id_security_findings_id_fk", + "tableFrom": "security_remediations", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_remediations_owner_check": { + "name": "security_remediations_owner_check", + "value": "(\n (\"security_remediations\".\"owned_by_user_id\" IS NOT NULL AND \"security_remediations\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_remediations\".\"owned_by_user_id\" IS NULL AND \"security_remediations\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_remediations_status_check": { + "name": "security_remediations_status_check", + "value": "\"security_remediations\".\"status\" IN ('queued', 'running', 'pr_opened', 'failed', 'blocked', 'no_changes_needed', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.shared_cli_sessions": { + "name": "shared_cli_sessions", + "schema": "", + "columns": { + "share_id": { + "name": "share_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shared_state": { + "name": "shared_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "api_conversation_history_blob_url": { + "name": "api_conversation_history_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_metadata_blob_url": { + "name": "task_metadata_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ui_messages_blob_url": { + "name": "ui_messages_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_state_blob_url": { + "name": "git_state_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_shared_cli_sessions_session_id": { + "name": "IDX_shared_cli_sessions_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_shared_cli_sessions_created_at": { + "name": "IDX_shared_cli_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_cli_sessions_session_id_cli_sessions_session_id_fk": { + "name": "shared_cli_sessions_session_id_cli_sessions_session_id_fk", + "tableFrom": "shared_cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shared_cli_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "shared_cli_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "shared_cli_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_cli_sessions_shared_state_check": { + "name": "shared_cli_sessions_shared_state_check", + "value": "\"shared_cli_sessions\".\"shared_state\" IN ('public', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.slack_bot_requests": { + "name": "slack_bot_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_team_name": { + "name": "slack_team_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message": { + "name": "user_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message_truncated": { + "name": "user_message_truncated", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_calls_made": { + "name": "tool_calls_made", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_slack_bot_requests_created_at": { + "name": "idx_slack_bot_requests_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_slack_team_id": { + "name": "idx_slack_bot_requests_slack_team_id", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_owned_by_org_id": { + "name": "idx_slack_bot_requests_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_owned_by_user_id": { + "name": "idx_slack_bot_requests_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_status": { + "name": "idx_slack_bot_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_event_type": { + "name": "idx_slack_bot_requests_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_team_created": { + "name": "idx_slack_bot_requests_team_created", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_bot_requests_owned_by_organization_id_organizations_id_fk": { + "name": "slack_bot_requests_owned_by_organization_id_organizations_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_bot_requests_owned_by_user_id_kilocode_users_id_fk": { + "name": "slack_bot_requests_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_bot_requests_platform_integration_id_platform_integrations_id_fk": { + "name": "slack_bot_requests_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_bot_requests_owner_check": { + "name": "slack_bot_requests_owner_check", + "value": "(\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NOT NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NULL) OR\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NOT NULL) OR\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.source_embeddings": { + "name": "source_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_line": { + "name": "start_line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_line": { + "name": "end_line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_base_branch": { + "name": "is_base_branch", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_source_embeddings_organization_id": { + "name": "IDX_source_embeddings_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_kilo_user_id": { + "name": "IDX_source_embeddings_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_project_id": { + "name": "IDX_source_embeddings_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_created_at": { + "name": "IDX_source_embeddings_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_updated_at": { + "name": "IDX_source_embeddings_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_file_path_lower": { + "name": "IDX_source_embeddings_file_path_lower", + "columns": [ + { + "expression": "LOWER(\"file_path\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_git_branch": { + "name": "IDX_source_embeddings_git_branch", + "columns": [ + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_org_project_branch": { + "name": "IDX_source_embeddings_org_project_branch", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "source_embeddings_organization_id_organizations_id_fk": { + "name": "source_embeddings_organization_id_organizations_id_fk", + "tableFrom": "source_embeddings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "source_embeddings_kilo_user_id_kilocode_users_id_fk": { + "name": "source_embeddings_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "source_embeddings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_source_embeddings_org_project_branch_file_lines": { + "name": "UQ_source_embeddings_org_project_branch_file_lines", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "project_id", + "git_branch", + "file_path", + "start_line", + "end_line" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stripe_dispute_actions": { + "name": "stripe_dispute_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "result_code": { + "name": "result_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_reference_id": { + "name": "result_reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_dispute_actions_case_id": { + "name": "IDX_stripe_dispute_actions_case_id", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_actions_claim_path": { + "name": "IDX_stripe_dispute_actions_claim_path", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_dispute_actions_case_id_stripe_dispute_cases_id_fk": { + "name": "stripe_dispute_actions_case_id_stripe_dispute_cases_id_fk", + "tableFrom": "stripe_dispute_actions", + "tableTo": "stripe_dispute_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_dispute_actions_case_type_target": { + "name": "UQ_stripe_dispute_actions_case_type_target", + "nullsNotDistinct": false, + "columns": [ + "case_id", + "action_type", + "target_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_dispute_actions_action_type_check": { + "name": "stripe_dispute_actions_action_type_check", + "value": "\"stripe_dispute_actions\".\"action_type\" IN ('stripe_acceptance', 'user_block', 'auto_top_up_disable', 'credit_balance_reset', 'subscription_cancellation', 'access_termination', 'kiloclaw_suspension')" + }, + "stripe_dispute_actions_status_check": { + "name": "stripe_dispute_actions_status_check", + "value": "\"stripe_dispute_actions\".\"status\" IN ('queued', 'processing', 'completed', 'failed', 'skipped')" + }, + "stripe_dispute_actions_attempt_count_non_negative_check": { + "name": "stripe_dispute_actions_attempt_count_non_negative_check", + "value": "\"stripe_dispute_actions\".\"attempt_count\" >= 0" + }, + "stripe_dispute_actions_target_key_not_empty_check": { + "name": "stripe_dispute_actions_target_key_not_empty_check", + "value": "length(\"stripe_dispute_actions\".\"target_key\") > 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_dispute_cases": { + "name": "stripe_dispute_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_dispute_id": { + "name": "stripe_dispute_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_event_created_at": { + "name": "stripe_event_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_intent_id": { + "name": "stripe_payment_intent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_minor_units": { + "name": "amount_minor_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dispute_reason": { + "name": "dispute_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_status": { + "name": "stripe_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_classification": { + "name": "owner_classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'needs_action'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_created_at": { + "name": "stripe_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "evidence_due_by": { + "name": "evidence_due_by", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_by_kilo_user_id": { + "name": "accepted_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance_started_at": { + "name": "acceptance_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "enforcement_completed_at": { + "name": "enforcement_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_required_at": { + "name": "review_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_dispute_cases_event_id": { + "name": "IDX_stripe_dispute_cases_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_charge_id": { + "name": "IDX_stripe_dispute_cases_charge_id", + "columns": [ + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_payment_intent_id": { + "name": "IDX_stripe_dispute_cases_payment_intent_id", + "columns": [ + { + "expression": "stripe_payment_intent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_customer_id": { + "name": "IDX_stripe_dispute_cases_customer_id", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_kilo_user_id": { + "name": "IDX_stripe_dispute_cases_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_organization_id": { + "name": "IDX_stripe_dispute_cases_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_status_due_by": { + "name": "IDX_stripe_dispute_cases_status_due_by", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "evidence_due_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stripe_created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_dispute_cases_kilo_user_id_kilocode_users_id_fk": { + "name": "stripe_dispute_cases_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "stripe_dispute_cases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "stripe_dispute_cases_organization_id_organizations_id_fk": { + "name": "stripe_dispute_cases_organization_id_organizations_id_fk", + "tableFrom": "stripe_dispute_cases", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "stripe_dispute_cases_accepted_by_kilo_user_id_kilocode_users_id_fk": { + "name": "stripe_dispute_cases_accepted_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "stripe_dispute_cases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "accepted_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_dispute_cases_dispute_id": { + "name": "UQ_stripe_dispute_cases_dispute_id", + "nullsNotDistinct": false, + "columns": [ + "stripe_dispute_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_dispute_cases_owner_classification_check": { + "name": "stripe_dispute_cases_owner_classification_check", + "value": "\"stripe_dispute_cases\".\"owner_classification\" IN ('personal', 'organization', 'ambiguous', 'unmatched')" + }, + "stripe_dispute_cases_status_check": { + "name": "stripe_dispute_cases_status_check", + "value": "\"stripe_dispute_cases\".\"status\" IN ('needs_action', 'processing', 'accepted', 'acceptance_failed', 'enforcement_failed', 'review_required', 'closed')" + }, + "stripe_dispute_cases_amount_minor_units_non_negative_check": { + "name": "stripe_dispute_cases_amount_minor_units_non_negative_check", + "value": "\"stripe_dispute_cases\".\"amount_minor_units\" IS NULL OR \"stripe_dispute_cases\".\"amount_minor_units\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_early_fraud_warning_actions": { + "name": "stripe_early_fraud_warning_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "result_code": { + "name": "result_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_reference_id": { + "name": "result_reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_early_fraud_warning_actions_case_id": { + "name": "IDX_stripe_early_fraud_warning_actions_case_id", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_actions_claim_path": { + "name": "IDX_stripe_early_fraud_warning_actions_claim_path", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_early_fraud_warning_actions_case_id_stripe_early_fraud_warning_cases_id_fk": { + "name": "stripe_early_fraud_warning_actions_case_id_stripe_early_fraud_warning_cases_id_fk", + "tableFrom": "stripe_early_fraud_warning_actions", + "tableTo": "stripe_early_fraud_warning_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_early_fraud_warning_actions_case_type_target": { + "name": "UQ_stripe_early_fraud_warning_actions_case_type_target", + "nullsNotDistinct": false, + "columns": [ + "case_id", + "action_type", + "target_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_early_fraud_warning_actions_action_type_check": { + "name": "stripe_early_fraud_warning_actions_action_type_check", + "value": "\"stripe_early_fraud_warning_actions\".\"action_type\" IN ('containment', 'refund', 'payment_value_clawback', 'subscription_termination', 'access_termination', 'kiloclaw_suspension', 'affiliate_payout_reversal', 'referral_reward_reversal', 'user_notice')" + }, + "stripe_early_fraud_warning_actions_status_check": { + "name": "stripe_early_fraud_warning_actions_status_check", + "value": "\"stripe_early_fraud_warning_actions\".\"status\" IN ('queued', 'processing', 'completed', 'failed', 'review_required', 'dismissed')" + }, + "stripe_early_fraud_warning_actions_attempt_count_non_negative_check": { + "name": "stripe_early_fraud_warning_actions_attempt_count_non_negative_check", + "value": "\"stripe_early_fraud_warning_actions\".\"attempt_count\" >= 0" + }, + "stripe_early_fraud_warning_actions_target_key_not_empty_check": { + "name": "stripe_early_fraud_warning_actions_target_key_not_empty_check", + "value": "length(\"stripe_early_fraud_warning_actions\".\"target_key\") > 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_early_fraud_warning_cases": { + "name": "stripe_early_fraud_warning_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_early_fraud_warning_id": { + "name": "stripe_early_fraud_warning_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_intent_id": { + "name": "stripe_payment_intent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_minor_units": { + "name": "amount_minor_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_classification": { + "name": "owner_classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "warning_created_at": { + "name": "warning_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "contained_at": { + "name": "contained_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_required_at": { + "name": "review_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "remediated_at": { + "name": "remediated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_early_fraud_warning_cases_event_id": { + "name": "IDX_stripe_early_fraud_warning_cases_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_charge_id": { + "name": "IDX_stripe_early_fraud_warning_cases_charge_id", + "columns": [ + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_payment_intent_id": { + "name": "IDX_stripe_early_fraud_warning_cases_payment_intent_id", + "columns": [ + { + "expression": "stripe_payment_intent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_customer_id": { + "name": "IDX_stripe_early_fraud_warning_cases_customer_id", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_kilo_user_id": { + "name": "IDX_stripe_early_fraud_warning_cases_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_organization_id": { + "name": "IDX_stripe_early_fraud_warning_cases_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_status_created_at": { + "name": "IDX_stripe_early_fraud_warning_cases_status_created_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_early_fraud_warning_cases_kilo_user_id_kilocode_users_id_fk": { + "name": "stripe_early_fraud_warning_cases_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "stripe_early_fraud_warning_cases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "stripe_early_fraud_warning_cases_organization_id_organizations_id_fk": { + "name": "stripe_early_fraud_warning_cases_organization_id_organizations_id_fk", + "tableFrom": "stripe_early_fraud_warning_cases", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_early_fraud_warning_cases_warning_id": { + "name": "UQ_stripe_early_fraud_warning_cases_warning_id", + "nullsNotDistinct": false, + "columns": [ + "stripe_early_fraud_warning_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_early_fraud_warning_cases_owner_classification_check": { + "name": "stripe_early_fraud_warning_cases_owner_classification_check", + "value": "\"stripe_early_fraud_warning_cases\".\"owner_classification\" IN ('personal', 'organization', 'ambiguous', 'unmatched')" + }, + "stripe_early_fraud_warning_cases_status_check": { + "name": "stripe_early_fraud_warning_cases_status_check", + "value": "\"stripe_early_fraud_warning_cases\".\"status\" IN ('queued', 'contained', 'processing', 'completed', 'review_required', 'failed', 'remediated', 'dismissed')" + }, + "stripe_early_fraud_warning_cases_amount_minor_units_non_negative_check": { + "name": "stripe_early_fraud_warning_cases_amount_minor_units_non_negative_check", + "value": "\"stripe_early_fraud_warning_cases\".\"amount_minor_units\" IS NULL OR \"stripe_early_fraud_warning_cases\".\"amount_minor_units\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.stytch_fingerprints": { + "name": "stytch_fingerprints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitor_fingerprint": { + "name": "visitor_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "browser_fingerprint": { + "name": "browser_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "browser_id": { + "name": "browser_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hardware_fingerprint": { + "name": "hardware_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "network_fingerprint": { + "name": "network_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitor_id": { + "name": "visitor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verdict_action": { + "name": "verdict_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detected_device_type": { + "name": "detected_device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_authentic_device": { + "name": "is_authentic_device", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{\"\"}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fingerprint_data": { + "name": "fingerprint_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_free_tier_allowed": { + "name": "kilo_free_tier_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_hardware_fingerprint": { + "name": "idx_hardware_fingerprint", + "columns": [ + { + "expression": "hardware_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_kilo_user_id": { + "name": "idx_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stytch_fingerprints_reasons_gin": { + "name": "idx_stytch_fingerprints_reasons_gin", + "columns": [ + { + "expression": "reasons", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_verdict_action": { + "name": "idx_verdict_action", + "columns": [ + { + "expression": "verdict_action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_visitor_fingerprint": { + "name": "idx_visitor_fingerprint", + "columns": [ + { + "expression": "visitor_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_prompt_prefix": { + "name": "system_prompt_prefix", + "schema": "", + "columns": { + "system_prompt_prefix_id": { + "name": "system_prompt_prefix_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "system_prompt_prefix": { + "name": "system_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_system_prompt_prefix": { + "name": "UQ_system_prompt_prefix", + "columns": [ + { + "expression": "system_prompt_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactional_email_log": { + "name": "transactional_email_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email_type": { + "name": "email_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_transactional_email_log_type_idempotency_key": { + "name": "UQ_transactional_email_log_type_idempotency_key", + "columns": [ + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_transactional_email_log_user_id": { + "name": "IDX_transactional_email_log_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_transactional_email_log_organization_id": { + "name": "IDX_transactional_email_log_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactional_email_log_user_id_kilocode_users_id_fk": { + "name": "transactional_email_log_user_id_kilocode_users_id_fk", + "tableFrom": "transactional_email_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "transactional_email_log_organization_id_organizations_id_fk": { + "name": "transactional_email_log_organization_id_organizations_id_fk", + "tableFrom": "transactional_email_log", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_transactional_email_log_owner": { + "name": "CHK_transactional_email_log_owner", + "value": "\"transactional_email_log\".\"user_id\" IS NOT NULL OR \"transactional_email_log\".\"organization_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.user_admin_notes": { + "name": "user_admin_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note_content": { + "name": "note_content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "admin_kilo_user_id": { + "name": "admin_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_34517df0b385234babc38fe81b": { + "name": "IDX_34517df0b385234babc38fe81b", + "columns": [ + { + "expression": "admin_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_ccbde98c4c14046daa5682ec4f": { + "name": "IDX_ccbde98c4c14046daa5682ec4f", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_d0270eb24ef6442d65a0b7853c": { + "name": "IDX_d0270eb24ef6442d65a0b7853c", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_affiliate_attributions": { + "name": "user_affiliate_attributions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tracking_id": { + "name": "tracking_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_affiliate_attributions_user_id": { + "name": "IDX_user_affiliate_attributions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_affiliate_attributions_user_id_kilocode_users_id_fk": { + "name": "user_affiliate_attributions_user_id_kilocode_users_id_fk", + "tableFrom": "user_affiliate_attributions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_affiliate_attributions_user_provider": { + "name": "UQ_user_affiliate_attributions_user_provider", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_affiliate_attributions_provider_check": { + "name": "user_affiliate_attributions_provider_check", + "value": "\"user_affiliate_attributions\".\"provider\" IN ('impact')" + } + }, + "isRLSEnabled": false + }, + "public.user_affiliate_events": { + "name": "user_affiliate_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_event_id": { + "name": "parent_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_action_id": { + "name": "impact_action_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_submission_uri": { + "name": "impact_submission_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_affiliate_events_claim_path": { + "name": "IDX_user_affiliate_events_claim_path", + "columns": [ + { + "expression": "delivery_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_affiliate_events_parent_event_id": { + "name": "IDX_user_affiliate_events_parent_event_id", + "columns": [ + { + "expression": "parent_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_affiliate_events_provider_event_type_charge": { + "name": "IDX_user_affiliate_events_provider_event_type_charge", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_affiliate_events_user_id_kilocode_users_id_fk": { + "name": "user_affiliate_events_user_id_kilocode_users_id_fk", + "tableFrom": "user_affiliate_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "user_affiliate_events_parent_event_id_fk": { + "name": "user_affiliate_events_parent_event_id_fk", + "tableFrom": "user_affiliate_events", + "tableTo": "user_affiliate_events", + "columnsFrom": [ + "parent_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_affiliate_events_dedupe_key": { + "name": "UQ_user_affiliate_events_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_affiliate_events_provider_check": { + "name": "user_affiliate_events_provider_check", + "value": "\"user_affiliate_events\".\"provider\" IN ('impact')" + }, + "user_affiliate_events_event_type_check": { + "name": "user_affiliate_events_event_type_check", + "value": "\"user_affiliate_events\".\"event_type\" IN ('signup', 'trial_start', 'trial_end', 'sale', 'sale_reversal')" + }, + "user_affiliate_events_delivery_state_check": { + "name": "user_affiliate_events_delivery_state_check", + "value": "\"user_affiliate_events\".\"delivery_state\" IN ('queued', 'blocked', 'sending', 'delivered', 'failed')" + }, + "user_affiliate_events_attempt_count_non_negative_check": { + "name": "user_affiliate_events_attempt_count_non_negative_check", + "value": "\"user_affiliate_events\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.user_auth_provider": { + "name": "user_auth_provider", + "schema": "", + "columns": { + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hosted_domain": { + "name": "hosted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_auth_provider_kilo_user_id": { + "name": "IDX_user_auth_provider_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_auth_provider_hosted_domain": { + "name": "IDX_user_auth_provider_hosted_domain", + "columns": [ + { + "expression": "hosted_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_auth_provider_provider_provider_account_id_pk": { + "name": "user_auth_provider_provider_provider_account_id_pk", + "columns": [ + "provider", + "provider_account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_feedback": { + "name": "user_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feedback_for": { + "name": "feedback_for", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "feedback_batch": { + "name": "feedback_batch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "context_json": { + "name": "context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_feedback_created_at": { + "name": "IDX_user_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_kilo_user_id": { + "name": "IDX_user_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_feedback_for": { + "name": "IDX_user_feedback_feedback_for", + "columns": [ + { + "expression": "feedback_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_feedback_batch": { + "name": "IDX_user_feedback_feedback_batch", + "columns": [ + { + "expression": "feedback_batch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_source": { + "name": "IDX_user_feedback_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "user_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_github_app_tokens": { + "name": "user_github_app_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "credential_version": { + "name": "credential_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_reason": { + "name": "revocation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_github_app_tokens_user_app": { + "name": "UQ_user_github_app_tokens_user_app", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_github_app_tokens_github_user_app": { + "name": "UQ_user_github_app_tokens_github_user_app", + "columns": [ + { + "expression": "github_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_github_app_tokens_kilo_user_id_kilocode_users_id_fk": { + "name": "user_github_app_tokens_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_github_app_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_github_app_tokens_app_type_check": { + "name": "user_github_app_tokens_app_type_check", + "value": "\"user_github_app_tokens\".\"github_app_type\" IN ('standard', 'lite')" + } + }, + "isRLSEnabled": false + }, + "public.user_period_cache": { + "name": "user_period_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cache_type": { + "name": "cache_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_type": { + "name": "period_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "shared_url_token": { + "name": "shared_url_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_user_period_cache_kilo_user_id": { + "name": "IDX_user_period_cache_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_period_cache": { + "name": "UQ_user_period_cache", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cache_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_period_cache_lookup": { + "name": "IDX_user_period_cache_lookup", + "columns": [ + { + "expression": "cache_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_period_cache_share_token": { + "name": "UQ_user_period_cache_share_token", + "columns": [ + { + "expression": "shared_url_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_period_cache\".\"shared_url_token\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_period_cache_kilo_user_id_kilocode_users_id_fk": { + "name": "user_period_cache_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_period_cache", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_period_cache_period_type_check": { + "name": "user_period_cache_period_type_check", + "value": "\"user_period_cache\".\"period_type\" IN ('year', 'quarter', 'month', 'week', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.user_push_tokens": { + "name": "user_push_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_push_tokens_token": { + "name": "UQ_user_push_tokens_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_push_tokens_user_id": { + "name": "IDX_user_push_tokens_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_push_tokens_user_id_kilocode_users_id_fk": { + "name": "user_push_tokens_user_id_kilocode_users_id_fk", + "tableFrom": "user_push_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_ip_city": { + "name": "vercel_ip_city", + "schema": "", + "columns": { + "vercel_ip_city_id": { + "name": "vercel_ip_city_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vercel_ip_city": { + "name": "vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_vercel_ip_city": { + "name": "UQ_vercel_ip_city", + "columns": [ + { + "expression": "vercel_ip_city", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_ip_country": { + "name": "vercel_ip_country", + "schema": "", + "columns": { + "vercel_ip_country_id": { + "name": "vercel_ip_country_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vercel_ip_country": { + "name": "vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_vercel_ip_country": { + "name": "UQ_vercel_ip_country", + "columns": [ + { + "expression": "vercel_ip_country", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_events": { + "name": "webhook_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_action": { + "name": "event_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "processed": { + "name": "processed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "handlers_triggered": { + "name": "handlers_triggered", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "errors": { + "name": "errors", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "event_signature": { + "name": "event_signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_webhook_events_owned_by_org_id": { + "name": "IDX_webhook_events_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_owned_by_user_id": { + "name": "IDX_webhook_events_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_platform": { + "name": "IDX_webhook_events_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_event_type": { + "name": "IDX_webhook_events_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_created_at": { + "name": "IDX_webhook_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_events_owned_by_organization_id_organizations_id_fk": { + "name": "webhook_events_owned_by_organization_id_organizations_id_fk", + "tableFrom": "webhook_events", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_events_owned_by_user_id_kilocode_users_id_fk": { + "name": "webhook_events_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "webhook_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_webhook_events_signature": { + "name": "UQ_webhook_events_signature", + "nullsNotDistinct": false, + "columns": [ + "event_signature" + ] + } + }, + "policies": {}, + "checkConstraints": { + "webhook_events_owner_check": { + "name": "webhook_events_owner_check", + "value": "(\n (\"webhook_events\".\"owned_by_user_id\" IS NOT NULL AND \"webhook_events\".\"owned_by_organization_id\" IS NULL) OR\n (\"webhook_events\".\"owned_by_user_id\" IS NULL AND \"webhook_events\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.microdollar_usage_view": { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_hit_tokens": { + "name": "cache_hit_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_model": { + "name": "requested_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_prompt_prefix": { + "name": "user_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_prefix": { + "name": "system_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_length": { + "name": "system_prompt_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_discount": { + "name": "cache_discount", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_middle_out_transform": { + "name": "has_middle_out_transform", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_error": { + "name": "has_error", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "abuse_classification": { + "name": "abuse_classification", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "inference_provider": { + "name": "inference_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "upstream_id": { + "name": "upstream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency": { + "name": "latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moderation_latency": { + "name": "moderation_latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "generation_time": { + "name": "generation_time", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "is_byok": { + "name": "is_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_user_byok": { + "name": "is_user_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "streamed": { + "name": "streamed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "editor_name": { + "name": "editor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_kind": { + "name": "api_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_tools": { + "name": "has_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_model": { + "name": "auto_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "market_cost": { + "name": "market_cost", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "abuse_delay": { + "name": "abuse_delay", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "abuse_downgraded_from": { + "name": "abuse_downgraded_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n mu.id,\n mu.kilo_user_id,\n meta.message_id,\n mu.cost,\n mu.input_tokens,\n mu.output_tokens,\n mu.cache_write_tokens,\n mu.cache_hit_tokens,\n mu.created_at,\n ip.http_ip AS http_x_forwarded_for,\n city.vercel_ip_city AS http_x_vercel_ip_city,\n country.vercel_ip_country AS http_x_vercel_ip_country,\n meta.vercel_ip_latitude AS http_x_vercel_ip_latitude,\n meta.vercel_ip_longitude AS http_x_vercel_ip_longitude,\n ja4.ja4_digest AS http_x_vercel_ja4_digest,\n mu.provider,\n mu.model,\n mu.requested_model,\n meta.user_prompt_prefix,\n spp.system_prompt_prefix,\n meta.system_prompt_length,\n ua.http_user_agent,\n mu.cache_discount,\n meta.max_tokens,\n meta.has_middle_out_transform,\n mu.has_error,\n mu.abuse_classification,\n mu.organization_id,\n mu.inference_provider,\n mu.project_id,\n meta.status_code,\n meta.upstream_id,\n frfr.finish_reason,\n meta.latency,\n meta.moderation_latency,\n meta.generation_time,\n meta.is_byok,\n meta.is_user_byok,\n meta.streamed,\n meta.cancelled,\n edit.editor_name,\n ak.api_kind,\n meta.has_tools,\n meta.machine_id,\n feat.feature,\n meta.session_id,\n md.mode,\n am.auto_model,\n meta.market_cost,\n meta.is_free,\n meta.abuse_delay,\n meta.abuse_downgraded_from\n FROM \"microdollar_usage\" mu\n LEFT JOIN \"microdollar_usage_metadata\" meta ON mu.id = meta.id\n LEFT JOIN \"http_ip\" ip ON meta.http_ip_id = ip.http_ip_id\n LEFT JOIN \"vercel_ip_city\" city ON meta.vercel_ip_city_id = city.vercel_ip_city_id\n LEFT JOIN \"vercel_ip_country\" country ON meta.vercel_ip_country_id = country.vercel_ip_country_id\n LEFT JOIN \"ja4_digest\" ja4 ON meta.ja4_digest_id = ja4.ja4_digest_id\n LEFT JOIN \"system_prompt_prefix\" spp ON meta.system_prompt_prefix_id = spp.system_prompt_prefix_id\n LEFT JOIN \"http_user_agent\" ua ON meta.http_user_agent_id = ua.http_user_agent_id\n LEFT JOIN \"finish_reason\" frfr ON meta.finish_reason_id = frfr.finish_reason_id\n LEFT JOIN \"editor_name\" edit ON meta.editor_name_id = edit.editor_name_id\n LEFT JOIN \"api_kind\" ak ON meta.api_kind_id = ak.api_kind_id\n LEFT JOIN \"feature\" feat ON meta.feature_id = feat.feature_id\n LEFT JOIN \"mode\" md ON meta.mode_id = md.mode_id\n LEFT JOIN \"auto_model\" am ON meta.auto_model_id = am.auto_model_id\n", + "name": "microdollar_usage_view", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 7e24ad7d8a..cf91e016e6 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1163,6 +1163,13 @@ "when": 1781527420968, "tag": "0165_clear_golden_guardian", "breakpoints": true + }, + { + "idx": 166, + "version": "7", + "when": 1781720353751, + "tag": "0166_curved_praxagora", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema-types.ts b/packages/db/src/schema-types.ts index 7e77545a0a..bc30d2dc12 100644 --- a/packages/db/src/schema-types.ts +++ b/packages/db/src/schema-types.ts @@ -131,11 +131,14 @@ export enum CliSessionSharedState { */ export enum SecurityAuditLogAction { FindingCreated = 'security.finding.created', + FindingSeverityChanged = 'security.finding.severity_changed', FindingStatusChange = 'security.finding.status_change', FindingDismissed = 'security.finding.dismissed', FindingAutoDismissed = 'security.finding.auto_dismissed', + FindingSuperseded = 'security.finding.superseded', FindingAnalysisStarted = 'security.finding.analysis_started', FindingAnalysisCompleted = 'security.finding.analysis_completed', + FindingAnalysisFailed = 'security.finding.analysis_failed', RemediationQueued = 'security.remediation.queued', RemediationStarted = 'security.remediation.started', RemediationPrOpened = 'security.remediation.pr_opened', @@ -151,6 +154,21 @@ export enum SecurityAuditLogAction { SyncTriggered = 'security.sync.triggered', SyncCompleted = 'security.sync.completed', AuditLogExported = 'security.audit_log.exported', + AuditReportGenerated = 'security.audit_report.generated', +} + +export enum SecurityFindingAuditSourceContext { + SecuritySync = 'security_sync', + Web = 'web', + AnalysisWorker = 'analysis_worker', + RemediationCallback = 'remediation_callback', + RolloutBaseline = 'rollout_baseline', +} + +export enum SecurityAuditLogActorType { + CustomerUser = 'customer_user', + KiloAdmin = 'kilo_admin', + System = 'system', } // --- KiloClaw enums --- @@ -1138,6 +1156,7 @@ export type SandboxSuggestedAction = export type SecurityFindingSandboxAnalysis = { isExploitable: boolean | 'unknown'; + extractionStatus?: 'succeeded' | 'failed'; exploitabilityReasoning: string; usageLocations: string[]; suggestedFix: string; @@ -1148,9 +1167,32 @@ export type SecurityFindingSandboxAnalysis = { modelUsed?: string; }; +export type SecurityFindingAnalysisInput = { + schemaVersion: 1; + source: string; + sourceId: string; + sourceUpdatedAt: string | null; + repoFullName: string; + status: string; + severity: string | null; + packageName: string; + packageEcosystem: string; + dependencyScope: string | null; + cveId: string | null; + ghsaId: string | null; + cweIds: string[]; + cvssScore: string | null; + title: string; + description: string | null; + vulnerableVersionRange: string | null; + patchedVersion: string | null; + manifestPath: string | null; +}; + export type SecurityFindingAnalysis = { triage?: SecurityFindingTriage; sandboxAnalysis?: SecurityFindingSandboxAnalysis; + findingDataSnapshot?: SecurityFindingAnalysisInput; rawMarkdown?: string; analyzedAt: string; modelUsed?: string; diff --git a/packages/db/src/schema.test.ts b/packages/db/src/schema.test.ts index 6cf1d92c38..140dc5bd9b 100644 --- a/packages/db/src/schema.test.ts +++ b/packages/db/src/schema.test.ts @@ -267,11 +267,22 @@ describe('database schema', () => { CliSessionSharedState: ['public', 'organization'], SecurityAuditLogAction: [ 'security.finding.created', + 'security.finding.severity_changed', 'security.finding.status_change', 'security.finding.dismissed', 'security.finding.auto_dismissed', + 'security.finding.superseded', 'security.finding.analysis_started', 'security.finding.analysis_completed', + 'security.finding.analysis_failed', + 'security.remediation.queued', + 'security.remediation.started', + 'security.remediation.pr_opened', + 'security.remediation.failed', + 'security.remediation.blocked', + 'security.remediation.no_changes_needed', + 'security.remediation.cancelled', + 'security.remediation.retried', 'security.finding.deleted', 'security.config.enabled', 'security.config.disabled', @@ -279,6 +290,15 @@ describe('database schema', () => { 'security.sync.triggered', 'security.sync.completed', 'security.audit_log.exported', + 'security.audit_report.generated', + ], + SecurityAuditLogActorType: ['customer_user', 'kilo_admin', 'system'], + SecurityFindingAuditSourceContext: [ + 'security_sync', + 'web', + 'analysis_worker', + 'remediation_callback', + 'rollout_baseline', ], KiloClawPlan: ['trial', 'commit', 'standard'], KiloClawScheduledPlan: ['commit', 'standard'], diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index d5cd4d6792..af2d5d87de 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -40,6 +40,8 @@ import { FeedbackSource, CliSessionSharedState, SecurityAuditLogAction, + SecurityAuditLogActorType, + SecurityFindingAuditSourceContext, SecurityFindingNotificationKind, SecurityFindingNotificationStatus, KiloClawPlan, @@ -179,6 +181,8 @@ export const SCHEMA_CHECK_ENUMS = { KiloPassScheduledChangeStatus, CliSessionSharedState, SecurityAuditLogAction, + SecurityAuditLogActorType, + SecurityFindingAuditSourceContext, KiloClawPlan, KiloClawScheduledPlan, KiloClawScheduledBy, @@ -5430,12 +5434,20 @@ export const security_audit_log = pgTable( actor_id: text(), actor_email: text(), actor_name: text(), + actor_type: text().$type(), action: text().$type().notNull(), resource_type: text().notNull(), resource_id: text().notNull(), before_state: jsonb().$type>(), after_state: jsonb().$type>(), metadata: jsonb().$type>(), + finding_id: uuid(), + occurred_at: timestamp({ withTimezone: true, mode: 'string' }), + source_occurred_at: timestamp({ withTimezone: true, mode: 'string' }), + event_key: text(), + schema_version: smallint(), + finding_snapshot: jsonb().$type>(), + source_context: text().$type(), created_at: timestamp({ withTimezone: true, mode: 'string' }).defaultNow().notNull(), }, table => [ @@ -5444,6 +5456,12 @@ export const security_audit_log = pgTable( sql`(${table.owned_by_user_id} IS NOT NULL AND ${table.owned_by_organization_id} IS NULL) OR (${table.owned_by_user_id} IS NULL AND ${table.owned_by_organization_id} IS NOT NULL)` ), enumCheck('security_audit_log_action_check', table.action, SecurityAuditLogAction), + enumCheck('security_audit_log_actor_type_check', table.actor_type, SecurityAuditLogActorType), + enumCheck( + 'security_audit_log_source_context_check', + table.source_context, + SecurityFindingAuditSourceContext + ), index('IDX_security_audit_log_org_created').on( table.owned_by_organization_id, table.created_at @@ -5452,6 +5470,20 @@ export const security_audit_log = pgTable( index('IDX_security_audit_log_resource').on(table.resource_type, table.resource_id), index('IDX_security_audit_log_actor').on(table.actor_id, table.created_at), index('IDX_security_audit_log_action').on(table.action, table.created_at), + uniqueIndex('UQ_security_audit_log_org_event_key') + .on(table.owned_by_organization_id, table.event_key) + .where(sql`${table.owned_by_organization_id} IS NOT NULL AND ${table.event_key} IS NOT NULL`), + uniqueIndex('UQ_security_audit_log_user_event_key') + .on(table.owned_by_user_id, table.event_key) + .where(sql`${table.owned_by_user_id} IS NOT NULL AND ${table.event_key} IS NOT NULL`), + index('IDX_security_audit_log_org_occurred') + .on(table.owned_by_organization_id, table.occurred_at, table.id) + .where( + sql`${table.owned_by_organization_id} IS NOT NULL AND ${table.occurred_at} IS NOT NULL` + ), + index('IDX_security_audit_log_user_occurred') + .on(table.owned_by_user_id, table.occurred_at, table.id) + .where(sql`${table.owned_by_user_id} IS NOT NULL AND ${table.occurred_at} IS NOT NULL`), ] ); diff --git a/packages/worker-utils/package.json b/packages/worker-utils/package.json index 1f8620ac9e..1f18ff362c 100644 --- a/packages/worker-utils/package.json +++ b/packages/worker-utils/package.json @@ -23,6 +23,7 @@ "./security-auto-analysis-policy": "./src/security-auto-analysis-policy.ts", "./security-remediation-policy": "./src/security-remediation-policy.ts", "./security-notification-policy": "./src/security-notification-policy.ts", + "./security-finding-audit": "./src/security-finding-audit.ts", "./dependabot-dismissal-target": "./src/dependabot-dismissal-target.ts", "./client-error": "./src/client-error.ts" }, diff --git a/packages/worker-utils/src/index.ts b/packages/worker-utils/src/index.ts index 066a693587..61ed9b6876 100644 --- a/packages/worker-utils/src/index.ts +++ b/packages/worker-utils/src/index.ts @@ -112,3 +112,33 @@ export type { CloudAgentQueueReport, CloudAgentRunStateReport, } from './cloud-agent-queue-report.js'; + +export { + REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS, + SECURITY_FINDING_AUDIT_EVENT_KEY_PREFIX, + SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + SecurityFindingAuditActorSchema, + SecurityFindingAuditEventSchema, + SecurityFindingAuditHumanActorSchema, + SecurityFindingAuditOwnerSchema, + SecurityFindingAuditSnapshotSchema, + buildSecurityFindingAuditHumanActor, + buildSecurityFindingAuditLogValues, + buildSecurityFindingAuditSnapshot, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, +} from './security-finding-audit.js'; +export type { + NewSecurityFindingAuditLogValues, + SecurityFindingAuditActor, + SecurityFindingAuditEventFinding, + SecurityFindingAuditEventInput, + SecurityFindingAuditHumanActor, + SecurityFindingAuditLogEntry, + SecurityFindingAuditOwner, + SecurityFindingAuditSnapshot, + SecurityFindingAuditSnapshotExtras, + SecurityFindingAuditSnapshotSource, + SecurityFindingAuditWriterDb, +} from './security-finding-audit.js'; diff --git a/packages/worker-utils/src/security-finding-audit.test.ts b/packages/worker-utils/src/security-finding-audit.test.ts new file mode 100644 index 0000000000..6a50321da9 --- /dev/null +++ b/packages/worker-utils/src/security-finding-audit.test.ts @@ -0,0 +1,236 @@ +import { describe, expect, it } from 'vitest'; +import { + SecurityAuditLogAction, + SecurityAuditLogActorType, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + buildSecurityFindingAuditHumanActor, + buildSecurityFindingAuditLogValues, + buildSecurityFindingAuditSnapshot, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type NewSecurityFindingAuditLogValues, + type SecurityFindingAuditEventFinding, + type SecurityFindingAuditWriterDb, +} from './security-finding-audit'; + +const finding = { + id: '11111111-1111-4111-8111-111111111111', + owned_by_user_id: 'user_123', + owned_by_organization_id: null, + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/example', + title: 'lodash vulnerable to prototype pollution', + severity: 'high', + status: 'open', + package_name: 'lodash', + package_ecosystem: 'npm', + manifest_path: 'package.json', + patched_version: '4.17.21', + ghsa_id: 'GHSA-xxxx-yyyy-zzzz', + cve_id: 'CVE-2026-1234', + cwe_ids: ['CWE-1321'], + cvss_score: '7.5', + dependabot_html_url: 'https://github.com/kilo/example/security/dependabot/42', + first_detected_at: '2026-06-01 12:30:00.000+00', + fixed_at: null, + sla_due_at: '2026-06-08 12:30:00.000+00', + session_id: 'ses_123', +} satisfies SecurityFindingAuditEventFinding; + +const baseInput = { + owner: { type: 'user' as const, userId: 'user_123' }, + finding, + actor: buildSecurityFindingAuditHumanActor({ + id: 'user_123', + email: 'owner@example.com', + name: 'Owner User', + isAdmin: false, + }), + action: SecurityAuditLogAction.FindingCreated, + occurredAt: '2026-06-12T10:00:00.000Z', + eventKey: deriveSecurityFindingAuditEventKey([ + 'user', + 'user_123', + finding.id, + SecurityAuditLogAction.FindingCreated, + 'source:42', + ]), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + metadata: { source: 'dependabot', alert_number: 42 }, +}; + +describe('security finding audit contract', () => { + it('builds compact snapshots with normalized timestamps only', () => { + const snapshot = buildSecurityFindingAuditSnapshot(finding); + + expect(snapshot).toMatchObject({ + finding_id: finding.id, + source: 'dependabot', + source_id: '42', + repo_full_name: 'kilo/example', + title: finding.title, + severity: 'high', + status: 'open', + first_detected_at: '2026-06-01T12:30:00.000Z', + sla_due_at: '2026-06-08T12:30:00.000Z', + }); + expect(snapshot.fixed_at).toBeNull(); + }); + + it('builds insert values with owner, finding, event, and snapshot fields', () => { + const values = buildSecurityFindingAuditLogValues(baseInput); + + expect(values).toMatchObject({ + owned_by_user_id: 'user_123', + owned_by_organization_id: null, + actor_id: 'user_123', + actor_email: 'owner@example.com', + actor_name: 'Owner User', + actor_type: SecurityAuditLogActorType.CustomerUser, + action: SecurityAuditLogAction.FindingCreated, + resource_type: 'security_finding', + resource_id: finding.id, + finding_id: finding.id, + occurred_at: '2026-06-12T10:00:00.000Z', + event_key: baseInput.eventKey, + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + source_context: SecurityFindingAuditSourceContext.SecuritySync, + }); + }); + + it('persists authoritative admin and system actor classifications', () => { + const adminValues = buildSecurityFindingAuditLogValues({ + ...baseInput, + actor: buildSecurityFindingAuditHumanActor({ + id: 'admin_123', + email: 'operator@example.com', + name: 'Operator', + isAdmin: true, + }), + }); + expect(adminValues).toMatchObject({ + actor_id: 'admin_123', + actor_email: 'operator@example.com', + actor_name: 'Operator', + actor_type: SecurityAuditLogActorType.KiloAdmin, + }); + + const systemValues = buildSecurityFindingAuditLogValues({ + ...baseInput, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + }); + expect(systemValues).toMatchObject({ + actor_id: null, + actor_email: null, + actor_name: null, + actor_type: SecurityAuditLogActorType.System, + }); + }); + + it('requires a typed actor with stable identity for human events', () => { + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + actor: undefined, + } as never) + ).toThrow(); + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + actor: { + type: SecurityAuditLogActorType.CustomerUser, + id: '', + email: null, + name: null, + }, + }) + ).toThrow(); + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + actor: { + type: SecurityAuditLogActorType.System, + id: 'unexpected-human-id', + }, + } as never) + ).toThrow(); + }); + + it('rejects owner mismatch before insert', () => { + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + owner: { type: 'user', userId: 'other_user' }, + }) + ).toThrow('owner does not match'); + }); + + it('rejects snapshot finding mismatch', () => { + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + snapshot: { + ...buildSecurityFindingAuditSnapshot(finding), + finding_id: '22222222-2222-4222-8222-222222222222', + }, + }) + ).toThrow('snapshot finding_id must match'); + }); + + it('rejects non-reportable actions', () => { + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + action: SecurityAuditLogAction.FindingAnalysisStarted, + }) + ).toThrow('Action is not reportable'); + }); + + it('rejects identity and sensitive values in JSON payloads', () => { + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + metadata: { actor_email: 'owner@example.com' }, + }) + ).toThrow('Audit JSON field is not allowed'); + + expect(() => + buildSecurityFindingAuditLogValues({ + ...baseInput, + metadata: { rationale: 'contact owner@example.com' }, + }) + ).toThrow('appears to contain an email'); + }); + + it('uses deterministic escaped event keys', () => { + expect(deriveSecurityFindingAuditEventKey(['owner:user_123', 'finding/42'])).toBe( + 'security_finding_audit:v1:owner%3Auser_123:finding%2F42' + ); + }); + + it('inserts idempotently through caller-provided db or transaction', async () => { + const insertedValues: NewSecurityFindingAuditLogValues[] = []; + const db: SecurityFindingAuditWriterDb = { + insert: () => ({ + values: values => ({ + onConflictDoNothing: () => ({ + returning: async () => { + insertedValues.push(values); + return [{ id: 'audit_1' }]; + }, + }), + }), + }), + }; + + const result = await insertSecurityFindingAuditEvent(db, baseInput); + + expect(result).toEqual({ inserted: true, id: 'audit_1' }); + expect(insertedValues[0]?.event_key).toBe(baseInput.eventKey); + }); +}); diff --git a/packages/worker-utils/src/security-finding-audit.ts b/packages/worker-utils/src/security-finding-audit.ts new file mode 100644 index 0000000000..a105fd15a5 --- /dev/null +++ b/packages/worker-utils/src/security-finding-audit.ts @@ -0,0 +1,463 @@ +import { security_audit_log } from '@kilocode/db/schema'; +import type { SecurityAuditLogEntry } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityAuditLogActorType, + SecurityFindingAuditSourceContext, + SecuritySeverity, +} from '@kilocode/db/schema-types'; +import * as z from 'zod'; + +export const SECURITY_FINDING_AUDIT_SCHEMA_VERSION = 1; +export const SECURITY_FINDING_AUDIT_EVENT_KEY_PREFIX = 'security_finding_audit:v1'; + +export const REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS = [ + SecurityAuditLogAction.FindingCreated, + SecurityAuditLogAction.FindingSeverityChanged, + SecurityAuditLogAction.FindingStatusChange, + SecurityAuditLogAction.FindingDismissed, + SecurityAuditLogAction.FindingAutoDismissed, + SecurityAuditLogAction.FindingSuperseded, + SecurityAuditLogAction.FindingAnalysisCompleted, + SecurityAuditLogAction.FindingAnalysisFailed, + SecurityAuditLogAction.RemediationQueued, + SecurityAuditLogAction.RemediationPrOpened, + SecurityAuditLogAction.RemediationFailed, + SecurityAuditLogAction.RemediationBlocked, + SecurityAuditLogAction.RemediationNoChangesNeeded, + SecurityAuditLogAction.RemediationCancelled, + SecurityAuditLogAction.FindingDeleted, +] as const; + +const ReportableSecurityFindingAuditActionSchema = z + .nativeEnum(SecurityAuditLogAction) + .refine( + (action): action is (typeof REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS)[number] => + REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS.includes( + action as (typeof REPORTABLE_SECURITY_FINDING_AUDIT_ACTIONS)[number] + ), + 'Action is not reportable Security Finding activity' + ); + +const IsoTimestampSchema = z.string().datetime({ offset: true }); +const NonEmptyStringSchema = z.string().trim().min(1); +const UuidSchema = z.string().uuid(); + +export const SecurityFindingAuditOwnerSchema = z.discriminatedUnion('type', [ + z.object({ type: z.literal('user'), userId: NonEmptyStringSchema }), + z.object({ type: z.literal('organization'), organizationId: UuidSchema }), +]); + +export type SecurityFindingAuditOwner = z.infer; + +const SecurityFindingAuditCustomerActorSchema = z + .object({ + type: z.literal(SecurityAuditLogActorType.CustomerUser), + id: NonEmptyStringSchema, + email: z.string().email().nullable(), + name: NonEmptyStringSchema.nullable(), + }) + .strict(); + +const SecurityFindingAuditAdminActorSchema = z + .object({ + type: z.literal(SecurityAuditLogActorType.KiloAdmin), + id: NonEmptyStringSchema, + email: z.string().email().nullable(), + name: NonEmptyStringSchema.nullable(), + }) + .strict(); + +const SecurityFindingAuditSystemActorSchema = z + .object({ type: z.literal(SecurityAuditLogActorType.System) }) + .strict(); + +export const SecurityFindingAuditHumanActorSchema = z.discriminatedUnion('type', [ + SecurityFindingAuditCustomerActorSchema, + SecurityFindingAuditAdminActorSchema, +]); + +export const SecurityFindingAuditActorSchema = z.discriminatedUnion('type', [ + SecurityFindingAuditCustomerActorSchema, + SecurityFindingAuditAdminActorSchema, + SecurityFindingAuditSystemActorSchema, +]); + +export type SecurityFindingAuditActor = z.infer; +export type SecurityFindingAuditHumanActor = z.infer; + +export const SECURITY_FINDING_AUDIT_SYSTEM_ACTOR = { + type: SecurityAuditLogActorType.System, +} satisfies SecurityFindingAuditActor; + +export function buildSecurityFindingAuditHumanActor(params: { + id: string; + email?: string | null; + name?: string | null; + isAdmin: boolean; +}): SecurityFindingAuditHumanActor { + return SecurityFindingAuditHumanActorSchema.parse({ + type: params.isAdmin + ? SecurityAuditLogActorType.KiloAdmin + : SecurityAuditLogActorType.CustomerUser, + id: params.id, + email: params.email ?? null, + name: params.name ?? null, + }); +} + +type SanitizedJsonValue = + | string + | number + | boolean + | null + | SanitizedJsonValue[] + | { [key: string]: SanitizedJsonValue }; + +const SanitizedJsonValueSchema: z.ZodType = z.lazy(() => + z.union([ + z.string(), + z.number().finite(), + z.boolean(), + z.null(), + z.array(SanitizedJsonValueSchema), + z.record(z.string(), SanitizedJsonValueSchema), + ]) +); + +const SanitizedJsonObjectSchema = z + .record(z.string(), SanitizedJsonValueSchema) + .superRefine((value, ctx) => { + validateSafeAuditJson(value, ctx); + }); + +export const SecurityFindingAuditSnapshotSchema = z + .object({ + finding_id: UuidSchema, + source: NonEmptyStringSchema, + source_id: NonEmptyStringSchema, + repo_full_name: NonEmptyStringSchema, + title: NonEmptyStringSchema, + severity: z.enum([ + SecuritySeverity.CRITICAL, + SecuritySeverity.HIGH, + SecuritySeverity.MEDIUM, + SecuritySeverity.LOW, + ]), + status: NonEmptyStringSchema, + package_name: NonEmptyStringSchema.optional(), + package_ecosystem: NonEmptyStringSchema.optional(), + manifest_path: NonEmptyStringSchema.optional(), + patched_version: NonEmptyStringSchema.optional(), + ghsa_id: NonEmptyStringSchema.optional(), + cve_id: NonEmptyStringSchema.optional(), + cwe_ids: z.array(NonEmptyStringSchema).optional(), + cvss_score: z.union([z.string(), z.number().finite()]).optional(), + dependabot_html_url: z.string().url().optional(), + first_detected_at: IsoTimestampSchema, + fixed_at: IsoTimestampSchema.nullable(), + sla_due_at: IsoTimestampSchema.nullable(), + canonical_finding_id: UuidSchema.optional(), + remediation_attempt_id: UuidSchema.optional(), + session_id: NonEmptyStringSchema.optional(), + notification_id: UuidSchema.optional(), + }) + .strict() + .superRefine((value, ctx) => { + validateSafeAuditJson(value, ctx); + }); + +export type SecurityFindingAuditSnapshot = z.infer; + +export type SecurityFindingAuditSnapshotSource = { + id: string; + source: string; + source_id: string; + repo_full_name: string; + title: string; + severity: string; + status: string; + package_name?: string | null; + package_ecosystem?: string | null; + manifest_path?: string | null; + patched_version?: string | null; + ghsa_id?: string | null; + cve_id?: string | null; + cwe_ids?: string[] | null; + cvss_score?: string | number | null; + dependabot_html_url?: string | null; + first_detected_at: string | Date; + fixed_at: string | Date | null; + sla_due_at: string | Date | null; + session_id?: string | null; +}; + +export type SecurityFindingAuditSnapshotExtras = { + canonical_finding_id?: string | null; + remediation_attempt_id?: string | null; + session_id?: string | null; + notification_id?: string | null; +}; + +export type SecurityFindingAuditEventFinding = SecurityFindingAuditSnapshotSource & { + owned_by_user_id: string | null; + owned_by_organization_id: string | null; +}; + +export const SecurityFindingAuditEventSchema = z.object({ + owner: SecurityFindingAuditOwnerSchema, + finding: z.object({ + id: UuidSchema, + owned_by_user_id: z.string().min(1).nullable(), + owned_by_organization_id: UuidSchema.nullable(), + }), + actor: SecurityFindingAuditActorSchema, + action: ReportableSecurityFindingAuditActionSchema, + occurredAt: IsoTimestampSchema, + sourceOccurredAt: IsoTimestampSchema.nullable().optional(), + eventKey: NonEmptyStringSchema, + sourceContext: z.nativeEnum(SecurityFindingAuditSourceContext), + snapshot: SecurityFindingAuditSnapshotSchema, + beforeState: SanitizedJsonObjectSchema.optional(), + afterState: SanitizedJsonObjectSchema.optional(), + metadata: SanitizedJsonObjectSchema.optional(), +}); + +export type SecurityFindingAuditEventInput = { + owner: SecurityFindingAuditOwner; + finding: SecurityFindingAuditEventFinding; + actor: SecurityFindingAuditActor; + action: SecurityAuditLogAction; + occurredAt: string | Date; + sourceOccurredAt?: string | Date | null; + eventKey: string; + sourceContext: SecurityFindingAuditSourceContext; + snapshot?: SecurityFindingAuditSnapshot; + snapshotExtras?: SecurityFindingAuditSnapshotExtras; + beforeState?: Record; + afterState?: Record; + metadata?: Record; +}; + +export type NewSecurityFindingAuditLogValues = typeof security_audit_log.$inferInsert; + +type SecurityFindingAuditInsertReturning = { + returning(selection: { id: typeof security_audit_log.id }): Promise>; +}; + +type SecurityFindingAuditInsertConflict = { + onConflictDoNothing(): SecurityFindingAuditInsertReturning; +}; + +type SecurityFindingAuditInsertValues = { + values(values: NewSecurityFindingAuditLogValues): SecurityFindingAuditInsertConflict; +}; + +export type SecurityFindingAuditWriterDb = { + insert(table: typeof security_audit_log): SecurityFindingAuditInsertValues; +}; + +export function buildSecurityFindingAuditSnapshot( + finding: SecurityFindingAuditSnapshotSource, + extras: SecurityFindingAuditSnapshotExtras = {} +): SecurityFindingAuditSnapshot { + const snapshot = { + finding_id: finding.id, + source: finding.source, + source_id: finding.source_id, + repo_full_name: finding.repo_full_name, + title: finding.title, + severity: finding.severity, + status: finding.status, + first_detected_at: normalizeAuditTimestamp(finding.first_detected_at), + fixed_at: normalizeAuditTimestamp(finding.fixed_at) ?? null, + sla_due_at: normalizeAuditTimestamp(finding.sla_due_at) ?? null, + ...pickPresent({ + package_name: finding.package_name, + package_ecosystem: finding.package_ecosystem, + manifest_path: finding.manifest_path, + patched_version: finding.patched_version, + ghsa_id: finding.ghsa_id, + cve_id: finding.cve_id, + cwe_ids: finding.cwe_ids, + cvss_score: finding.cvss_score, + dependabot_html_url: finding.dependabot_html_url, + canonical_finding_id: extras.canonical_finding_id, + remediation_attempt_id: extras.remediation_attempt_id, + session_id: extras.session_id ?? finding.session_id, + notification_id: extras.notification_id, + }), + }; + + return SecurityFindingAuditSnapshotSchema.parse(snapshot); +} + +export function buildSecurityFindingAuditLogValues( + input: SecurityFindingAuditEventInput +): NewSecurityFindingAuditLogValues { + const snapshot = + input.snapshot ?? buildSecurityFindingAuditSnapshot(input.finding, input.snapshotExtras); + const occurredAt = normalizeAuditTimestamp(input.occurredAt); + if (!occurredAt) throw new Error('occurredAt is required'); + + const sourceOccurredAt = normalizeAuditTimestamp(input.sourceOccurredAt); + const parsed = SecurityFindingAuditEventSchema.parse({ + owner: input.owner, + finding: { + id: input.finding.id, + owned_by_user_id: input.finding.owned_by_user_id, + owned_by_organization_id: input.finding.owned_by_organization_id, + }, + actor: input.actor, + action: input.action, + occurredAt, + sourceOccurredAt, + eventKey: input.eventKey, + sourceContext: input.sourceContext, + snapshot, + beforeState: input.beforeState, + afterState: input.afterState, + metadata: input.metadata, + }); + + assertFindingOwnerMatchesEventOwner(parsed.owner, input.finding); + if (parsed.snapshot.finding_id !== input.finding.id) { + throw new Error('Security Finding audit snapshot finding_id must match finding_id'); + } + + const ownerColumns = getAuditOwnerColumns(parsed.owner); + const humanActor = parsed.actor.type === SecurityAuditLogActorType.System ? null : parsed.actor; + + return { + ...ownerColumns, + actor_id: humanActor?.id ?? null, + actor_email: humanActor?.email ?? null, + actor_name: humanActor?.name ?? null, + actor_type: parsed.actor.type, + action: parsed.action, + resource_type: 'security_finding', + resource_id: input.finding.id, + before_state: parsed.beforeState, + after_state: parsed.afterState, + metadata: parsed.metadata, + finding_id: input.finding.id, + occurred_at: parsed.occurredAt, + source_occurred_at: parsed.sourceOccurredAt ?? null, + event_key: parsed.eventKey, + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + finding_snapshot: parsed.snapshot, + source_context: parsed.sourceContext, + }; +} + +export async function insertSecurityFindingAuditEvent( + db: SecurityFindingAuditWriterDb, + input: SecurityFindingAuditEventInput +): Promise<{ inserted: boolean; id: string | null }> { + const values = buildSecurityFindingAuditLogValues(input); + const inserted = await db + .insert(security_audit_log) + .values(values) + .onConflictDoNothing() + .returning({ id: security_audit_log.id }); + + return { inserted: inserted.length > 0, id: inserted[0]?.id ?? null }; +} + +export function deriveSecurityFindingAuditEventKey(parts: readonly string[]): string { + if (parts.length === 0) throw new Error('Security Finding audit event key requires parts'); + return [ + SECURITY_FINDING_AUDIT_EVENT_KEY_PREFIX, + ...parts.map(part => encodeURIComponent(part)), + ].join(':'); +} + +function assertFindingOwnerMatchesEventOwner( + owner: SecurityFindingAuditOwner, + finding: Pick< + SecurityFindingAuditEventFinding, + 'owned_by_user_id' | 'owned_by_organization_id' | 'id' + > +): void { + if (owner.type === 'user') { + if (finding.owned_by_user_id !== owner.userId || finding.owned_by_organization_id !== null) { + throw new Error('Security Finding audit event owner does not match finding owner'); + } + return; + } + + if ( + finding.owned_by_organization_id !== owner.organizationId || + finding.owned_by_user_id !== null + ) { + throw new Error('Security Finding audit event owner does not match finding owner'); + } +} + +function getAuditOwnerColumns(owner: SecurityFindingAuditOwner): { + owned_by_user_id: string | null; + owned_by_organization_id: string | null; +} { + if (owner.type === 'user') { + return { owned_by_user_id: owner.userId, owned_by_organization_id: null }; + } + return { owned_by_user_id: null, owned_by_organization_id: owner.organizationId }; +} + +function normalizeAuditTimestamp(value: string | Date | null | undefined): string | undefined { + if (value === null || value === undefined) return undefined; + const date = value instanceof Date ? value : new Date(value); + if (Number.isNaN(date.getTime())) throw new Error(`Invalid audit timestamp: ${String(value)}`); + return date.toISOString(); +} + +function pickPresent>(values: T): Partial { + const entries = Object.entries(values).filter(([, value]) => { + if (value === null || value === undefined) return false; + if (Array.isArray(value) && value.length === 0) return false; + return value !== ''; + }); + return Object.fromEntries(entries) as Partial; +} + +const DISALLOWED_AUDIT_JSON_KEY_PATTERN = + /(^|_)(actor|recipient|email|prompt|rawmarkdown|raw_markdown|transcript|assistant|provider_response|authorization|auth_header|cookie|token|secret|password|credential|headers|raw_error)(_|$)/i; +const EMAIL_VALUE_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i; + +function validateSafeAuditJson( + value: unknown, + ctx: z.RefinementCtx, + path: (string | number)[] = [] +) { + if (Array.isArray(value)) { + for (const [index, item] of value.entries()) { + validateSafeAuditJson(item, ctx, [...path, index]); + } + return; + } + + if (value && typeof value === 'object') { + for (const [key, child] of Object.entries(value)) { + const childPath = [...path, key]; + if (DISALLOWED_AUDIT_JSON_KEY_PATTERN.test(key)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Audit JSON field is not allowed: ${childPath.join('.')}`, + path: childPath, + }); + } + validateSafeAuditJson(child, ctx, childPath); + } + return; + } + + if (typeof value === 'string' && EMAIL_VALUE_PATTERN.test(value)) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: `Audit JSON value appears to contain an email address: ${path.join('.')}`, + path, + }); + } +} + +export type SecurityFindingAuditLogEntry = SecurityAuditLogEntry; diff --git a/packages/worker-utils/src/security-remediation-policy.test.ts b/packages/worker-utils/src/security-remediation-policy.test.ts index a48df43bf9..2be12640a8 100644 --- a/packages/worker-utils/src/security-remediation-policy.test.ts +++ b/packages/worker-utils/src/security-remediation-policy.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest'; import { + buildSecurityFindingAnalysisInput, computeSecurityRemediationAnalysisFingerprint, decideSecurityRemediationEligibility, type SecurityRemediationConfig, @@ -14,20 +15,36 @@ const baseConfig: SecurityRemediationConfig = { auto_remediation_enabled_at: '2026-01-01T00:00:00.000Z', }; -const baseFinding: SecurityRemediationFinding = { +const baseFindingData = { id: 'finding-1', + source: 'dependabot', + source_id: '42', status: 'open', severity: 'high', repo_full_name: 'kilo/repo', package_name: 'lodash', package_ecosystem: 'npm', + dependency_scope: 'runtime', + cve_id: 'CVE-2021-23337', + ghsa_id: 'GHSA-35jh-r3h4-6jhm', + cwe_ids: ['CWE-1321'], + cvss_score: '7.2', + title: 'Command Injection in lodash', + description: 'Versions before 4.17.21 are vulnerable.', + vulnerable_version_range: '< 4.17.21', patched_version: '4.17.21', manifest_path: 'package.json', + raw_data: { updated_at: '2026-01-02T00:00:00.000Z' }, +}; + +const baseFinding: SecurityRemediationFinding = { + ...baseFindingData, last_synced_at: '2026-01-02T00:00:00.000Z', analysis_status: 'completed', analysis_completed_at: '2026-01-02T00:05:00.000Z', analysis: { analyzedAt: '2026-01-02T00:05:00.000Z', + findingDataSnapshot: buildSecurityFindingAnalysisInput(baseFindingData), sandboxAnalysis: { isExploitable: true, suggestedAction: 'open_pr', @@ -41,6 +58,20 @@ const baseFinding: SecurityRemediationFinding = { }, }; +function withCurrentFindingDataSnapshot( + finding: SecurityRemediationFinding +): SecurityRemediationFinding { + return { + ...finding, + analysis: finding.analysis + ? { + ...finding.analysis, + findingDataSnapshot: buildSecurityFindingAnalysisInput(finding), + } + : null, + }; +} + const emptyBlockState = { hasActiveAttempt: false, hasPrOpened: false, @@ -69,10 +100,94 @@ describe('decideSecurityRemediationEligibility', () => { ); }); - it('rejects stale analysis after later sync', () => { + it('keeps analysis fresh when a later sync observes unchanged finding data', () => { + const decision = decideSecurityRemediationEligibility({ + finding: { + ...baseFinding, + last_synced_at: '2026-01-03T00:00:00.000Z', + }, + config: baseConfig, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/repo'], + origin: 'manual', + blockState: emptyBlockState, + }); + + expect(decision).toMatchObject({ eligible: true, reason: 'eligible' }); + }); + + it('uses the database completion timestamp when legacy analysis JSON omits timestamps', () => { + const decision = decideSecurityRemediationEligibility({ + finding: { + ...baseFinding, + last_synced_at: '2026-01-03T00:00:00.000Z', + analysis: { + ...baseFinding.analysis, + analyzedAt: null, + sandboxAnalysis: { + ...baseFinding.analysis?.sandboxAnalysis, + isExploitable: true, + suggestedAction: 'open_pr', + analysisAt: null, + }, + }, + }, + config: baseConfig, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/repo'], + origin: 'manual', + blockState: emptyBlockState, + }); + + expect(decision).toMatchObject({ + eligible: true, + reason: 'eligible', + analysisCompletedAt: '2026-01-02T00:05:00.000Z', + }); + }); + + it('rejects analysis when material finding data changed', () => { + const decision = decideSecurityRemediationEligibility({ + finding: { + ...baseFinding, + patched_version: '4.17.22', + last_synced_at: '2026-01-03T00:01:00.000Z', + }, + config: baseConfig, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/repo'], + origin: 'manual', + blockState: emptyBlockState, + }); + + expect(decision).toMatchObject({ eligible: false, reason: 'stale_analysis' }); + }); + + it('rejects analysis when the source revision changed during analysis', () => { const decision = decideSecurityRemediationEligibility({ finding: { ...baseFinding, + raw_data: { updated_at: '2026-01-02T00:03:00.000Z' }, + }, + config: baseConfig, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/repo'], + origin: 'manual', + blockState: emptyBlockState, + }); + + expect(decision).toMatchObject({ eligible: false, reason: 'stale_analysis' }); + }); + + it('uses source revision time for legacy analysis without a finding snapshot', () => { + const legacyAnalysis = { + ...baseFinding.analysis, + findingDataSnapshot: undefined, + }; + const unchangedDecision = decideSecurityRemediationEligibility({ + finding: { + ...baseFinding, + analysis: legacyAnalysis, last_synced_at: '2026-01-03T00:00:00.000Z', }, config: baseConfig, @@ -81,12 +196,45 @@ describe('decideSecurityRemediationEligibility', () => { origin: 'manual', blockState: emptyBlockState, }); + const changedDecision = decideSecurityRemediationEligibility({ + finding: { + ...baseFinding, + analysis: legacyAnalysis, + raw_data: { updated_at: '2026-01-03T00:00:00.000Z' }, + }, + config: baseConfig, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/repo'], + origin: 'manual', + blockState: emptyBlockState, + }); + + expect(unchangedDecision).toMatchObject({ eligible: true, reason: 'eligible' }); + expect(changedDecision).toMatchObject({ eligible: false, reason: 'stale_analysis' }); + }); + + it('fails closed when legacy analysis has no source revision', () => { + const decision = decideSecurityRemediationEligibility({ + finding: { + ...baseFinding, + raw_data: null, + analysis: { + ...baseFinding.analysis, + findingDataSnapshot: undefined, + }, + }, + config: baseConfig, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/repo'], + origin: 'manual', + blockState: emptyBlockState, + }); expect(decision).toMatchObject({ eligible: false, reason: 'stale_analysis' }); }); it('allows manual remediation after review when exploitability is unknown but patch path is concrete', () => { - const manualReviewFinding: SecurityRemediationFinding = { + const manualReviewFinding = withCurrentFindingDataSnapshot({ ...baseFinding, package_name: 'handlebars', patched_version: '4.7.7', @@ -101,7 +249,7 @@ describe('decideSecurityRemediationEligibility', () => { usageLocations: [], }, }, - }; + }); const decision = decideSecurityRemediationEligibility({ finding: manualReviewFinding, @@ -140,7 +288,7 @@ describe('decideSecurityRemediationEligibility', () => { it('rejects manual review override without a concrete fix path', () => { const decision = decideSecurityRemediationEligibility({ - finding: { + finding: withCurrentFindingDataSnapshot({ ...baseFinding, patched_version: null, manifest_path: null, @@ -154,7 +302,7 @@ describe('decideSecurityRemediationEligibility', () => { usageLocations: [], }, }, - }, + }), config: baseConfig, isAgentEnabled: true, repoFullNamesInScope: ['kilo/repo'], @@ -201,7 +349,7 @@ describe('decideSecurityRemediationEligibility', () => { it('gates automatic policy by threshold and enablement time', () => { const belowThreshold = decideSecurityRemediationEligibility({ - finding: { ...baseFinding, severity: 'medium' }, + finding: withCurrentFindingDataSnapshot({ ...baseFinding, severity: 'medium' }), config: baseConfig, isAgentEnabled: true, repoFullNamesInScope: ['kilo/repo'], diff --git a/packages/worker-utils/src/security-remediation-policy.ts b/packages/worker-utils/src/security-remediation-policy.ts index 8b7fece973..42652bfc5f 100644 --- a/packages/worker-utils/src/security-remediation-policy.ts +++ b/packages/worker-utils/src/security-remediation-policy.ts @@ -1,3 +1,5 @@ +import type { SecurityFindingAnalysisInput } from '@kilocode/db/schema-types'; + export type SecurityRemediationOrigin = 'auto_policy' | 'bulk_existing' | 'manual'; export type SecurityRemediationMinSeverity = 'critical' | 'high' | 'medium' | 'all'; export type SecurityRemediationSeverityRank = 0 | 1 | 2 | 3; @@ -23,6 +25,7 @@ export type SecurityRemediationSandboxAnalysis = { export type SecurityRemediationAnalysis = { sandboxAnalysis?: SecurityRemediationSandboxAnalysis; + findingDataSnapshot?: SecurityFindingAnalysisInput; analyzedAt?: string | null; modelUsed?: string | null; analysisModel?: string | null; @@ -30,19 +33,33 @@ export type SecurityRemediationAnalysis = { correlationId?: string | null; }; -export type SecurityRemediationFinding = { - id: string; +export type SecurityFindingAnalysisInputSource = { + source: string; + source_id: string; status: string; severity: string | null; repo_full_name: string; package_name: string; package_ecosystem: string; - patched_version?: string | null; - manifest_path?: string | null; - last_synced_at?: string | null; - analysis_status?: string | null; - analysis_completed_at?: string | null; - analysis?: SecurityRemediationAnalysis | null; + dependency_scope: string | null; + cve_id: string | null; + ghsa_id: string | null; + cwe_ids: string[] | null; + cvss_score: string | number | null; + title: string; + description: string | null; + vulnerable_version_range: string | null; + patched_version: string | null; + manifest_path: string | null; + raw_data: { updated_at?: string | null } | null; +}; + +export type SecurityRemediationFinding = SecurityFindingAnalysisInputSource & { + id: string; + last_synced_at: string | null; + analysis_status: string | null; + analysis_completed_at: string | null; + analysis: SecurityRemediationAnalysis | null; }; export type SecurityRemediationBlockState = { @@ -52,28 +69,40 @@ export type SecurityRemediationBlockState = { hasRetryableTerminalForFinding?: boolean; }; -export type SecurityRemediationCapabilityReason = - | 'eligible' - | 'finding_not_open' - | 'repo_not_in_scope' - | 'analysis_required' - | 'sandbox_analysis_required' - | 'stale_analysis' - | 'not_exploitable' - | 'exploitability_unknown' - | 'manual_review_required' - | 'monitor_required' - | 'triage_only' - | 'action_not_concrete' - | 'remediation_active' - | 'pr_already_opened' - | 'duplicate_analysis_result' - | 'retry_not_allowed' - | 'security_agent_disabled' - | 'auto_remediation_disabled' - | 'include_existing_disabled' - | 'below_threshold' - | 'before_enablement'; +export const SECURITY_REMEDIATION_REJECTION_REASONS = [ + 'finding_not_open', + 'repo_not_in_scope', + 'analysis_required', + 'sandbox_analysis_required', + 'stale_analysis', + 'not_exploitable', + 'exploitability_unknown', + 'manual_review_required', + 'monitor_required', + 'triage_only', + 'action_not_concrete', + 'remediation_active', + 'pr_already_opened', + 'duplicate_analysis_result', + 'retry_not_allowed', + 'security_agent_disabled', + 'auto_remediation_disabled', + 'include_existing_disabled', + 'below_threshold', + 'before_enablement', +] as const; + +export type SecurityRemediationRejectionReason = + (typeof SECURITY_REMEDIATION_REJECTION_REASONS)[number]; +export type SecurityRemediationCapabilityReason = 'eligible' | SecurityRemediationRejectionReason; + +export const SECURITY_REMEDIATION_ADMISSION_REJECTION_REASONS = [ + ...SECURITY_REMEDIATION_REJECTION_REASONS, + 'finding_not_found', +] as const; + +export type SecurityRemediationAdmissionRejectionReason = + (typeof SECURITY_REMEDIATION_ADMISSION_REJECTION_REASONS)[number]; export type SecurityRemediationEligibilityParams = { finding: SecurityRemediationFinding; @@ -133,6 +162,71 @@ function normalizeTimestamp(value: string | null | undefined): string | null { return Number.isFinite(time) ? new Date(time).toISOString() : null; } +export function buildSecurityFindingAnalysisInput( + finding: SecurityFindingAnalysisInputSource +): SecurityFindingAnalysisInput { + return { + schemaVersion: 1, + source: finding.source, + sourceId: finding.source_id, + sourceUpdatedAt: normalizeTimestamp(finding.raw_data?.updated_at), + repoFullName: finding.repo_full_name, + status: finding.status, + severity: finding.severity, + packageName: finding.package_name, + packageEcosystem: finding.package_ecosystem, + dependencyScope: finding.dependency_scope, + cveId: finding.cve_id, + ghsaId: finding.ghsa_id, + cweIds: [...new Set(finding.cwe_ids ?? [])].sort(), + cvssScore: finding.cvss_score === null ? null : String(finding.cvss_score), + title: finding.title, + description: finding.description, + vulnerableVersionRange: finding.vulnerable_version_range, + patchedVersion: finding.patched_version, + manifestPath: finding.manifest_path, + }; +} + +function serializeSecurityFindingAnalysisInput( + input: SecurityFindingAnalysisInput | undefined +): string | null { + if (!input || input.schemaVersion !== 1 || !Array.isArray(input.cweIds)) return null; + return JSON.stringify([ + input.schemaVersion, + input.source, + input.sourceId, + input.sourceUpdatedAt, + input.repoFullName, + input.status, + input.severity, + input.packageName, + input.packageEcosystem, + input.dependencyScope, + input.cveId, + input.ghsaId, + input.cweIds, + input.cvssScore, + input.title, + input.description, + input.vulnerableVersionRange, + input.patchedVersion, + input.manifestPath, + ]); +} + +function securityFindingAnalysisInputMatches( + analyzedInput: SecurityFindingAnalysisInput, + finding: SecurityFindingAnalysisInputSource +): boolean { + const serializedAnalyzedInput = serializeSecurityFindingAnalysisInput(analyzedInput); + if (!serializedAnalyzedInput) return false; + return ( + serializedAnalyzedInput === + serializeSecurityFindingAnalysisInput(buildSecurityFindingAnalysisInput(finding)) + ); +} + export function getSecurityRemediationAnalysisCompletedAt( finding: SecurityRemediationFinding ): string | null { @@ -207,9 +301,14 @@ function hasConcreteRemediationPath(finding: SecurityRemediationFinding): boolea } function isAnalysisFresh(finding: SecurityRemediationFinding, completedAt: string): boolean { - const lastSyncedAt = normalizeTimestamp(finding.last_synced_at); - if (!lastSyncedAt) return true; - return Date.parse(completedAt) >= Date.parse(lastSyncedAt); + const analyzedInput = finding.analysis?.findingDataSnapshot; + if (analyzedInput !== undefined) { + return securityFindingAnalysisInputMatches(analyzedInput, finding); + } + + const sourceUpdatedAt = normalizeTimestamp(finding.raw_data?.updated_at); + if (!sourceUpdatedAt) return false; + return Date.parse(completedAt) >= Date.parse(sourceUpdatedAt); } function isRepoInScope(params: { diff --git a/services/security-auto-analysis/src/analysis-start-lifecycle.integration.test.ts b/services/security-auto-analysis/src/analysis-start-lifecycle.integration.test.ts index 6839fc2b59..a82255c998 100644 --- a/services/security-auto-analysis/src/analysis-start-lifecycle.integration.test.ts +++ b/services/security-auto-analysis/src/analysis-start-lifecycle.integration.test.ts @@ -1,7 +1,20 @@ import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest'; import { randomUUID } from 'crypto'; import { createDrizzleClient } from '@kilocode/db/client'; -import { kilocode_users, security_analysis_queue, security_findings } from '@kilocode/db/schema'; +import { + kilocode_users, + security_analysis_queue, + security_audit_log, + security_findings, +} from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + deriveSecurityFindingAuditEventKey, + SECURITY_FINDING_AUDIT_SCHEMA_VERSION, +} from '@kilocode/worker-utils/security-finding-audit'; import { eq, inArray } from 'drizzle-orm'; import { transitionAnalysisCallbackLifecycle, @@ -33,6 +46,7 @@ describe('analysis start lifecycle durable transitions', () => { await client.db .delete(security_analysis_queue) .where(inArray(security_analysis_queue.finding_id, ids)); + await client.db.delete(security_audit_log).where(inArray(security_audit_log.finding_id, ids)); await client.db.delete(security_findings).where(inArray(security_findings.id, ids)); }); @@ -83,6 +97,34 @@ describe('analysis start lifecycle durable transitions', () => { .from(security_analysis_queue) .where(eq(security_analysis_queue.finding_id, findingId)); expect(queueRows).toEqual([{ status: 'completed' }]); + + const auditRows = await getFindingAuditRows(findingId); + expect(auditRows).toHaveLength(1); + expect(auditRows[0]).toMatchObject({ + action: SecurityAuditLogAction.FindingAnalysisCompleted, + finding_id: findingId, + resource_type: 'security_finding', + resource_id: findingId, + event_key: deriveSecurityFindingAuditEventKey([ + `user:${testUserId}`, + findingId, + SecurityAuditLogAction.FindingAnalysisCompleted, + 'manual-triage-claim', + ]), + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + source_context: SecurityFindingAuditSourceContext.AnalysisWorker, + before_state: { analysis_status: 'pending' }, + after_state: { + analysis_status: 'completed', + suggested_action: 'manual_review', + confidence: 'high', + }, + finding_snapshot: expect.objectContaining({ + finding_id: findingId, + repo_full_name: 'kilo/manual-triage-complete', + }), + }); + expect(auditRows[0]?.occurred_at).toEqual(expect.any(String)); }); it('terminalizes completed callbacks with queue and finding state settled together', async () => { @@ -93,7 +135,7 @@ describe('analysis start lifecycle durable transitions', () => { jobId: 'callback-completed-job', queueStatus: 'running', }); - const analysis = createAnalysis('callback-completed'); + const analysis = createSandboxAnalysis('callback-completed', 'succeeded'); await expect( transitionAnalysisCallbackLifecycle(client.db as never, { @@ -128,6 +170,70 @@ describe('analysis start lifecycle durable transitions', () => { .from(security_analysis_queue) .where(eq(security_analysis_queue.finding_id, findingId)); expect(queueRows).toEqual([{ status: 'completed', failureCode: null }]); + + const auditRows = await getFindingAuditRows(findingId); + expect(auditRows).toHaveLength(1); + expect(auditRows[0]).toMatchObject({ + action: SecurityAuditLogAction.FindingAnalysisCompleted, + finding_id: findingId, + resource_id: findingId, + event_key: deriveSecurityFindingAuditEventKey([ + `user:${testUserId}`, + findingId, + SecurityAuditLogAction.FindingAnalysisCompleted, + 'callback-completed-claim', + ]), + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + source_context: SecurityFindingAuditSourceContext.AnalysisWorker, + before_state: { analysis_status: 'running' }, + after_state: { + analysis_status: 'completed', + structured_extraction_status: 'succeeded', + suggested_action: 'dismiss', + is_exploitable: false, + }, + finding_snapshot: expect.objectContaining({ + finding_id: findingId, + repo_full_name: 'kilo/callback-completed', + status: 'open', + }), + }); + expect(auditRows[0]?.occurred_at).toEqual(expect.any(String)); + }); + + it('records structured extraction failures without claiming an exploitability result', async () => { + const findingId = await insertFinding('callback-extraction-failed', 'running'); + await insertQueueClaim({ + findingId, + claimToken: 'callback-extraction-failed-claim', + jobId: 'callback-extraction-failed-job', + queueStatus: 'running', + }); + const analysis = createSandboxAnalysis('callback-extraction-failed', 'failed'); + + await expect( + transitionAnalysisCallbackLifecycle(client.db as never, { + findingId, + attemptToken: 'callback-extraction-failed-claim', + outcome: { + type: 'completed', + analysis, + }, + }) + ).resolves.toEqual({ status: 'completed' }); + + const auditRows = await getFindingAuditRows(findingId); + expect(auditRows).toHaveLength(1); + expect(auditRows[0]).toMatchObject({ + action: SecurityAuditLogAction.FindingAnalysisCompleted, + after_state: { + analysis_status: 'completed', + structured_extraction_status: 'failed', + suggested_action: 'manual_review', + }, + }); + expect(auditRows[0]?.after_state).not.toHaveProperty('is_exploitable'); + expect(auditRows[0]?.after_state).not.toHaveProperty('confidence'); }); it('terminalizes failed callbacks with queue and finding failure state settled together', async () => { @@ -171,6 +277,28 @@ describe('analysis start lifecycle durable transitions', () => { expect(queueRows).toEqual([ { status: 'failed', failureCode: 'UPSTREAM_5XX', lastError: 'upstream 503' }, ]); + + const auditRows = await getFindingAuditRows(findingId); + expect(auditRows).toHaveLength(1); + expect(auditRows[0]).toMatchObject({ + action: SecurityAuditLogAction.FindingAnalysisFailed, + finding_id: findingId, + resource_id: findingId, + event_key: deriveSecurityFindingAuditEventKey([ + `user:${testUserId}`, + findingId, + SecurityAuditLogAction.FindingAnalysisFailed, + 'callback-failed-claim', + ]), + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + source_context: SecurityFindingAuditSourceContext.AnalysisWorker, + before_state: { analysis_status: 'running' }, + after_state: { analysis_status: 'failed' }, + metadata: { failure_code: 'UPSTREAM_5XX' }, + finding_snapshot: expect.objectContaining({ finding_id: findingId }), + }); + expect(auditRows[0]?.occurred_at).toEqual(expect.any(String)); + expect(JSON.stringify(auditRows[0])).not.toContain('upstream 503'); }); it('clears superseded callback capacity while settling its queue row', async () => { @@ -342,6 +470,52 @@ describe('analysis start lifecycle durable transitions', () => { expect(queueRows).toEqual([{ status: 'running', attemptCount: 1 }]); }); + it('records terminal analysis start failures with current audit evidence', async () => { + const findingId = await insertFinding('scheduled-terminal-failure', 'running'); + const queueRowId = await insertQueueClaim({ + findingId, + claimToken: 'scheduled-terminal-failure-claim', + jobId: 'scheduled-terminal-failure-job', + queueStatus: 'running', + }); + + await expect( + transitionAnalysisStartLifecycle(client.db as never, { + claim: { + source: 'scheduled', + findingId, + queueRowId, + claimToken: 'scheduled-terminal-failure-claim', + }, + outcome: { + type: 'start-failed', + errorMessage: 'permanent permission failure', + queueStatus: 'failed', + failureCode: 'PERMISSION_DENIED_PERMANENT', + incrementAttempt: true, + nextRetryAt: null, + }, + }) + ).resolves.toEqual({ transitioned: true }); + + const auditRows = await getFindingAuditRows(findingId); + expect(auditRows).toHaveLength(1); + expect(auditRows[0]).toMatchObject({ + action: SecurityAuditLogAction.FindingAnalysisFailed, + finding_id: findingId, + event_key: deriveSecurityFindingAuditEventKey([ + `user:${testUserId}`, + findingId, + SecurityAuditLogAction.FindingAnalysisFailed, + 'scheduled-terminal-failure-claim', + ]), + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + source_context: SecurityFindingAuditSourceContext.AnalysisWorker, + metadata: { failure_code: 'PERMISSION_DENIED_PERMANENT' }, + }); + expect(JSON.stringify(auditRows[0])).not.toContain('permanent permission failure'); + }); + it('requeues retryable scheduled start failures after running promotion without split state', async () => { const findingId = await insertFinding('scheduled-retryable-failure', 'running'); const queueRowId = await insertQueueClaim({ @@ -379,8 +553,8 @@ describe('analysis start lifecycle durable transitions', () => { .where(eq(security_findings.id, findingId)); expect(findingRows).toEqual([ { - analysisStatus: 'failed', - analysisError: 'prepareSession timed out', + analysisStatus: null, + analysisError: null, }, ]); @@ -403,9 +577,17 @@ describe('analysis start lifecycle durable transitions', () => { claimToken: null, }, ]); + expect(await getFindingAuditRows(findingId)).toEqual([]); }); }); +function getFindingAuditRows(findingId: string) { + return client.db + .select() + .from(security_audit_log) + .where(eq(security_audit_log.finding_id, findingId)); +} + async function insertFinding( suffix: string, analysisStatus: 'pending' | 'running' = 'pending' @@ -470,3 +652,36 @@ function createAnalysis(suffix: string): SecurityFindingAnalysis { correlationId: `correlation-${suffix}`, }; } + +function createSandboxAnalysis( + suffix: string, + extractionStatus: 'succeeded' | 'failed' +): SecurityFindingAnalysis { + const extractionFailed = extractionStatus === 'failed'; + return { + ...createAnalysis(suffix), + triage: { + needsSandboxAnalysis: true, + needsSandboxReasoning: `Sandbox required for ${suffix}`, + suggestedAction: 'analyze_codebase', + confidence: 'low', + triageAt: '2026-05-19T08:00:00.000Z', + }, + sandboxAnalysis: { + isExploitable: extractionFailed ? 'unknown' : false, + extractionStatus, + exploitabilityReasoning: extractionFailed + ? 'Extraction failed. Review raw analysis.' + : 'No reachable usage.', + usageLocations: [], + suggestedFix: extractionFailed ? 'Review raw analysis.' : 'Remove the unused dependency.', + suggestedAction: extractionFailed ? 'manual_review' : 'dismiss', + summary: extractionFailed + ? 'Analysis completed but structured extraction failed.' + : 'Dependency is not exploitable.', + rawMarkdown: '# Raw analysis', + analysisAt: '2026-05-19T08:01:00.000Z', + modelUsed: 'analysis/model', + }, + }; +} diff --git a/services/security-auto-analysis/src/analysis-start-lifecycle.ts b/services/security-auto-analysis/src/analysis-start-lifecycle.ts index a2538d6359..7818afd097 100644 --- a/services/security-auto-analysis/src/analysis-start-lifecycle.ts +++ b/services/security-auto-analysis/src/analysis-start-lifecycle.ts @@ -1,5 +1,20 @@ import type { WorkerDb } from '@kilocode/db/client'; -import { security_analysis_queue, security_findings } from '@kilocode/db/schema'; +import { + security_analysis_queue, + security_findings, + type SecurityFinding, +} from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditOwner, + type SecurityFindingAuditWriterDb, +} from '@kilocode/worker-utils/security-finding-audit'; import { and, eq, inArray, isNull, like, not, or, sql } from 'drizzle-orm'; import type { AutoAnalysisFailureCode, SecurityFindingAnalysis } from './types.js'; @@ -62,6 +77,104 @@ class AnalysisStartQueueTransitionRejected extends Error { } } +type TerminalAnalysisAuditOutcome = + | { type: 'completed'; analysis: SecurityFindingAnalysis } + | { type: 'failed'; failureCode: AutoAnalysisFailureCode }; + +function toAnalysisAuditOwner( + finding: Pick +): SecurityFindingAuditOwner { + if (finding.owned_by_organization_id) { + return { type: 'organization', organizationId: finding.owned_by_organization_id }; + } + if (finding.owned_by_user_id) { + return { type: 'user', userId: finding.owned_by_user_id }; + } + throw new Error('Security analysis finding has no audit owner'); +} + +function analysisAuditOwnerKey( + finding: Pick +): string { + if (finding.owned_by_organization_id) { + return `organization:${finding.owned_by_organization_id}`; + } + if (finding.owned_by_user_id) return `user:${finding.owned_by_user_id}`; + throw new Error('Security analysis finding has no audit owner'); +} + +async function insertTerminalAnalysisAuditEvent( + db: SecurityFindingAuditWriterDb, + params: { + previousAnalysisStatus: string | null; + finding: SecurityFinding; + attemptToken: string; + outcome: TerminalAnalysisAuditOutcome; + } +): Promise { + const { finding, outcome } = params; + const occurredAt = finding.analysis_completed_at; + if (!occurredAt) throw new Error('Terminal Security Finding analysis has no completion time'); + + const action = + outcome.type === 'completed' + ? SecurityAuditLogAction.FindingAnalysisCompleted + : SecurityAuditLogAction.FindingAnalysisFailed; + const analysis = outcome.type === 'completed' ? outcome.analysis : null; + const modelSlug = + analysis?.analysisModel ?? + analysis?.modelUsed ?? + analysis?.sandboxAnalysis?.modelUsed ?? + analysis?.triageModel; + const sandboxAnalysis = analysis?.sandboxAnalysis; + const suggestedAction = sandboxAnalysis?.suggestedAction ?? analysis?.triage?.suggestedAction; + + await insertSecurityFindingAuditEvent(db, { + owner: toAnalysisAuditOwner(finding), + finding, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + analysisAuditOwnerKey(finding), + finding.id, + action, + params.attemptToken, + ]), + sourceContext: SecurityFindingAuditSourceContext.AnalysisWorker, + beforeState: { analysis_status: params.previousAnalysisStatus ?? 'unknown' }, + afterState: + outcome.type === 'completed' + ? { + analysis_status: 'completed', + ...(suggestedAction ? { suggested_action: suggestedAction } : {}), + ...(!sandboxAnalysis && analysis?.triage?.confidence + ? { confidence: analysis.triage.confidence } + : {}), + ...(sandboxAnalysis?.extractionStatus + ? { structured_extraction_status: sandboxAnalysis.extractionStatus } + : {}), + ...(sandboxAnalysis?.isExploitable !== undefined && + sandboxAnalysis.extractionStatus !== 'failed' + ? { is_exploitable: sandboxAnalysis.isExploitable } + : {}), + } + : { analysis_status: 'failed' }, + metadata: + outcome.type === 'completed' + ? { + ...(analysis?.correlationId ? { correlation_id: analysis.correlationId } : {}), + ...(modelSlug ? { model_slug: modelSlug } : {}), + ...(analysis?.triageModel ? { triage_model_slug: analysis.triageModel } : {}), + ...(analysis?.analysisModel ? { analysis_model_slug: analysis.analysisModel } : {}), + ...(analysis?.triage?.needsSandboxAnalysis !== undefined + ? { needs_sandbox_analysis: analysis.triage.needsSandboxAnalysis } + : {}), + } + : { failure_code: outcome.failureCode }, + }); +} + export async function transitionAnalysisCallbackLifecycle( db: WorkerDb, params: { @@ -131,7 +244,14 @@ export async function transitionAnalysisCallbackLifecycle( return { status: 'superseded' }; } - const findingRows = await tx + const [previousFinding] = await tx + .select() + .from(security_findings) + .where(eq(security_findings.id, params.findingId)) + .for('update') + .limit(1); + + const [updatedFinding] = await tx .update(security_findings) .set( params.outcome.type === 'completed' @@ -158,9 +278,9 @@ export async function transitionAnalysisCallbackLifecycle( ) ) ) - .returning({ id: security_findings.id }); + .returning(); - if (findingRows.length === 0) { + if (!updatedFinding) { await tx .update(security_findings) .set({ @@ -201,6 +321,16 @@ export async function transitionAnalysisCallbackLifecycle( ) ); + await insertTerminalAnalysisAuditEvent(tx, { + previousAnalysisStatus: previousFinding?.analysis_status ?? null, + finding: updatedFinding, + attemptToken: params.attemptToken, + outcome: + params.outcome.type === 'completed' + ? params.outcome + : { type: 'failed', failureCode: params.outcome.failureCode }, + }); + return { status: params.outcome.type }; }); } @@ -214,8 +344,14 @@ export async function transitionAnalysisStartLifecycle( ): Promise<{ transitioned: boolean }> { try { return await db.transaction(async tx => { - const findingRows = await transitionFinding(tx, params.claim, params.outcome); - if (findingRows.length === 0) { + const [previousFinding] = await tx + .select() + .from(security_findings) + .where(eq(security_findings.id, params.claim.findingId)) + .for('update') + .limit(1); + const [updatedFinding] = await transitionFinding(tx, params.claim, params.outcome); + if (!updatedFinding) { return { transitioned: false }; } @@ -261,6 +397,25 @@ export async function transitionAnalysisStartLifecycle( throw new AnalysisStartQueueTransitionRejected(); } + if (params.outcome.type === 'triage-only-completed') { + await insertTerminalAnalysisAuditEvent(tx, { + previousAnalysisStatus: previousFinding?.analysis_status ?? null, + finding: updatedFinding, + attemptToken: params.claim.claimToken, + outcome: { type: 'completed', analysis: params.outcome.analysis }, + }); + } else if ( + params.outcome.type === 'start-failed' && + params.outcome.queueStatus === 'failed' + ) { + await insertTerminalAnalysisAuditEvent(tx, { + previousAnalysisStatus: previousFinding?.analysis_status ?? null, + finding: updatedFinding, + attemptToken: params.claim.claimToken, + outcome: { type: 'failed', failureCode: params.outcome.failureCode }, + }); + } + return { transitioned: true }; }); } catch (error) { @@ -308,20 +463,29 @@ function transitionFinding( updated_at: sql`now()`.mapWith(String), }) .where(and(eq(security_findings.id, claim.findingId), ignoredReasonGuard)) - .returning({ id: security_findings.id }); + .returning(); } if (outcome.type === 'start-failed') { return tx .update(security_findings) - .set({ - analysis_status: 'failed', - analysis_error: outcome.errorMessage, - analysis_completed_at: sql`now()`.mapWith(String), - updated_at: sql`now()`.mapWith(String), - }) + .set( + outcome.queueStatus === 'failed' + ? { + analysis_status: 'failed', + analysis_error: outcome.errorMessage, + analysis_completed_at: sql`now()`.mapWith(String), + updated_at: sql`now()`.mapWith(String), + } + : { + analysis_status: null, + analysis_error: null, + analysis_completed_at: null, + updated_at: sql`now()`.mapWith(String), + } + ) .where(and(eq(security_findings.id, claim.findingId), ignoredReasonGuard)) - .returning({ id: security_findings.id }); + .returning(); } return tx @@ -336,5 +500,5 @@ function transitionFinding( updated_at: sql`now()`.mapWith(String), }) .where(and(eq(security_findings.id, claim.findingId), ignoredReasonGuard)) - .returning({ id: security_findings.id }); + .returning(); } diff --git a/services/security-auto-analysis/src/auto-dismiss.test.ts b/services/security-auto-analysis/src/auto-dismiss.test.ts index d1b6de4fed..d32d682497 100644 --- a/services/security-auto-analysis/src/auto-dismiss.test.ts +++ b/services/security-auto-analysis/src/auto-dismiss.test.ts @@ -1,41 +1,175 @@ +import type { SecurityFinding } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { SECURITY_FINDING_AUDIT_SCHEMA_VERSION } from '@kilocode/worker-utils/security-finding-audit'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { maybeAutoDismissCompletedAnalysis } from './auto-dismiss.js'; +import type { SecurityFindingAnalysis } from './types.js'; + +const FINDING_ID = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; +const ORGANIZATION_ID = 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa'; +const INTEGRATION_ID = 'cccccccc-cccc-4ccc-8ccc-cccccccccccc'; + +function makeFinding(overrides: Partial = {}): SecurityFinding { + return { + id: FINDING_ID, + owned_by_organization_id: ORGANIZATION_ID, + owned_by_user_id: null, + platform_integration_id: INTEGRATION_ID, + repo_full_name: 'kilo/repo', + source: 'dependabot', + source_id: '42', + severity: 'high', + ghsa_id: 'GHSA-1234-5678', + cve_id: 'CVE-2026-1234', + package_name: 'example-package', + package_ecosystem: 'npm', + vulnerable_version_range: '<2.0.0', + patched_version: '2.0.0', + manifest_path: 'package.json', + title: 'Example vulnerability', + description: 'Example description', + status: 'open', + ignored_reason: null, + ignored_by: null, + fixed_at: null, + sla_due_at: null, + dependabot_html_url: 'https://github.com/kilo/repo/security/dependabot/42', + cwe_ids: ['CWE-79'], + cvss_score: '7.5', + dependency_scope: 'runtime', + session_id: 'agent-session', + cli_session_id: 'kilo-session', + analysis_status: 'completed', + analysis_started_at: '2026-05-18T09:55:00.000Z', + analysis_completed_at: '2026-05-18T10:00:00.000Z', + analysis_error: null, + analysis: null, + raw_data: null, + first_detected_at: '2026-05-17T10:00:00.000Z', + last_synced_at: '2026-05-18T09:00:00.000Z', + created_at: '2026-05-17T10:00:00.000Z', + updated_at: '2026-05-18T10:00:00.000Z', + ...overrides, + }; +} + +function sandboxAnalysis( + correlationId: string, + overrides: Partial> = {} +): SecurityFindingAnalysis { + return { + analyzedAt: '2026-05-18T10:00:00.000Z', + correlationId, + sandboxAnalysis: { + isExploitable: false, + exploitabilityReasoning: + 'The dependency is installed, but the vulnerable template function is never called.', + usageLocations: ['package.json:17'], + suggestedFix: 'Upgrade', + suggestedAction: 'dismiss', + summary: 'The vulnerable code path is not reachable.', + rawMarkdown: '# Not exploitable', + analysisAt: '2026-05-18T10:00:00.000Z', + ...overrides, + }, + }; +} + +function createDbHarness( + options: { + finding?: SecurityFinding; + config?: Record; + installationId?: string; + auditError?: Error; + } = {} +) { + const state = { + finding: options.finding ?? makeFinding(), + auditRows: [] as Array>, + committedUpdates: [] as Array>, + transactionCalls: 0, + }; + let rootSelectCount = 0; + + const db = { + select: () => ({ + from: () => ({ + where: () => ({ + limit: async () => { + rootSelectCount += 1; + return rootSelectCount === 1 + ? [ + { + config: options.config ?? { + auto_dismiss_enabled: true, + auto_dismiss_confidence_threshold: 'high', + }, + }, + ] + : [{ installationId: options.installationId ?? 'installation-123' }]; + }, + }), + }), + }), + transaction: async (callback: (tx: unknown) => Promise) => { + state.transactionCalls += 1; + let stagedFinding = state.finding; + const stagedAuditRows: Array> = []; + const stagedUpdates: Array> = []; + const tx = { + select: () => ({ + from: () => ({ + where: () => ({ + for: () => ({ limit: async () => [stagedFinding] }), + }), + }), + }), + update: () => ({ + set: (values: Record) => ({ + where: () => ({ + returning: async () => { + const updatedFinding = { ...stagedFinding, ...values } as SecurityFinding; + stagedFinding = updatedFinding; + stagedUpdates.push(values); + return [updatedFinding]; + }, + }), + }), + }), + insert: () => ({ + values: (values: Record) => ({ + onConflictDoNothing: () => ({ + returning: async () => { + if (options.auditError) throw options.auditError; + stagedAuditRows.push(values); + return [{ id: 'audit-row-1' }]; + }, + }), + }), + }), + }; + + const result = await callback(tx); + state.finding = stagedFinding; + state.auditRows.push(...stagedAuditRows); + state.committedUpdates.push(...stagedUpdates); + return result; + }, + }; + + return { db, state }; +} describe('maybeAutoDismissCompletedAnalysis', () => { afterEach(() => { vi.unstubAllGlobals(); }); - it('preserves Worker auto-dismiss state, Dependabot writeback, and audit trail', async () => { - let selectCount = 0; - const updates: unknown[] = []; - const auditRows: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => { - selectCount += 1; - return selectCount === 1 - ? [{ config: { auto_dismiss_enabled: true } }] - : [{ installationId: 'installation-123' }]; - }, - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), - }; + it('commits sandbox dismissal with canonical current audit evidence before writeback', async () => { + const { db, state } = createDbHarness(); const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); vi.stubGlobal('fetch', fetchSpy); @@ -44,75 +178,55 @@ describe('maybeAutoDismissCompletedAnalysis', () => { env: { GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, } as unknown as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - source: 'dependabot', - source_id: '42', - platform_integration_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - repo_full_name: 'kilo/repo', - } as never, - analysis: { - analyzedAt: '2026-05-18T10:00:00.000Z', - correlationId: 'correlation-123', - sandboxAnalysis: { - isExploitable: false, - exploitabilityReasoning: 'Dependency is not reachable.', - usageLocations: [], - suggestedFix: 'Upgrade', - suggestedAction: 'dismiss', - summary: 'Not exploitable', - rawMarkdown: '# Not exploitable', - analysisAt: '2026-05-18T10:00:00.000Z', - }, - }, + findingId: FINDING_ID, + finding: makeFinding() as never, + analysis: sandboxAnalysis('correlation-123'), }); - expect(updates[0]).toMatchObject({ + expect(state.finding).toMatchObject({ status: 'ignored', ignored_reason: 'not_used', ignored_by: 'auto-sandbox', }); - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(auditRows[0]).toMatchObject({ - action: 'security.finding.auto_dismissed', - resource_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - metadata: { correlationId: 'correlation-123', dismissSource: 'sandbox' }, + expect(state.auditRows).toHaveLength(1); + expect(state.auditRows[0]).toMatchObject({ + action: SecurityAuditLogAction.FindingAutoDismissed, + finding_id: FINDING_ID, + resource_type: 'security_finding', + resource_id: FINDING_ID, + occurred_at: expect.any(String), + event_key: expect.stringContaining('correlation-123'), + schema_version: SECURITY_FINDING_AUDIT_SCHEMA_VERSION, + source_context: SecurityFindingAuditSourceContext.AnalysisWorker, + before_state: { status: 'open' }, + after_state: { status: 'ignored', reason_code: 'not_used' }, + metadata: { + reason_code: 'not_used', + trigger: 'auto_dismiss_policy', + dismiss_source: 'sandbox', + correlation_id: 'correlation-123', + }, + finding_snapshot: expect.objectContaining({ + finding_id: FINDING_ID, + status: 'ignored', + repo_full_name: 'kilo/repo', + }), }); + expect(fetchSpy).toHaveBeenCalledWith( + expect.stringContaining('/dependabot/alerts/42'), + expect.objectContaining({ + body: JSON.stringify({ + state: 'dismissed', + dismissed_reason: 'not_used', + dismissed_comment: + '[Kilo Code auto-dismiss] The dependency is installed, but the vulnerable template function is never called.', + }), + }) + ); }); - it('keeps automatic dismissal state and audit when upstream writeback fails', async () => { - let selectCount = 0; - const updates: unknown[] = []; - const auditRows: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => { - selectCount += 1; - return selectCount === 1 - ? [{ config: { auto_dismiss_enabled: true } }] - : [{ installationId: 'installation-123' }]; - }, - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), - }; + it('keeps canonical local dismissal when upstream writeback fails', async () => { + const { db, state } = createDbHarness(); const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 503 })); vi.stubGlobal('fetch', fetchSpy); @@ -121,75 +235,22 @@ describe('maybeAutoDismissCompletedAnalysis', () => { env: { GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, } as unknown as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - source: 'dependabot', - source_id: '42', - platform_integration_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - repo_full_name: 'kilo/repo', - } as never, - analysis: { - analyzedAt: '2026-05-18T10:00:00.000Z', - correlationId: 'correlation-writeback-503', - sandboxAnalysis: { - isExploitable: false, - exploitabilityReasoning: 'Dependency is not reachable.', - usageLocations: [], - suggestedFix: 'Upgrade', - suggestedAction: 'dismiss', - summary: 'Not exploitable', - rawMarkdown: '# Not exploitable', - analysisAt: '2026-05-18T10:00:00.000Z', - }, - }, + findingId: FINDING_ID, + finding: makeFinding() as never, + analysis: sandboxAnalysis('correlation-writeback-503'), }); - expect(updates[0]).toMatchObject({ - status: 'ignored', - ignored_reason: 'not_used', - ignored_by: 'auto-sandbox', - }); + expect(state.finding.status).toBe('ignored'); + expect(state.auditRows).toHaveLength(1); expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(auditRows[0]).toMatchObject({ - action: 'security.finding.auto_dismissed', - resource_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - metadata: { correlationId: 'correlation-writeback-503', dismissSource: 'sandbox' }, - }); }); - it('keeps automatic dismissal durable while skipping partially numeric Dependabot alert IDs', async () => { - let selectCount = 0; - const updates: unknown[] = []; - const auditRows: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => { - selectCount += 1; - return selectCount === 1 - ? [{ config: { auto_dismiss_enabled: true } }] - : [{ installationId: 'installation-123' }]; - }, - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), - }; + it.each([ + ['partially numeric alert ID', { source_id: '42junk' }], + ['malformed repository name', { repo_full_name: 'kilo/repo/extra' }], + ])('keeps local dismissal while skipping %s', async (_label, findingOverrides) => { + const finding = makeFinding(findingOverrides); + const { db, state } = createDbHarness({ finding }); const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); vi.stubGlobal('fetch', fetchSpy); @@ -198,75 +259,19 @@ describe('maybeAutoDismissCompletedAnalysis', () => { env: { GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, } as unknown as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - source: 'dependabot', - source_id: '42junk', - platform_integration_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - repo_full_name: 'kilo/repo', - } as never, - analysis: { - analyzedAt: '2026-05-18T10:00:00.000Z', - correlationId: 'correlation-partial-source', - sandboxAnalysis: { - isExploitable: false, - exploitabilityReasoning: 'Dependency is not reachable.', - usageLocations: [], - suggestedFix: 'Upgrade', - suggestedAction: 'dismiss', - summary: 'Not exploitable', - rawMarkdown: '# Not exploitable', - analysisAt: '2026-05-18T10:00:00.000Z', - }, - }, + findingId: FINDING_ID, + finding: finding as never, + analysis: sandboxAnalysis('correlation-invalid-target'), }); - expect(updates[0]).toMatchObject({ - status: 'ignored', - ignored_reason: 'not_used', - ignored_by: 'auto-sandbox', - }); + expect(state.finding.status).toBe('ignored'); + expect(state.auditRows).toHaveLength(1); expect(fetchSpy).not.toHaveBeenCalled(); - expect(auditRows[0]).toMatchObject({ - action: 'security.finding.auto_dismissed', - resource_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - metadata: { correlationId: 'correlation-partial-source', dismissSource: 'sandbox' }, - }); }); - it('keeps automatic dismissal durable while skipping malformed Dependabot repo names', async () => { - let selectCount = 0; - const updates: unknown[] = []; - const auditRows: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => { - selectCount += 1; - return selectCount === 1 - ? [{ config: { auto_dismiss_enabled: true } }] - : [{ installationId: 'installation-123' }]; - }, - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), - }; + it('does not re-dismiss findings that are already ignored', async () => { + const finding = makeFinding({ status: 'ignored' }); + const { db, state } = createDbHarness({ finding }); const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); vi.stubGlobal('fetch', fetchSpy); @@ -275,176 +280,108 @@ describe('maybeAutoDismissCompletedAnalysis', () => { env: { GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, } as unknown as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - source: 'dependabot', - source_id: '42', - platform_integration_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - repo_full_name: 'kilo/repo/extra', - } as never, - analysis: { - analyzedAt: '2026-05-18T10:00:00.000Z', - correlationId: 'correlation-invalid-repo', - sandboxAnalysis: { - isExploitable: false, - exploitabilityReasoning: 'Dependency is not reachable.', - usageLocations: [], - suggestedFix: 'Upgrade', - suggestedAction: 'dismiss', - summary: 'Not exploitable', - rawMarkdown: '# Not exploitable', - analysisAt: '2026-05-18T10:00:00.000Z', - }, - }, + findingId: FINDING_ID, + finding: finding as never, + analysis: sandboxAnalysis('correlation-already-ignored'), }); - expect(updates[0]).toMatchObject({ - status: 'ignored', - ignored_reason: 'not_used', - ignored_by: 'auto-sandbox', - }); + expect(state.committedUpdates).toHaveLength(0); + expect(state.auditRows).toHaveLength(0); expect(fetchSpy).not.toHaveBeenCalled(); - expect(auditRows[0]).toMatchObject({ - action: 'security.finding.auto_dismissed', - resource_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - metadata: { correlationId: 'correlation-invalid-repo', dismissSource: 'sandbox' }, - }); }); - it('does not re-dismiss findings that are already ignored', async () => { - let selectCount = 0; - const updates: unknown[] = []; - const auditRows: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => { - selectCount += 1; - return selectCount === 1 - ? [{ config: { auto_dismiss_enabled: true } }] - : [{ installationId: 'installation-123' }]; - }, - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), - }; + it('records configured high-confidence triage dismissal as current evidence', async () => { + const { db, state } = createDbHarness(); const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); vi.stubGlobal('fetch', fetchSpy); + const analysis: SecurityFindingAnalysis = { + analyzedAt: '2026-05-18T10:00:00.000Z', + correlationId: 'correlation-456', + triage: { + needsSandboxAnalysis: false, + needsSandboxReasoning: 'No relevant runtime path.', + suggestedAction: 'dismiss', + confidence: 'high', + triageAt: '2026-05-18T09:59:00.000Z', + }, + }; await maybeAutoDismissCompletedAnalysis({ db: db as never, env: { GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, } as unknown as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - source: 'dependabot', - source_id: '42', - status: 'ignored', - platform_integration_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - repo_full_name: 'kilo/repo', - } as never, - analysis: { - analyzedAt: '2026-05-18T10:00:00.000Z', - correlationId: 'correlation-already-ignored', - sandboxAnalysis: { - isExploitable: false, - exploitabilityReasoning: 'Dependency is not reachable.', - usageLocations: [], - suggestedFix: 'Upgrade', - suggestedAction: 'dismiss', - summary: 'Not exploitable', - rawMarkdown: '# Not exploitable', - analysisAt: '2026-05-18T10:00:00.000Z', - }, - }, + findingId: FINDING_ID, + finding: makeFinding() as never, + analysis, }); - expect(updates).toHaveLength(0); - expect(fetchSpy).not.toHaveBeenCalled(); - expect(auditRows).toHaveLength(0); + expect(state.finding).toMatchObject({ status: 'ignored', ignored_by: 'auto-triage' }); + expect(state.auditRows[0]).toMatchObject({ + source_context: SecurityFindingAuditSourceContext.AnalysisWorker, + metadata: { + reason_code: 'not_used', + trigger: 'auto_dismiss_policy', + dismiss_source: 'triage', + confidence: 'high', + correlation_id: 'correlation-456', + }, + }); + expect(fetchSpy).toHaveBeenCalledTimes(1); }); - it('auto-dismisses high-confidence triage decisions at the configured threshold', async () => { - let selectCount = 0; - const updates: unknown[] = []; - const auditRows: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => { - selectCount += 1; - return selectCount === 1 - ? [ - { - config: { - auto_dismiss_enabled: true, - auto_dismiss_confidence_threshold: 'high', - }, - }, - ] - : [{ installationId: 'installation-123' }]; - }, - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), - }; - const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); - vi.stubGlobal('fetch', fetchSpy); + it.each([ + ['exploitable', true, 'open_pr'], + ['unknown exploitability', 'unknown', 'manual_review'], + ['inconsistent not-exploitable result', false, 'manual_review'], + ] as const)( + 'keeps findings open when authoritative sandbox result is %s', + async (_label, isExploitable, suggestedAction) => { + const { db, state } = createDbHarness(); + const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); + vi.stubGlobal('fetch', fetchSpy); + const analysis = sandboxAnalysis('correlation-authoritative-sandbox', { + isExploitable, + suggestedAction, + }); + analysis.triage = { + needsSandboxAnalysis: false, + needsSandboxReasoning: 'Earlier triage recommended dismissal.', + suggestedAction: 'dismiss', + confidence: 'high', + triageAt: '2026-05-18T09:59:00.000Z', + }; + + await maybeAutoDismissCompletedAnalysis({ + db: db as never, + env: { + GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, + } as unknown as CloudflareEnv, + findingId: FINDING_ID, + finding: makeFinding() as never, + analysis, + }); + + expect(state.transactionCalls).toBe(0); + expect(state.finding.status).toBe('open'); + expect(state.auditRows).toEqual([]); + expect(fetchSpy).not.toHaveBeenCalled(); + } + ); + + it('keeps triage findings open when triage says sandbox analysis is needed', async () => { + const { db, state } = createDbHarness(); await maybeAutoDismissCompletedAnalysis({ db: db as never, - env: { - GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, - } as unknown as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - source: 'dependabot', - source_id: '42', - platform_integration_id: 'cccccccc-cccc-4ccc-8ccc-cccccccccccc', - repo_full_name: 'kilo/repo', - } as never, + env: {} as CloudflareEnv, + findingId: FINDING_ID, + finding: makeFinding() as never, analysis: { analyzedAt: '2026-05-18T10:00:00.000Z', - correlationId: 'correlation-456', triage: { - needsSandboxAnalysis: false, - needsSandboxReasoning: 'No relevant runtime path.', + needsSandboxAnalysis: true, + needsSandboxReasoning: 'Codebase usage must be checked.', suggestedAction: 'dismiss', confidence: 'high', triageAt: '2026-05-18T09:59:00.000Z', @@ -452,54 +389,24 @@ describe('maybeAutoDismissCompletedAnalysis', () => { }, }); - expect(updates[0]).toMatchObject({ - status: 'ignored', - ignored_reason: 'not_used', - ignored_by: 'auto-triage', - }); - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(auditRows[0]).toMatchObject({ - metadata: { - correlationId: 'correlation-456', - dismissSource: 'triage', - confidence: 'high', - }, - }); + expect(state.transactionCalls).toBe(0); + expect(state.finding.status).toBe('open'); + expect(state.auditRows).toEqual([]); }); - it('keeps low-confidence triage findings open above their confidence threshold', async () => { - const updates: unknown[] = []; - const db = { - select: () => ({ - from: () => ({ - where: () => ({ - limit: async () => [ - { - config: { auto_dismiss_enabled: true, auto_dismiss_confidence_threshold: 'medium' }, - }, - ], - }), - }), - }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ values: async () => undefined }), - }; + it('keeps low-confidence triage findings open above configured threshold', async () => { + const { db, state } = createDbHarness({ + config: { + auto_dismiss_enabled: true, + auto_dismiss_confidence_threshold: 'medium', + }, + }); await maybeAutoDismissCompletedAnalysis({ db: db as never, env: {} as CloudflareEnv, - findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - finding: { - id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', - owned_by_user_id: null, - } as never, + findingId: FINDING_ID, + finding: makeFinding() as never, analysis: { analyzedAt: '2026-05-18T10:00:00.000Z', triage: { @@ -512,6 +419,63 @@ describe('maybeAutoDismissCompletedAnalysis', () => { }, }); - expect(updates).toHaveLength(0); + expect(state.transactionCalls).toBe(0); + expect(state.finding.status).toBe('open'); + expect(state.auditRows).toEqual([]); + }); + + it('rolls back local dismissal when canonical audit insertion fails', async () => { + const { db, state } = createDbHarness({ auditError: new Error('audit insert failed') }); + const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); + vi.stubGlobal('fetch', fetchSpy); + + await expect( + maybeAutoDismissCompletedAnalysis({ + db: db as never, + env: { + GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, + } as unknown as CloudflareEnv, + findingId: FINDING_ID, + finding: makeFinding() as never, + analysis: sandboxAnalysis('correlation-rollback'), + }) + ).rejects.toThrow('audit insert failed'); + + expect(state.finding.status).toBe('open'); + expect(state.auditRows).toEqual([]); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it('fails before dismissing when audit identity is missing from malformed analysis', async () => { + const { db, state } = createDbHarness(); + const fetchSpy = vi.fn().mockResolvedValue(new Response('', { status: 200 })); + vi.stubGlobal('fetch', fetchSpy); + + await expect( + maybeAutoDismissCompletedAnalysis({ + db: db as never, + env: { + GIT_TOKEN_SERVICE: { getToken: async () => 'github-token' }, + } as unknown as CloudflareEnv, + findingId: FINDING_ID, + finding: makeFinding() as never, + analysis: { + sandboxAnalysis: { + isExploitable: false, + exploitabilityReasoning: 'No reachable vulnerable code path.', + usageLocations: [], + suggestedFix: 'No fix required.', + suggestedAction: 'dismiss', + summary: 'Not exploitable.', + rawMarkdown: '# Not exploitable', + }, + } as unknown as SecurityFindingAnalysis, + }) + ).rejects.toThrow('Auto-dismiss audit event requires an analysis identity'); + + expect(state.transactionCalls).toBe(0); + expect(state.finding.status).toBe('open'); + expect(state.auditRows).toEqual([]); + expect(fetchSpy).not.toHaveBeenCalled(); }); }); diff --git a/services/security-auto-analysis/src/auto-dismiss.ts b/services/security-auto-analysis/src/auto-dismiss.ts index 4de99d0d55..7f4d3e2f1a 100644 --- a/services/security-auto-analysis/src/auto-dismiss.ts +++ b/services/security-auto-analysis/src/auto-dismiss.ts @@ -1,8 +1,21 @@ import type { WorkerDb } from '@kilocode/db/client'; -import { platform_integrations, security_audit_log, security_findings } from '@kilocode/db/schema'; -import { SecurityAuditLogAction } from '@kilocode/db/schema-types'; +import { + platform_integrations, + security_findings, + type SecurityFinding, +} from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; import { parseDependabotDismissalTarget } from '@kilocode/worker-utils/dependabot-dismissal-target'; -import { eq, sql } from 'drizzle-orm'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditOwner, +} from '@kilocode/worker-utils/security-finding-audit'; +import { and, eq } from 'drizzle-orm'; import { getSecurityAgentConfigForOwner, type SecurityFindingRecord } from './db/queries.js'; import { logger } from './logger.js'; import type { QueueOwner, SecurityFindingAnalysis } from './types.js'; @@ -17,6 +30,22 @@ function findingOwner(finding: SecurityFindingRecord): QueueOwner | null { return null; } +function toAuditOwner(owner: QueueOwner): SecurityFindingAuditOwner { + return owner.type === 'org' + ? { type: 'organization', organizationId: owner.id } + : { type: 'user', userId: owner.id }; +} + +function ownerAuditKeyPart(owner: QueueOwner): string { + return owner.type === 'org' ? `organization:${owner.id}` : `user:${owner.id}`; +} + +function ownerFindingCondition(owner: QueueOwner) { + return owner.type === 'org' + ? eq(security_findings.owned_by_organization_id, owner.id) + : eq(security_findings.owned_by_user_id, owner.id); +} + function meetsAutoDismissConfidenceThreshold( threshold: 'high' | 'medium' | 'low', confidence: 'high' | 'medium' | 'low' @@ -85,6 +114,80 @@ async function writeBackDependabotDismissal(params: { } } +async function dismissFindingWithAuditEvent(params: { + db: WorkerDb; + findingId: string; + owner: QueueOwner; + analysis: SecurityFindingAnalysis; + dismissSource: 'sandbox' | 'triage'; + confidence?: 'high' | 'medium' | 'low'; +}): Promise { + const occurredAt = new Date().toISOString(); + const analysisIdentity = + params.analysis.correlationId ?? + params.analysis.sandboxAnalysis?.analysisAt ?? + params.analysis.triage?.triageAt ?? + params.analysis.analyzedAt; + if (!analysisIdentity) { + throw new Error('Auto-dismiss audit event requires an analysis identity'); + } + + return params.db.transaction(async tx => { + const [finding] = await tx + .select() + .from(security_findings) + .where(and(eq(security_findings.id, params.findingId), ownerFindingCondition(params.owner))) + .for('update') + .limit(1); + if (!finding || finding.status !== 'open') return null; + + const [updatedFinding] = await tx + .update(security_findings) + .set({ + status: 'ignored', + ignored_reason: 'not_used', + ignored_by: `auto-${params.dismissSource}`, + updated_at: occurredAt, + }) + .where( + and( + eq(security_findings.id, params.findingId), + ownerFindingCondition(params.owner), + eq(security_findings.status, 'open') + ) + ) + .returning(); + if (!updatedFinding) return null; + + await insertSecurityFindingAuditEvent(tx, { + owner: toAuditOwner(params.owner), + finding: updatedFinding, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action: SecurityAuditLogAction.FindingAutoDismissed, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ownerAuditKeyPart(params.owner), + updatedFinding.id, + SecurityAuditLogAction.FindingAutoDismissed, + params.dismissSource, + analysisIdentity, + ]), + sourceContext: SecurityFindingAuditSourceContext.AnalysisWorker, + beforeState: { status: finding.status }, + afterState: { status: 'ignored', reason_code: 'not_used' }, + metadata: { + reason_code: 'not_used', + trigger: 'auto_dismiss_policy', + dismiss_source: params.dismissSource, + ...(params.confidence ? { confidence: params.confidence } : {}), + ...(params.analysis.correlationId ? { correlation_id: params.analysis.correlationId } : {}), + }, + }); + + return updatedFinding; + }); +} + export async function maybeAutoDismissCompletedAnalysis(params: { db: WorkerDb; env: CloudflareEnv; @@ -92,55 +195,37 @@ export async function maybeAutoDismissCompletedAnalysis(params: { finding: SecurityFindingRecord; analysis: SecurityFindingAnalysis; }): Promise { - if (params.finding.status === 'ignored') return; - const owner = findingOwner(params.finding); if (!owner) return; const config = await getSecurityAgentConfigForOwner(params.db, owner); if (!config.auto_dismiss_enabled) return; const sandbox = params.analysis.sandboxAnalysis; - if (sandbox?.isExploitable === false) { - await params.db - .update(security_findings) - .set({ - status: 'ignored', - ignored_reason: 'not_used', - ignored_by: 'auto-sandbox', - updated_at: sql`now()`.mapWith(String), - }) - .where(eq(security_findings.id, params.findingId)); + if (sandbox) { + if (sandbox.isExploitable !== false || sandbox.suggestedAction !== 'dismiss') return; + + const dismissedFinding = await dismissFindingWithAuditEvent({ + db: params.db, + findingId: params.findingId, + owner, + analysis: params.analysis, + dismissSource: 'sandbox', + }); + if (!dismissedFinding) return; await writeBackDependabotDismissal({ db: params.db, env: params.env, - finding: params.finding, + finding: dismissedFinding, comment: sandbox.exploitabilityReasoning, }); - - await params.db.insert(security_audit_log).values({ - owned_by_organization_id: params.finding.owned_by_organization_id, - owned_by_user_id: params.finding.owned_by_user_id, - actor_id: null, - actor_email: null, - actor_name: null, - action: SecurityAuditLogAction.FindingAutoDismissed, - resource_type: 'security_finding', - resource_id: params.findingId, - after_state: { status: 'ignored' }, - metadata: { - source: 'system', - trigger: 'auto_dismiss_policy', - dismissSource: 'sandbox', - correlationId: params.analysis.correlationId, - }, - }); return; } const triage = params.analysis.triage; if ( - triage?.suggestedAction !== 'dismiss' || + triage?.needsSandboxAnalysis !== false || + triage.suggestedAction !== 'dismiss' || !meetsAutoDismissConfidenceThreshold( config.auto_dismiss_confidence_threshold, triage.confidence @@ -149,39 +234,20 @@ export async function maybeAutoDismissCompletedAnalysis(params: { return; } - await params.db - .update(security_findings) - .set({ - status: 'ignored', - ignored_reason: 'not_used', - ignored_by: 'auto-triage', - updated_at: sql`now()`.mapWith(String), - }) - .where(eq(security_findings.id, params.findingId)); + const dismissedFinding = await dismissFindingWithAuditEvent({ + db: params.db, + findingId: params.findingId, + owner, + analysis: params.analysis, + dismissSource: 'triage', + confidence: triage.confidence, + }); + if (!dismissedFinding) return; await writeBackDependabotDismissal({ db: params.db, env: params.env, - finding: params.finding, + finding: dismissedFinding, comment: triage.needsSandboxReasoning, }); - - await params.db.insert(security_audit_log).values({ - owned_by_organization_id: params.finding.owned_by_organization_id, - owned_by_user_id: params.finding.owned_by_user_id, - actor_id: null, - actor_email: null, - actor_name: null, - action: SecurityAuditLogAction.FindingAutoDismissed, - resource_type: 'security_finding', - resource_id: params.findingId, - after_state: { status: 'ignored' }, - metadata: { - source: 'system', - trigger: 'auto_dismiss_policy', - dismissSource: 'triage', - confidence: triage.confidence, - correlationId: params.analysis.correlationId, - }, - }); } diff --git a/services/security-auto-analysis/src/callbacks.test.ts b/services/security-auto-analysis/src/callbacks.test.ts index 3f4ecd0a32..b76e061a4a 100644 --- a/services/security-auto-analysis/src/callbacks.test.ts +++ b/services/security-auto-analysis/src/callbacks.test.ts @@ -130,7 +130,6 @@ describe('finalizeCompletedAnalysisCallback', () => { it('persists extracted sandbox analysis and terminal queue status for completed callbacks', async () => { const updates: unknown[] = []; const executes: unknown[] = []; - const auditRows: unknown[] = []; const db = { select: () => ({ from: () => ({ @@ -171,11 +170,6 @@ describe('finalizeCompletedAnalysisCallback', () => { executes.push(statement); return { rows: [] }; }, - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), }; const autoDismissCalls: unknown[] = []; const analyticsCalls: unknown[] = []; @@ -195,6 +189,7 @@ describe('finalizeCompletedAnalysisCallback', () => { }, extractSandboxAnalysis: async ({ rawMarkdown }) => ({ isExploitable: false, + extractionStatus: 'succeeded', exploitabilityReasoning: 'No reachable usage', usageLocations: [], suggestedFix: 'Upgrade package', @@ -221,10 +216,10 @@ describe('finalizeCompletedAnalysisCallback', () => { type: 'completed', analysis: expect.objectContaining({ rawMarkdown: '# Completed analysis', + sandboxAnalysis: expect.objectContaining({ extractionStatus: 'succeeded' }), }), }), }); - expect(auditRows).toHaveLength(1); expect(autoDismissCalls).toHaveLength(1); expect(analyticsCalls).toHaveLength(1); }); diff --git a/services/security-auto-analysis/src/callbacks.ts b/services/security-auto-analysis/src/callbacks.ts index d52a1f644c..7f120ec62b 100644 --- a/services/security-auto-analysis/src/callbacks.ts +++ b/services/security-auto-analysis/src/callbacks.ts @@ -1,6 +1,4 @@ import { getWorkerDb, type WorkerDb } from '@kilocode/db/client'; -import { security_audit_log } from '@kilocode/db/schema'; -import { SecurityAuditLogAction } from '@kilocode/db/schema-types'; import { CloudAgentCallbackFailureSchema, type CloudAgentSafeFailure, @@ -299,24 +297,6 @@ export async function finalizeCompletedAnalysisCallback(params: { }); if (lifecycleTransition.status === 'superseded') return { status: 'superseded' }; if (lifecycleTransition.status === 'stale-attempt') return { status: 'stale-attempt' }; - await params.db.insert(security_audit_log).values({ - owned_by_organization_id: finding.owned_by_organization_id, - owned_by_user_id: finding.owned_by_user_id, - actor_id: null, - actor_email: null, - actor_name: null, - action: SecurityAuditLogAction.FindingAnalysisCompleted, - resource_type: 'security_finding', - resource_id: params.findingId, - metadata: { - source: 'system', - model: completedAnalysis.modelUsed, - triageModel: completedAnalysis.triageModel, - analysisModel: completedAnalysis.analysisModel, - correlationId: completedAnalysis.correlationId, - triggeredByUserId: completedAnalysis.triggeredByUserId, - }, - }); await params.maybeAutoDismissAnalysis?.({ findingId: params.findingId, analysis: completedAnalysis, diff --git a/services/security-auto-analysis/src/db/queries.integration.test.ts b/services/security-auto-analysis/src/db/queries.integration.test.ts index 9ac7a1ce0c..182edc1734 100644 --- a/services/security-auto-analysis/src/db/queries.integration.test.ts +++ b/services/security-auto-analysis/src/db/queries.integration.test.ts @@ -12,8 +12,10 @@ import { discoverDueOwners, ensureManualAnalysisQueueRow, getSecurityFindingById, + prepareActiveAnalysisRestart, reconcileStaleAnalysisQueueRows, } from './queries.js'; +import { transitionAnalysisCallbackLifecycle } from '../analysis-start-lifecycle.js'; const connectionString = process.env.POSTGRES_URL ?? 'postgres://postgres:postgres@localhost:5432/postgres'; @@ -50,6 +52,44 @@ describe('security analysis durable database invariants', () => { await client.pool.end(); }); + it('loads complete finding evidence for audit snapshots', async () => { + const findingId = await insertFinding('audit-snapshot-evidence'); + await client.db + .update(security_findings) + .set({ + cwe_ids: ['CWE-1321'], + cvss_score: '9.8', + dependabot_html_url: + 'https://github.com/kilo/audit-snapshot-evidence/security/dependabot/42', + first_detected_at: '2026-03-04T12:00:00.000Z', + fixed_at: null, + sla_due_at: '2026-03-19T12:00:00.000Z', + last_synced_at: '2026-03-06T12:00:00.000Z', + analysis_completed_at: '2026-03-05T12:00:00.000Z', + }) + .where(eq(security_findings.id, findingId)); + + const finding = await getSecurityFindingById(client.db as never, findingId); + expect(finding).toMatchObject({ + cwe_ids: ['CWE-1321'], + cvss_score: '9.8', + dependabot_html_url: 'https://github.com/kilo/audit-snapshot-evidence/security/dependabot/42', + fixed_at: null, + }); + expect(finding?.first_detected_at && new Date(finding.first_detected_at).toISOString()).toBe( + '2026-03-04T12:00:00.000Z' + ); + expect(finding?.sla_due_at && new Date(finding.sla_due_at).toISOString()).toBe( + '2026-03-19T12:00:00.000Z' + ); + expect(finding?.last_synced_at && new Date(finding.last_synced_at).toISOString()).toBe( + '2026-03-06T12:00:00.000Z' + ); + expect( + finding?.analysis_completed_at && new Date(finding.analysis_completed_at).toISOString() + ).toBe('2026-03-05T12:00:00.000Z'); + }); + it('enforces one manual queue row per finding against Postgres constraints', async () => { const findingId = await insertFinding('manual-unique'); const finding = await getSecurityFindingById(client.db as never, findingId); @@ -188,6 +228,189 @@ describe('security analysis durable database invariants', () => { ]); }); + it('atomically fences an active run and preserves triage for restart', async () => { + const findingId = await insertFinding('active-restart', 'running'); + const priorAnalysis = { + analyzedAt: '2026-05-18T07:55:00.000Z', + triage: { + needsSandboxAnalysis: true, + needsSandboxReasoning: 'Repository inspection required', + suggestedAction: 'analyze_codebase' as const, + confidence: 'high' as const, + triageAt: '2026-05-18T07:55:00.000Z', + }, + }; + await client.db + .update(security_findings) + .set({ + analysis: priorAnalysis, + analysis_error: 'prior transient error', + analysis_started_at: '2026-05-18T08:00:00.000Z', + analysis_completed_at: '2026-05-18T08:05:00.000Z', + session_id: 'agent-old-session', + cli_session_id: 'ses-old-session', + }) + .where(eq(security_findings.id, findingId)); + await client.db.insert(security_analysis_queue).values({ + finding_id: findingId, + owned_by_user_id: testUserId, + queue_status: 'running', + severity_rank: 1, + queued_at: '2026-05-18T08:00:00.000Z', + claimed_at: '2026-05-18T08:00:00.000Z', + claimed_by_job_id: 'old-job', + claim_token: 'old-attempt-token', + attempt_count: 3, + reopen_requeue_count: 2, + next_retry_at: '2026-05-18T09:00:00.000Z', + failure_code: 'NETWORK_TIMEOUT', + last_error_redacted: 'prior failure', + }); + + const commandId = randomUUID(); + const claimToken = `manual-restart:${commandId}`; + await expect( + prepareActiveAnalysisRestart(client.db as never, { + findingId, + owner: { type: 'user', id: testUserId }, + commandId, + }) + ).resolves.toEqual({ + status: 'ready', + claimToken, + oldCloudAgentSessionId: 'agent-old-session', + }); + + const queueRows = await client.db + .select({ + status: security_analysis_queue.queue_status, + claimToken: security_analysis_queue.claim_token, + claimedByJobId: security_analysis_queue.claimed_by_job_id, + attemptCount: security_analysis_queue.attempt_count, + reopenRequeueCount: security_analysis_queue.reopen_requeue_count, + nextRetryAt: security_analysis_queue.next_retry_at, + failureCode: security_analysis_queue.failure_code, + lastErrorRedacted: security_analysis_queue.last_error_redacted, + }) + .from(security_analysis_queue) + .where(eq(security_analysis_queue.finding_id, findingId)); + expect(queueRows).toEqual([ + { + status: 'pending', + claimToken, + claimedByJobId: claimToken, + attemptCount: 0, + reopenRequeueCount: 0, + nextRetryAt: null, + failureCode: null, + lastErrorRedacted: null, + }, + ]); + + const findingRows = await client.db + .select({ + analysisStatus: security_findings.analysis_status, + analysis: security_findings.analysis, + analysisError: security_findings.analysis_error, + analysisStartedAt: security_findings.analysis_started_at, + analysisCompletedAt: security_findings.analysis_completed_at, + sessionId: security_findings.session_id, + cliSessionId: security_findings.cli_session_id, + }) + .from(security_findings) + .where(eq(security_findings.id, findingId)); + expect(findingRows).toEqual([ + { + analysisStatus: null, + analysis: priorAnalysis, + analysisError: null, + analysisStartedAt: null, + analysisCompletedAt: null, + sessionId: null, + cliSessionId: null, + }, + ]); + + await expect( + transitionAnalysisCallbackLifecycle(client.db as never, { + findingId, + attemptToken: 'old-attempt-token', + outcome: { + type: 'failed', + errorMessage: 'late old callback', + failureCode: 'STATE_GUARD_REJECTED', + }, + }) + ).resolves.toEqual({ status: 'stale-attempt' }); + + await expect( + prepareActiveAnalysisRestart(client.db as never, { + findingId, + owner: { type: 'user', id: testUserId }, + commandId, + }) + ).resolves.toEqual({ + status: 'ready', + claimToken, + oldCloudAgentSessionId: null, + }); + }); + + it('no-ops when callback terminal state wins before active restart takeover', async () => { + const findingId = await insertFinding('active-restart-callback-won', 'completed'); + await client.db.insert(security_analysis_queue).values({ + finding_id: findingId, + owned_by_user_id: testUserId, + queue_status: 'completed', + severity_rank: 1, + queued_at: '2026-05-18T08:00:00.000Z', + claimed_at: '2026-05-18T08:00:00.000Z', + claimed_by_job_id: 'completed-job', + claim_token: 'completed-attempt-token', + }); + + await expect( + prepareActiveAnalysisRestart(client.db as never, { + findingId, + owner: { type: 'user', id: testUserId }, + commandId: randomUUID(), + }) + ).resolves.toEqual({ status: 'no-op' }); + + const queueRows = await client.db + .select({ + status: security_analysis_queue.queue_status, + claimToken: security_analysis_queue.claim_token, + }) + .from(security_analysis_queue) + .where(eq(security_analysis_queue.finding_id, findingId)); + expect(queueRows).toEqual([{ status: 'completed', claimToken: 'completed-attempt-token' }]); + }); + + it('treats same-command running redelivery as already launched', async () => { + const findingId = await insertFinding('active-restart-redelivery', 'running'); + const commandId = randomUUID(); + const claimToken = `manual-restart:${commandId}`; + await client.db.insert(security_analysis_queue).values({ + finding_id: findingId, + owned_by_user_id: testUserId, + queue_status: 'running', + severity_rank: 1, + queued_at: '2026-05-18T08:00:00.000Z', + claimed_at: '2026-05-18T08:00:00.000Z', + claimed_by_job_id: claimToken, + claim_token: claimToken, + }); + + await expect( + prepareActiveAnalysisRestart(client.db as never, { + findingId, + owner: { type: 'user', id: testUserId }, + commandId, + }) + ).resolves.toEqual({ status: 'launch-started' }); + }); + it('requeues stale pending rows and terminalizes stale running rows in real SQL', async () => { const pendingFindingId = await insertFinding('stale-pending'); const runningFindingId = await insertFinding('stale-running', 'running'); diff --git a/services/security-auto-analysis/src/db/queries.ts b/services/security-auto-analysis/src/db/queries.ts index 66c055ba3e..990b9f624e 100644 --- a/services/security-auto-analysis/src/db/queries.ts +++ b/services/security-auto-analysis/src/db/queries.ts @@ -27,9 +27,15 @@ export type ClaimedQueueRow = { export type ActorUser = { id: string; + email?: string; + name?: string; api_token_pepper: string | null; }; +export type AuthoritativeActorUser = ActorUser & { + is_admin: boolean; +}; + type ClaimRowsForOwnerResult = { rows: ClaimedQueueRow[]; config: SecurityAgentConfig; @@ -421,6 +427,110 @@ export async function ensureManualAnalysisQueueRow( return rows.length > 0; } +export type ActiveAnalysisRestartPreparation = + | { + status: 'ready'; + claimToken: string; + oldCloudAgentSessionId: string | null; + } + | { status: 'launch-started' | 'no-op' }; + +export async function prepareActiveAnalysisRestart( + db: WorkerDb, + params: { findingId: string; owner: QueueOwner; commandId: string } +): Promise { + const claimToken = `manual-restart:${params.commandId}`; + const jobId = `manual-restart:${params.commandId}`; + + return db.transaction(async tx => { + const queueRows = await tx + .select({ + id: security_analysis_queue.id, + status: security_analysis_queue.queue_status, + claimToken: security_analysis_queue.claim_token, + ownedByOrganizationId: security_analysis_queue.owned_by_organization_id, + ownedByUserId: security_analysis_queue.owned_by_user_id, + }) + .from(security_analysis_queue) + .where(eq(security_analysis_queue.finding_id, params.findingId)) + .for('update') + .limit(1); + const queue = queueRows[0]; + if (!queue) return { status: 'no-op' }; + + const findingRows = await tx + .select({ + id: security_findings.id, + status: security_findings.status, + analysisStatus: security_findings.analysis_status, + cloudAgentSessionId: security_findings.session_id, + ownedByOrganizationId: security_findings.owned_by_organization_id, + ownedByUserId: security_findings.owned_by_user_id, + }) + .from(security_findings) + .where(eq(security_findings.id, params.findingId)) + .for('update') + .limit(1); + const finding = findingRows[0]; + if (!finding || finding.status !== 'open') return { status: 'no-op' }; + + const expectedOrganizationId = params.owner.type === 'org' ? params.owner.id : null; + const expectedUserId = params.owner.type === 'user' ? params.owner.id : null; + const ownerMatches = + queue.ownedByOrganizationId === expectedOrganizationId && + queue.ownedByUserId === expectedUserId && + finding.ownedByOrganizationId === expectedOrganizationId && + finding.ownedByUserId === expectedUserId; + if (!ownerMatches) return { status: 'no-op' }; + + if (queue.claimToken === claimToken) { + if (queue.status === 'running' || finding.analysisStatus === 'running') { + return { status: 'launch-started' }; + } + if ( + queue.status !== 'pending' || + (finding.analysisStatus !== null && finding.analysisStatus !== 'pending') + ) { + return { status: 'no-op' }; + } + } else if (queue.status !== 'running' || finding.analysisStatus !== 'running') { + return { status: 'no-op' }; + } + + const oldCloudAgentSessionId = finding.cloudAgentSessionId; + await tx + .update(security_analysis_queue) + .set({ + queue_status: 'pending', + queued_at: sql`now()`.mapWith(String), + claimed_at: sql`now()`.mapWith(String), + claimed_by_job_id: jobId, + claim_token: claimToken, + attempt_count: 0, + reopen_requeue_count: 0, + next_retry_at: null, + failure_code: null, + last_error_redacted: null, + updated_at: sql`now()`.mapWith(String), + }) + .where(eq(security_analysis_queue.id, queue.id)); + await tx + .update(security_findings) + .set({ + analysis_status: null, + analysis_error: null, + analysis_started_at: null, + analysis_completed_at: null, + session_id: null, + cli_session_id: null, + updated_at: sql`now()`.mapWith(String), + }) + .where(eq(security_findings.id, finding.id)); + + return { status: 'ready', claimToken, oldCloudAgentSessionId }; + }); +} + export async function transitionManualAnalysisQueueFromStart( db: WorkerDb, params: { @@ -447,9 +557,15 @@ export async function transitionManualAnalysisQueueFromStart( export async function getAnalysisActorById( db: WorkerDb, userId: string -): Promise { +): Promise { const rows = await db - .select({ id: kilocode_users.id, api_token_pepper: kilocode_users.api_token_pepper }) + .select({ + id: kilocode_users.id, + email: kilocode_users.google_user_email, + name: kilocode_users.google_user_name, + api_token_pepper: kilocode_users.api_token_pepper, + is_admin: kilocode_users.is_admin, + }) .from(kilocode_users) .where(and(eq(kilocode_users.id, userId), isNull(kilocode_users.blocked_reason))) .limit(1); @@ -464,6 +580,8 @@ export async function resolveAutoAnalysisActor( const rows = await db .select({ id: kilocode_users.id, + email: kilocode_users.google_user_email, + name: kilocode_users.google_user_name, api_token_pepper: kilocode_users.api_token_pepper, }) .from(kilocode_users) @@ -582,15 +700,23 @@ export async function getSecurityFindingById(db: WorkerDb, findingId: string) { dependency_scope: security_findings.dependency_scope, cve_id: security_findings.cve_id, ghsa_id: security_findings.ghsa_id, + cwe_ids: security_findings.cwe_ids, + cvss_score: security_findings.cvss_score, + dependabot_html_url: security_findings.dependabot_html_url, title: security_findings.title, description: security_findings.description, vulnerable_version_range: security_findings.vulnerable_version_range, patched_version: security_findings.patched_version, manifest_path: security_findings.manifest_path, + first_detected_at: security_findings.first_detected_at, + fixed_at: security_findings.fixed_at, + sla_due_at: security_findings.sla_due_at, raw_data: security_findings.raw_data, + last_synced_at: security_findings.last_synced_at, analysis_status: security_findings.analysis_status, analysis: security_findings.analysis, analysis_started_at: security_findings.analysis_started_at, + analysis_completed_at: security_findings.analysis_completed_at, session_id: security_findings.session_id, cli_session_id: security_findings.cli_session_id, ignored_reason: security_findings.ignored_reason, diff --git a/services/security-auto-analysis/src/extraction.test.ts b/services/security-auto-analysis/src/extraction.test.ts new file mode 100644 index 0000000000..8216338bf6 --- /dev/null +++ b/services/security-auto-analysis/src/extraction.test.ts @@ -0,0 +1,194 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import type { SecurityFindingRecord } from './db/queries.js'; +import { extractSandboxAnalysis } from './extraction.js'; + +const finding = { + id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + package_name: 'example-package', + package_ecosystem: 'npm', + severity: 'high', + dependency_scope: 'runtime', + cve_id: 'CVE-2026-1234', + ghsa_id: 'GHSA-1234-5678', + title: 'Example vulnerability', + vulnerable_version_range: '<2.0.0', + patched_version: '2.0.0', +} as SecurityFindingRecord; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('extractSandboxAnalysis', () => { + it('uses automatic tool selection for reasoning-model compatibility', async () => { + const fetchSpy = vi.fn().mockResolvedValue( + Response.json({ + choices: [ + { + message: { + tool_calls: [ + { + type: 'function', + function: { + name: 'submit_analysis_extraction', + arguments: JSON.stringify({ + isExploitable: true, + exploitabilityReasoning: 'Vulnerable package is reachable at runtime.', + usageLocations: ['src/server.ts'], + suggestedFix: 'Upgrade example-package to 2.0.0.', + suggestedAction: 'open_pr', + summary: 'Runtime path reaches vulnerable package.', + }), + }, + }, + ], + }, + }, + ], + }) + ); + vi.stubGlobal('fetch', fetchSpy); + + const result = await extractSandboxAnalysis({ + finding, + rawMarkdown: '# Analysis', + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + const requestBody = JSON.parse(String(fetchSpy.mock.calls[0]?.[1]?.body)); + expect(requestBody.tool_choice).toBe('auto'); + expect(requestBody.tools[0].function.name).toBe('submit_analysis_extraction'); + expect(result).toMatchObject({ + isExploitable: true, + extractionStatus: 'succeeded', + suggestedAction: 'open_pr', + modelUsed: 'kilo-auto/balanced', + }); + }); + + it('normalizes boolean strings returned in tool arguments', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + Response.json({ + choices: [ + { + message: { + tool_calls: [ + { + type: 'function', + function: { + name: 'submit_analysis_extraction', + arguments: JSON.stringify({ + isExploitable: 'false', + exploitabilityReasoning: 'Package is not used.', + usageLocations: [], + suggestedFix: 'Remove the package.', + suggestedAction: 'dismiss', + summary: 'Unused package is not exploitable.', + }), + }, + }, + ], + }, + }, + ], + }) + ) + ); + + const result = await extractSandboxAnalysis({ + finding, + rawMarkdown: '# Analysis', + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + expect(result).toMatchObject({ + isExploitable: false, + extractionStatus: 'succeeded', + suggestedAction: 'dismiss', + }); + }); + + it('accepts a content-only JSON result when the model does not call the tool', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + Response.json({ + choices: [ + { + message: { + content: `\`\`\`json +{"isExploitable":false,"exploitabilityReasoning":"Package is not used.","usageLocations":[],"suggestedFix":"Remove the package.","suggestedAction":"dismiss","summary":"Unused package is not exploitable."} +\`\`\``, + }, + }, + ], + }) + ) + ); + + const result = await extractSandboxAnalysis({ + finding, + rawMarkdown: '# Analysis', + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + expect(result).toMatchObject({ + isExploitable: false, + extractionStatus: 'succeeded', + suggestedAction: 'dismiss', + modelUsed: 'kilo-auto/balanced', + }); + }); + + it('keeps the conservative fallback for malformed content', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + Response.json({ + choices: [{ message: { content: 'The package appears safe.' } }], + }) + ) + ); + + const result = await extractSandboxAnalysis({ + finding, + rawMarkdown: '# Analysis', + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + expect(result).toMatchObject({ + isExploitable: 'unknown', + extractionStatus: 'failed', + suggestedAction: 'manual_review', + }); + }); + + it('marks fallback output when structured extraction request fails', async () => { + vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(null, { status: 503 }))); + + const result = await extractSandboxAnalysis({ + finding, + rawMarkdown: '# Analysis\n\nPackage is not used.', + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + expect(result).toMatchObject({ + isExploitable: 'unknown', + extractionStatus: 'failed', + suggestedAction: 'manual_review', + summary: 'Analysis completed but structured extraction failed. Review raw output.', + }); + }); +}); diff --git a/services/security-auto-analysis/src/extraction.ts b/services/security-auto-analysis/src/extraction.ts index 40dd11be0b..6ea79368ff 100644 --- a/services/security-auto-analysis/src/extraction.ts +++ b/services/security-auto-analysis/src/extraction.ts @@ -7,7 +7,16 @@ const EXTRACTION_SERVICE_VERSION = '5.0.0'; const EXTRACTION_SERVICE_USER_AGENT = `Kilo-Security-Extraction/${EXTRACTION_SERVICE_VERSION}`; const ExtractionResultSchema = z.object({ - isExploitable: z.union([z.boolean(), z.literal('unknown')]), + isExploitable: z.preprocess( + value => { + if (typeof value !== 'string') return value; + const normalized = value.trim().toLowerCase(); + if (normalized === 'true') return true; + if (normalized === 'false') return false; + return normalized; + }, + z.union([z.boolean(), z.literal('unknown')]) + ), exploitabilityReasoning: z.string(), usageLocations: z.array(z.string()), suggestedFix: z.string(), @@ -19,6 +28,7 @@ const ExtractionResponseSchema = z.object({ choices: z.array( z.object({ message: z.object({ + content: z.string().nullable().optional(), tool_calls: z .array( z.object({ @@ -48,12 +58,22 @@ function buildExtractionPrompt(finding: SecurityFindingRecord, rawMarkdown: stri ${rawMarkdown} -Please extract structured analysis and call submit_analysis_extraction.`; +Please extract structured analysis and call submit_analysis_extraction. If tool calls are unavailable, return only a JSON object matching the tool parameters, without markdown or prose.`; +} + +function extractJsonContent(content: string | null | undefined): string | null { + const trimmed = content?.trim(); + if (!trimmed) return null; + if (trimmed.startsWith('{') && trimmed.endsWith('}')) return trimmed; + + const fencedJson = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```$/i); + return fencedJson?.[1]?.trim() || null; } function fallbackExtraction(rawMarkdown: string, reason: string): SecurityFindingSandboxAnalysis { return { isExploitable: 'unknown', + extractionStatus: 'failed', exploitabilityReasoning: `Extraction failed: ${reason}. Please review the raw analysis.`, usageLocations: [], suggestedFix: 'Review the raw analysis for fix recommendations.', @@ -79,6 +99,7 @@ function parseExtractionResult( if (!result.success) return null; return { ...result.data, + extractionStatus: 'succeeded', rawMarkdown, analysisAt: new Date().toISOString(), modelUsed: model, @@ -148,7 +169,7 @@ export async function extractSandboxAnalysis(params: { }, }, ], - tool_choice: { type: 'function', function: { name: 'submit_analysis_extraction' } }, + tool_choice: 'auto', stream: false, }), signal: AbortSignal.timeout(45_000), @@ -161,15 +182,22 @@ export async function extractSandboxAnalysis(params: { return fallbackExtraction(params.rawMarkdown, `API error: ${response.status}`); } const parsedResponse = ExtractionResponseSchema.safeParse(await response.json()); - const toolCall = parsedResponse.success - ? parsedResponse.data.choices[0]?.message.tool_calls?.[0] - : undefined; - if (!toolCall || toolCall.function.name !== 'submit_analysis_extraction') { - return fallbackExtraction(params.rawMarkdown, 'Tool call missing'); + if (!parsedResponse.success) { + return fallbackExtraction(params.rawMarkdown, 'Invalid response shape'); } + + const message = parsedResponse.data.choices[0]?.message; + const toolCall = message?.tool_calls?.find( + candidate => candidate.function.name === 'submit_analysis_extraction' + ); + const structuredJson = toolCall?.function.arguments ?? extractJsonContent(message?.content); + if (!structuredJson) { + return fallbackExtraction(params.rawMarkdown, 'Structured response missing'); + } + return ( - parseExtractionResult(toolCall.function.arguments, params.rawMarkdown, params.model) ?? - fallbackExtraction(params.rawMarkdown, 'Tool call arguments invalid') + parseExtractionResult(structuredJson, params.rawMarkdown, params.model) ?? + fallbackExtraction(params.rawMarkdown, 'Structured response invalid') ); } catch (error) { logger.error('Extraction call threw', { diff --git a/services/security-auto-analysis/src/index.test.ts b/services/security-auto-analysis/src/index.test.ts index 9d99e6fc95..377e375164 100644 --- a/services/security-auto-analysis/src/index.test.ts +++ b/services/security-auto-analysis/src/index.test.ts @@ -5,6 +5,8 @@ import { import { getWorkerDb } from '@kilocode/db/client'; import { deriveCallbackToken } from '@kilocode/worker-utils'; import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type * as RemediationModule from './remediation.js'; +import { startManualRemediation } from './remediation.js'; import worker from './index.js'; vi.mock('@kilocode/db', () => ({ @@ -12,6 +14,10 @@ vi.mock('@kilocode/db', () => ({ markSecurityAgentCommandQueueAdmissionFailed: vi.fn(), })); vi.mock('@kilocode/db/client', () => ({ getWorkerDb: vi.fn() })); +vi.mock('./remediation.js', async importOriginal => ({ + ...(await importOriginal()), + startManualRemediation: vi.fn(), +})); beforeEach(() => { vi.clearAllMocks(); @@ -66,6 +72,83 @@ async function remediationCallbackTokenFor( }); } +function manualRemediationRequest(): Request { + return new Request('https://security-auto-analysis/internal/remediation/start', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-internal-api-key': 'worker-secret', + }, + body: JSON.stringify({ + schemaVersion: 1, + findingId: FINDING_ID, + owner: { userId: 'user-123' }, + actorUserId: 'user-123', + }), + }); +} + +describe('manual remediation ingress', () => { + it('returns HTTP 409 with analysis_required for a policy rejection', async () => { + vi.mocked(startManualRemediation).mockResolvedValue({ + admitted: false, + reason: 'analysis_required', + }); + + const response = await worker.fetch(manualRemediationRequest(), { + INTERNAL_API_SECRET: { get: async () => 'worker-secret' }, + } as CloudflareEnv); + + expect(response.status).toBe(409); + await expect(response.json()).resolves.toEqual({ + success: false, + accepted: false, + admitted: false, + reason: 'analysis_required', + }); + }); + + it('returns HTTP 404 when the finding no longer exists', async () => { + vi.mocked(startManualRemediation).mockResolvedValue({ + admitted: false, + reason: 'finding_not_found', + }); + + const response = await worker.fetch(manualRemediationRequest(), { + INTERNAL_API_SECRET: { get: async () => 'worker-secret' }, + } as CloudflareEnv); + + expect(response.status).toBe(404); + await expect(response.json()).resolves.toMatchObject({ + admitted: false, + reason: 'finding_not_found', + }); + }); + + it('returns HTTP 202 with attempt correlation after accepted admission', async () => { + vi.mocked(startManualRemediation).mockResolvedValue({ + admitted: true, + remediationId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', + attemptId: REMEDIATION_ATTEMPT_ID, + attemptNumber: 1, + }); + + const response = await worker.fetch(manualRemediationRequest(), { + INTERNAL_API_SECRET: { get: async () => 'worker-secret' }, + } as CloudflareEnv); + + expect(response.status).toBe(202); + await expect(response.json()).resolves.toEqual({ + success: true, + accepted: true, + admitted: true, + remediationId: 'dddddddd-dddd-4ddd-8ddd-dddddddddddd', + attemptId: REMEDIATION_ATTEMPT_ID, + attemptNumber: 1, + }); + }); +}); + describe('security analysis callback ingress', () => { it('rejects callback traffic without a scoped callback token', async () => { const response = await worker.fetch(callbackRequest(), { diff --git a/services/security-auto-analysis/src/index.ts b/services/security-auto-analysis/src/index.ts index 26d656c1b8..92a9fe31b7 100644 --- a/services/security-auto-analysis/src/index.ts +++ b/services/security-auto-analysis/src/index.ts @@ -149,7 +149,7 @@ async function handleFetch(request: Request, env: CloudflareEnv): Promise ({ from: () => ({ @@ -79,18 +86,39 @@ function createAutoDismissDb() { }), }), }), - update: () => ({ - set: (values: unknown) => ({ - where: async () => { - updates.push(values); - }, - }), - }), - insert: () => ({ - values: async (values: unknown) => { - auditRows.push(values); - }, - }), + transaction: async (callback: (tx: unknown) => Promise) => { + const tx = { + select: () => ({ + from: () => ({ + where: () => ({ + for: () => ({ limit: async () => [currentFinding] }), + }), + }), + }), + update: () => ({ + set: (values: Record) => ({ + where: () => ({ + returning: async () => { + updates.push(values); + currentFinding = { ...currentFinding, ...values }; + return [currentFinding]; + }, + }), + }), + }), + insert: () => ({ + values: (values: unknown) => ({ + onConflictDoNothing: () => ({ + returning: async () => { + auditRows.push(values); + return [{ id: 'audit-row-1' }]; + }, + }), + }), + }), + }; + return callback(tx); + }, }; return { db, updates, auditRows }; } @@ -284,7 +312,16 @@ describe('startSecurityAnalysis retrySandboxOnly', () => { 2, {}, finding.id, - expect.objectContaining({ triage: existingTriage }) + expect.objectContaining({ + triage: existingTriage, + findingDataSnapshot: expect.objectContaining({ + schemaVersion: 1, + source: 'dependabot', + sourceId: '42', + repoFullName: 'kilo/repo', + packageName: 'package-name', + }), + }) ); expect(transitionAnalysisStartLifecycle).toHaveBeenCalledWith( {}, @@ -318,7 +355,16 @@ describe('startSecurityAnalysis retrySandboxOnly', () => { {}, expect.objectContaining({ claim: expect.objectContaining({ source: 'manual', claimToken: 'manual-claim-token' }), - outcome: expect.objectContaining({ type: 'triage-only-completed' }), + outcome: expect.objectContaining({ + type: 'triage-only-completed', + analysis: expect.objectContaining({ + findingDataSnapshot: expect.objectContaining({ + schemaVersion: 1, + sourceId: '42', + packageName: 'package-name', + }), + }), + }), }) ); expect(clearAnalysisStatus).not.toHaveBeenCalled(); @@ -406,8 +452,18 @@ describe('startSecurityAnalysis retrySandboxOnly', () => { expect(fetchSpy).toHaveBeenCalledTimes(1); expect(auditRows[0]).toMatchObject({ action: 'security.finding.auto_dismissed', + finding_id: finding.id, resource_id: finding.id, - metadata: { dismissSource: 'triage', confidence: 'high' }, + occurred_at: expect.any(String), + schema_version: 1, + source_context: 'analysis_worker', + metadata: { + reason_code: 'not_used', + trigger: 'auto_dismiss_policy', + dismiss_source: 'triage', + confidence: 'high', + correlation_id: expect.any(String), + }, }); }); diff --git a/services/security-auto-analysis/src/launch.ts b/services/security-auto-analysis/src/launch.ts index f05700c436..5e11cf8721 100644 --- a/services/security-auto-analysis/src/launch.ts +++ b/services/security-auto-analysis/src/launch.ts @@ -2,6 +2,7 @@ import { randomUUID } from 'crypto'; import { z } from 'zod'; import type { WorkerDb } from '@kilocode/db/client'; import { deriveCallbackToken } from '@kilocode/worker-utils'; +import { buildSecurityFindingAnalysisInput } from '@kilocode/worker-utils/security-remediation-policy'; import { clearAnalysisStatus, getSecurityFindingById, @@ -171,6 +172,7 @@ export async function startSecurityAnalysis( if (!finding) { return { started: false, error: `Finding not found: ${params.findingId}` }; } + const findingDataSnapshot = buildSecurityFindingAnalysisInput(finding); const leaseAcquired = await tryAcquireAnalysisStartLease(params.db, params.findingId); if (!leaseAcquired) { @@ -214,6 +216,7 @@ export async function startSecurityAnalysis( if (!runSandbox) { const triageOnlyAnalysis: SecurityFindingAnalysis = { triage, + findingDataSnapshot, analyzedAt: new Date().toISOString(), modelUsed: params.triageModel, triageModel: params.triageModel, @@ -241,6 +244,7 @@ export async function startSecurityAnalysis( const partialAnalysis: SecurityFindingAnalysis = { triage, + findingDataSnapshot, analyzedAt: new Date().toISOString(), modelUsed: params.analysisModel, triageModel: params.triageModel, diff --git a/services/security-auto-analysis/src/manual-analysis.test.ts b/services/security-auto-analysis/src/manual-analysis.test.ts index d0433dfa78..735c23f2f8 100644 --- a/services/security-auto-analysis/src/manual-analysis.test.ts +++ b/services/security-auto-analysis/src/manual-analysis.test.ts @@ -6,7 +6,8 @@ import { import type * as DbModule from '@kilocode/db'; import { getWorkerDb } from '@kilocode/db/client'; import { transitionAnalysisStartLifecycle } from './analysis-start-lifecycle.js'; -import { ensureManualAnalysisQueueRow } from './db/queries.js'; +import { ensureManualAnalysisQueueRow, prepareActiveAnalysisRestart } from './db/queries.js'; +import type * as QueriesModule from './db/queries.js'; import { InsufficientCreditsError, startSecurityAnalysis } from './launch.js'; import { consumeManualAnalysisBatch, @@ -27,6 +28,13 @@ vi.mock('@kilocode/db', async importOriginal => { }; }); vi.mock('@kilocode/db/client', () => ({ getWorkerDb: vi.fn() })); +vi.mock('./db/queries.js', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + prepareActiveAnalysisRestart: vi.fn(), + }; +}); vi.mock('./analysis-start-lifecycle.js', () => ({ transitionAnalysisStartLifecycle: vi.fn(), })); @@ -55,6 +63,7 @@ const finding = { beforeEach(() => { vi.mocked(startSecurityAnalysis).mockReset(); + vi.mocked(prepareActiveAnalysisRestart).mockReset(); vi.mocked(transitionAnalysisStartLifecycle).mockReset(); vi.mocked(transitionAnalysisStartLifecycle).mockResolvedValue({ transitioned: true }); vi.mocked(getWorkerDb).mockReturnValue({} as never); @@ -314,6 +323,209 @@ describe('processManualAnalysisStart', () => { expect(execute).not.toHaveBeenCalled(); }); + it('restarts an active run without owner capacity and keeps interruption best-effort', async () => { + let selectCount = 0; + const auditRows: unknown[] = []; + const db = { + select: () => { + selectCount += 1; + if (selectCount === 1) { + return { + from: () => ({ + where: () => ({ + limit: async () => [ + { + ...finding, + status: 'open', + analysis_status: 'running', + session_id: 'agent-old-session', + }, + ], + }), + }), + }; + } + if (selectCount === 2) { + return { + from: () => ({ + where: () => ({ limit: async () => [{ id: 'user-123', api_token_pepper: null }] }), + }), + }; + } + return { + from: () => ({ + where: () => ({ + limit: async () => [ + { + config: { + analysis_mode: 'shallow', + triage_model_slug: 'config/triage', + analysis_model_slug: 'config/analysis', + }, + }, + ], + }), + }), + }; + }, + insert: () => ({ + values: async (values: unknown) => { + auditRows.push(values); + }, + }), + }; + const cloudAgentFetch = vi.fn().mockRejectedValue(new Error('interrupt unavailable')); + vi.mocked(prepareActiveAnalysisRestart).mockResolvedValue({ + status: 'ready', + claimToken: `manual-restart:${command.commandId}`, + oldCloudAgentSessionId: 'agent-old-session', + }); + vi.mocked(startSecurityAnalysis).mockResolvedValue({ started: true, triageOnly: false }); + + await expect( + processManualAnalysisStart({ + db: db as never, + env: { + GIT_TOKEN_SERVICE: { + getTokenForRepo: async () => ({ + success: true, + token: 'github-token', + installationId: 'installation-123', + accountLogin: 'kilo', + appType: 'standard', + }), + }, + NEXTAUTH_SECRET: { get: async () => 'next-auth-secret' }, + INTERNAL_API_SECRET: { get: async () => 'internal-secret' }, + CALLBACK_TOKEN_SECRET: { get: async () => 'callback-token-secret' }, + CLOUD_AGENT_NEXT: { fetch: cloudAgentFetch }, + ENVIRONMENT: 'development', + } as unknown as CloudflareEnv, + command: { ...command, restartActive: true }, + }) + ).resolves.toEqual({ status: 'started', resultCode: 'ANALYSIS_RESTART_STARTED' }); + + expect(selectCount).toBe(3); + expect(prepareActiveAnalysisRestart).toHaveBeenCalledWith(db, { + findingId: command.findingId, + owner: { type: 'org', id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, + commandId: command.commandId, + }); + expect(cloudAgentFetch).toHaveBeenCalledTimes(1); + const interruptRequest = cloudAgentFetch.mock.calls[0]?.[0]; + expect(interruptRequest).toBeInstanceOf(Request); + if (!(interruptRequest instanceof Request)) throw new Error('Expected interrupt request'); + await expect(interruptRequest.json()).resolves.toEqual({ sessionId: 'agent-old-session' }); + expect(startSecurityAnalysis).toHaveBeenCalledWith( + expect.objectContaining({ + forceSandbox: true, + retrySandboxOnly: true, + lifecycleClaim: { + source: 'manual', + findingId: command.findingId, + claimToken: `manual-restart:${command.commandId}`, + }, + }) + ); + expect(auditRows[0]).toMatchObject({ + metadata: { + restartActive: true, + triageOnly: false, + }, + }); + }); + + it('leaves active run untouched when restart prerequisites fail', async () => { + let selectCount = 0; + const db = { + select: () => { + selectCount += 1; + if (selectCount === 1) { + return { from: () => ({ where: () => ({ limit: async () => [finding] }) }) }; + } + if (selectCount === 2) { + return { + from: () => ({ + where: () => ({ limit: async () => [{ id: 'user-123', api_token_pepper: null }] }), + }), + }; + } + return { + from: () => ({ + where: () => ({ limit: async () => [{ config: { analysis_mode: 'auto' } }] }), + }), + }; + }, + }; + + await expect( + processManualAnalysisStart({ + db: db as never, + env: { + GIT_TOKEN_SERVICE: { + getTokenForRepo: async () => ({ success: false, reason: 'no_installation_found' }), + }, + NEXTAUTH_SECRET: { get: async () => 'next-auth-secret' }, + INTERNAL_API_SECRET: { get: async () => 'internal-secret' }, + CALLBACK_TOKEN_SECRET: { get: async () => 'callback-token-secret' }, + } as unknown as CloudflareEnv, + command: { ...command, restartActive: true }, + }) + ).resolves.toEqual({ status: 'token-missing' }); + + expect(prepareActiveAnalysisRestart).not.toHaveBeenCalled(); + expect(startSecurityAnalysis).not.toHaveBeenCalled(); + }); + + it('safely no-ops active restart after callback or redelivery wins', async () => { + let selectCount = 0; + const db = { + select: () => { + selectCount += 1; + if (selectCount === 1) { + return { from: () => ({ where: () => ({ limit: async () => [finding] }) }) }; + } + if (selectCount === 2) { + return { + from: () => ({ + where: () => ({ limit: async () => [{ id: 'user-123', api_token_pepper: null }] }), + }), + }; + } + return { + from: () => ({ + where: () => ({ limit: async () => [{ config: { analysis_mode: 'auto' } }] }), + }), + }; + }, + }; + vi.mocked(prepareActiveAnalysisRestart).mockResolvedValue({ status: 'launch-started' }); + + await expect( + processManualAnalysisStart({ + db: db as never, + env: { + GIT_TOKEN_SERVICE: { + getTokenForRepo: async () => ({ + success: true, + token: 'github-token', + installationId: 'installation-123', + accountLogin: 'kilo', + appType: 'standard', + }), + }, + NEXTAUTH_SECRET: { get: async () => 'next-auth-secret' }, + INTERNAL_API_SECRET: { get: async () => 'internal-secret' }, + CALLBACK_TOKEN_SECRET: { get: async () => 'callback-token-secret' }, + ENVIRONMENT: 'development', + } as unknown as CloudflareEnv, + command: { ...command, restartActive: true }, + }) + ).resolves.toEqual({ status: 'duplicate' }); + + expect(startSecurityAnalysis).not.toHaveBeenCalled(); + }); + it('settles post-lease manual start failures through the lifecycle transition', async () => { let selectCount = 0; let insertCount = 0; diff --git a/services/security-auto-analysis/src/manual-analysis.ts b/services/security-auto-analysis/src/manual-analysis.ts index 4015aca337..d1844f1623 100644 --- a/services/security-auto-analysis/src/manual-analysis.ts +++ b/services/security-auto-analysis/src/manual-analysis.ts @@ -16,11 +16,13 @@ import { getAnalysisActorById, getSecurityAgentConfigForOwner, getSecurityFindingById, + prepareActiveAnalysisRestart, transitionManualAnalysisQueueFromStart, type SecurityFindingRecord, } from './db/queries.js'; import { transitionAnalysisStartLifecycle } from './analysis-start-lifecycle.js'; import { InsufficientCreditsError, startSecurityAnalysis } from './launch.js'; +import { generateApiToken } from './token.js'; import { resolveSecurityAgentModels, SECURITY_ANALYSIS_OWNER_CAP, @@ -51,6 +53,7 @@ export const ManualAnalysisStartCommandSchema = z.object({ .optional(), forceSandbox: z.boolean().optional(), retrySandboxOnly: z.boolean().optional(), + restartActive: z.boolean().optional(), }); export const ManualAnalysisStartRequestSchema = ManualAnalysisStartCommandSchema.omit({ @@ -76,6 +79,36 @@ function findingMatchesOwner( : finding.owned_by_user_id === owner.id; } +async function interruptActiveAnalysisSession(params: { + env: CloudflareEnv; + authToken: string; + cloudAgentSessionId: string; + commandId: string; +}): Promise { + try { + const response = await params.env.CLOUD_AGENT_NEXT.fetch( + new Request('https://cloud-agent-next/trpc/interruptSession', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.authToken}`, + }, + body: JSON.stringify({ sessionId: params.cloudAgentSessionId }), + }) + ); + if (!response.ok) { + console.warn('Cloud Agent analysis restart interrupt returned non-OK status', { + command_id: params.commandId, + status: response.status, + }); + } + } catch { + console.warn('Cloud Agent analysis restart interrupt failed', { + command_id: params.commandId, + }); + } +} + export async function processManualAnalysisStart(params: { db: WorkerDb; env: CloudflareEnv; @@ -96,34 +129,89 @@ export async function processManualAnalysisStart(params: { if (!finding || !findingMatchesOwner(finding, owner)) { return { status: 'finding-missing' }; } - const inflight = await countOwnerInflightAnalyses(params.db, owner); - if (inflight >= SECURITY_ANALYSIS_OWNER_CAP) return { status: 'owner-cap' }; + const isActiveRestart = params.command.restartActive === true; + if (!isActiveRestart) { + const inflight = await countOwnerInflightAnalyses(params.db, owner); + if (inflight >= SECURITY_ANALYSIS_OWNER_CAP) return { status: 'owner-cap' }; + } + const actor = await getAnalysisActorById(params.db, params.command.actorUserId); if (!actor) return { status: 'actor-missing' }; - const claimToken = randomUUID(); - const jobId = `manual:${claimToken}`; - if (!(await ensureManualAnalysisQueueRow(params.db, { finding, claimToken, jobId }))) { - return { status: 'duplicate' }; - } + let claimToken: string; + let tokenResult: GitTokenForRepoResult; + let config: Awaited>; + let nextAuthSecret: string; + let internalApiSecret: string; + let callbackTokenSecret: string; - const tokenResult = await params.env.GIT_TOKEN_SERVICE.getTokenForRepo({ - githubRepo: finding.repo_full_name, - userId: actor.id, - orgId: owner.type === 'org' ? owner.id : undefined, - }); - if (!tokenResult.success) { - await transitionManualAnalysisQueueFromStart(params.db, { + if (isActiveRestart) { + [tokenResult, config, nextAuthSecret, internalApiSecret, callbackTokenSecret] = + await Promise.all([ + params.env.GIT_TOKEN_SERVICE.getTokenForRepo({ + githubRepo: finding.repo_full_name, + userId: actor.id, + orgId: owner.type === 'org' ? owner.id : undefined, + }), + getSecurityAgentConfigForOwner(params.db, owner), + params.env.NEXTAUTH_SECRET.get(), + params.env.INTERNAL_API_SECRET.get(), + params.env.CALLBACK_TOKEN_SECRET.get(), + ]); + if (!tokenResult.success) return { status: 'token-missing' }; + + const authToken = await generateApiToken( + actor, + nextAuthSecret, + params.env.ENVIRONMENT === 'production' ? 'production' : 'development' + ); + const restart = await prepareActiveAnalysisRestart(params.db, { findingId: finding.id, - claimToken, - status: 'failed', - failureCode: 'GITHUB_TOKEN_UNAVAILABLE', - errorMessage: 'GitHub token unavailable', + owner, + commandId: params.command.commandId, + }); + if (restart.status !== 'ready') return { status: 'duplicate' }; + + claimToken = restart.claimToken; + if (restart.oldCloudAgentSessionId) { + await interruptActiveAnalysisSession({ + env: params.env, + authToken, + cloudAgentSessionId: restart.oldCloudAgentSessionId, + commandId: params.command.commandId, + }); + } + } else { + claimToken = randomUUID(); + const jobId = `manual:${claimToken}`; + if (!(await ensureManualAnalysisQueueRow(params.db, { finding, claimToken, jobId }))) { + return { status: 'duplicate' }; + } + + tokenResult = await params.env.GIT_TOKEN_SERVICE.getTokenForRepo({ + githubRepo: finding.repo_full_name, + userId: actor.id, + orgId: owner.type === 'org' ? owner.id : undefined, }); - return { status: 'token-missing' }; + if (!tokenResult.success) { + await transitionManualAnalysisQueueFromStart(params.db, { + findingId: finding.id, + claimToken, + status: 'failed', + failureCode: 'GITHUB_TOKEN_UNAVAILABLE', + errorMessage: 'GitHub token unavailable', + }); + return { status: 'token-missing' }; + } + + config = await getSecurityAgentConfigForOwner(params.db, owner); + [nextAuthSecret, internalApiSecret, callbackTokenSecret] = await Promise.all([ + params.env.NEXTAUTH_SECRET.get(), + params.env.INTERNAL_API_SECRET.get(), + params.env.CALLBACK_TOKEN_SECRET.get(), + ]); } - const config = await getSecurityAgentConfigForOwner(params.db, owner); const resolvedModels = resolveSecurityAgentModels(config); const triageModel = params.command.requestedModels?.triageModel ?? @@ -133,11 +221,6 @@ export async function processManualAnalysisStart(params: { params.command.requestedModels?.analysisModel ?? params.command.requestedModels?.model ?? resolvedModels.analysisModel; - const [nextAuthSecret, internalApiSecret, callbackTokenSecret] = await Promise.all([ - params.env.NEXTAUTH_SECRET.get(), - params.env.INTERNAL_API_SECRET.get(), - params.env.CALLBACK_TOKEN_SECRET.get(), - ]); let result: Awaited>; try { result = await startSecurityAnalysis({ @@ -153,8 +236,8 @@ export async function processManualAnalysisStart(params: { nextAuthSecret, internalApiSecret, callbackTokenSecret, - forceSandbox: params.command.forceSandbox, - retrySandboxOnly: params.command.retrySandboxOnly, + forceSandbox: isActiveRestart ? true : params.command.forceSandbox, + retrySandboxOnly: isActiveRestart ? true : params.command.retrySandboxOnly, lifecycleClaim: { source: 'manual', findingId: finding.id, @@ -226,9 +309,13 @@ export async function processManualAnalysisStart(params: { analysisModel, analysisMode: config.analysis_mode, triageOnly: result.triageOnly ?? false, + ...(isActiveRestart ? { restartActive: true } : {}), }, }); - return { status: 'started' }; + return { + status: 'started', + resultCode: isActiveRestart ? 'ANALYSIS_RESTART_STARTED' : undefined, + }; } function manualAnalysisCommandTerminalState(result: { @@ -244,7 +331,10 @@ function manualAnalysisCommandTerminalState(result: { }): { status: 'succeeded' | 'failed' | 'no_op'; resultCode: string } { switch (result.status) { case 'started': - return { status: 'succeeded', resultCode: 'ANALYSIS_LAUNCH_STARTED' }; + return { + status: 'succeeded', + resultCode: result.resultCode ?? 'ANALYSIS_LAUNCH_STARTED', + }; case 'duplicate': return { status: 'no_op', resultCode: 'ALREADY_IN_PROGRESS' }; case 'owner-cap': diff --git a/services/security-auto-analysis/src/remediation-admission.integration.test.ts b/services/security-auto-analysis/src/remediation-admission.integration.test.ts new file mode 100644 index 0000000000..e43af955b1 --- /dev/null +++ b/services/security-auto-analysis/src/remediation-admission.integration.test.ts @@ -0,0 +1,166 @@ +import { randomUUID } from 'crypto'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; +import { createDrizzleClient } from '@kilocode/db/client'; +import { + kilocode_users, + security_audit_log, + security_findings, + security_remediation_attempts, + security_remediations, +} from '@kilocode/db/schema'; +import { eq } from 'drizzle-orm'; +import { buildSecurityFindingAnalysisInput } from '@kilocode/worker-utils/security-remediation-policy'; +import { admitRemediationAttempt } from './remediation.js'; +import { DEFAULT_SECURITY_AGENT_CONFIG } from './types.js'; + +const connectionString = + process.env.POSTGRES_URL ?? 'postgres://postgres:postgres@localhost:5432/postgres'; +const testUserId = `security-remediation-admission-${randomUUID()}`; +const findingId = randomUUID(); +let client: ReturnType; + +describe('security remediation admission persistence', () => { + beforeAll(async () => { + client = createDrizzleClient({ connectionString, ssl: false }); + await client.db.insert(kilocode_users).values({ + id: testUserId, + google_user_email: `${testUserId}@example.com`, + google_user_name: 'Security Remediation Admission Test', + google_user_image_url: 'https://example.com/avatar.png', + stripe_customer_id: `cus_${randomUUID()}`, + }); + await client.db.insert(security_findings).values({ + id: findingId, + owned_by_user_id: testUserId, + repo_full_name: 'kilo/remediation-admission-test', + source: 'dependabot', + source_id: 'remediation-admission-test', + severity: 'high', + package_name: 'lodash', + package_ecosystem: 'npm', + title: 'Remediation admission test finding', + status: 'open', + analysis_status: 'pending', + }); + }); + + afterAll(async () => { + await client.db.delete(security_audit_log).where(eq(security_audit_log.finding_id, findingId)); + await client.db + .delete(security_remediation_attempts) + .where(eq(security_remediation_attempts.finding_id, findingId)); + await client.db + .delete(security_remediations) + .where(eq(security_remediations.finding_id, findingId)); + await client.db.delete(security_findings).where(eq(security_findings.id, findingId)); + await client.db.delete(kilocode_users).where(eq(kilocode_users.id, testUserId)); + await client.pool.end(); + }); + + it('creates no remediation state when analysis is required', async () => { + const result = await admitRemediationAttempt({ + db: client.db as never, + findingId, + origin: 'manual', + owner: { type: 'user', id: testUserId }, + runtimeConfig: { + config: DEFAULT_SECURITY_AGENT_CONFIG, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/remediation-admission-test'], + }, + }); + + expect(result).toEqual({ admitted: false, reason: 'analysis_required' }); + await expect( + client.db + .select({ id: security_remediations.id }) + .from(security_remediations) + .where(eq(security_remediations.finding_id, findingId)) + ).resolves.toEqual([]); + await expect( + client.db + .select({ id: security_remediation_attempts.id }) + .from(security_remediation_attempts) + .where(eq(security_remediation_attempts.finding_id, findingId)) + ).resolves.toEqual([]); + }); + + it('admits the Worker projection using the database completion timestamp fallback', async () => { + const analysisInput = buildSecurityFindingAnalysisInput({ + source: 'dependabot', + source_id: 'remediation-admission-test', + status: 'open', + severity: 'high', + repo_full_name: 'kilo/remediation-admission-test', + package_name: 'lodash', + package_ecosystem: 'npm', + dependency_scope: null, + cve_id: null, + ghsa_id: null, + cwe_ids: null, + cvss_score: null, + title: 'Remediation admission test finding', + description: null, + vulnerable_version_range: null, + patched_version: null, + manifest_path: null, + raw_data: null, + }); + await client.db + .update(security_findings) + .set({ + analysis_status: 'completed', + analysis_completed_at: '2026-06-16T12:00:00.000Z', + last_synced_at: '2026-06-16T12:05:00.000Z', + analysis: { + analyzedAt: null, + findingDataSnapshot: analysisInput, + sandboxAnalysis: { + isExploitable: true, + exploitabilityReasoning: 'Vulnerable package is reachable.', + usageLocations: [], + suggestedFix: 'Upgrade lodash to a patched version.', + suggestedAction: 'open_pr', + summary: 'Reachable vulnerable dependency.', + rawMarkdown: '', + analysisAt: null, + }, + } as never, + }) + .where(eq(security_findings.id, findingId)); + + const result = await admitRemediationAttempt({ + db: client.db as never, + findingId, + origin: 'manual', + owner: { type: 'user', id: testUserId }, + requestedByUserId: testUserId, + requestedByActor: { + id: testUserId, + email: `${testUserId}@example.com`, + name: 'Security Remediation Admission Test', + api_token_pepper: null, + is_admin: false, + }, + runtimeConfig: { + config: DEFAULT_SECURITY_AGENT_CONFIG, + isAgentEnabled: true, + repoFullNamesInScope: ['kilo/remediation-admission-test'], + }, + }); + + expect(result).toMatchObject({ admitted: true, attemptNumber: 1 }); + await expect( + client.db + .select({ analysisCompletedAt: security_remediation_attempts.analysis_completed_at }) + .from(security_remediation_attempts) + .where(eq(security_remediation_attempts.finding_id, findingId)) + ).resolves.toHaveLength(1); + await expect( + client.db + .select({ id: security_remediations.id }) + .from(security_remediations) + .where(eq(security_remediations.finding_id, findingId)) + ).resolves.toHaveLength(1); + }); +}); diff --git a/services/security-auto-analysis/src/remediation.test.ts b/services/security-auto-analysis/src/remediation.test.ts index 48e1dd0e3b..03b2300560 100644 --- a/services/security-auto-analysis/src/remediation.test.ts +++ b/services/security-auto-analysis/src/remediation.test.ts @@ -1,5 +1,45 @@ -import { describe, expect, it } from 'vitest'; -import { buildRemediationPrepareSessionBody, buildRemediationPrompt } from './remediation.js'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type * as QueriesModule from './db/queries.js'; +import { getSecurityFindingById } from './db/queries.js'; +import { logger } from './logger.js'; +import { + admitRemediationAttempt, + buildRemediationPrepareSessionBody, + buildRemediationPrompt, +} from './remediation.js'; + +vi.mock('./db/queries.js', async importOriginal => ({ + ...(await importOriginal()), + getSecurityFindingById: vi.fn(), +})); + +beforeEach(() => { + vi.clearAllMocks(); +}); + +describe('security remediation admission', () => { + it('reports a missing finding without starting persistence', async () => { + const findingId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; + const transaction = vi.fn(); + const log = vi.spyOn(logger, 'info'); + vi.mocked(getSecurityFindingById).mockResolvedValue(null as never); + + await expect( + admitRemediationAttempt({ + db: { transaction } as never, + findingId, + origin: 'manual', + }) + ).resolves.toEqual({ admitted: false, reason: 'finding_not_found' }); + + expect(transaction).not.toHaveBeenCalled(); + expect(log).toHaveBeenCalledWith('Security remediation admission rejected', { + finding_id: findingId, + origin: 'manual', + reason: 'finding_not_found', + }); + }); +}); describe('security remediation launch contract', () => { it('does not pass the new remediation branch as upstream checkout branch', () => { diff --git a/services/security-auto-analysis/src/remediation.ts b/services/security-auto-analysis/src/remediation.ts index 7d37c5231a..2eedaaac68 100644 --- a/services/security-auto-analysis/src/remediation.ts +++ b/services/security-auto-analysis/src/remediation.ts @@ -11,12 +11,25 @@ import { type NewSecurityRemediationAttempt, type SecurityRemediationAttempt, } from '@kilocode/db/schema'; -import { SecurityAuditLogAction } from '@kilocode/db/schema-types'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; import { deriveCallbackToken } from '@kilocode/worker-utils'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + buildSecurityFindingAuditHumanActor, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditActor, + type SecurityFindingAuditEventFinding, + type SecurityFindingAuditOwner, + type SecurityFindingAuditWriterDb, +} from '@kilocode/worker-utils/security-finding-audit'; import { computeSecurityRemediationAnalysisFingerprint, decideSecurityRemediationEligibility, - type SecurityRemediationCapabilityReason, + type SecurityRemediationAdmissionRejectionReason, type SecurityRemediationOrigin, } from '@kilocode/worker-utils/security-remediation-policy'; import { and, asc, desc, eq, inArray, isNull, lte, sql } from 'drizzle-orm'; @@ -27,6 +40,7 @@ import { parseSecurityConfig, resolveAutoAnalysisActor, type ActorUser, + type AuthoritativeActorUser, type SecurityFindingRecord, } from './db/queries.js'; import { InsufficientCreditsError } from './launch.js'; @@ -183,9 +197,22 @@ type AdmissionResult = } | { admitted: false; - reason: SecurityRemediationCapabilityReason; + reason: SecurityRemediationAdmissionRejectionReason; }; +function rejectRemediationAdmission(params: { + findingId: string; + origin: SecurityRemediationOrigin; + reason: SecurityRemediationAdmissionRejectionReason; +}): AdmissionResult { + logger.info('Security remediation admission rejected', { + finding_id: params.findingId, + origin: params.origin, + reason: params.reason, + }); + return { admitted: false, reason: params.reason }; +} + type ApplyAutoRemediationCommandResult = { scanned: number; admitted: number; @@ -248,6 +275,83 @@ function ownerValues(owner: QueueOwner) { }; } +function toFindingAuditOwner(finding: SecurityFindingRecord): SecurityFindingAuditOwner { + if (finding.owned_by_organization_id) { + return { type: 'organization', organizationId: finding.owned_by_organization_id }; + } + if (finding.owned_by_user_id) { + return { type: 'user', userId: finding.owned_by_user_id }; + } + throw new Error('Security remediation finding has no audit owner'); +} + +function findingAuditOwnerKey(finding: SecurityFindingRecord): string { + if (finding.owned_by_organization_id) return `organization:${finding.owned_by_organization_id}`; + if (finding.owned_by_user_id) return `user:${finding.owned_by_user_id}`; + throw new Error('Security remediation finding has no audit owner'); +} + +function toFindingAuditRecord(finding: SecurityFindingRecord): SecurityFindingAuditEventFinding { + return finding; +} + +async function writeRemediationFindingAuditEvent( + db: SecurityFindingAuditWriterDb, + params: { + finding: SecurityFindingRecord; + attempt: Pick< + SecurityRemediationAttempt, + | 'id' + | 'remediation_id' + | 'origin' + | 'requested_by_user_id' + | 'remediation_model_slug' + | 'branch_name' + >; + action: SecurityAuditLogAction; + actor: SecurityFindingAuditActor; + occurredAt: string; + beforeState?: Record; + afterState?: Record; + metadata?: Record; + } +): Promise { + await insertSecurityFindingAuditEvent(db, { + owner: toFindingAuditOwner(params.finding), + finding: toFindingAuditRecord(params.finding), + actor: params.actor, + action: params.action, + occurredAt: params.occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + findingAuditOwnerKey(params.finding), + params.finding.id, + params.action, + params.attempt.id, + ]), + sourceContext: SecurityFindingAuditSourceContext.RemediationCallback, + snapshotExtras: { remediation_attempt_id: params.attempt.id }, + beforeState: params.beforeState, + afterState: params.afterState, + metadata: { + remediation_id: params.attempt.remediation_id, + attempt_id: params.attempt.id, + origin: params.attempt.origin, + remediation_model_slug: params.attempt.remediation_model_slug, + branch_name: params.attempt.branch_name, + ...params.metadata, + }, + }); +} + +function toSecurityFindingAuditHumanActor(actor: AuthoritativeActorUser) { + return buildSecurityFindingAuditHumanActor({ + id: actor.id, + email: actor.email, + name: actor.name, + isAdmin: actor.is_admin, + }); +} + function ownerWhereAgentConfig(owner: QueueOwner) { return owner.type === 'org' ? eq(agent_configs.owned_by_organization_id, owner.id) @@ -471,6 +575,47 @@ async function markAttemptQueueAdmissionFailed(db: WorkerDb, attemptId: string): .where(eq(security_remediation_attempts.id, attemptId)) .returning(); if (!attempt) return; + const [finding] = await tx + .select({ + id: security_findings.id, + platform_integration_id: security_findings.platform_integration_id, + repo_full_name: security_findings.repo_full_name, + source: security_findings.source, + source_id: security_findings.source_id, + created_at: security_findings.created_at, + status: security_findings.status, + severity: security_findings.severity, + package_name: security_findings.package_name, + package_ecosystem: security_findings.package_ecosystem, + dependency_scope: security_findings.dependency_scope, + cve_id: security_findings.cve_id, + ghsa_id: security_findings.ghsa_id, + cwe_ids: security_findings.cwe_ids, + cvss_score: security_findings.cvss_score, + dependabot_html_url: security_findings.dependabot_html_url, + title: security_findings.title, + description: security_findings.description, + vulnerable_version_range: security_findings.vulnerable_version_range, + patched_version: security_findings.patched_version, + manifest_path: security_findings.manifest_path, + first_detected_at: security_findings.first_detected_at, + fixed_at: security_findings.fixed_at, + sla_due_at: security_findings.sla_due_at, + raw_data: security_findings.raw_data, + last_synced_at: security_findings.last_synced_at, + analysis_status: security_findings.analysis_status, + analysis: security_findings.analysis, + analysis_started_at: security_findings.analysis_started_at, + analysis_completed_at: security_findings.analysis_completed_at, + session_id: security_findings.session_id, + cli_session_id: security_findings.cli_session_id, + ignored_reason: security_findings.ignored_reason, + owned_by_organization_id: security_findings.owned_by_organization_id, + owned_by_user_id: security_findings.owned_by_user_id, + }) + .from(security_findings) + .where(eq(security_findings.id, attempt.finding_id)) + .limit(1); await tx .update(security_remediations) .set({ @@ -481,6 +626,21 @@ async function markAttemptQueueAdmissionFailed(db: WorkerDb, attemptId: string): updated_at: sql`now()`, }) .where(eq(security_remediations.id, attempt.remediation_id)); + if (finding) { + await writeRemediationFindingAuditEvent(tx, { + finding, + attempt, + action: SecurityAuditLogAction.RemediationFailed, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + occurredAt: new Date().toISOString(), + beforeState: { remediation_status: 'queued' }, + afterState: { + remediation_status: 'failed', + failure_code: 'QUEUE_ADMISSION_FAILED', + }, + metadata: { failure_code: 'QUEUE_ADMISSION_FAILED' }, + }); + } }); } @@ -489,7 +649,7 @@ async function recordRemediationAudit(params: { finding: SecurityFindingRecord; remediationId: string; attemptId: string; - action: SecurityAuditLogAction; + action: SecurityAuditLogAction.RemediationStarted | SecurityAuditLogAction.RemediationRetried; actorId?: string | null; metadata?: Record; }): Promise { @@ -510,20 +670,47 @@ async function recordRemediationAudit(params: { }); } +function logNonReportableRemediationAuditWriteFailure( + params: { + remediationId: string; + attemptId: string; + action: SecurityAuditLogAction.RemediationStarted | SecurityAuditLogAction.RemediationRetried; + }, + error: unknown +): void { + logger.error('Non-reportable remediation audit write failed', { + remediation_id: params.remediationId, + attempt_id: params.attemptId, + action: params.action, + error: error instanceof Error ? error.message : String(error), + }); +} + export async function admitRemediationAttempt(params: { db: WorkerDb; findingId: string; origin: SecurityRemediationOrigin; owner?: QueueOwner; requestedByUserId?: string | null; + requestedByActor?: AuthoritativeActorUser | null; allowManualRetry?: boolean; runtimeConfig?: RuntimeConfig; }): Promise { const finding = await getSecurityFindingById(params.db, params.findingId); - if (!finding) return { admitted: false, reason: 'analysis_required' }; + if (!finding) { + return rejectRemediationAdmission({ + findingId: params.findingId, + origin: params.origin, + reason: 'finding_not_found', + }); + } const owner = params.owner ?? ownerFromFinding(finding); if (!owner || !findingMatchesOwner(finding, owner)) { - return { admitted: false, reason: 'repo_not_in_scope' }; + return rejectRemediationAdmission({ + findingId: params.findingId, + origin: params.origin, + reason: 'repo_not_in_scope', + }); } const runtime = params.runtimeConfig ?? (await getRuntimeConfig(params.db, owner)); @@ -541,8 +728,29 @@ export async function admitRemediationAttempt(params: { const acceptedAnalysisFingerprint = decision.analysisFingerprint; const acceptedAnalysisCompletedAt = decision.analysisCompletedAt; - if (!decision.eligible || !acceptedAnalysisFingerprint || !acceptedAnalysisCompletedAt) { - return { admitted: false, reason: decision.reason }; + if (!decision.eligible) { + return rejectRemediationAdmission({ + findingId: params.findingId, + origin: params.origin, + reason: decision.reason === 'eligible' ? 'analysis_required' : decision.reason, + }); + } + if (!acceptedAnalysisFingerprint || !acceptedAnalysisCompletedAt) { + return rejectRemediationAdmission({ + findingId: params.findingId, + origin: params.origin, + reason: 'analysis_required', + }); + } + + const auditActor = + params.origin === 'auto_policy' + ? SECURITY_FINDING_AUDIT_SYSTEM_ACTOR + : params.requestedByActor + ? toSecurityFindingAuditHumanActor(params.requestedByActor) + : null; + if (!auditActor) { + throw new Error('Human remediation request requires an authoritative actor'); } return params.db.transaction(async tx => { @@ -629,6 +837,18 @@ export async function admitRemediationAttempt(params: { }) .where(eq(security_remediations.id, remediation.id)); + await writeRemediationFindingAuditEvent(tx, { + finding, + attempt, + action: SecurityAuditLogAction.RemediationQueued, + actor: auditActor, + occurredAt: new Date().toISOString(), + afterState: { + remediation_status: 'queued', + attempt_number: attemptNumber, + }, + }); + return { admitted: true, remediationId: remediation.id, @@ -683,6 +903,7 @@ function nextRetryAt(attemptCount: number): string { async function transitionAttemptLaunchFailure(params: { db: WorkerDb; attempt: SecurityRemediationAttempt; + finding?: SecurityFindingRecord; failureCode: string; errorMessage: string; retryable: boolean; @@ -720,6 +941,18 @@ async function transitionAttemptLaunchFailure(params: { updated_at: sql`now()`, }) .where(eq(security_remediations.id, params.attempt.remediation_id)); + if (params.finding) { + await writeRemediationFindingAuditEvent(tx, { + finding: params.finding, + attempt: params.attempt, + action: SecurityAuditLogAction.RemediationFailed, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + occurredAt: new Date().toISOString(), + beforeState: { remediation_status: params.attempt.status }, + afterState: { remediation_status: 'failed', failure_code: params.failureCode }, + metadata: { failure_code: params.failureCode }, + }); + } } }); } @@ -727,6 +960,7 @@ async function transitionAttemptLaunchFailure(params: { async function blockAttempt(params: { db: WorkerDb; attempt: SecurityRemediationAttempt; + finding?: SecurityFindingRecord; reason: string; summary: string; }): Promise { @@ -751,6 +985,18 @@ async function blockAttempt(params: { updated_at: sql`now()`, }) .where(eq(security_remediations.id, params.attempt.remediation_id)); + if (params.finding) { + await writeRemediationFindingAuditEvent(tx, { + finding: params.finding, + attempt: params.attempt, + action: SecurityAuditLogAction.RemediationBlocked, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + occurredAt: new Date().toISOString(), + beforeState: { remediation_status: params.attempt.status }, + afterState: { remediation_status: 'blocked', blocked_reason_code: params.reason }, + metadata: { blocked_reason_code: params.reason }, + }); + } }); } @@ -847,6 +1093,7 @@ async function finalizeAttemptCancellation(params: { attempt: params.attempt, result: { status: 'cancelled', summary: params.summary, validation: [] }, finalAssistantMessage: undefined, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, }); } @@ -1048,6 +1295,7 @@ export async function processRemediationAttempt(params: { await blockAttempt({ db, attempt, + finding, reason: decision.reason.toUpperCase(), summary: `Remediation no longer eligible: ${decision.reason}`, }); @@ -1060,6 +1308,7 @@ export async function processRemediationAttempt(params: { await blockAttempt({ db, attempt, + finding, reason: 'COVERED_BY_EXISTING_REMEDIATION_PR', summary: 'Another open remediation PR covers same package and manifest', }); @@ -1071,6 +1320,7 @@ export async function processRemediationAttempt(params: { await transitionAttemptLaunchFailure({ db, attempt, + finding, failureCode: 'ACTOR_RESOLUTION_FAILED', errorMessage: 'Remediation actor unavailable', retryable: false, @@ -1080,20 +1330,32 @@ export async function processRemediationAttempt(params: { try { await launchAttempt({ db, env: params.env, attempt, finding, owner, actor }); - await recordRemediationAudit({ - db, - finding, - remediationId: attempt.remediation_id, - attemptId: attempt.id, - action: SecurityAuditLogAction.RemediationStarted, - actorId: attempt.requested_by_user_id, - metadata: { origin: attempt.origin, dispatchId: params.dispatchId }, - }); + try { + await recordRemediationAudit({ + db, + finding, + remediationId: attempt.remediation_id, + attemptId: attempt.id, + action: SecurityAuditLogAction.RemediationStarted, + actorId: attempt.requested_by_user_id, + metadata: { origin: attempt.origin, dispatchId: params.dispatchId }, + }); + } catch (auditError) { + logNonReportableRemediationAuditWriteFailure( + { + remediationId: attempt.remediation_id, + attemptId: attempt.id, + action: SecurityAuditLogAction.RemediationStarted, + }, + auditError + ); + } return 'launched'; } catch (error) { await transitionAttemptLaunchFailure({ db, attempt, + finding, failureCode: error instanceof InsufficientCreditsError ? 'INSUFFICIENT_CREDITS' : 'LAUNCH_UPSTREAM_5XX', errorMessage: error instanceof Error ? error.message : String(error), @@ -1247,6 +1509,7 @@ async function finalizeAttemptOutcome(params: { attempt: SecurityRemediationAttempt; result: StructuredRemediationResult; finalAssistantMessage: string | undefined; + actor: SecurityFindingAuditActor; }): Promise { const status = params.result.status; const auditAction = @@ -1299,20 +1562,29 @@ async function finalizeAttemptOutcome(params: { updated_at: sql`now()`, }) .where(eq(security_remediations.id, params.attempt.remediation_id)); - }); - await recordRemediationAudit({ - db: params.db, - finding: params.finding, - remediationId: params.attempt.remediation_id, - attemptId: params.attempt.id, - action: auditAction, - actorId: params.attempt.requested_by_user_id, - metadata: { - origin: params.attempt.origin, - prUrl: params.result.prUrl ?? null, - prNumber: params.result.prNumber ?? null, - status, - }, + await writeRemediationFindingAuditEvent(tx, { + finding: params.finding, + attempt: params.attempt, + action: auditAction, + actor: params.actor, + occurredAt: new Date().toISOString(), + beforeState: { remediation_status: params.attempt.status }, + afterState: { + remediation_status: status, + ...(params.result.prNumber ? { pr_number: params.result.prNumber } : {}), + ...(params.result.draft !== undefined && params.result.draft !== null + ? { pr_draft: params.result.draft } + : {}), + }, + metadata: { + status, + ...(params.result.prUrl ? { pr_url: params.result.prUrl } : {}), + ...(params.result.prNumber ? { pr_number: params.result.prNumber } : {}), + validation_count: params.result.validation?.length ?? 0, + ...(status === 'failed' ? { failure_code: 'CLOUD_AGENT_FAILED' } : {}), + ...(status === 'blocked' ? { blocked_reason_code: 'blocked' } : {}), + }, + }); }); } @@ -1344,15 +1616,16 @@ async function finalizeAttemptAsFailed(params: { updated_at: sql`now()`, }) .where(eq(security_remediations.id, params.attempt.remediation_id)); - }); - await recordRemediationAudit({ - db: params.db, - finding: params.finding, - remediationId: params.attempt.remediation_id, - attemptId: params.attempt.id, - action: SecurityAuditLogAction.RemediationFailed, - actorId: params.attempt.requested_by_user_id, - metadata: { failureCode: params.failureCode }, + await writeRemediationFindingAuditEvent(tx, { + finding: params.finding, + attempt: params.attempt, + action: SecurityAuditLogAction.RemediationFailed, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + occurredAt: new Date().toISOString(), + beforeState: { remediation_status: params.attempt.status }, + afterState: { remediation_status: 'failed', failure_code: params.failureCode }, + metadata: { failure_code: params.failureCode }, + }); }); } @@ -1401,6 +1674,7 @@ export async function finalizeRemediationCallbackFromEnv(params: { validation: [], }, finalAssistantMessage: params.payload.lastAssistantMessageText, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, }); return { status: 'cancelled-finalized' }; } @@ -1451,6 +1725,7 @@ export async function finalizeRemediationCallbackFromEnv(params: { attempt, result, finalAssistantMessage: params.payload.lastAssistantMessageText, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, }); return { status: `${result.status}-finalized` }; } @@ -1462,13 +1737,20 @@ export async function startManualRemediation(params: { const db = getWorkerDb(params.env.HYPERDRIVE.connectionString, { statement_timeout: 30_000 }); const owner = commandOwner(params.request.owner); const actor = await getAnalysisActorById(db, params.request.actorUserId); - if (!actor) return { admitted: false, reason: 'security_agent_disabled' }; + if (!actor) { + return rejectRemediationAdmission({ + findingId: params.request.findingId, + origin: 'manual', + reason: 'security_agent_disabled', + }); + } const result = await admitRemediationAttempt({ db, findingId: params.request.findingId, origin: 'manual', owner, requestedByUserId: params.request.actorUserId, + requestedByActor: actor, allowManualRetry: params.request.retry, }); if (!result.admitted) return result; @@ -1478,19 +1760,30 @@ export async function startManualRemediation(params: { await markAttemptQueueAdmissionFailed(db, result.attemptId); throw error; } - const finding = await getSecurityFindingById(db, params.request.findingId); - if (finding) { - await recordRemediationAudit({ - db, - finding, - remediationId: result.remediationId, - attemptId: result.attemptId, - action: params.request.retry - ? SecurityAuditLogAction.RemediationRetried - : SecurityAuditLogAction.RemediationQueued, - actorId: params.request.actorUserId, - metadata: { origin: 'manual' }, - }); + if (params.request.retry) { + const finding = await getSecurityFindingById(db, params.request.findingId); + if (finding) { + try { + await recordRemediationAudit({ + db, + finding, + remediationId: result.remediationId, + attemptId: result.attemptId, + action: SecurityAuditLogAction.RemediationRetried, + actorId: params.request.actorUserId, + metadata: { origin: 'manual' }, + }); + } catch (auditError) { + logNonReportableRemediationAuditWriteFailure( + { + remediationId: result.remediationId, + attemptId: result.attemptId, + action: SecurityAuditLogAction.RemediationRetried, + }, + auditError + ); + } + } } return result; } @@ -1510,6 +1803,8 @@ export async function applyAutoRemediationCommand(params: { owner.type === 'org' ? eq(security_findings.owned_by_organization_id, owner.id) : eq(security_findings.owned_by_user_id, owner.id); + const actor = await getAnalysisActorById(db, params.command.actorUserId); + if (!actor) throw new Error('Bulk remediation actor unavailable'); const runtime = await getRuntimeConfig(db, owner); const candidateRows = await db .select({ id: security_findings.id }) @@ -1546,6 +1841,7 @@ export async function applyAutoRemediationCommand(params: { origin: 'bulk_existing', owner, requestedByUserId: params.command.actorUserId, + requestedByActor: actor, runtimeConfig: runtime, }); if (result.admitted) { @@ -1590,18 +1886,6 @@ export async function maybeAdmitAutoRemediationForCompletedAnalysis(params: { await markAttemptQueueAdmissionFailed(params.db, result.attemptId); throw error; } - const finding = await getSecurityFindingById(params.db, params.findingId); - if (finding) { - await recordRemediationAudit({ - db: params.db, - finding, - remediationId: result.remediationId, - attemptId: result.attemptId, - action: SecurityAuditLogAction.RemediationQueued, - actorId: null, - metadata: { origin: 'auto_policy' }, - }); - } return result; } @@ -1611,6 +1895,9 @@ export async function cancelRemediation(params: { }): Promise<{ success: true; status: 'cancelled' | 'cancellation_requested' }> { const db = getWorkerDb(params.env.HYPERDRIVE.connectionString, { statement_timeout: 30_000 }); const owner = commandOwner(params.request.owner); + const actor = await getAnalysisActorById(db, params.request.actorUserId); + if (!actor) throw new Error('Remediation actor unavailable'); + const auditActor = toSecurityFindingAuditHumanActor(actor); const ownerCondition = owner.type === 'org' ? eq(security_remediation_attempts.owned_by_organization_id, owner.id) @@ -1630,6 +1917,7 @@ export async function cancelRemediation(params: { attempt, result: { status: 'cancelled', summary: 'Cancelled before launch', validation: [] }, finalAssistantMessage: undefined, + actor: auditActor, }); } else { await db.transaction(async tx => { @@ -1665,16 +1953,13 @@ export async function cancelRemediation(params: { }) .where(eq(security_remediation_attempts.id, attempt.id)); if (attempt.cloud_agent_session_id) { - const actor = await getAnalysisActorById(db, params.request.actorUserId); - if (actor) { - const nextAuthSecret = await params.env.NEXTAUTH_SECRET.get(); - const authToken = await generateApiToken(actor, nextAuthSecret, params.env.ENVIRONMENT); - await interruptCloudAgentSession({ - env: params.env, - authToken, - cloudAgentSessionId: attempt.cloud_agent_session_id, - }); - } + const nextAuthSecret = await params.env.NEXTAUTH_SECRET.get(); + const authToken = await generateApiToken(actor, nextAuthSecret, params.env.ENVIRONMENT); + await interruptCloudAgentSession({ + env: params.env, + authToken, + cloudAgentSessionId: attempt.cloud_agent_session_id, + }); } return { success: true, status: 'cancellation_requested' }; } diff --git a/services/security-auto-analysis/src/triage.test.ts b/services/security-auto-analysis/src/triage.test.ts new file mode 100644 index 0000000000..3316ff4532 --- /dev/null +++ b/services/security-auto-analysis/src/triage.test.ts @@ -0,0 +1,128 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import type { SecurityFindingRecord } from './db/queries.js'; +import { triageSecurityFinding } from './triage.js'; + +const finding = { + id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + package_name: 'example-package', + package_ecosystem: 'npm', + severity: 'high', + dependency_scope: 'runtime', + cve_id: 'CVE-2026-1234', + ghsa_id: 'GHSA-1234-5678', + title: 'Example vulnerability', + description: 'Example description', + vulnerable_version_range: '<2.0.0', + patched_version: '2.0.0', + manifest_path: 'package.json', + raw_data: null, +} as SecurityFindingRecord; + +afterEach(() => { + vi.unstubAllGlobals(); +}); + +describe('triageSecurityFinding', () => { + it('uses automatic tool selection for reasoning-model compatibility', async () => { + const fetchSpy = vi.fn().mockResolvedValue( + Response.json({ + choices: [ + { + message: { + tool_calls: [ + { + type: 'function', + function: { + name: 'submit_triage_result', + arguments: JSON.stringify({ + needsSandboxAnalysis: true, + needsSandboxReasoning: 'Runtime dependency requires usage analysis.', + suggestedAction: 'analyze_codebase', + confidence: 'high', + }), + }, + }, + ], + }, + }, + ], + }) + ); + vi.stubGlobal('fetch', fetchSpy); + + const result = await triageSecurityFinding({ + finding, + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + const requestBody = JSON.parse(String(fetchSpy.mock.calls[0]?.[1]?.body)); + expect(requestBody.tool_choice).toBe('auto'); + expect(requestBody.tools[0].function.name).toBe('submit_triage_result'); + expect(result).toMatchObject({ + needsSandboxAnalysis: true, + suggestedAction: 'analyze_codebase', + confidence: 'high', + }); + }); + + it('accepts a content-only JSON result when the model does not call the tool', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + Response.json({ + choices: [ + { + message: { + content: JSON.stringify({ + needsSandboxAnalysis: true, + needsSandboxReasoning: 'Runtime usage determines exploitability.', + suggestedAction: 'analyze_codebase', + confidence: 'high', + }), + }, + }, + ], + }) + ) + ); + + const result = await triageSecurityFinding({ + finding, + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + expect(result).toMatchObject({ + needsSandboxAnalysis: true, + suggestedAction: 'analyze_codebase', + confidence: 'high', + }); + }); + + it('keeps the conservative fallback for malformed content', async () => { + vi.stubGlobal( + 'fetch', + vi.fn().mockResolvedValue( + Response.json({ + choices: [{ message: { content: 'Result: analyze the codebase.' } }], + }) + ) + ); + + const result = await triageSecurityFinding({ + finding, + authToken: 'test-token', + model: 'kilo-auto/balanced', + backendBaseUrl: 'http://localhost:3000', + }); + + expect(result).toMatchObject({ + needsSandboxAnalysis: true, + suggestedAction: 'analyze_codebase', + confidence: 'low', + }); + }); +}); diff --git a/services/security-auto-analysis/src/triage.ts b/services/security-auto-analysis/src/triage.ts index 642e07cd6d..b8bdf0601d 100644 --- a/services/security-auto-analysis/src/triage.ts +++ b/services/security-auto-analysis/src/triage.ts @@ -48,6 +48,7 @@ const TriageResponseSchema = z.object({ choices: z.array( z.object({ message: z.object({ + content: z.string().nullable().optional(), tool_calls: z .array( z.object({ @@ -88,7 +89,16 @@ function buildTriagePrompt(finding: SecurityFindingRecord): string { **Patched Version**: ${finding.patched_version ?? 'No patch available'} **Manifest Path**: ${finding.manifest_path ?? 'Unknown'}${cweContext} -Please analyze this vulnerability and call the submit_triage_result tool with your assessment.`; +Please analyze this vulnerability and call the submit_triage_result tool with your assessment. If tool calls are unavailable, return only a JSON object matching the tool parameters, without markdown or prose.`; +} + +function extractJsonContent(content: string | null | undefined): string | null { + const trimmed = content?.trim(); + if (!trimmed) return null; + if (trimmed.startsWith('{') && trimmed.endsWith('}')) return trimmed; + + const fencedJson = trimmed.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?```$/i); + return fencedJson?.[1]?.trim() || null; } function createFallbackTriage(reason: string): SecurityFindingTriage { @@ -148,10 +158,7 @@ export async function triageSecurityFinding(params: { }, }, ], - tool_choice: { - type: 'function', - function: { name: 'submit_triage_result' }, - }, + tool_choice: 'auto', stream: false, }; @@ -192,21 +199,24 @@ export async function triageSecurityFinding(params: { return createFallbackTriage('Invalid response shape'); } - const firstChoice = parsedResponse.data.choices[0]; - const firstToolCall = firstChoice?.message.tool_calls?.[0]; - if (!firstToolCall || firstToolCall.function.name !== 'submit_triage_result') { - return createFallbackTriage('Tool call missing'); + const message = parsedResponse.data.choices[0]?.message; + const toolCall = message?.tool_calls?.find( + candidate => candidate.function.name === 'submit_triage_result' + ); + const structuredJson = toolCall?.function.arguments ?? extractJsonContent(message?.content); + if (!structuredJson) { + return createFallbackTriage('Structured response missing'); } let args: unknown; try { - args = JSON.parse(firstToolCall.function.arguments); + args = JSON.parse(structuredJson); } catch { - return createFallbackTriage('Tool call arguments not valid JSON'); + return createFallbackTriage('Structured response not valid JSON'); } const parsedArgs = TriagedResultSchema.safeParse(args); if (!parsedArgs.success) { - return createFallbackTriage('Tool call arguments invalid'); + return createFallbackTriage('Structured response invalid'); } return { diff --git a/services/security-auto-analysis/src/types.ts b/services/security-auto-analysis/src/types.ts index ad915d1087..11dbb5626b 100644 --- a/services/security-auto-analysis/src/types.ts +++ b/services/security-auto-analysis/src/types.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import type { SecurityFindingAnalysisInput } from '@kilocode/db/schema-types'; export const AUTO_ANALYSIS_OWNER_CAP = 2; export const SECURITY_ANALYSIS_OWNER_CAP = 3; @@ -115,6 +116,7 @@ export type SecurityFindingTriage = { export type SecurityFindingSandboxAnalysis = { isExploitable: boolean | 'unknown'; + extractionStatus?: 'succeeded' | 'failed'; exploitabilityReasoning: string; usageLocations: string[]; suggestedFix: string; @@ -128,6 +130,7 @@ export type SecurityFindingSandboxAnalysis = { export type SecurityFindingAnalysis = { triage?: SecurityFindingTriage; sandboxAnalysis?: SecurityFindingSandboxAnalysis; + findingDataSnapshot?: SecurityFindingAnalysisInput; rawMarkdown?: string; analyzedAt: string; modelUsed?: string; diff --git a/services/security-sync/src/dismiss.test.ts b/services/security-sync/src/dismiss.test.ts index 71d1317ef2..665f66e0aa 100644 --- a/services/security-sync/src/dismiss.test.ts +++ b/services/security-sync/src/dismiss.test.ts @@ -7,14 +7,42 @@ const finding = { source: 'dependabot', source_id: '42', repo_full_name: 'kilo/repo', + title: 'lodash vulnerable to prototype pollution', + severity: 'high', status: 'open', + package_name: 'lodash', + package_ecosystem: 'npm', + manifest_path: 'package.json', + patched_version: '4.17.21', + ghsa_id: 'GHSA-xxxx-yyyy-zzzz', + cve_id: 'CVE-2026-1234', + cwe_ids: ['CWE-1321'], + cvss_score: '7.5', + dependabot_html_url: 'https://github.com/kilo/repo/security/dependabot/42', + first_detected_at: '2026-05-17 08:30:00.000+00', + fixed_at: null, + sla_due_at: '2026-05-24 08:30:00.000+00', + session_id: null, owned_by_organization_id: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', owned_by_user_id: null, }; -function createDb(selectedFinding = finding, options: { failAuditInsert?: boolean } = {}) { +function createDb( + selectedFinding = finding, + options: { + failAuditInsert?: boolean; + actor?: { id: string; email: string; name: string; isAdmin: boolean }; + } = {} +) { const updates: unknown[] = []; const auditRows: unknown[] = []; + let selectCount = 0; + const actor = options.actor ?? { + id: 'user-123', + email: 'owner@example.com', + name: 'Owner Example', + isAdmin: false, + }; function createOperations(targetUpdates: unknown[], targetAuditRows: unknown[]) { return { update: () => ({ @@ -25,12 +53,17 @@ function createDb(selectedFinding = finding, options: { failAuditInsert?: boolea }), }), insert: () => ({ - values: async (values: unknown) => { - if (options.failAuditInsert) { - throw new Error('audit insert failed'); - } - targetAuditRows.push(values); - }, + values: (values: unknown) => ({ + onConflictDoNothing: () => ({ + returning: async () => { + if (options.failAuditInsert) { + throw new Error('audit insert failed'); + } + targetAuditRows.push(values); + return [{ id: 'audit-row-1' }]; + }, + }), + }), }), }; } @@ -38,7 +71,7 @@ function createDb(selectedFinding = finding, options: { failAuditInsert?: boolea select: () => ({ from: () => ({ where: () => ({ - limit: async () => [selectedFinding], + limit: async () => [selectCount++ === 0 ? selectedFinding : actor], }), }), }), @@ -65,7 +98,7 @@ function createMessage(): SecurityDismissMessage { messageId: 'dismiss-message-123', dispatchedAt: '2026-05-18T08:30:00.000Z', owner: { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, - actor: { id: 'user-123', email: 'owner@example.com', name: 'Owner Example' }, + actor: { id: 'user-123' }, findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', installationId: 'installation-123', reason: 'not_used', @@ -102,9 +135,48 @@ describe('processSecurityFindingDismissal', () => { }); expect(auditRows[0]).toMatchObject({ actor_id: 'user-123', + actor_type: 'customer_user', action: 'security.finding.dismissed', resource_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', - after_state: { status: 'ignored', ignoredReason: 'not_used' }, + finding_id: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', + event_key: + 'security_finding_audit:v1:organization%3Aaaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb:security.finding.dismissed:dddddddd-dddd-4ddd-8ddd-dddddddddddd', + schema_version: 1, + source_context: 'security_sync', + after_state: { status: 'ignored', reason_code: 'not_used' }, + metadata: { + source: 'dependabot', + reason_code: 'not_used', + source_writeback_outcome: 'dismissed', + }, + finding_snapshot: { + status: 'ignored', + first_detected_at: '2026-05-17T08:30:00.000Z', + }, + }); + }); + + it('classifies the actor from authoritative user state at event-write time', async () => { + const { db, auditRows } = createDb(finding, { + actor: { + id: 'user-123', + email: 'customer-domain@example.com', + name: 'Kilo Operator', + isAdmin: true, + }, + }); + vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, status: 200 })); + + await processSecurityFindingDismissal({ + db, + gitTokenService: { getToken: async () => 'github-token' } as GitTokenService, + message: createMessage(), + }); + + expect(auditRows[0]).toMatchObject({ + actor_id: 'user-123', + actor_email: 'customer-domain@example.com', + actor_type: 'kilo_admin', }); }); diff --git a/services/security-sync/src/dismiss.ts b/services/security-sync/src/dismiss.ts index fc253b3b45..ea44c85bcd 100644 --- a/services/security-sync/src/dismiss.ts +++ b/services/security-sync/src/dismiss.ts @@ -1,7 +1,17 @@ import type { WorkerDb } from '@kilocode/db/client'; -import { security_audit_log, security_findings } from '@kilocode/db/schema'; -import { SecurityAuditLogAction } from '@kilocode/db/schema-types'; +import { kilocode_users, security_findings } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; import { parseDependabotDismissalTarget } from '@kilocode/worker-utils/dependabot-dismissal-target'; +import { + buildSecurityFindingAuditHumanActor, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditHumanActor, + type SecurityFindingAuditOwner, +} from '@kilocode/worker-utils/security-finding-audit'; import { eq, sql } from 'drizzle-orm'; import type { SecurityDismissMessage } from './index.js'; @@ -27,6 +37,46 @@ function findingMatchesOwner( return Boolean(owner.userId && finding.owned_by_user_id === owner.userId); } +function toAuditOwner(owner: SecurityDismissMessage['owner']): SecurityFindingAuditOwner { + if (owner.organizationId) return { type: 'organization', organizationId: owner.organizationId }; + if (owner.userId) return { type: 'user', userId: owner.userId }; + throw new Error('Security Finding dismissal owner is missing'); +} + +function dismissalEventKey(params: { + owner: SecurityDismissMessage['owner']; + findingId: string; + commandId: string; +}): string { + const ownerPart = params.owner.organizationId + ? `organization:${params.owner.organizationId}` + : `user:${params.owner.userId}`; + return deriveSecurityFindingAuditEventKey([ + ownerPart, + params.findingId, + SecurityAuditLogAction.FindingDismissed, + params.commandId, + ]); +} + +async function getDismissalAuditActor( + db: WorkerDb, + actorUserId: string +): Promise { + const [actor] = await db + .select({ + id: kilocode_users.id, + email: kilocode_users.google_user_email, + name: kilocode_users.google_user_name, + isAdmin: kilocode_users.is_admin, + }) + .from(kilocode_users) + .where(eq(kilocode_users.id, actorUserId)) + .limit(1); + if (!actor) throw new Error('Security Finding dismissal actor unavailable'); + return buildSecurityFindingAuditHumanActor(actor); +} + export async function processSecurityFindingDismissal(params: { db: WorkerDb; gitTokenService: GitTokenService; @@ -38,7 +88,22 @@ export async function processSecurityFindingDismissal(params: { source: security_findings.source, source_id: security_findings.source_id, repo_full_name: security_findings.repo_full_name, + title: security_findings.title, + severity: security_findings.severity, status: security_findings.status, + package_name: security_findings.package_name, + package_ecosystem: security_findings.package_ecosystem, + manifest_path: security_findings.manifest_path, + patched_version: security_findings.patched_version, + ghsa_id: security_findings.ghsa_id, + cve_id: security_findings.cve_id, + cwe_ids: security_findings.cwe_ids, + cvss_score: security_findings.cvss_score, + dependabot_html_url: security_findings.dependabot_html_url, + first_detected_at: security_findings.first_detected_at, + fixed_at: security_findings.fixed_at, + sla_due_at: security_findings.sla_due_at, + session_id: security_findings.session_id, owned_by_organization_id: security_findings.owned_by_organization_id, owned_by_user_id: security_findings.owned_by_user_id, }) @@ -116,33 +181,41 @@ export async function processSecurityFindingDismissal(params: { } } + const actor = await getDismissalAuditActor(params.db, params.message.actor.id); + await params.db.transaction(async tx => { await tx .update(security_findings) .set({ status: 'ignored', ignored_reason: params.message.reason, - ignored_by: params.message.actor.email ?? params.message.actor.id, + ignored_by: actor.email ?? actor.id, updated_at: sql`now()`, }) .where(eq(security_findings.id, finding.id)); - await tx.insert(security_audit_log).values({ - owned_by_organization_id: params.message.owner.organizationId ?? null, - owned_by_user_id: params.message.owner.userId ?? null, - actor_id: params.message.actor.id, - actor_email: params.message.actor.email ?? null, - actor_name: params.message.actor.name ?? null, + await insertSecurityFindingAuditEvent(tx, { + owner: toAuditOwner(params.message.owner), + finding: { ...finding, status: 'ignored' }, + actor, action: SecurityAuditLogAction.FindingDismissed, - resource_type: 'security_finding', - resource_id: finding.id, - before_state: { status: finding.status }, - after_state: { status: 'ignored', ignoredReason: params.message.reason }, + occurredAt: new Date(), + eventKey: dismissalEventKey({ + owner: params.message.owner, + findingId: finding.id, + commandId: params.message.commandId, + }), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + beforeState: { status: finding.status }, + afterState: { status: 'ignored', reason_code: params.message.reason }, metadata: { source: finding.source, - runId: params.message.runId, - messageId: params.message.messageId, + run_id: params.message.runId, + command_id: params.message.commandId, + message_id: params.message.messageId, trigger: 'worker_queue', + reason_code: params.message.reason, + source_writeback_outcome: finding.source === 'dependabot' ? 'dismissed' : 'not_applicable', }, }); }); diff --git a/services/security-sync/src/index.test.ts b/services/security-sync/src/index.test.ts index f7745e63f0..b8a23f7f7f 100644 --- a/services/security-sync/src/index.test.ts +++ b/services/security-sync/src/index.test.ts @@ -446,11 +446,7 @@ describe('manual dismissal dispatch', () => { body: JSON.stringify({ schemaVersion: 1, owner: { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, - actor: { - id: 'user-123', - email: 'owner@example.com', - name: 'Owner Example', - }, + actor: { id: 'user-123' }, findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', installationId: 'installation-123', reason: 'not_used', @@ -473,7 +469,7 @@ describe('manual dismissal dispatch', () => { expect(queuedBatches[0]?.[0]?.body).toMatchObject({ kind: 'dismiss', owner: { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, - actor: { id: 'user-123', email: 'owner@example.com', name: 'Owner Example' }, + actor: { id: 'user-123' }, findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', installationId: 'installation-123', reason: 'not_used', @@ -494,11 +490,7 @@ describe('manual dismissal dispatch', () => { body: JSON.stringify({ schemaVersion: 1, owner: { userId: legacyUserId }, - actor: { - id: legacyUserId, - email: 'owner@example.com', - name: 'Owner Example', - }, + actor: { id: legacyUserId }, findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', installationId: 'installation-123', reason: 'not_used', @@ -520,7 +512,7 @@ describe('manual dismissal dispatch', () => { expect(queuedBatches[0]?.[0]?.body).toMatchObject({ kind: 'dismiss', owner: { userId: legacyUserId }, - actor: { id: legacyUserId, email: 'owner@example.com', name: 'Owner Example' }, + actor: { id: legacyUserId }, }); }); @@ -691,7 +683,7 @@ describe('manual dismissal dispatch', () => { messageId: 'dismiss-message-123', dispatchedAt: '2026-05-18T08:30:00.000Z', owner: { organizationId: 'aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }, - actor: { id: 'user-123', email: 'owner@example.com', name: 'Owner Example' }, + actor: { id: 'user-123' }, findingId: 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', installationId: 'installation-123', reason: 'not_used', diff --git a/services/security-sync/src/index.ts b/services/security-sync/src/index.ts index 4e940d3e4a..fe67fa6877 100644 --- a/services/security-sync/src/index.ts +++ b/services/security-sync/src/index.ts @@ -32,6 +32,10 @@ const SecuritySyncActorSchema = z.object({ name: z.string().min(1).nullable().optional(), }); +const SecuritySyncActorIdSchema = z.object({ + id: z.string().min(1), +}); + const SecuritySyncMessageSchema = z .object({ schemaVersion: z.literal(1), @@ -71,7 +75,7 @@ const DependabotDismissReasonSchema = z.enum([ const ManualFindingDismissalCommandSchema = z.object({ schemaVersion: z.literal(1), owner: SecuritySyncOwnerSchema, - actor: SecuritySyncActorSchema, + actor: SecuritySyncActorIdSchema, findingId: z.string().uuid(), installationId: z.string().min(1), reason: DependabotDismissReasonSchema, diff --git a/services/security-sync/src/notifications/sweep.test.ts b/services/security-sync/src/notifications/sweep.test.ts index 8b36618a22..22a357a299 100644 --- a/services/security-sync/src/notifications/sweep.test.ts +++ b/services/security-sync/src/notifications/sweep.test.ts @@ -44,6 +44,55 @@ function createStagedRecoveryDb( }), }), }), + transaction: async (callback: (tx: unknown) => Promise) => { + const tx = { + execute: async () => { + operations.push('canonicalize'); + return { + rows: [ + { + findingId: '11111111-1111-4111-8111-111111111111', + previousStatus: 'open', + previousSeverity: 'high', + effectiveStatus: 'ignored', + effectiveSeverity: 'high', + findingCreatedAt: '2026-06-01T10:00:00.000Z', + ownedByUserId: 'user-1', + ownedByOrganizationId: null, + source: 'dependabot', + sourceId: '42', + repoFullName: 'acme/api', + title: 'Prototype Pollution in lodash', + packageName: 'lodash', + packageEcosystem: 'npm', + manifestPath: 'package.json', + patchedVersion: '4.17.21', + ghsaId: 'GHSA-xxxx-yyyy-zzzz', + cveId: null, + cweIds: null, + cvssScore: null, + dependabotHtmlUrl: null, + firstDetectedAt: '2026-06-01T10:00:00.000Z', + fixedAt: null, + slaDueAt: '2026-06-08T10:00:00.000Z', + canonicalFindingId: '22222222-2222-4222-8222-222222222222', + }, + ], + }; + }, + insert: () => ({ + values: (values: Record) => { + operations.push(`insert-audit:${String(values.action)}`); + return { + onConflictDoNothing: () => ({ + returning: async () => [{ id: 'audit-1' }], + }), + }; + }, + }), + }; + return callback(tx); + }, select: () => { selectCount++; if (selectCount === 1) { @@ -78,7 +127,7 @@ function createStagedRecoveryDb( return [ { notificationId: 'notification-1', - findingId: 'finding-1', + findingId: '11111111-1111-4111-8111-111111111111', recipientUserId: 'user-1', kind: 'new_finding', status: 'staged', @@ -100,7 +149,7 @@ function createStagedRecoveryDb( }, execute: async () => { executeCount++; - operations.push(executeCount === 1 ? 'canonicalize' : 'load-backlog-observability'); + operations.push(`load-backlog-observability:${executeCount}`); return { rows: [] }; }, }; @@ -170,7 +219,12 @@ describe('runSecurityNotificationSweep', () => { }); expect(result).toMatchObject({ stagedRecovered: 0, cancelled: 1 }); - expect(operations.slice(0, 3)).toEqual(['recover-stuck-claims', 'canonicalize', 'update']); + expect(operations.slice(0, 4)).toEqual([ + 'recover-stuck-claims', + 'canonicalize', + 'insert-audit:security.finding.superseded', + 'update', + ]); }); it('canonicalizes owner-scoped duplicate findings before publishing staged notifications', async () => { @@ -185,9 +239,10 @@ describe('runSecurityNotificationSweep', () => { }); expect(result).toMatchObject({ stagedRecovered: 1, cancelled: 0 }); - expect(operations.slice(0, 3)).toEqual([ + expect(operations.slice(0, 4)).toEqual([ 'recover-stuck-claims', 'canonicalize', + 'insert-audit:security.finding.superseded', 'publish-staged', ]); }); diff --git a/services/security-sync/src/notifications/sweep.ts b/services/security-sync/src/notifications/sweep.ts index 257d6ef60e..8d6cba2402 100644 --- a/services/security-sync/src/notifications/sweep.ts +++ b/services/security-sync/src/notifications/sweep.ts @@ -4,6 +4,17 @@ import { security_findings, security_finding_notifications, } from '@kilocode/db/schema'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditEventFinding, + type SecurityFindingAuditOwner, +} from '@kilocode/worker-utils/security-finding-audit'; import { SecurityNotificationPolicySchema, calculateSlaWarningBoundary, @@ -63,6 +74,39 @@ type NotificationFindingRow = { ignoredReason: string | null; }; +const dbTimestampSchema = z.union([z.string(), z.date()]); +const nullableDbTimestampSchema = dbTimestampSchema.nullable(); +const securityFindingStatusSchema = z.enum(['open', 'fixed', 'ignored']); +const securitySeveritySchema = z.enum(['critical', 'high', 'medium', 'low']); +const supersededSecurityFindingResultSchema = z.object({ + findingId: z.string().uuid(), + previousStatus: securityFindingStatusSchema.nullable(), + previousSeverity: securitySeveritySchema.nullable(), + effectiveStatus: securityFindingStatusSchema, + effectiveSeverity: securitySeveritySchema, + findingCreatedAt: dbTimestampSchema, + ownedByUserId: z.string().nullable(), + ownedByOrganizationId: z.string().uuid().nullable(), + source: z.string(), + sourceId: z.string(), + repoFullName: z.string(), + title: z.string(), + packageName: z.string(), + packageEcosystem: z.string(), + manifestPath: z.string().nullable(), + patchedVersion: z.string().nullable(), + ghsaId: z.string().nullable(), + cveId: z.string().nullable(), + cweIds: z.array(z.string()).nullable(), + cvssScore: z.union([z.string(), z.number()]).nullable(), + dependabotHtmlUrl: z.string().nullable(), + firstDetectedAt: dbTimestampSchema, + fixedAt: nullableDbTimestampSchema, + slaDueAt: nullableDbTimestampSchema, + canonicalFindingId: z.string().uuid(), +}); +type SupersededSecurityFindingResult = z.infer; + type KindCount = { kind: 'new_finding' | 'sla_warning' | 'sla_breach'; status: 'staged' | 'pending' | 'sending' | 'sent' | 'failed' | 'cancelled'; @@ -366,7 +410,7 @@ async function recoverStagedRows( } async function canonicalizeStagedOwnerRepos( - db: Pick, + db: Pick, rows: NotificationFindingRow[], states: Map ): Promise { @@ -388,56 +432,158 @@ async function canonicalizeStagedOwnerRepos( } async function supersedeDuplicateFindings( - db: Pick, + db: Pick, repoFullName: string, owner: SecurityNotificationOwner ): Promise { const partitionColumn = ownerPartitionColumn(owner); - await db.execute(sql` - WITH ranked AS ( - SELECT - ${security_findings.id} AS id, - ROW_NUMBER() OVER ( - PARTITION BY ${partitionColumn}, - ${security_findings.repo_full_name}, - ${security_findings.source}, - ${security_findings.ghsa_id}, - ${security_findings.package_name}, - ${security_findings.manifest_path} - ORDER BY CASE - WHEN ${security_findings.source_id} ~ '^[0-9]+$' THEN ${security_findings.source_id}::int - ELSE 0 - END DESC - ) AS rn, - FIRST_VALUE(${security_findings.id}) OVER ( - PARTITION BY ${partitionColumn}, - ${security_findings.repo_full_name}, - ${security_findings.source}, - ${security_findings.ghsa_id}, - ${security_findings.package_name}, - ${security_findings.manifest_path} - ORDER BY CASE - WHEN ${security_findings.source_id} ~ '^[0-9]+$' THEN ${security_findings.source_id}::int - ELSE 0 - END DESC - ) AS canonical_id - FROM ${security_findings} - WHERE ${security_findings.repo_full_name} = ${repoFullName} - AND ${ownerPredicate(owner)} - AND ${security_findings.source} = 'dependabot' - AND ${security_findings.ghsa_id} IS NOT NULL - AND ${security_findings.status} = 'open' - ) - UPDATE ${security_findings} - SET - ${sql.identifier(security_findings.status.name)} = 'ignored', - ${sql.identifier(security_findings.ignored_reason.name)} = 'superseded:' || ranked.canonical_id, - ${sql.identifier(security_findings.ignored_by.name)} = 'system', - ${sql.identifier(security_findings.updated_at.name)} = now() - FROM ranked - WHERE ${security_findings.id} = ranked.id - AND ranked.rn > 1 - `); + await db.transaction(async tx => { + const result = await tx.execute>(sql` + WITH ranked AS ( + SELECT + ${security_findings.id} AS id, + ${security_findings.status} AS previous_status, + ${security_findings.severity} AS previous_severity, + ROW_NUMBER() OVER ( + PARTITION BY ${partitionColumn}, + ${security_findings.repo_full_name}, + ${security_findings.source}, + ${security_findings.ghsa_id}, + ${security_findings.package_name}, + ${security_findings.manifest_path} + ORDER BY CASE + WHEN ${security_findings.source_id} ~ '^[0-9]+$' THEN ${security_findings.source_id}::int + ELSE 0 + END DESC + ) AS rn, + FIRST_VALUE(${security_findings.id}) OVER ( + PARTITION BY ${partitionColumn}, + ${security_findings.repo_full_name}, + ${security_findings.source}, + ${security_findings.ghsa_id}, + ${security_findings.package_name}, + ${security_findings.manifest_path} + ORDER BY CASE + WHEN ${security_findings.source_id} ~ '^[0-9]+$' THEN ${security_findings.source_id}::int + ELSE 0 + END DESC + ) AS canonical_id + FROM ${security_findings} + WHERE ${security_findings.repo_full_name} = ${repoFullName} + AND ${ownerPredicate(owner)} + AND ${security_findings.source} = 'dependabot' + AND ${security_findings.ghsa_id} IS NOT NULL + AND ${security_findings.status} = 'open' + ), + superseded AS ( + UPDATE ${security_findings} + SET + ${sql.identifier(security_findings.status.name)} = 'ignored', + ${sql.identifier(security_findings.ignored_reason.name)} = 'superseded:' || ranked.canonical_id, + ${sql.identifier(security_findings.ignored_by.name)} = 'system', + ${sql.identifier(security_findings.updated_at.name)} = now() + FROM ranked + WHERE ${security_findings.id} = ranked.id + AND ranked.rn > 1 + RETURNING + ${security_findings.id} AS "findingId", + ranked.previous_status AS "previousStatus", + ranked.previous_severity AS "previousSeverity", + ${security_findings.status} AS "effectiveStatus", + ${security_findings.severity} AS "effectiveSeverity", + ${security_findings.created_at} AS "findingCreatedAt", + ${security_findings.owned_by_user_id} AS "ownedByUserId", + ${security_findings.owned_by_organization_id} AS "ownedByOrganizationId", + ${security_findings.source} AS "source", + ${security_findings.source_id} AS "sourceId", + ${security_findings.repo_full_name} AS "repoFullName", + ${security_findings.title} AS "title", + ${security_findings.package_name} AS "packageName", + ${security_findings.package_ecosystem} AS "packageEcosystem", + ${security_findings.manifest_path} AS "manifestPath", + ${security_findings.patched_version} AS "patchedVersion", + ${security_findings.ghsa_id} AS "ghsaId", + ${security_findings.cve_id} AS "cveId", + ${security_findings.cwe_ids} AS "cweIds", + ${security_findings.cvss_score} AS "cvssScore", + ${security_findings.dependabot_html_url} AS "dependabotHtmlUrl", + ${security_findings.first_detected_at} AS "firstDetectedAt", + ${security_findings.fixed_at} AS "fixedAt", + ${security_findings.sla_due_at} AS "slaDueAt", + ranked.canonical_id AS "canonicalFindingId" + ) + SELECT * FROM superseded + `); + const superseded = result.rows.map(row => supersededSecurityFindingResultSchema.parse(row)); + const occurredAt = new Date().toISOString(); + for (const finding of superseded) { + await insertSecurityFindingAuditEvent(tx, { + owner: toSecurityFindingAuditOwner(owner), + finding: toAuditEventFinding(finding), + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action: SecurityAuditLogAction.FindingSuperseded, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ownerAuditKeyPart(owner), + finding.findingId, + SecurityAuditLogAction.FindingSuperseded, + finding.canonicalFindingId, + ]), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + snapshotExtras: { canonical_finding_id: finding.canonicalFindingId }, + beforeState: { status: finding.previousStatus ?? 'open' }, + afterState: { + status: finding.effectiveStatus, + reason_code: 'superseded', + canonical_finding_id: finding.canonicalFindingId, + }, + metadata: { + repo_full_name: finding.repoFullName, + source_alert_number: finding.sourceId, + }, + }); + } + }); +} + +function toSecurityFindingAuditOwner(owner: SecurityNotificationOwner): SecurityFindingAuditOwner { + return isOrganizationNotificationOwner(owner) + ? { type: 'organization', organizationId: owner.organizationId } + : { type: 'user', userId: owner.userId }; +} + +function ownerAuditKeyPart(owner: SecurityNotificationOwner): string { + return isOrganizationNotificationOwner(owner) + ? `organization:${owner.organizationId}` + : `user:${owner.userId}`; +} + +function toAuditEventFinding( + finding: SupersededSecurityFindingResult +): SecurityFindingAuditEventFinding { + return { + id: finding.findingId, + owned_by_user_id: finding.ownedByUserId, + owned_by_organization_id: finding.ownedByOrganizationId, + source: finding.source, + source_id: finding.sourceId, + repo_full_name: finding.repoFullName, + title: finding.title, + severity: finding.effectiveSeverity, + status: finding.effectiveStatus, + package_name: finding.packageName, + package_ecosystem: finding.packageEcosystem, + manifest_path: finding.manifestPath, + patched_version: finding.patchedVersion, + ghsa_id: finding.ghsaId, + cve_id: finding.cveId, + cwe_ids: finding.cweIds, + cvss_score: finding.cvssScore, + dependabot_html_url: finding.dependabotHtmlUrl, + first_detected_at: finding.firstDetectedAt, + fixed_at: finding.fixedAt, + sla_due_at: finding.slaDueAt, + }; } async function selectNotificationRows( diff --git a/services/security-sync/src/sync.test.ts b/services/security-sync/src/sync.test.ts index e4e4456e73..1a9cf0ce55 100644 --- a/services/security-sync/src/sync.test.ts +++ b/services/security-sync/src/sync.test.ts @@ -325,6 +325,99 @@ describe('Worker GitHub auth-invalid sync', () => { expect(thrown).toEqual(new Error('GitHub API error 500 for acme/widgets')); expect(thrown).not.toHaveProperty('message', expect.stringContaining('Service unavailable')); }); + + it('records a v1 finding-created audit event when importing a new alert', async () => { + const { db } = createFakeDb(); + const gitTokenService = createGitTokenService(); + const auditRows: Array> = []; + const findingId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; + let executeCount = 0; + const mutableDb = db as unknown as { + execute: () => Promise<{ rows: unknown[] }>; + insert: () => { + values: (values: Record) => { + onConflictDoNothing: () => { returning: () => Promise> }; + onConflictDoUpdate: () => Promise; + }; + }; + }; + + mutableDb.execute = async () => { + executeCount++; + if (executeCount === 1) { + return { + rows: [ + { + findingId, + wasInserted: true, + previousStatus: null, + previousSeverity: null, + effectiveStatus: 'open', + effectiveSeverity: 'high', + findingCreatedAt: '2026-05-18T10:00:00.000Z', + ownedByUserId: 'user-1', + ownedByOrganizationId: null, + source: 'dependabot', + sourceId: '23', + repoFullName: 'acme/widgets', + title: 'Prototype pollution in lodash', + packageName: 'lodash', + packageEcosystem: 'npm', + manifestPath: 'package.json', + patchedVersion: '4.17.21', + ghsaId: 'GHSA-1234-5678-90ab', + cveId: null, + cweIds: ['CWE-1321'], + cvssScore: '7.5', + dependabotHtmlUrl: 'https://github.com/acme/widgets/security/dependabot/23', + firstDetectedAt: '2026-05-18T10:00:00.000Z', + fixedAt: null, + slaDueAt: '2026-06-17T10:00:00.000Z', + }, + ], + }; + } + return { rows: [] }; + }; + mutableDb.insert = () => ({ + values: (values: Record) => ({ + onConflictDoNothing: () => ({ + returning: async () => { + auditRows.push(values); + return [{ id: 'audit-row-1' }]; + }, + }), + onConflictDoUpdate: async () => undefined, + }), + }); + stubFetch(new Response(JSON.stringify([createDependabotAlert()]), { status: 200 })); + + await expect( + syncOwner({ + db: db as never, + gitTokenService, + owner: { userId: 'user-1' }, + runId: 'run-1', + }) + ).resolves.toMatchObject({ synced: 1, errors: 0 }); + + expect(auditRows).toHaveLength(1); + expect(auditRows[0]).toMatchObject({ + action: 'security.finding.created', + resource_type: 'security_finding', + resource_id: findingId, + finding_id: findingId, + event_key: + 'security_finding_audit:v1:user%3Auser-1:bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb:security.finding.created:2026-05-18T10%3A00%3A00Z', + schema_version: 1, + source_context: 'security_sync', + finding_snapshot: expect.objectContaining({ + finding_id: findingId, + source: 'dependabot', + repo_full_name: 'acme/widgets', + }), + }); + }); }); describe('Worker auto-analysis queue sync', () => { diff --git a/services/security-sync/src/sync.ts b/services/security-sync/src/sync.ts index 829c8218db..d46077acf2 100644 --- a/services/security-sync/src/sync.ts +++ b/services/security-sync/src/sync.ts @@ -23,11 +23,22 @@ import { security_analysis_owner_state, security_audit_log, } from '@kilocode/db/schema'; -import { SecurityAuditLogAction } from '@kilocode/db/schema-types'; +import { + SecurityAuditLogAction, + SecurityFindingAuditSourceContext, +} from '@kilocode/db/schema-types'; import { decideAutoAnalysisEligibility, type AutoAnalysisMinSeverity, } from '@kilocode/worker-utils/security-auto-analysis-policy'; +import { + SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + deriveSecurityFindingAuditEventKey, + insertSecurityFindingAuditEvent, + type SecurityFindingAuditEventFinding, + type SecurityFindingAuditOwner, + type SecurityFindingAuditWriterDb, +} from '@kilocode/worker-utils/security-finding-audit'; import { SecurityNotificationPolicySchema, isOpenFindingEligibleForNewFindingNotification, @@ -598,19 +609,46 @@ const securityFindingStatusSchema = z.enum([ SecurityFindingStatus.IGNORED, ]); +const dbTimestampSchema = z + .union([z.string(), z.date()]) + .transform(value => + value instanceof Date ? value.toISOString() : new Date(value).toISOString() + ); +const nullableDbTimestampSchema = dbTimestampSchema.nullable(); + const upsertSecurityFindingResultSchema = z.object({ findingId: z.string().uuid(), wasInserted: z.boolean(), previousStatus: securityFindingStatusSchema.nullable(), + previousSeverity: securitySeveritySchema.nullable(), effectiveStatus: securityFindingStatusSchema, - findingCreatedAt: z - .union([z.string(), z.date()]) - .transform(value => - value instanceof Date ? value.toISOString() : new Date(value).toISOString() - ), + effectiveSeverity: securitySeveritySchema, + findingCreatedAt: dbTimestampSchema, + ownedByUserId: z.string().nullable(), + ownedByOrganizationId: z.string().uuid().nullable(), + source: z.string(), + sourceId: z.string(), + repoFullName: z.string(), + title: z.string(), + packageName: z.string(), + packageEcosystem: z.string(), + manifestPath: z.string().nullable(), + patchedVersion: z.string().nullable(), + ghsaId: z.string().nullable(), + cveId: z.string().nullable(), + cweIds: z.array(z.string()).nullable(), + cvssScore: z.union([z.string(), z.number()]).nullable(), + dependabotHtmlUrl: z.string().nullable(), + firstDetectedAt: dbTimestampSchema, + fixedAt: nullableDbTimestampSchema, + slaDueAt: nullableDbTimestampSchema, }); type UpsertSecurityFindingResult = z.infer; +const supersededSecurityFindingResultSchema = upsertSecurityFindingResultSchema.extend({ + canonicalFindingId: z.string().uuid(), +}); +type SupersededSecurityFindingResult = z.infer; async function upsertSecurityFinding( db: Pick, @@ -629,7 +667,8 @@ async function upsertSecurityFinding( const result = await db.execute>(sql` WITH existing_match AS ( SELECT ${security_findings.id} AS id, - ${security_findings.status} AS previous_status + ${security_findings.status} AS previous_status, + ${security_findings.severity} AS previous_severity FROM ${security_findings} WHERE ${security_findings.repo_full_name} = ${repoFullName} AND ${security_findings.source} = ${finding.source} @@ -702,8 +741,11 @@ async function upsertSecurityFinding( ${sql.identifier(security_findings.severity.name)} = EXCLUDED.${sql.identifier(security_findings.severity.name)}, ${sql.identifier(security_findings.ghsa_id.name)} = EXCLUDED.${sql.identifier(security_findings.ghsa_id.name)}, ${sql.identifier(security_findings.cve_id.name)} = EXCLUDED.${sql.identifier(security_findings.cve_id.name)}, + ${sql.identifier(security_findings.package_name.name)} = EXCLUDED.${sql.identifier(security_findings.package_name.name)}, + ${sql.identifier(security_findings.package_ecosystem.name)} = EXCLUDED.${sql.identifier(security_findings.package_ecosystem.name)}, ${sql.identifier(security_findings.vulnerable_version_range.name)} = EXCLUDED.${sql.identifier(security_findings.vulnerable_version_range.name)}, ${sql.identifier(security_findings.patched_version.name)} = EXCLUDED.${sql.identifier(security_findings.patched_version.name)}, + ${sql.identifier(security_findings.manifest_path.name)} = EXCLUDED.${sql.identifier(security_findings.manifest_path.name)}, ${sql.identifier(security_findings.title.name)} = EXCLUDED.${sql.identifier(security_findings.title.name)}, ${sql.identifier(security_findings.description.name)} = EXCLUDED.${sql.identifier(security_findings.description.name)}, ${sql.identifier(security_findings.status.name)} = CASE @@ -731,7 +773,26 @@ async function upsertSecurityFinding( RETURNING ${security_findings.id} AS id, (xmax = 0) AS was_inserted, + ${security_findings.owned_by_user_id} AS owned_by_user_id, + ${security_findings.owned_by_organization_id} AS owned_by_organization_id, + ${security_findings.source} AS source, + ${security_findings.source_id} AS source_id, + ${security_findings.repo_full_name} AS repo_full_name, + ${security_findings.severity} AS effective_severity, ${security_findings.status} AS effective_status, + ${security_findings.package_name} AS package_name, + ${security_findings.package_ecosystem} AS package_ecosystem, + ${security_findings.manifest_path} AS manifest_path, + ${security_findings.patched_version} AS patched_version, + ${security_findings.ghsa_id} AS ghsa_id, + ${security_findings.cve_id} AS cve_id, + ${security_findings.title} AS title, + ${security_findings.cwe_ids} AS cwe_ids, + ${security_findings.cvss_score} AS cvss_score, + ${security_findings.dependabot_html_url} AS dependabot_html_url, + ${security_findings.first_detected_at} AS first_detected_at, + ${security_findings.fixed_at} AS fixed_at, + ${security_findings.sla_due_at} AS sla_due_at, ${security_findings.created_at} AS created_at ) SELECT @@ -741,8 +802,31 @@ async function upsertSecurityFinding( WHEN upserted.was_inserted THEN NULL::text ELSE COALESCE(existing_match.previous_status, upserted.effective_status) END AS "previousStatus", + CASE + WHEN upserted.was_inserted THEN NULL::text + ELSE COALESCE(existing_match.previous_severity, upserted.effective_severity) + END AS "previousSeverity", upserted.effective_status AS "effectiveStatus", - upserted.created_at AS "findingCreatedAt" + upserted.effective_severity AS "effectiveSeverity", + upserted.created_at AS "findingCreatedAt", + upserted.owned_by_user_id AS "ownedByUserId", + upserted.owned_by_organization_id AS "ownedByOrganizationId", + upserted.source AS "source", + upserted.source_id AS "sourceId", + upserted.repo_full_name AS "repoFullName", + upserted.title AS "title", + upserted.package_name AS "packageName", + upserted.package_ecosystem AS "packageEcosystem", + upserted.manifest_path AS "manifestPath", + upserted.patched_version AS "patchedVersion", + upserted.ghsa_id AS "ghsaId", + upserted.cve_id AS "cveId", + upserted.cwe_ids AS "cweIds", + upserted.cvss_score AS "cvssScore", + upserted.dependabot_html_url AS "dependabotHtmlUrl", + upserted.first_detected_at AS "firstDetectedAt", + upserted.fixed_at AS "fixedAt", + upserted.sla_due_at AS "slaDueAt" FROM upserted LEFT JOIN existing_match ON existing_match.id = upserted.id LIMIT 1 @@ -756,8 +840,28 @@ async function upsertSecurityFinding( ${security_findings.id} AS "findingId", false AS "wasInserted", ${security_findings.status} AS "previousStatus", + ${security_findings.severity} AS "previousSeverity", ${security_findings.status} AS "effectiveStatus", - ${security_findings.created_at} AS "findingCreatedAt" + ${security_findings.severity} AS "effectiveSeverity", + ${security_findings.created_at} AS "findingCreatedAt", + ${security_findings.owned_by_user_id} AS "ownedByUserId", + ${security_findings.owned_by_organization_id} AS "ownedByOrganizationId", + ${security_findings.source} AS "source", + ${security_findings.source_id} AS "sourceId", + ${security_findings.repo_full_name} AS "repoFullName", + ${security_findings.title} AS "title", + ${security_findings.package_name} AS "packageName", + ${security_findings.package_ecosystem} AS "packageEcosystem", + ${security_findings.manifest_path} AS "manifestPath", + ${security_findings.patched_version} AS "patchedVersion", + ${security_findings.ghsa_id} AS "ghsaId", + ${security_findings.cve_id} AS "cveId", + ${security_findings.cwe_ids} AS "cweIds", + ${security_findings.cvss_score} AS "cvssScore", + ${security_findings.dependabot_html_url} AS "dependabotHtmlUrl", + ${security_findings.first_detected_at} AS "firstDetectedAt", + ${security_findings.fixed_at} AS "fixedAt", + ${security_findings.sla_due_at} AS "slaDueAt" FROM ${security_findings} WHERE ${security_findings.repo_full_name} = ${repoFullName} AND ${security_findings.source} = ${finding.source} @@ -770,6 +874,162 @@ async function upsertSecurityFinding( return upsertSecurityFindingResultSchema.parse(recovered); } +function toSecurityFindingAuditOwner(owner: SecurityReviewOwner): SecurityFindingAuditOwner { + return isOrgOwner(owner) + ? { type: 'organization', organizationId: owner.organizationId } + : { type: 'user', userId: owner.userId }; +} + +function ownerAuditKeyPart(owner: SecurityReviewOwner): string { + return isOrgOwner(owner) ? `organization:${owner.organizationId}` : `user:${owner.userId}`; +} + +function toAuditEventFinding( + upserted: UpsertSecurityFindingResult +): SecurityFindingAuditEventFinding { + return { + id: upserted.findingId, + owned_by_user_id: upserted.ownedByUserId, + owned_by_organization_id: upserted.ownedByOrganizationId, + source: upserted.source, + source_id: upserted.sourceId, + repo_full_name: upserted.repoFullName, + title: upserted.title, + severity: upserted.effectiveSeverity, + status: upserted.effectiveStatus, + package_name: upserted.packageName, + package_ecosystem: upserted.packageEcosystem, + manifest_path: upserted.manifestPath, + patched_version: upserted.patchedVersion, + ghsa_id: upserted.ghsaId, + cve_id: upserted.cveId, + cwe_ids: upserted.cweIds, + cvss_score: upserted.cvssScore, + dependabot_html_url: upserted.dependabotHtmlUrl, + first_detected_at: upserted.firstDetectedAt, + fixed_at: upserted.fixedAt, + sla_due_at: upserted.slaDueAt, + }; +} + +async function writeSyncFindingAuditEvents( + db: SecurityFindingAuditWriterDb, + params: { + owner: SecurityReviewOwner; + upserted: UpsertSecurityFindingResult; + finding: ParsedSecurityFinding; + runId: string; + occurredAt: string; + } +): Promise { + const { owner, upserted, finding, runId, occurredAt } = params; + const auditOwner = toSecurityFindingAuditOwner(owner); + const auditFinding = toAuditEventFinding(upserted); + const keyBase = [ownerAuditKeyPart(owner), upserted.findingId]; + const commonMetadata = { + run_id: runId, + repo_full_name: upserted.repoFullName, + source_state: finding.raw_data.state, + source_alert_number: finding.source_id, + }; + + if (upserted.wasInserted) { + await insertSecurityFindingAuditEvent(db, { + owner: auditOwner, + finding: auditFinding, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action: SecurityAuditLogAction.FindingCreated, + occurredAt, + sourceOccurredAt: finding.first_detected_at, + eventKey: deriveSecurityFindingAuditEventKey([ + ...keyBase, + SecurityAuditLogAction.FindingCreated, + finding.first_detected_at, + ]), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + afterState: { + status: upserted.effectiveStatus, + severity: upserted.effectiveSeverity, + }, + metadata: commonMetadata, + }); + return; + } + + if ( + upserted.previousSeverity !== null && + upserted.previousSeverity !== upserted.effectiveSeverity + ) { + await insertSecurityFindingAuditEvent(db, { + owner: auditOwner, + finding: auditFinding, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action: SecurityAuditLogAction.FindingSeverityChanged, + occurredAt, + sourceOccurredAt: finding.raw_data.updated_at, + eventKey: deriveSecurityFindingAuditEventKey([ + ...keyBase, + SecurityAuditLogAction.FindingSeverityChanged, + finding.raw_data.updated_at, + upserted.previousSeverity, + upserted.effectiveSeverity, + ]), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + beforeState: { severity: upserted.previousSeverity }, + afterState: { severity: upserted.effectiveSeverity }, + metadata: commonMetadata, + }); + } + + if (upserted.previousStatus !== null && upserted.previousStatus !== upserted.effectiveStatus) { + const action = getSyncStatusAuditAction(finding.raw_data.state); + const sourceOccurredAt = getStatusSourceOccurredAt(finding); + await insertSecurityFindingAuditEvent(db, { + owner: auditOwner, + finding: auditFinding, + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action, + occurredAt, + sourceOccurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ...keyBase, + action, + sourceOccurredAt, + upserted.previousStatus, + upserted.effectiveStatus, + ]), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + beforeState: { status: upserted.previousStatus }, + afterState: { + status: upserted.effectiveStatus, + ...(finding.ignored_reason ? { reason_code: finding.ignored_reason } : {}), + ...(finding.fixed_at ? { fixed_at: new Date(finding.fixed_at).toISOString() } : {}), + }, + metadata: commonMetadata, + }); + } +} + +function getSyncStatusAuditAction(state: DependabotAlertState): SecurityAuditLogAction { + if (state === 'auto_dismissed') return SecurityAuditLogAction.FindingAutoDismissed; + if (state === 'dismissed') return SecurityAuditLogAction.FindingDismissed; + return SecurityAuditLogAction.FindingStatusChange; +} + +function getStatusSourceOccurredAt(finding: ParsedSecurityFinding): string { + if (finding.raw_data.state === 'auto_dismissed') { + return ( + finding.raw_data.auto_dismissed_at ?? + finding.raw_data.dismissed_at ?? + finding.raw_data.updated_at + ); + } + if (finding.raw_data.state === 'dismissed') { + return finding.raw_data.dismissed_at ?? finding.raw_data.updated_at; + } + return finding.fixed_at ?? finding.raw_data.updated_at; +} + async function insertStagedNewFindingNotifications( db: Pick, params: { @@ -1005,15 +1265,17 @@ export async function syncAutoAnalysisQueueForFinding( type SupersedeResult = { count: number; supersededFindingIds: string[] }; async function supersedeDuplicateFindings( - db: Pick, + db: Pick & SecurityFindingAuditWriterDb, repoFullName: string, owner: SecurityReviewOwner ): Promise { const ownerPartitionColumn = findingOwnerPartitionColumn(owner); - const result = await db.execute<{ id: string }>(sql` + const result = await db.execute>(sql` WITH ranked AS ( SELECT id, + status AS previous_status, + severity AS previous_severity, ROW_NUMBER() OVER ( PARTITION BY ${ownerPartitionColumn}, repo_full_name, source, ghsa_id, package_name, manifest_path ORDER BY CASE WHEN source_id ~ '^[0-9]+$' THEN source_id::int ELSE 0 END DESC @@ -1039,14 +1301,81 @@ async function supersedeDuplicateFindings( FROM ranked WHERE security_findings.id = ranked.id AND ranked.rn > 1 - RETURNING security_findings.id + RETURNING + security_findings.id AS "findingId", + false AS "wasInserted", + ranked.previous_status AS "previousStatus", + ranked.previous_severity AS "previousSeverity", + security_findings.status AS "effectiveStatus", + security_findings.severity AS "effectiveSeverity", + security_findings.created_at AS "findingCreatedAt", + security_findings.owned_by_user_id AS "ownedByUserId", + security_findings.owned_by_organization_id AS "ownedByOrganizationId", + security_findings.source AS "source", + security_findings.source_id AS "sourceId", + security_findings.repo_full_name AS "repoFullName", + security_findings.title AS "title", + security_findings.package_name AS "packageName", + security_findings.package_ecosystem AS "packageEcosystem", + security_findings.manifest_path AS "manifestPath", + security_findings.patched_version AS "patchedVersion", + security_findings.ghsa_id AS "ghsaId", + security_findings.cve_id AS "cveId", + security_findings.cwe_ids AS "cweIds", + security_findings.cvss_score AS "cvssScore", + security_findings.dependabot_html_url AS "dependabotHtmlUrl", + security_findings.first_detected_at AS "firstDetectedAt", + security_findings.fixed_at AS "fixedAt", + security_findings.sla_due_at AS "slaDueAt", + ranked.canonical_id AS "canonicalFindingId" ) - SELECT id FROM superseded + SELECT * FROM superseded `); - const supersededFindingIds = result.rows.map(r => r.id); + const superseded = result.rows.map(row => supersededSecurityFindingResultSchema.parse(row)); + const occurredAt = new Date().toISOString(); + for (const finding of superseded) { + await writeSupersededFindingAuditEvent(db, { owner, finding, occurredAt }); + } + const supersededFindingIds = superseded.map(r => r.findingId); return { count: supersededFindingIds.length, supersededFindingIds }; } +async function writeSupersededFindingAuditEvent( + db: SecurityFindingAuditWriterDb, + params: { + owner: SecurityReviewOwner; + finding: SupersededSecurityFindingResult; + occurredAt: string; + } +): Promise { + const { owner, finding, occurredAt } = params; + await insertSecurityFindingAuditEvent(db, { + owner: toSecurityFindingAuditOwner(owner), + finding: toAuditEventFinding(finding), + actor: SECURITY_FINDING_AUDIT_SYSTEM_ACTOR, + action: SecurityAuditLogAction.FindingSuperseded, + occurredAt, + eventKey: deriveSecurityFindingAuditEventKey([ + ownerAuditKeyPart(owner), + finding.findingId, + SecurityAuditLogAction.FindingSuperseded, + finding.canonicalFindingId, + ]), + sourceContext: SecurityFindingAuditSourceContext.SecuritySync, + snapshotExtras: { canonical_finding_id: finding.canonicalFindingId }, + beforeState: { status: finding.previousStatus ?? SecurityFindingStatus.OPEN }, + afterState: { + status: finding.effectiveStatus, + reason_code: 'superseded', + canonical_finding_id: finding.canonicalFindingId, + }, + metadata: { + repo_full_name: finding.repoFullName, + source_alert_number: finding.sourceId, + }, + }); +} + /** * Remove superseded findings from the auto-analysis queue so the worker * doesn't analyze findings that are no longer open. @@ -1112,7 +1441,7 @@ async function writeAuditLog( params: { owner: SecurityReviewOwner; actor?: { id: string; email?: string | null; name?: string | null }; - action: SecurityAuditLogAction; + action: SecurityAuditLogAction.SyncCompleted; resource_type: string; resource_id: string; metadata: Record; @@ -1312,6 +1641,7 @@ export async function syncOwner(params: { gitTokenService, installationId: config.installationId, owner, + runId, platformIntegrationId: config.platformIntegrationId, repoFullName, slaConfig: config.slaConfig, @@ -1475,6 +1805,7 @@ async function syncRepo(params: { gitTokenService: GitTokenService; installationId: string; owner: SecurityReviewOwner; + runId: string; platformIntegrationId: string; repoFullName: string; slaConfig: SecurityAgentConfig; @@ -1487,6 +1818,7 @@ async function syncRepo(params: { gitTokenService, installationId, owner, + runId, platformIntegrationId, repoFullName, slaConfig, @@ -1575,6 +1907,14 @@ async function syncRepo(params: { slaDueAt, }); + await writeSyncFindingAuditEvents(tx, { + owner, + upserted, + finding, + runId, + occurredAt: new Date().toISOString(), + }); + if ( notificationPolicy && isOpenFindingEligibleForNewFindingNotification({ From ab275929b95c02f7e2ceca79b826db6c03afecec Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Wed, 17 Jun 2026 20:42:18 +0200 Subject: [PATCH 2/3] fix(security-agent): harden auto-dismiss review feedback --- .../services/auto-dismiss-service.test.ts | 18 ++++++++++++++++++ .../services/auto-dismiss-service.ts | 18 ++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts b/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts index 408a60a45c..bc61eeb933 100644 --- a/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts +++ b/apps/web/src/lib/security-agent/services/auto-dismiss-service.test.ts @@ -576,5 +576,23 @@ describe('maybeAutoDismissAnalysis', () => { byConfidence: { high: 1, medium: 0, low: 0 }, }); }); + + it('excludes malformed triage confidence from eligibility counts', async () => { + mockBulkFindings.push({ + id: 'malformed-confidence', + analysis: { + analyzedAt: '2026-06-16T10:00:00.000Z', + triage: { + ...triageResult, + confidence: 'certain', + }, + } as unknown as SecurityFindingAnalysis, + }); + + await expect(countEligibleForAutoDismiss({ userId: 'user-1' }, 'user-1')).resolves.toEqual({ + eligible: 0, + byConfidence: { high: 0, medium: 0, low: 0 }, + }); + }); }); }); diff --git a/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts b/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts index f58cef9aaa..1bcdf7f6a1 100644 --- a/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts +++ b/apps/web/src/lib/security-agent/services/auto-dismiss-service.ts @@ -31,7 +31,6 @@ import { deriveSecurityFindingAuditEventKey, insertSecurityFindingAuditEvent, type SecurityFindingAuditActor, - type SecurityFindingAuditEventFinding, type SecurityFindingAuditHumanActor, type SecurityFindingAuditOwner, } from '@kilocode/worker-utils/security-finding-audit'; @@ -39,6 +38,13 @@ import { parseDependabotDismissalTarget } from '@kilocode/worker-utils/dependabo const log = sentryLogger('security-agent:auto-dismiss', 'info'); const logError = sentryLogger('security-agent:auto-dismiss', 'error'); +const TRIAGE_CONFIDENCES = ['high', 'medium', 'low'] as const; + +type TriageConfidence = (typeof TRIAGE_CONFIDENCES)[number]; + +function isTriageConfidence(value: unknown): value is TriageConfidence { + return TRIAGE_CONFIDENCES.includes(value as TriageConfidence); +} /** * Convert SecurityReviewOwner + userId to Owner format for config lookups. @@ -81,12 +87,6 @@ function ownerFindingCondition(owner: SecurityReviewOwner) { throw new Error('Invalid owner: must have either organizationId or userId'); } -function toAuditFinding( - finding: SecurityFindingAuditEventFinding -): SecurityFindingAuditEventFinding { - return finding; -} - /** * Dismiss a security finding with the given reason */ @@ -158,7 +158,7 @@ async function dismissFindingWithAuditEvent( await insertSecurityFindingAuditEvent(tx, { owner: toAuditOwner(params.owner), - finding: toAuditFinding(updatedFinding), + finding: updatedFinding, actor: params.actor, action: SecurityAuditLogAction.FindingAutoDismissed, occurredAt, @@ -554,6 +554,8 @@ export async function countEligibleForAutoDismiss( continue; } + if (!isTriageConfidence(triage.confidence)) continue; + eligible++; byConfidence[triage.confidence]++; } From 7174b18d822f689a72b3d320d70b7413e0e06eca Mon Sep 17 00:00:00 2001 From: Jean du Plessis Date: Wed, 17 Jun 2026 22:01:49 +0200 Subject: [PATCH 3/3] fix(security-agent): address audit report review feedback --- .env.local.example | 2 + ENVIRONMENT.md | 1 + apps/web/.env.development.local.example | 3 + apps/web/.env.test | 1 + .../db/security-audit-report.test.ts | 13 +-- .../db/security-audit-report.ts | 45 +++++---- .../src/security-finding-audit.ts | 56 ++++++++--- services/security-sync/src/sync.test.ts | 95 +++++++++++++++++++ 8 files changed, 174 insertions(+), 42 deletions(-) diff --git a/.env.local.example b/.env.local.example index 0192743dca..1e7ab79c21 100644 --- a/.env.local.example +++ b/.env.local.example @@ -148,6 +148,8 @@ USER_DEPLOYMENTS_API_BASE_URL= USER_DEPLOYMENTS_API_AUTH_KEY= USER_DEPLOYMENTS_DISPATCHER_AUTH_KEY= USER_DEPLOYMENTS_ENV_VARS_PUBLIC_KEY= +# Security Agent Audit Reports +SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START=2026-06-17T00:00:00.000Z # Slack integration SLACK_CLIENT_ID= SLACK_CLIENT_SECRET= diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index cf56279507..e23999c0ad 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -28,6 +28,7 @@ This document lists all environment variables used in the Kilo Code cloud monore - `JEST_SILENT` - When `false`, shows verbose Jest output; read in `apps/web/jest.config.ts` and `apps/web/.env.test`. [SERVER] - `JEST_WORKER_ID` - Set by Jest to identify the current worker thread; used by db connection pooling and libraries to handle worker-specific state. [SERVER] - `IS_SCRIPT` - Set to `'true'` by `apps/web/src/scripts/index.ts` to indicate a script-mode run (bypasses web server logic). Used by Drizzle in `packages/db/src/database-url.ts`. [SERVER] +- `SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START` - Earliest ISO timestamp from which Security Agent Audit Report event coverage is reliable. [SERVER] ### Analytics & Monitoring diff --git a/apps/web/.env.development.local.example b/apps/web/.env.development.local.example index ae816bb2c2..d7ef403376 100644 --- a/apps/web/.env.development.local.example +++ b/apps/web/.env.development.local.example @@ -28,6 +28,9 @@ SECURITY_SYNC_WORKER_URL=http://localhost:8812 # @url cloudflare-security-auto-analysis SECURITY_AUTO_ANALYSIS_WORKER_URL=http://localhost:8797 +# Earliest timestamp from which Security Agent Audit Report activity coverage is reliable. +SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START=2026-06-17T00:00:00.000Z + # @url cloudflare-app-builder APP_BUILDER_URL=http://localhost:8790 diff --git a/apps/web/.env.test b/apps/web/.env.test index e466ad2c92..e24e702e51 100644 --- a/apps/web/.env.test +++ b/apps/web/.env.test @@ -67,6 +67,7 @@ ARTIFICIAL_ANALYSIS_API_KEY='mock-key' QDRANT_HOST= QDRANT_API_KEY= BYOK_ENCRYPTION_KEY='qF/nUaiikTlbnhMXhU1p3mexS/zqmJtpRFtZ82dKotk=' +SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START=2026-06-17T00:00:00.000Z # Test RSA public key for agent environment profile secrets (base64 encoded) AGENT_ENV_VARS_PUBLIC_KEY='LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE0RitnQ2JYTFIyZEt1bU1SQmdKNApBOW5OVWlkNE84ZGpGUG1XUmgrcDFJWWYrQlg0U3RLQjlIK1BBRUtUanJCZnduU3BIUEViSDcxNFpuS0FGeHYvClcxQmFoZDFvWGppNXFDSFM0WXdVMDh5OUpVWVdPNDhLZVVKSk9CM2pOVndHU01RQVNXWmhLTityUko5SzRSalQKdzV1dlNZL24xbFF4Qk1NVG14Z1N5dEdtbnAwYlFVdEIvZUlPWEx6ZGlVYjZ6QUxIMkxweWZ6Nm83N3ZFYmxPMApLQmVhd0g5WmlVSHFRQnR3dDV0UGhoUHd5eWJBdVFLQldlV2dEemFaSHg0NFA1QkliMlAvRVBkOEhXVzUrK1RxCnRTSWFWcUt0aE5kcFl5NFRGVjltSmlRRVVGc0xyaFhOWC9qMGhQR3hiSkF4VVJoMnpzbkZESWRTSHE2aDJjTXIKUlFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg==' diff --git a/apps/web/src/lib/security-agent/db/security-audit-report.test.ts b/apps/web/src/lib/security-agent/db/security-audit-report.test.ts index 0de563fb6a..3160a3e387 100644 --- a/apps/web/src/lib/security-agent/db/security-audit-report.test.ts +++ b/apps/web/src/lib/security-agent/db/security-audit-report.test.ts @@ -87,7 +87,6 @@ function row(overrides: Partial): AuditReportRow { id: '00000000-0000-4000-8000-000000000001', action: SecurityAuditLogAction.FindingCreated, actor_id: null, - actor_email: null, actor_name: null, actor_type: SecurityAuditLogActorType.System, before_state: null, @@ -125,9 +124,15 @@ describe('resolveSecurityAgentAuditReliableCoverageStart', () => { expect(() => resolveSecurityAgentAuditReliableCoverageStart('')).toThrow( 'SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START is required' ); + expect(() => resolveSecurityAgentAuditReliableCoverageStart('')).toThrow( + SecurityAgentAuditReportQueryError + ); expect(() => resolveSecurityAgentAuditReliableCoverageStart('2026-06-17')).toThrow( 'SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START must be an ISO timestamp' ); + expect(() => resolveSecurityAgentAuditReliableCoverageStart('2026-06-17')).toThrow( + SecurityAgentAuditReportQueryError + ); }); }); @@ -360,7 +365,6 @@ describe('buildSecurityAgentAuditReportFromRows', () => { id: '00000000-0000-4000-8000-000000000003', action: SecurityAuditLogAction.FindingDismissed, actor_id: 'admin-user', - actor_email: 'ops@kilocode.ai', actor_name: 'Ops User', actor_type: SecurityAuditLogActorType.KiloAdmin, after_state: { status: 'ignored', token: 'secret-token' }, @@ -423,26 +427,23 @@ describe('buildSecurityAgentAuditReportFromRows', () => { expect(report.hasLegacySupplementalActivity).toBe(true); }); - it('masks actors from persisted classification without inferring from email', () => { + it('masks actors from persisted classification', () => { const rows = [ row({ id: '00000000-0000-4000-8000-000000000011', actor_id: 'admin-user', - actor_email: 'operator@example.com', actor_name: 'Internal Operator', actor_type: SecurityAuditLogActorType.KiloAdmin, }), row({ id: '00000000-0000-4000-8000-000000000012', actor_id: 'customer-user', - actor_email: 'customer@kilocode.ai', actor_name: 'Customer User', actor_type: SecurityAuditLogActorType.CustomerUser, }), row({ id: '00000000-0000-4000-8000-000000000013', actor_id: 'legacy-user', - actor_email: 'legacy@example.com', actor_name: 'Legacy User', actor_type: null, }), diff --git a/apps/web/src/lib/security-agent/db/security-audit-report.ts b/apps/web/src/lib/security-agent/db/security-audit-report.ts index 6457472d4d..8a7bf0dbe0 100644 --- a/apps/web/src/lib/security-agent/db/security-audit-report.ts +++ b/apps/web/src/lib/security-agent/db/security-audit-report.ts @@ -19,6 +19,22 @@ export const SECURITY_AGENT_AUDIT_REPORT_REQUEST_TIMEOUT_MS = 25_000; export const SECURITY_AGENT_AUDIT_REPORT_QUERY_TIMEOUT_MS = 8_000; const DateOnlySchema = z.string().regex(/^\d{4}-\d{2}-\d{2}$/); +type SecurityAgentAuditReportFailureStage = + | 'data_through' + | 'count' + | 'budget' + | 'scan' + | 'request'; + +export class SecurityAgentAuditReportQueryError extends Error { + constructor( + message = 'Report query did not finish', + readonly stage: SecurityAgentAuditReportFailureStage = 'request' + ) { + super(message); + this.name = 'SecurityAgentAuditReportQueryError'; + } +} export const SecurityAgentAuditReportInputSchema = z.object({ startDate: DateOnlySchema.optional(), @@ -31,12 +47,18 @@ export function resolveSecurityAgentAuditReliableCoverageStart( value = process.env.SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START ): string { if (!value) { - throw new Error('SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START is required'); + throw new SecurityAgentAuditReportQueryError( + 'SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START is required', + 'request' + ); } const parsed = ReliableCoverageStartSchema.safeParse(value); if (!parsed.success) { - throw new Error('SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START must be an ISO timestamp'); + throw new SecurityAgentAuditReportQueryError( + 'SECURITY_AGENT_AUDIT_RELIABLE_COVERAGE_START must be an ISO timestamp', + 'request' + ); } return new Date(parsed.data).toISOString(); @@ -133,7 +155,6 @@ type AuditReportRow = { id: string; action: SecurityAuditLogAction; actor_id: string | null; - actor_email: string | null; actor_name: string | null; actor_type: SecurityAuditLogActorType | null; before_state: Record | null; @@ -154,13 +175,6 @@ type AuditReportCursor = { id: string; }; -type SecurityAgentAuditReportFailureStage = - | 'data_through' - | 'count' - | 'budget' - | 'scan' - | 'request'; - const ACTION_LABELS: Partial> = { [SecurityAuditLogAction.FindingCreated]: 'Imported', [SecurityAuditLogAction.FindingSeverityChanged]: 'Severity changed', @@ -284,16 +298,6 @@ const EMPTY_EVIDENCE_FIELDS = { const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; -export class SecurityAgentAuditReportQueryError extends Error { - constructor( - message = 'Report query did not finish', - readonly stage: SecurityAgentAuditReportFailureStage = 'request' - ) { - super(message); - this.name = 'SecurityAgentAuditReportQueryError'; - } -} - export function defaultSecurityAgentAuditReportInput( now = new Date() ): Required { @@ -587,7 +591,6 @@ async function scanReportPage( id: security_audit_log.id, action: security_audit_log.action, actor_id: security_audit_log.actor_id, - actor_email: security_audit_log.actor_email, actor_name: security_audit_log.actor_name, actor_type: security_audit_log.actor_type, before_state: security_audit_log.before_state, diff --git a/packages/worker-utils/src/security-finding-audit.ts b/packages/worker-utils/src/security-finding-audit.ts index a105fd15a5..56eb2f1f65 100644 --- a/packages/worker-utils/src/security-finding-audit.ts +++ b/packages/worker-utils/src/security-finding-audit.ts @@ -263,28 +263,28 @@ export function buildSecurityFindingAuditSnapshot( ): SecurityFindingAuditSnapshot { const snapshot = { finding_id: finding.id, - source: finding.source, - source_id: finding.source_id, - repo_full_name: finding.repo_full_name, - title: finding.title, + source: auditText(finding.source), + source_id: auditText(finding.source_id), + repo_full_name: auditText(finding.repo_full_name), + title: auditText(finding.title), severity: finding.severity, - status: finding.status, + status: auditText(finding.status), first_detected_at: normalizeAuditTimestamp(finding.first_detected_at), fixed_at: normalizeAuditTimestamp(finding.fixed_at) ?? null, sla_due_at: normalizeAuditTimestamp(finding.sla_due_at) ?? null, ...pickPresent({ - package_name: finding.package_name, - package_ecosystem: finding.package_ecosystem, - manifest_path: finding.manifest_path, - patched_version: finding.patched_version, - ghsa_id: finding.ghsa_id, - cve_id: finding.cve_id, - cwe_ids: finding.cwe_ids, - cvss_score: finding.cvss_score, - dependabot_html_url: finding.dependabot_html_url, + package_name: optionalAuditText(finding.package_name), + package_ecosystem: optionalAuditText(finding.package_ecosystem), + manifest_path: optionalAuditText(finding.manifest_path), + patched_version: optionalAuditText(finding.patched_version), + ghsa_id: optionalAuditText(finding.ghsa_id), + cve_id: optionalAuditText(finding.cve_id), + cwe_ids: finding.cwe_ids?.map(auditText), + cvss_score: auditCvssScore(finding.cvss_score), + dependabot_html_url: auditUrl(finding.dependabot_html_url), canonical_finding_id: extras.canonical_finding_id, remediation_attempt_id: extras.remediation_attempt_id, - session_id: extras.session_id ?? finding.session_id, + session_id: optionalAuditText(extras.session_id ?? finding.session_id), notification_id: extras.notification_id, }), }; @@ -424,6 +424,32 @@ const DISALLOWED_AUDIT_JSON_KEY_PATTERN = /(^|_)(actor|recipient|email|prompt|rawmarkdown|raw_markdown|transcript|assistant|provider_response|authorization|auth_header|cookie|token|secret|password|credential|headers|raw_error)(_|$)/i; const EMAIL_VALUE_PATTERN = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i; +function auditText(value: string): string { + return value.replace(EMAIL_VALUE_PATTERN, '[redacted-email]'); +} + +function optionalAuditText(value: string | null | undefined): string | undefined { + if (!value) return undefined; + return auditText(value); +} + +function auditCvssScore(value: string | number | null | undefined): string | number | undefined { + if (value === null || value === undefined || value === '') return undefined; + if (typeof value === 'number') return Number.isFinite(value) ? value : undefined; + return auditText(value); +} + +function auditUrl(value: string | null | undefined): string | undefined { + if (!value || EMAIL_VALUE_PATTERN.test(value)) return undefined; + try { + const url = new URL(value); + if (url.protocol !== 'https:' && url.protocol !== 'http:') return undefined; + return url.toString(); + } catch { + return undefined; + } +} + function validateSafeAuditJson( value: unknown, ctx: z.RefinementCtx, diff --git a/services/security-sync/src/sync.test.ts b/services/security-sync/src/sync.test.ts index 1a9cf0ce55..bb7f0d427a 100644 --- a/services/security-sync/src/sync.test.ts +++ b/services/security-sync/src/sync.test.ts @@ -418,6 +418,101 @@ describe('Worker GitHub auth-invalid sync', () => { }), }); }); + + it('does not let unsafe source snapshot values block finding sync', async () => { + const { db } = createFakeDb(); + const gitTokenService = createGitTokenService(); + const auditRows: Array> = []; + const findingId = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'; + let executeCount = 0; + const mutableDb = db as unknown as { + execute: () => Promise<{ rows: unknown[] }>; + insert: () => { + values: (values: Record) => { + onConflictDoNothing: () => { returning: () => Promise> }; + onConflictDoUpdate: () => Promise; + }; + }; + }; + + mutableDb.execute = async () => { + executeCount++; + if (executeCount === 1) { + return { + rows: [ + { + findingId, + wasInserted: true, + previousStatus: null, + previousSeverity: null, + effectiveStatus: 'open', + effectiveSeverity: 'high', + findingCreatedAt: '2026-05-18T10:00:00.000Z', + ownedByUserId: 'user-1', + ownedByOrganizationId: null, + source: 'dependabot', + sourceId: '23', + repoFullName: 'acme/widgets', + title: 'Contact security@example.com about lodash', + packageName: 'lodash', + packageEcosystem: 'npm', + manifestPath: 'package.json', + patchedVersion: '4.17.21', + ghsaId: 'GHSA-1234-5678-90ab', + cveId: null, + cweIds: ['CWE-1321'], + cvssScore: '7.5', + dependabotHtmlUrl: 'not a valid url', + firstDetectedAt: '2026-05-18T10:00:00.000Z', + fixedAt: null, + slaDueAt: '2026-06-17T10:00:00.000Z', + }, + ], + }; + } + return { rows: [] }; + }; + mutableDb.insert = () => ({ + values: (values: Record) => ({ + onConflictDoNothing: () => ({ + returning: async () => { + auditRows.push(values); + return [{ id: 'audit-row-1' }]; + }, + }), + onConflictDoUpdate: async () => undefined, + }), + }); + stubFetch( + new Response( + JSON.stringify([ + createDependabotAlert({ + html_url: 'not a valid url', + security_advisory: { + ...createDependabotAlert().security_advisory, + summary: 'Contact security@example.com about lodash', + }, + }), + ]), + { status: 200 } + ) + ); + + await expect( + syncOwner({ + db: db as never, + gitTokenService, + owner: { userId: 'user-1' }, + runId: 'run-1', + }) + ).resolves.toMatchObject({ synced: 1, errors: 0 }); + + expect(auditRows).toHaveLength(1); + expect(auditRows[0]?.finding_snapshot).toMatchObject({ + title: 'Contact [redacted-email] about lodash', + }); + expect(auditRows[0]?.finding_snapshot).not.toHaveProperty('dependabot_html_url'); + }); }); describe('Worker auto-analysis queue sync', () => {