@cat-factory/node-server 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +297 -0
- package/dist/config.js.map +1 -0
- package/dist/container.d.ts +88 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +937 -0
- package/dist/container.js.map +1 -0
- package/dist/db/client.d.ts +13 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +21 -0
- package/dist/db/client.js.map +1 -0
- package/dist/db/migrate.d.ts +12 -0
- package/dist/db/migrate.d.ts.map +1 -0
- package/dist/db/migrate.js +40 -0
- package/dist/db/migrate.js.map +1 -0
- package/dist/db/schema.d.ts +7858 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +928 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/environments.d.ts +11 -0
- package/dist/environments.d.ts.map +1 -0
- package/dist/environments.js +31 -0
- package/dist/environments.js.map +1 -0
- package/dist/execution/bootstrapRunner.d.ts +27 -0
- package/dist/execution/bootstrapRunner.d.ts.map +1 -0
- package/dist/execution/bootstrapRunner.js +79 -0
- package/dist/execution/bootstrapRunner.js.map +1 -0
- package/dist/execution/config.d.ts +37 -0
- package/dist/execution/config.d.ts.map +1 -0
- package/dist/execution/config.js +86 -0
- package/dist/execution/config.js.map +1 -0
- package/dist/execution/drive.d.ts +6 -0
- package/dist/execution/drive.d.ts.map +1 -0
- package/dist/execution/drive.js +13 -0
- package/dist/execution/drive.js.map +1 -0
- package/dist/execution/pgBossRunner.d.ts +82 -0
- package/dist/execution/pgBossRunner.d.ts.map +1 -0
- package/dist/execution/pgBossRunner.js +163 -0
- package/dist/execution/pgBossRunner.js.map +1 -0
- package/dist/gateways.d.ts +4 -0
- package/dist/gateways.d.ts.map +1 -0
- package/dist/gateways.js +91 -0
- package/dist/gateways.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +9 -0
- package/dist/main.js.map +1 -0
- package/dist/modelProvider.d.ts +6 -0
- package/dist/modelProvider.d.ts.map +1 -0
- package/dist/modelProvider.js +72 -0
- package/dist/modelProvider.js.map +1 -0
- package/dist/realtime.d.ts +62 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +171 -0
- package/dist/realtime.js.map +1 -0
- package/dist/recurring.d.ts +11 -0
- package/dist/recurring.d.ts.map +1 -0
- package/dist/recurring.js +33 -0
- package/dist/recurring.js.map +1 -0
- package/dist/repositories/bootstrap.d.ts +25 -0
- package/dist/repositories/bootstrap.d.ts.map +1 -0
- package/dist/repositories/bootstrap.js +280 -0
- package/dist/repositories/bootstrap.js.map +1 -0
- package/dist/repositories/containerExecution.d.ts +33 -0
- package/dist/repositories/containerExecution.d.ts.map +1 -0
- package/dist/repositories/containerExecution.js +199 -0
- package/dist/repositories/containerExecution.js.map +1 -0
- package/dist/repositories/documents.d.ts +31 -0
- package/dist/repositories/documents.d.ts.map +1 -0
- package/dist/repositories/documents.js +176 -0
- package/dist/repositories/documents.js.map +1 -0
- package/dist/repositories/drizzle.d.ts +105 -0
- package/dist/repositories/drizzle.d.ts.map +1 -0
- package/dist/repositories/drizzle.js +1872 -0
- package/dist/repositories/drizzle.js.map +1 -0
- package/dist/repositories/environments.d.ts +23 -0
- package/dist/repositories/environments.d.ts.map +1 -0
- package/dist/repositories/environments.js +162 -0
- package/dist/repositories/environments.js.map +1 -0
- package/dist/repositories/fragments.d.ts +23 -0
- package/dist/repositories/fragments.d.ts.map +1 -0
- package/dist/repositories/fragments.js +190 -0
- package/dist/repositories/fragments.js.map +1 -0
- package/dist/repositories/github.d.ts +53 -0
- package/dist/repositories/github.d.ts.map +1 -0
- package/dist/repositories/github.js +441 -0
- package/dist/repositories/github.js.map +1 -0
- package/dist/repositories/localModelEndpoint.d.ts +12 -0
- package/dist/repositories/localModelEndpoint.d.ts.map +1 -0
- package/dist/repositories/localModelEndpoint.js +75 -0
- package/dist/repositories/localModelEndpoint.js.map +1 -0
- package/dist/repositories/notifications.d.ts +11 -0
- package/dist/repositories/notifications.d.ts.map +1 -0
- package/dist/repositories/notifications.js +88 -0
- package/dist/repositories/notifications.js.map +1 -0
- package/dist/repositories/personalSubscription.d.ts +22 -0
- package/dist/repositories/personalSubscription.d.ts.map +1 -0
- package/dist/repositories/personalSubscription.js +159 -0
- package/dist/repositories/personalSubscription.js.map +1 -0
- package/dist/repositories/providerApiKey.d.ts +18 -0
- package/dist/repositories/providerApiKey.d.ts.map +1 -0
- package/dist/repositories/providerApiKey.js +111 -0
- package/dist/repositories/providerApiKey.js.map +1 -0
- package/dist/repositories/providerSubscription.d.ts +16 -0
- package/dist/repositories/providerSubscription.d.ts.map +1 -0
- package/dist/repositories/providerSubscription.js +88 -0
- package/dist/repositories/providerSubscription.js.map +1 -0
- package/dist/repositories/slack.d.ts +23 -0
- package/dist/repositories/slack.d.ts.map +1 -0
- package/dist/repositories/slack.js +150 -0
- package/dist/repositories/slack.js.map +1 -0
- package/dist/repositories/tasks.d.ts +24 -0
- package/dist/repositories/tasks.d.ts.map +1 -0
- package/dist/repositories/tasks.js +194 -0
- package/dist/repositories/tasks.js.map +1 -0
- package/dist/retention.d.ts +38 -0
- package/dist/retention.d.ts.map +1 -0
- package/dist/retention.js +53 -0
- package/dist/retention.js.map +1 -0
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +13 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +138 -0
- package/dist/server.js.map +1 -0
- package/dist/tasks/JiraProvider.d.ts +27 -0
- package/dist/tasks/JiraProvider.d.ts.map +1 -0
- package/dist/tasks/JiraProvider.js +79 -0
- package/dist/tasks/JiraProvider.js.map +1 -0
- package/drizzle/20260622175812_flashy_maginty/migration.sql +689 -0
- package/drizzle/20260622175812_flashy_maginty/snapshot.json +8318 -0
- package/drizzle/20260623172634_loud_wallop/migration.sql +11 -0
- package/drizzle/20260623172634_loud_wallop/snapshot.json +8439 -0
- package/drizzle/20260623174706_acoustic_zemo/migration.sql +16 -0
- package/drizzle/20260623174706_acoustic_zemo/snapshot.json +8506 -0
- package/drizzle/20260623184400_silent_cardiac/migration.sql +24 -0
- package/drizzle/20260623184400_silent_cardiac/snapshot.json +8639 -0
- package/drizzle/20260623205323_quick_arclight/migration.sql +1 -0
- package/drizzle/20260623205323_quick_arclight/snapshot.json +8963 -0
- package/drizzle/20260623221910_black_zombie/migration.sql +22 -0
- package/drizzle/20260623221910_black_zombie/snapshot.json +9189 -0
- package/drizzle/20260624131343_far_lily_hollister/migration.sql +3 -0
- package/drizzle/20260624131343_far_lily_hollister/snapshot.json +9228 -0
- package/drizzle/20260624135452_tiny_norman_osborn/migration.sql +11 -0
- package/drizzle/20260624135452_tiny_norman_osborn/snapshot.json +9126 -0
- package/drizzle/20260624140138_wandering_avengers/migration.sql +1 -0
- package/drizzle/20260624140138_wandering_avengers/snapshot.json +9045 -0
- package/package.json +62 -0
|
@@ -0,0 +1,928 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { bigint, doublePrecision, index, integer, pgTable, primaryKey, serial, text, uniqueIndex, } from 'drizzle-orm/pg-core';
|
|
3
|
+
// Postgres schema mirroring the Cloudflare D1 tables column-for-column (snake_case
|
|
4
|
+
// field names = column names) so the shared row<->domain mappers in
|
|
5
|
+
// @cat-factory/server work unchanged against either store. JSON-shaped columns are
|
|
6
|
+
// `text` (the mappers (de)serialise them), and epoch-ms / GitHub-id columns are
|
|
7
|
+
// `bigint({ mode: 'number' })` so they read back as JS numbers. The indexes mirror
|
|
8
|
+
// the D1 migrations 1:1 so query plans (and the unique personal-account constraint)
|
|
9
|
+
// match across stores.
|
|
10
|
+
export const workspaces = pgTable('workspaces', {
|
|
11
|
+
id: text('id').primaryKey(),
|
|
12
|
+
name: text('name').notNull(),
|
|
13
|
+
description: text('description'),
|
|
14
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
15
|
+
account_id: text('account_id'),
|
|
16
|
+
owner_user_id: text('owner_user_id'),
|
|
17
|
+
},
|
|
18
|
+
// listVisible filters by owner_user_id (legacy) and account_id (membership scope).
|
|
19
|
+
(t) => [
|
|
20
|
+
index('idx_workspaces_owner').on(t.owner_user_id),
|
|
21
|
+
index('idx_workspaces_account').on(t.account_id),
|
|
22
|
+
]);
|
|
23
|
+
// Canonical user identity (decoupled from GitHub). Everything else keys off users.id.
|
|
24
|
+
export const users = pgTable('users', {
|
|
25
|
+
id: text('id').primaryKey(),
|
|
26
|
+
name: text('name'),
|
|
27
|
+
email: text('email'),
|
|
28
|
+
avatar_url: text('avatar_url'),
|
|
29
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
30
|
+
}, (t) => [
|
|
31
|
+
uniqueIndex('idx_users_email')
|
|
32
|
+
.on(t.email)
|
|
33
|
+
.where(sql `email IS NOT NULL`),
|
|
34
|
+
]);
|
|
35
|
+
// A linked login identity for a user. (provider, subject) is unique.
|
|
36
|
+
export const userIdentities = pgTable('user_identities', {
|
|
37
|
+
user_id: text('user_id').notNull(),
|
|
38
|
+
provider: text('provider').notNull(),
|
|
39
|
+
subject: text('subject').notNull(),
|
|
40
|
+
secret: text('secret'),
|
|
41
|
+
metadata: text('metadata'),
|
|
42
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
43
|
+
}, (t) => [
|
|
44
|
+
primaryKey({ columns: [t.provider, t.subject] }),
|
|
45
|
+
index('idx_user_identities_user').on(t.user_id),
|
|
46
|
+
]);
|
|
47
|
+
export const accounts = pgTable('accounts', {
|
|
48
|
+
id: text('id').primaryKey(),
|
|
49
|
+
type: text('type').notNull(),
|
|
50
|
+
name: text('name').notNull(),
|
|
51
|
+
github_account_login: text('github_account_login'),
|
|
52
|
+
// The user who owns a personal account (its account-of-one). Null for orgs.
|
|
53
|
+
owner_user_id: text('owner_user_id'),
|
|
54
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
55
|
+
// The default cloud provider new services in this account inherit.
|
|
56
|
+
default_cloud_provider: text('default_cloud_provider'),
|
|
57
|
+
},
|
|
58
|
+
// Enforce one personal account per user (a correctness constraint, not just a
|
|
59
|
+
// lookup index) — the partial unique index `findPersonalByUser` relies on.
|
|
60
|
+
(t) => [
|
|
61
|
+
uniqueIndex('idx_accounts_personal')
|
|
62
|
+
.on(t.owner_user_id)
|
|
63
|
+
.where(sql `type = 'personal'`),
|
|
64
|
+
]);
|
|
65
|
+
export const memberships = pgTable('memberships', {
|
|
66
|
+
account_id: text('account_id').notNull(),
|
|
67
|
+
user_id: text('user_id').notNull(),
|
|
68
|
+
// Combinable roles (admin / developer / product) as a CSV; defaults to developer.
|
|
69
|
+
roles: text('roles').notNull().default('developer'),
|
|
70
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
71
|
+
}, (t) => [
|
|
72
|
+
primaryKey({ columns: [t.account_id, t.user_id] }),
|
|
73
|
+
index('idx_memberships_user').on(t.user_id),
|
|
74
|
+
]);
|
|
75
|
+
// Per-account transactional-email sender (UI-onboarded). The provider API key is
|
|
76
|
+
// sealed at rest (SecretCipher), never plaintext.
|
|
77
|
+
export const emailConnections = pgTable('email_connections', {
|
|
78
|
+
account_id: text('account_id').primaryKey(),
|
|
79
|
+
provider: text('provider').notNull(),
|
|
80
|
+
from_address: text('from_address').notNull(),
|
|
81
|
+
api_key_cipher: text('api_key_cipher').notNull(),
|
|
82
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
83
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
84
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
85
|
+
});
|
|
86
|
+
// Email invitations into an org account. Only the token's hash is stored.
|
|
87
|
+
export const accountInvitations = pgTable('account_invitations', {
|
|
88
|
+
id: text('id').primaryKey(),
|
|
89
|
+
account_id: text('account_id').notNull(),
|
|
90
|
+
email: text('email').notNull(),
|
|
91
|
+
roles: text('roles').notNull().default('developer'),
|
|
92
|
+
token_hash: text('token_hash').notNull(),
|
|
93
|
+
invited_by: text('invited_by').notNull(),
|
|
94
|
+
status: text('status').notNull().default('pending'),
|
|
95
|
+
expires_at: bigint('expires_at', { mode: 'number' }).notNull(),
|
|
96
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
97
|
+
}, (t) => [
|
|
98
|
+
index('idx_account_invitations_account').on(t.account_id),
|
|
99
|
+
uniqueIndex('idx_account_invitations_token').on(t.token_hash),
|
|
100
|
+
]);
|
|
101
|
+
export const blocks = pgTable('blocks', {
|
|
102
|
+
workspace_id: text('workspace_id').notNull(),
|
|
103
|
+
id: text('id').notNull(),
|
|
104
|
+
title: text('title').notNull(),
|
|
105
|
+
type: text('type').notNull(),
|
|
106
|
+
description: text('description').notNull().default(''),
|
|
107
|
+
pos_x: doublePrecision('pos_x').notNull().default(0),
|
|
108
|
+
pos_y: doublePrecision('pos_y').notNull().default(0),
|
|
109
|
+
// Explicit, user-dragged frame size; null => the board auto-sizes from content.
|
|
110
|
+
width: doublePrecision('width'),
|
|
111
|
+
height: doublePrecision('height'),
|
|
112
|
+
status: text('status').notNull(),
|
|
113
|
+
progress: doublePrecision('progress').notNull().default(0),
|
|
114
|
+
depends_on: text('depends_on').notNull().default('[]'),
|
|
115
|
+
execution_id: text('execution_id'),
|
|
116
|
+
level: text('level').notNull().default('frame'),
|
|
117
|
+
parent_id: text('parent_id'),
|
|
118
|
+
confidence: doublePrecision('confidence'),
|
|
119
|
+
module_name: text('module_name'),
|
|
120
|
+
fragment_ids: text('fragment_ids'),
|
|
121
|
+
// Service-level (frame): the service's selected best-practice fragment ids (JSON array).
|
|
122
|
+
service_fragment_ids: text('service_fragment_ids'),
|
|
123
|
+
model_id: text('model_id'),
|
|
124
|
+
pull_request: text('pull_request'),
|
|
125
|
+
merge_preset_id: text('merge_preset_id'),
|
|
126
|
+
pipeline_id: text('pipeline_id'),
|
|
127
|
+
// Task-level agent config-contribution values (JSON id->value map).
|
|
128
|
+
agent_config: text('agent_config'),
|
|
129
|
+
// Service-level (frame): Tester local-infra docker-compose path, the "no infra
|
|
130
|
+
// dependencies" flag, the cloud provider and the abstract instance size.
|
|
131
|
+
test_compose_path: text('test_compose_path'),
|
|
132
|
+
no_infra_dependencies: integer('no_infra_dependencies'),
|
|
133
|
+
cloud_provider: text('cloud_provider'),
|
|
134
|
+
instance_size: text('instance_size'),
|
|
135
|
+
// The account-owned service this block belongs to (migration 0031); will become the
|
|
136
|
+
// physical scope key once the repositories switch off workspace_id.
|
|
137
|
+
service_id: text('service_id'),
|
|
138
|
+
// GitHub user id of the block's creator (migration 0038); drives "notify the task
|
|
139
|
+
// creator" routing. Nullable — legacy blocks / auth-disabled dev have no creator.
|
|
140
|
+
created_by: text('created_by'),
|
|
141
|
+
// The responsible product person (usr_*): notified when requirement review flags it.
|
|
142
|
+
responsible_product_user_id: text('responsible_product_user_id'),
|
|
143
|
+
// Task-level: the task-estimator's triage (complexity/risk/impact + rationale) as
|
|
144
|
+
// JSON; persisted on the block for gating consensus steps + UI ratings.
|
|
145
|
+
estimate: text('estimate'),
|
|
146
|
+
}, (t) => [
|
|
147
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
148
|
+
index('idx_blocks_parent').on(t.workspace_id, t.parent_id),
|
|
149
|
+
index('idx_blocks_service').on(t.service_id),
|
|
150
|
+
// findById looks a block up by id alone (no workspace_id), so it can't use the
|
|
151
|
+
// (workspace_id, id) PK — index id directly to avoid scanning the largest table.
|
|
152
|
+
// Block ids are only unique within a workspace, so this is a plain lookup index.
|
|
153
|
+
index('idx_blocks_id').on(t.id),
|
|
154
|
+
]);
|
|
155
|
+
// In-org shared services: account-owned service + per-workspace mount (migration 0030).
|
|
156
|
+
export const services = pgTable('services', {
|
|
157
|
+
id: text('id').primaryKey(),
|
|
158
|
+
account_id: text('account_id'),
|
|
159
|
+
frame_block_id: text('frame_block_id').notNull(),
|
|
160
|
+
installation_id: bigint('installation_id', { mode: 'number' }),
|
|
161
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }),
|
|
162
|
+
// Subdirectory within the linked monorepo this service lives in (NULL = whole repo).
|
|
163
|
+
directory: text('directory'),
|
|
164
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
165
|
+
}, (t) => [
|
|
166
|
+
index('idx_services_account').on(t.account_id),
|
|
167
|
+
// One service per frame block *within an account* (the frame↔service mapping is 1:1).
|
|
168
|
+
// Scoped by account_id, not global: block ids are only unique within a workspace, so a
|
|
169
|
+
// reused/seeded frame id recurs across workspaces; NULL account ids are SQL-distinct, so
|
|
170
|
+
// the auth-disabled/local path stays unconstrained while real accounts stay 1:1.
|
|
171
|
+
uniqueIndex('idx_services_frame').on(t.account_id, t.frame_block_id),
|
|
172
|
+
// getByFrameBlock resolves a service by frame_block_id alone (no account_id), so it
|
|
173
|
+
// can't use the composite idx_services_frame above. This lookup runs in a loop walking
|
|
174
|
+
// a block's ancestry on every agent run's repo resolution + on board reads — index it.
|
|
175
|
+
index('idx_services_frame_block').on(t.frame_block_id),
|
|
176
|
+
index('idx_services_repo').on(t.installation_id, t.repo_github_id),
|
|
177
|
+
]);
|
|
178
|
+
export const workspaceServices = pgTable('workspace_services', {
|
|
179
|
+
workspace_id: text('workspace_id').notNull(),
|
|
180
|
+
service_id: text('service_id').notNull(),
|
|
181
|
+
pos_x: doublePrecision('pos_x').notNull().default(0),
|
|
182
|
+
pos_y: doublePrecision('pos_y').notNull().default(0),
|
|
183
|
+
width: doublePrecision('width'),
|
|
184
|
+
height: doublePrecision('height'),
|
|
185
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
186
|
+
}, (t) => [
|
|
187
|
+
primaryKey({ columns: [t.workspace_id, t.service_id] }),
|
|
188
|
+
index('idx_workspace_services_service').on(t.service_id),
|
|
189
|
+
]);
|
|
190
|
+
export const pipelines = pgTable('pipelines', {
|
|
191
|
+
workspace_id: text('workspace_id').notNull(),
|
|
192
|
+
id: text('id').notNull(),
|
|
193
|
+
name: text('name').notNull(),
|
|
194
|
+
agent_kinds: text('agent_kinds').notNull().default('[]'),
|
|
195
|
+
gates: text('gates'),
|
|
196
|
+
thresholds: text('thresholds'),
|
|
197
|
+
// Nullable JSON array of per-step enable flags; truthy `builtin` marks the curated
|
|
198
|
+
// read-only catalog templates (mirror of D1 migration 0002).
|
|
199
|
+
enabled: text('enabled'),
|
|
200
|
+
builtin: integer('builtin'),
|
|
201
|
+
// Nullable JSON array of per-step consensus configs, parallel to agent_kinds (set in
|
|
202
|
+
// the pipeline builder for steps whose kind carries a consensus capability trait).
|
|
203
|
+
consensus: text('consensus'),
|
|
204
|
+
// Monotonic insert sequence (Postgres has no SQLite rowid): a workspace's pipelines
|
|
205
|
+
// are read back in the order they were seeded — the curated `seedPipelines()` order
|
|
206
|
+
// — so the catalog order (and the UI's default `pipelines[0]`) is deterministic and
|
|
207
|
+
// matches the Cloudflare facade (which orders by `rowid`). Auto-assigned on insert.
|
|
208
|
+
seq: serial('seq').notNull(),
|
|
209
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.id] })]);
|
|
210
|
+
export const agentRuns = pgTable('agent_runs', {
|
|
211
|
+
workspace_id: text('workspace_id').notNull(),
|
|
212
|
+
id: text('id').notNull(),
|
|
213
|
+
kind: text('kind').notNull(),
|
|
214
|
+
block_id: text('block_id'),
|
|
215
|
+
status: text('status').notNull(),
|
|
216
|
+
detail: text('detail').notNull().default('{}'),
|
|
217
|
+
subtasks: text('subtasks'),
|
|
218
|
+
error: text('error'),
|
|
219
|
+
failure: text('failure'),
|
|
220
|
+
workflow_instance_id: text('workflow_instance_id'),
|
|
221
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
222
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
223
|
+
// The service this run targets (migration 0031), derived from its block.
|
|
224
|
+
service_id: text('service_id'),
|
|
225
|
+
}, (t) => [
|
|
226
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
227
|
+
// listByWorkspace filters by workspace_id and orders by created_at.
|
|
228
|
+
index('idx_agent_runs_workspace').on(t.workspace_id, t.created_at),
|
|
229
|
+
index('idx_agent_runs_status_lease').on(t.status, t.updated_at),
|
|
230
|
+
index('idx_agent_runs_block').on(t.workspace_id, t.block_id),
|
|
231
|
+
index('idx_agent_runs_service').on(t.service_id),
|
|
232
|
+
]);
|
|
233
|
+
export const tokenUsage = pgTable('token_usage', {
|
|
234
|
+
id: text('id').primaryKey(),
|
|
235
|
+
workspace_id: text('workspace_id').notNull(),
|
|
236
|
+
execution_id: text('execution_id'),
|
|
237
|
+
agent_kind: text('agent_kind').notNull(),
|
|
238
|
+
provider: text('provider').notNull(),
|
|
239
|
+
model: text('model').notNull(),
|
|
240
|
+
input_tokens: integer('input_tokens').notNull().default(0),
|
|
241
|
+
output_tokens: integer('output_tokens').notNull().default(0),
|
|
242
|
+
cost_estimate: doublePrecision('cost_estimate').notNull().default(0),
|
|
243
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
244
|
+
}, (t) => [index('idx_token_usage_created').on(t.created_at)]);
|
|
245
|
+
// Per-workspace, per-agent-kind default model selection (mirror of D1 migration
|
|
246
|
+
// 0028). One row per (workspace, agent kind); the model each kind defaults to,
|
|
247
|
+
// overriding the env routing for that workspace. A kind absent for a workspace
|
|
248
|
+
// falls back to the env routing.
|
|
249
|
+
export const workspaceModelDefaults = pgTable('workspace_model_defaults', {
|
|
250
|
+
workspace_id: text('workspace_id').notNull(),
|
|
251
|
+
agent_kind: text('agent_kind').notNull(),
|
|
252
|
+
model_id: text('model_id').notNull(),
|
|
253
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
254
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.agent_kind] })]);
|
|
255
|
+
// Per-workspace default service-fragment selection (mirror of D1 migration 0040). One
|
|
256
|
+
// row per workspace; the best-practice fragment ids new services inherit, JSON array.
|
|
257
|
+
export const workspaceFragmentDefaults = pgTable('workspace_fragment_defaults', {
|
|
258
|
+
workspace_id: text('workspace_id').primaryKey(),
|
|
259
|
+
fragment_ids: text('fragment_ids').notNull(),
|
|
260
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
261
|
+
});
|
|
262
|
+
// Prompt-fragment library (ADR 0006; mirror of D1 migration 0020). The managed,
|
|
263
|
+
// tenant-scoped catalog of best-practice fragments, scoped by an (owner_kind,
|
|
264
|
+
// owner_id) pair so one table backs both the account and workspace tiers. JSON-shaped
|
|
265
|
+
// columns (`applies_to`, `tags`) are `text`; a tombstone (`deleted_at`) suppresses an
|
|
266
|
+
// inherited or removed-upstream fragment.
|
|
267
|
+
export const promptFragments = pgTable('prompt_fragments', {
|
|
268
|
+
fragment_id: text('fragment_id').notNull(),
|
|
269
|
+
owner_kind: text('owner_kind').notNull(),
|
|
270
|
+
owner_id: text('owner_id').notNull(),
|
|
271
|
+
version: text('version').notNull(),
|
|
272
|
+
title: text('title').notNull(),
|
|
273
|
+
category: text('category'),
|
|
274
|
+
summary: text('summary').notNull(),
|
|
275
|
+
body: text('body').notNull(),
|
|
276
|
+
applies_to: text('applies_to'),
|
|
277
|
+
tags: text('tags'),
|
|
278
|
+
source_id: text('source_id'),
|
|
279
|
+
source_path: text('source_path'),
|
|
280
|
+
source_sha: text('source_sha'),
|
|
281
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
282
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
283
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
284
|
+
}, (t) => [
|
|
285
|
+
primaryKey({ columns: [t.owner_kind, t.owner_id, t.fragment_id] }),
|
|
286
|
+
index('idx_prompt_fragments_owner')
|
|
287
|
+
.on(t.owner_kind, t.owner_id)
|
|
288
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
289
|
+
index('idx_prompt_fragments_source')
|
|
290
|
+
.on(t.source_id)
|
|
291
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
292
|
+
]);
|
|
293
|
+
// A repo directory linked as a source of Markdown guideline files (ADR 0006 §3;
|
|
294
|
+
// mirror of D1 migration 0020). At most one live source per (owner, repo, ref, dir) —
|
|
295
|
+
// the unique index is the upsert key; a partial owner index powers the list.
|
|
296
|
+
export const fragmentSources = pgTable('fragment_sources', {
|
|
297
|
+
id: text('id').primaryKey(),
|
|
298
|
+
owner_kind: text('owner_kind').notNull(),
|
|
299
|
+
owner_id: text('owner_id').notNull(),
|
|
300
|
+
repo_owner: text('repo_owner').notNull(),
|
|
301
|
+
repo_name: text('repo_name').notNull(),
|
|
302
|
+
git_ref: text('git_ref').notNull().default('HEAD'),
|
|
303
|
+
dir_path: text('dir_path').notNull().default(''),
|
|
304
|
+
last_synced_sha: text('last_synced_sha'),
|
|
305
|
+
last_synced_at: bigint('last_synced_at', { mode: 'number' }),
|
|
306
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
307
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
308
|
+
}, (t) => [
|
|
309
|
+
uniqueIndex('idx_fragment_sources_unique').on(t.owner_kind, t.owner_id, t.repo_owner, t.repo_name, t.git_ref, t.dir_path),
|
|
310
|
+
index('idx_fragment_sources_owner')
|
|
311
|
+
.on(t.owner_kind, t.owner_id)
|
|
312
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
313
|
+
]);
|
|
314
|
+
// LLM observability sink (mirror of D1 migration 0026). One row per proxied
|
|
315
|
+
// container-agent model call: full prompt/response, output-limit headroom and the
|
|
316
|
+
// transport-vs-execution latency split. Pruned aggressively by retention (the full
|
|
317
|
+
// bodies make it heavy); booleans are integer 0/1 to match the SQLite store.
|
|
318
|
+
export const llmCallMetrics = pgTable('llm_call_metrics', {
|
|
319
|
+
id: text('id').primaryKey(),
|
|
320
|
+
workspace_id: text('workspace_id').notNull(),
|
|
321
|
+
execution_id: text('execution_id'),
|
|
322
|
+
agent_kind: text('agent_kind').notNull(),
|
|
323
|
+
provider: text('provider').notNull(),
|
|
324
|
+
model: text('model').notNull(),
|
|
325
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
326
|
+
streaming: integer('streaming').notNull().default(0),
|
|
327
|
+
message_count: integer('message_count').notNull().default(0),
|
|
328
|
+
tool_count: integer('tool_count').notNull().default(0),
|
|
329
|
+
request_max_tokens: integer('request_max_tokens'),
|
|
330
|
+
prompt_tokens: integer('prompt_tokens').notNull().default(0),
|
|
331
|
+
cached_prompt_tokens: integer('cached_prompt_tokens').notNull().default(0),
|
|
332
|
+
completion_tokens: integer('completion_tokens').notNull().default(0),
|
|
333
|
+
total_tokens: integer('total_tokens').notNull().default(0),
|
|
334
|
+
finish_reason: text('finish_reason'),
|
|
335
|
+
upstream_ms: integer('upstream_ms').notNull().default(0),
|
|
336
|
+
overhead_ms: integer('overhead_ms').notNull().default(0),
|
|
337
|
+
total_ms: integer('total_ms').notNull().default(0),
|
|
338
|
+
ok: integer('ok').notNull().default(1),
|
|
339
|
+
http_status: integer('http_status'),
|
|
340
|
+
error_message: text('error_message'),
|
|
341
|
+
// prompt_text is stored as a DELTA (only the messages this call appended beyond
|
|
342
|
+
// prompt_prefix_count); the full prompt is rebuilt on export. See D1 migration 0027.
|
|
343
|
+
prompt_text: text('prompt_text').notNull().default(''),
|
|
344
|
+
prompt_prefix_count: integer('prompt_prefix_count').notNull().default(0),
|
|
345
|
+
prompt_hash: text('prompt_hash').notNull().default(''),
|
|
346
|
+
response_text: text('response_text').notNull().default(''),
|
|
347
|
+
// The model's reasoning/"thinking" trace on a separate channel, when emitted (a
|
|
348
|
+
// reasoning model can spend its whole output budget here and return empty
|
|
349
|
+
// response_text). Mirrors D1 migration 0002_llm_reasoning_text.
|
|
350
|
+
reasoning_text: text('reasoning_text').notNull().default(''),
|
|
351
|
+
}, (t) => [
|
|
352
|
+
index('idx_llm_call_metrics_execution').on(t.workspace_id, t.execution_id, t.created_at),
|
|
353
|
+
index('idx_llm_call_metrics_created').on(t.created_at),
|
|
354
|
+
]);
|
|
355
|
+
// Recurring pipelines (mirror of D1 migration 0029). A schedule attaches a pipeline
|
|
356
|
+
// to a service frame and owns one reused on-board block; the sweeper fires every
|
|
357
|
+
// enabled schedule whose `next_run_at <= now`. `weekdays` is a JSON array (text),
|
|
358
|
+
// epoch-ms columns are bigint. Each fire is recorded in `pipeline_schedule_runs`.
|
|
359
|
+
export const pipelineSchedules = pgTable('pipeline_schedules', {
|
|
360
|
+
workspace_id: text('workspace_id').notNull(),
|
|
361
|
+
id: text('id').notNull(),
|
|
362
|
+
service_id: text('service_id'),
|
|
363
|
+
block_id: text('block_id').notNull(),
|
|
364
|
+
frame_id: text('frame_id').notNull(),
|
|
365
|
+
pipeline_id: text('pipeline_id').notNull(),
|
|
366
|
+
template: text('template').notNull(),
|
|
367
|
+
name: text('name').notNull(),
|
|
368
|
+
interval_hours: integer('interval_hours').notNull(),
|
|
369
|
+
weekdays: text('weekdays').notNull().default('[]'),
|
|
370
|
+
window_start_hour: integer('window_start_hour'),
|
|
371
|
+
window_end_hour: integer('window_end_hour'),
|
|
372
|
+
timezone: text('timezone').notNull().default('UTC'),
|
|
373
|
+
enabled: integer('enabled').notNull().default(1),
|
|
374
|
+
last_run_at: bigint('last_run_at', { mode: 'number' }),
|
|
375
|
+
next_run_at: bigint('next_run_at', { mode: 'number' }).notNull(),
|
|
376
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
377
|
+
}, (t) => [
|
|
378
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
379
|
+
index('idx_pipeline_schedules_due').on(t.enabled, t.next_run_at),
|
|
380
|
+
index('idx_pipeline_schedules_block').on(t.workspace_id, t.block_id),
|
|
381
|
+
index('idx_pipeline_schedules_service').on(t.service_id),
|
|
382
|
+
]);
|
|
383
|
+
export const pipelineScheduleRuns = pgTable('pipeline_schedule_runs', {
|
|
384
|
+
workspace_id: text('workspace_id').notNull(),
|
|
385
|
+
id: text('id').notNull(),
|
|
386
|
+
schedule_id: text('schedule_id').notNull(),
|
|
387
|
+
execution_id: text('execution_id'),
|
|
388
|
+
status: text('status').notNull(),
|
|
389
|
+
started_at: bigint('started_at', { mode: 'number' }).notNull(),
|
|
390
|
+
finished_at: bigint('finished_at', { mode: 'number' }),
|
|
391
|
+
outcome: text('outcome'),
|
|
392
|
+
}, (t) => [
|
|
393
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
394
|
+
index('idx_schedule_runs_schedule').on(t.workspace_id, t.schedule_id, t.started_at),
|
|
395
|
+
index('idx_schedule_runs_started').on(t.started_at),
|
|
396
|
+
]);
|
|
397
|
+
// Requirements reviews (mirror of D1 migration 0021). One row per review; the
|
|
398
|
+
// reviewed `items` live as a JSON array (text). At most one live review per block —
|
|
399
|
+
// the service deletes a block's prior review before inserting a fresh one, so
|
|
400
|
+
// `getByBlock` returns the current one. `incorporated_requirements` holds the
|
|
401
|
+
// reworked, standard-format requirements document the rework step produced.
|
|
402
|
+
export const requirementReviews = pgTable('requirement_reviews', {
|
|
403
|
+
workspace_id: text('workspace_id').notNull(),
|
|
404
|
+
id: text('id').notNull(),
|
|
405
|
+
block_id: text('block_id').notNull(),
|
|
406
|
+
status: text('status').notNull(),
|
|
407
|
+
items: text('items').notNull().default('[]'),
|
|
408
|
+
model: text('model'),
|
|
409
|
+
incorporated_requirements: text('incorporated_requirements'),
|
|
410
|
+
// Reviewer-pass counter + its budget for the iterative review loop (the initial
|
|
411
|
+
// review is iteration 1; an "extra round" choice bumps max_iterations).
|
|
412
|
+
iteration: integer('iteration').notNull().default(1),
|
|
413
|
+
max_iterations: integer('max_iterations').notNull().default(1),
|
|
414
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
415
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
416
|
+
}, (t) => [
|
|
417
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
418
|
+
// getByBlock looks up a block's reviews (newest wins), mirroring D1 migration 0021.
|
|
419
|
+
index('idx_requirement_reviews_block').on(t.workspace_id, t.block_id),
|
|
420
|
+
]);
|
|
421
|
+
// Consensus session transcripts (mirror of D1 migration 0002): one row per
|
|
422
|
+
// (execution, step) recording the multi-model process — participants, round-by-round
|
|
423
|
+
// contributions/votes, and the synthesized result. The observability surface the
|
|
424
|
+
// dedicated Consensus Session window renders; written by `@cat-factory/consensus`.
|
|
425
|
+
export const consensusSessions = pgTable('consensus_sessions', {
|
|
426
|
+
workspace_id: text('workspace_id').notNull(),
|
|
427
|
+
id: text('id').notNull(),
|
|
428
|
+
block_id: text('block_id').notNull(),
|
|
429
|
+
execution_id: text('execution_id'),
|
|
430
|
+
step_index: integer('step_index').notNull(),
|
|
431
|
+
agent_kind: text('agent_kind').notNull(),
|
|
432
|
+
strategy: text('strategy').notNull(),
|
|
433
|
+
status: text('status').notNull(),
|
|
434
|
+
participants: text('participants').notNull().default('[]'),
|
|
435
|
+
rounds: text('rounds').notNull().default('[]'),
|
|
436
|
+
synthesis: text('synthesis'),
|
|
437
|
+
confidence: doublePrecision('confidence'),
|
|
438
|
+
dissent: text('dissent').notNull().default('[]'),
|
|
439
|
+
error: text('error'),
|
|
440
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
441
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
442
|
+
}, (t) => [
|
|
443
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
444
|
+
index('idx_consensus_sessions_step').on(t.workspace_id, t.execution_id, t.step_index),
|
|
445
|
+
index('idx_consensus_sessions_block').on(t.workspace_id, t.block_id, t.created_at),
|
|
446
|
+
]);
|
|
447
|
+
// Clarity (bug-report triage) reviews (mirror of D1 migration 0002_clarity_reviews). The
|
|
448
|
+
// clarity analogue of `requirement_reviews`: items as a JSON array, at most one live review
|
|
449
|
+
// per block. `clarified_report` holds the standard-format clarified bug report.
|
|
450
|
+
export const clarityReviews = pgTable('clarity_reviews', {
|
|
451
|
+
workspace_id: text('workspace_id').notNull(),
|
|
452
|
+
id: text('id').notNull(),
|
|
453
|
+
block_id: text('block_id').notNull(),
|
|
454
|
+
status: text('status').notNull(),
|
|
455
|
+
items: text('items').notNull().default('[]'),
|
|
456
|
+
model: text('model'),
|
|
457
|
+
clarified_report: text('clarified_report'),
|
|
458
|
+
iteration: integer('iteration').notNull().default(1),
|
|
459
|
+
max_iterations: integer('max_iterations').notNull().default(1),
|
|
460
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
461
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
462
|
+
}, (t) => [
|
|
463
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
464
|
+
index('idx_clarity_reviews_block').on(t.workspace_id, t.block_id),
|
|
465
|
+
]);
|
|
466
|
+
// A workspace's issue-tracker selection (mirror of D1 migration 0029).
|
|
467
|
+
export const trackerSettings = pgTable('tracker_settings', {
|
|
468
|
+
workspace_id: text('workspace_id').primaryKey(),
|
|
469
|
+
tracker: text('tracker'),
|
|
470
|
+
jira_project_key: text('jira_project_key'),
|
|
471
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
472
|
+
});
|
|
473
|
+
// Task-source integration (mirror of D1 migration 0014): a workspace's connections
|
|
474
|
+
// to external issue trackers (Jira) and local projections of the issues it imported.
|
|
475
|
+
// `credentials` is an encrypted JSON bag (AES-256-GCM envelope), never sent on the
|
|
476
|
+
// wire. At most one live connection per (workspace, source); a `deleted_at` tombstone
|
|
477
|
+
// lets a workspace disconnect/reconnect.
|
|
478
|
+
export const taskConnections = pgTable('task_connections', {
|
|
479
|
+
workspace_id: text('workspace_id').notNull(),
|
|
480
|
+
source: text('source').notNull(),
|
|
481
|
+
credentials: text('credentials').notNull(),
|
|
482
|
+
label: text('label').notNull().default(''),
|
|
483
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
484
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
485
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.source] })]);
|
|
486
|
+
export const tasks = pgTable('tasks', {
|
|
487
|
+
workspace_id: text('workspace_id').notNull(),
|
|
488
|
+
source: text('source').notNull(),
|
|
489
|
+
external_id: text('external_id').notNull(),
|
|
490
|
+
title: text('title').notNull(),
|
|
491
|
+
url: text('url').notNull(),
|
|
492
|
+
status: text('status').notNull().default(''),
|
|
493
|
+
type: text('type').notNull().default(''),
|
|
494
|
+
assignee: text('assignee'),
|
|
495
|
+
priority: text('priority'),
|
|
496
|
+
labels: text('labels').notNull().default('[]'),
|
|
497
|
+
description: text('description').notNull().default(''),
|
|
498
|
+
comments: text('comments').notNull().default('[]'),
|
|
499
|
+
excerpt: text('excerpt').notNull().default(''),
|
|
500
|
+
linked_block_id: text('linked_block_id'),
|
|
501
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
502
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
503
|
+
}, (t) => [
|
|
504
|
+
primaryKey({ columns: [t.workspace_id, t.source, t.external_id] }),
|
|
505
|
+
index('idx_tasks_block').on(t.workspace_id, t.linked_block_id),
|
|
506
|
+
]);
|
|
507
|
+
// A workspace's binding to a self-hosted runner pool (mirror of D1 migration 0013):
|
|
508
|
+
// the validated manifest + the encrypted scheduler-API secret bundle. The container
|
|
509
|
+
// agent executor dispatches repo-operating jobs to this pool when one is registered.
|
|
510
|
+
// `secrets_cipher` is opaque ciphertext (WebCryptoSecretCipher); never plaintext.
|
|
511
|
+
export const runnerPoolConnections = pgTable('runner_pool_connections', {
|
|
512
|
+
workspace_id: text('workspace_id').notNull(),
|
|
513
|
+
provider_id: text('provider_id').notNull(),
|
|
514
|
+
label: text('label').notNull(),
|
|
515
|
+
base_url: text('base_url').notNull(),
|
|
516
|
+
manifest_json: text('manifest_json').notNull(),
|
|
517
|
+
secrets_cipher: text('secrets_cipher').notNull(),
|
|
518
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
519
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
520
|
+
}, (t) => [
|
|
521
|
+
primaryKey({ columns: [t.workspace_id, t.provider_id] }),
|
|
522
|
+
// A workspace has at most one live pool (the partial unique mirrors D1).
|
|
523
|
+
uniqueIndex('idx_runner_pool_conn_workspace')
|
|
524
|
+
.on(t.workspace_id)
|
|
525
|
+
.where(sql `deleted_at IS NULL`),
|
|
526
|
+
]);
|
|
527
|
+
// Human-actionable notifications (mirror of D1 migration 0024). First-class items
|
|
528
|
+
// surfaced on the board that outlive the run that raised them (merge_review /
|
|
529
|
+
// pipeline_complete / ci_failed). The optional structured `payload` (assessment /
|
|
530
|
+
// PR url / pipeline name) is JSON text. Closing the Node parity gap so the
|
|
531
|
+
// notification subsystem — and any channel, including Slack — fires here too.
|
|
532
|
+
export const notifications = pgTable('notifications', {
|
|
533
|
+
workspace_id: text('workspace_id').notNull(),
|
|
534
|
+
id: text('id').notNull(),
|
|
535
|
+
type: text('type').notNull(),
|
|
536
|
+
status: text('status').notNull(),
|
|
537
|
+
block_id: text('block_id'),
|
|
538
|
+
execution_id: text('execution_id'),
|
|
539
|
+
title: text('title').notNull(),
|
|
540
|
+
body: text('body').notNull(),
|
|
541
|
+
payload: text('payload'),
|
|
542
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
543
|
+
resolved_at: bigint('resolved_at', { mode: 'number' }),
|
|
544
|
+
}, (t) => [
|
|
545
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
546
|
+
index('idx_notifications_open').on(t.workspace_id, t.status, t.created_at),
|
|
547
|
+
index('idx_notifications_block').on(t.workspace_id, t.block_id, t.type, t.status),
|
|
548
|
+
]);
|
|
549
|
+
// Per-workspace merge threshold presets (mirror of D1 migration 0024's
|
|
550
|
+
// `merge_threshold_presets`). A task selects one via `blocks.merge_preset_id`; none →
|
|
551
|
+
// the workspace default (`is_default`, exactly one per workspace — the repository
|
|
552
|
+
// demotes the prior default when promoting a new one). `is_default` is 0/1 to mirror
|
|
553
|
+
// the D1 integer flag. Carries the auto-merge ceilings + `ci_max_attempts`.
|
|
554
|
+
export const mergeThresholdPresets = pgTable('merge_threshold_presets', {
|
|
555
|
+
workspace_id: text('workspace_id').notNull(),
|
|
556
|
+
id: text('id').notNull(),
|
|
557
|
+
name: text('name').notNull(),
|
|
558
|
+
max_complexity: doublePrecision('max_complexity').notNull(),
|
|
559
|
+
max_risk: doublePrecision('max_risk').notNull(),
|
|
560
|
+
max_impact: doublePrecision('max_impact').notNull(),
|
|
561
|
+
ci_max_attempts: integer('ci_max_attempts').notNull(),
|
|
562
|
+
max_requirement_iterations: integer('max_requirement_iterations').notNull().default(3),
|
|
563
|
+
max_requirement_concern_allowed: text('max_requirement_concern_allowed')
|
|
564
|
+
.notNull()
|
|
565
|
+
.default('none'),
|
|
566
|
+
is_default: integer('is_default').notNull().default(0),
|
|
567
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
568
|
+
}, (t) => [
|
|
569
|
+
primaryKey({ columns: [t.workspace_id, t.id] }),
|
|
570
|
+
// Fast lookup of a workspace's default preset (mirrors idx_merge_presets_default).
|
|
571
|
+
index('idx_merge_presets_default').on(t.workspace_id, t.is_default),
|
|
572
|
+
]);
|
|
573
|
+
// Board-scan feature: the persisted "repository blueprint" — a repo decomposed into
|
|
574
|
+
// the canonical service → modules tree (mirror of D1 migration 0011). Exactly one
|
|
575
|
+
// blueprint per (workspace, repo): a re-scan replaces it in place (the unique index
|
|
576
|
+
// is the upsert key). The tree is stored whole as JSON in `service_json`.
|
|
577
|
+
export const repoBlueprints = pgTable('repo_blueprints', {
|
|
578
|
+
id: text('id').primaryKey(),
|
|
579
|
+
workspace_id: text('workspace_id').notNull(),
|
|
580
|
+
repo_owner: text('repo_owner').notNull(),
|
|
581
|
+
repo_name: text('repo_name').notNull(),
|
|
582
|
+
source: text('source').notNull(),
|
|
583
|
+
service_json: text('service_json').notNull(),
|
|
584
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
585
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
586
|
+
}, (t) => [
|
|
587
|
+
uniqueIndex('idx_repo_blueprints_repo').on(t.workspace_id, t.repo_owner, t.repo_name),
|
|
588
|
+
index('idx_repo_blueprints_workspace').on(t.workspace_id, t.updated_at),
|
|
589
|
+
]);
|
|
590
|
+
// Document-source integration (mirror of D1 migration 0012). A `source`
|
|
591
|
+
// discriminator tags every row so one pair of tables serves every provider. The
|
|
592
|
+
// credential bag is encrypted at rest (a WebCryptoSecretCipher envelope), never sent
|
|
593
|
+
// on the wire; at most one live connection per (workspace, source) — reconnecting
|
|
594
|
+
// replaces the row.
|
|
595
|
+
export const documentConnections = pgTable('document_connections', {
|
|
596
|
+
workspace_id: text('workspace_id').notNull(),
|
|
597
|
+
source: text('source').notNull(),
|
|
598
|
+
credentials: text('credentials').notNull(),
|
|
599
|
+
label: text('label').notNull().default(''),
|
|
600
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
601
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
602
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.source] })]);
|
|
603
|
+
// One row per imported page: `body` holds the normalized Markdown the planner +
|
|
604
|
+
// agent-context injection consume, `linked_block_id` attaches it to a board block.
|
|
605
|
+
export const documents = pgTable('documents', {
|
|
606
|
+
workspace_id: text('workspace_id').notNull(),
|
|
607
|
+
source: text('source').notNull(),
|
|
608
|
+
external_id: text('external_id').notNull(),
|
|
609
|
+
title: text('title').notNull(),
|
|
610
|
+
url: text('url').notNull(),
|
|
611
|
+
excerpt: text('excerpt').notNull().default(''),
|
|
612
|
+
body: text('body').notNull().default(''),
|
|
613
|
+
linked_block_id: text('linked_block_id'),
|
|
614
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
615
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
616
|
+
}, (t) => [
|
|
617
|
+
primaryKey({ columns: [t.workspace_id, t.source, t.external_id] }),
|
|
618
|
+
index('idx_documents_block').on(t.workspace_id, t.linked_block_id),
|
|
619
|
+
]);
|
|
620
|
+
// Ephemeral-environment integration (mirror of D1 migration 0008). A workspace's
|
|
621
|
+
// binding to its own environment-management API (a declarative manifest) and the
|
|
622
|
+
// registry of environments provisioned from it. Credentials are opaque ciphertext
|
|
623
|
+
// (SecretCipher envelopes), never plaintext. At most one live provider per workspace
|
|
624
|
+
// (the partial unique index lets a tombstoned binding be replaced).
|
|
625
|
+
export const environmentConnections = pgTable('environment_connections', {
|
|
626
|
+
workspace_id: text('workspace_id').notNull(),
|
|
627
|
+
provider_id: text('provider_id').notNull(),
|
|
628
|
+
label: text('label').notNull(),
|
|
629
|
+
base_url: text('base_url').notNull(),
|
|
630
|
+
manifest_json: text('manifest_json').notNull(),
|
|
631
|
+
secrets_cipher: text('secrets_cipher').notNull(),
|
|
632
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
633
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
634
|
+
}, (t) => [
|
|
635
|
+
primaryKey({ columns: [t.workspace_id, t.provider_id] }),
|
|
636
|
+
uniqueIndex('idx_environment_conn_workspace')
|
|
637
|
+
.on(t.workspace_id)
|
|
638
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
639
|
+
]);
|
|
640
|
+
// One row per provisioned environment. `access_cipher` holds the env's own access
|
|
641
|
+
// creds (what the tester uses); `provision_fields_cipher` holds the fields captured at
|
|
642
|
+
// provision time that status/teardown calls interpolate.
|
|
643
|
+
export const environments = pgTable('environments', {
|
|
644
|
+
id: text('id').primaryKey(),
|
|
645
|
+
workspace_id: text('workspace_id').notNull(),
|
|
646
|
+
block_id: text('block_id'),
|
|
647
|
+
execution_id: text('execution_id'),
|
|
648
|
+
provider_id: text('provider_id').notNull(),
|
|
649
|
+
external_id: text('external_id'),
|
|
650
|
+
url: text('url'),
|
|
651
|
+
status: text('status').notNull(),
|
|
652
|
+
access_cipher: text('access_cipher'),
|
|
653
|
+
provision_fields_cipher: text('provision_fields_cipher'),
|
|
654
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
655
|
+
expires_at: bigint('expires_at', { mode: 'number' }),
|
|
656
|
+
last_error: text('last_error'),
|
|
657
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
658
|
+
}, (t) => [
|
|
659
|
+
index('idx_environments_block')
|
|
660
|
+
.on(t.workspace_id, t.block_id)
|
|
661
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
662
|
+
index('idx_environments_expiry')
|
|
663
|
+
.on(t.expires_at)
|
|
664
|
+
.where(sql `${t.deleted_at} IS NULL AND ${t.expires_at} IS NOT NULL`),
|
|
665
|
+
]);
|
|
666
|
+
// Repo-bootstrap feature: managed reference architectures a new repo is bootstrapped
|
|
667
|
+
// from (mirror of D1 migration 0010). The bootstrap *runs* themselves are stored as
|
|
668
|
+
// kind='bootstrap' rows of the unified agent_runs table (no separate table), exactly
|
|
669
|
+
// like the Worker.
|
|
670
|
+
export const referenceArchitectures = pgTable('reference_architectures', {
|
|
671
|
+
id: text('id').primaryKey(),
|
|
672
|
+
workspace_id: text('workspace_id').notNull(),
|
|
673
|
+
name: text('name').notNull(),
|
|
674
|
+
description: text('description').notNull().default(''),
|
|
675
|
+
repo_owner: text('repo_owner').notNull(),
|
|
676
|
+
repo_name: text('repo_name').notNull(),
|
|
677
|
+
default_instructions: text('default_instructions').notNull().default(''),
|
|
678
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
679
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
680
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
681
|
+
}, (t) => [
|
|
682
|
+
index('idx_reference_architectures_workspace')
|
|
683
|
+
.on(t.workspace_id)
|
|
684
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
685
|
+
]);
|
|
686
|
+
// Slack integration (mirror of D1 migration 0037). An additional delivery transport
|
|
687
|
+
// for the notification mechanism. Per-account connection (+ encrypted bot token,
|
|
688
|
+
// `token_cipher` is a WebCryptoSecretCipher envelope, never plaintext), per-workspace
|
|
689
|
+
// routing, and the per-account GitHub→Slack member map for @-mentions.
|
|
690
|
+
export const slackConnections = pgTable('slack_connections', {
|
|
691
|
+
account_id: text('account_id').primaryKey(),
|
|
692
|
+
team_id: text('team_id').notNull(),
|
|
693
|
+
team_name: text('team_name').notNull(),
|
|
694
|
+
team_icon_url: text('team_icon_url'),
|
|
695
|
+
bot_user_id: text('bot_user_id'),
|
|
696
|
+
scopes: text('scopes'),
|
|
697
|
+
token_cipher: text('token_cipher').notNull(),
|
|
698
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
699
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
700
|
+
},
|
|
701
|
+
// A Slack team binds to at most one live account (mirrors the D1 partial unique).
|
|
702
|
+
(t) => [
|
|
703
|
+
uniqueIndex('idx_slack_conn_team')
|
|
704
|
+
.on(t.team_id)
|
|
705
|
+
.where(sql `deleted_at IS NULL`),
|
|
706
|
+
]);
|
|
707
|
+
export const slackSettings = pgTable('slack_settings', {
|
|
708
|
+
workspace_id: text('workspace_id').primaryKey(),
|
|
709
|
+
routes: text('routes').notNull().default('{}'),
|
|
710
|
+
mentions_enabled: integer('mentions_enabled').notNull().default(0),
|
|
711
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
712
|
+
});
|
|
713
|
+
export const slackMemberMappings = pgTable('slack_member_mappings', {
|
|
714
|
+
account_id: text('account_id').primaryKey(),
|
|
715
|
+
entries: text('entries').notNull().default('[]'),
|
|
716
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
717
|
+
});
|
|
718
|
+
// Provider-subscription token pool (mirror of D1 migration 0035): per-workspace,
|
|
719
|
+
// per-vendor subscription credentials (Claude Pro/Max OAuth token, ChatGPT
|
|
720
|
+
// auth.json) authenticating the Claude Code / Codex harnesses. The credential is
|
|
721
|
+
// stored as an opaque SecretCipher envelope; usage counters drive usage-aware
|
|
722
|
+
// rotation. A workspace may hold many tokens per vendor (a pool).
|
|
723
|
+
export const providerSubscriptionTokens = pgTable('provider_subscription_tokens', {
|
|
724
|
+
id: text('id').primaryKey(),
|
|
725
|
+
workspace_id: text('workspace_id').notNull(),
|
|
726
|
+
vendor: text('vendor').notNull(),
|
|
727
|
+
label: text('label').notNull(),
|
|
728
|
+
token_cipher: text('token_cipher').notNull(),
|
|
729
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
730
|
+
last_used_at: bigint('last_used_at', { mode: 'number' }),
|
|
731
|
+
window_started_at: bigint('window_started_at', { mode: 'number' }),
|
|
732
|
+
input_tokens: bigint('input_tokens', { mode: 'number' }).notNull().default(0),
|
|
733
|
+
output_tokens: bigint('output_tokens', { mode: 'number' }).notNull().default(0),
|
|
734
|
+
request_count: integer('request_count').notNull().default(0),
|
|
735
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
736
|
+
}, (t) => [index('idx_provider_subs_pool').on(t.workspace_id, t.vendor, t.deleted_at)]);
|
|
737
|
+
// Direct-provider API-key pool: UI-onboarded vendor API keys scoped to an
|
|
738
|
+
// account, workspace, or user (mirror of D1 migration 0042). The key is stored as
|
|
739
|
+
// an opaque SecretCipher envelope — never plaintext.
|
|
740
|
+
export const providerApiKeys = pgTable('provider_api_keys', {
|
|
741
|
+
id: text('id').primaryKey(),
|
|
742
|
+
scope: text('scope').notNull(),
|
|
743
|
+
scope_id: text('scope_id').notNull(),
|
|
744
|
+
provider: text('provider').notNull(),
|
|
745
|
+
label: text('label').notNull(),
|
|
746
|
+
key_cipher: text('key_cipher').notNull(),
|
|
747
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
748
|
+
last_used_at: bigint('last_used_at', { mode: 'number' }),
|
|
749
|
+
window_started_at: bigint('window_started_at', { mode: 'number' }),
|
|
750
|
+
input_tokens: bigint('input_tokens', { mode: 'number' }).notNull().default(0),
|
|
751
|
+
output_tokens: bigint('output_tokens', { mode: 'number' }).notNull().default(0),
|
|
752
|
+
request_count: integer('request_count').notNull().default(0),
|
|
753
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
754
|
+
}, (t) => [index('idx_provider_api_keys_pool').on(t.scope, t.scope_id, t.provider, t.deleted_at)]);
|
|
755
|
+
// Individual-usage subscriptions (Claude): per-USER, never pooled (mirror of D1
|
|
756
|
+
// migration 0039). The credential is double-encrypted (password layer inside the
|
|
757
|
+
// system layer).
|
|
758
|
+
export const personalSubscriptions = pgTable('personal_subscriptions', {
|
|
759
|
+
id: text('id').primaryKey(),
|
|
760
|
+
user_id: text('user_id').notNull(),
|
|
761
|
+
vendor: text('vendor').notNull(),
|
|
762
|
+
label: text('label').notNull(),
|
|
763
|
+
token_cipher: text('token_cipher').notNull(),
|
|
764
|
+
expires_at: bigint('expires_at', { mode: 'number' }),
|
|
765
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
766
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
767
|
+
last_used_at: bigint('last_used_at', { mode: 'number' }),
|
|
768
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
769
|
+
}, (t) => [
|
|
770
|
+
uniqueIndex('idx_personal_subs_user_vendor')
|
|
771
|
+
.on(t.user_id, t.vendor)
|
|
772
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
773
|
+
index('idx_personal_subs_expiry')
|
|
774
|
+
.on(t.expires_at)
|
|
775
|
+
.where(sql `${t.deleted_at} IS NULL`),
|
|
776
|
+
]);
|
|
777
|
+
// Per-USER locally-run model endpoints (Ollama / LM Studio / llama.cpp / vLLM / custom),
|
|
778
|
+
// keyed by (user_id, provider). The optional bearer key is system-key-encrypted in
|
|
779
|
+
// `api_key_cipher`; `models` is a JSON array of enabled model ids (mirror of D1
|
|
780
|
+
// migration 0002).
|
|
781
|
+
export const localModelEndpoints = pgTable('local_model_endpoints', {
|
|
782
|
+
user_id: text('user_id').notNull(),
|
|
783
|
+
provider: text('provider').notNull(),
|
|
784
|
+
label: text('label').notNull(),
|
|
785
|
+
base_url: text('base_url').notNull(),
|
|
786
|
+
api_key_cipher: text('api_key_cipher'),
|
|
787
|
+
models: text('models').notNull(),
|
|
788
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
789
|
+
updated_at: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
790
|
+
}, (t) => [primaryKey({ columns: [t.user_id, t.provider] })]);
|
|
791
|
+
// Per-run activations of a personal credential: the raw token re-encrypted with the
|
|
792
|
+
// system key only, scoped to one execution with a TTL (mirror of D1 migration 0039).
|
|
793
|
+
export const subscriptionActivations = pgTable('subscription_activations', {
|
|
794
|
+
id: text('id').primaryKey(),
|
|
795
|
+
execution_id: text('execution_id').notNull(),
|
|
796
|
+
user_id: text('user_id').notNull(),
|
|
797
|
+
vendor: text('vendor').notNull(),
|
|
798
|
+
token_cipher: text('token_cipher').notNull(),
|
|
799
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
800
|
+
expires_at: bigint('expires_at', { mode: 'number' }).notNull(),
|
|
801
|
+
}, (t) => [
|
|
802
|
+
uniqueIndex('idx_sub_activations_run').on(t.execution_id, t.user_id, t.vendor),
|
|
803
|
+
index('idx_sub_activations_expiry').on(t.expires_at),
|
|
804
|
+
]);
|
|
805
|
+
// GitHub App installation bindings (mirror of D1 migration 0004 + the account_id /
|
|
806
|
+
// app_id columns from 0017 / 0019). The container executor reads this to resolve a
|
|
807
|
+
// run's installation id and mint a short-lived push token; tokens are cached
|
|
808
|
+
// in-memory by the auth adapter, never persisted here.
|
|
809
|
+
export const githubInstallations = pgTable('github_installations', {
|
|
810
|
+
installation_id: bigint('installation_id', { mode: 'number' }).primaryKey(),
|
|
811
|
+
workspace_id: text('workspace_id').notNull(),
|
|
812
|
+
account_id: text('account_id'),
|
|
813
|
+
account_login: text('account_login').notNull(),
|
|
814
|
+
target_type: text('target_type').notNull(),
|
|
815
|
+
app_id: text('app_id'),
|
|
816
|
+
cached_token: text('cached_token'),
|
|
817
|
+
token_expires_at: bigint('token_expires_at', { mode: 'number' }),
|
|
818
|
+
created_at: bigint('created_at', { mode: 'number' }).notNull(),
|
|
819
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
820
|
+
}, (t) => [
|
|
821
|
+
uniqueIndex('idx_gh_install_workspace')
|
|
822
|
+
.on(t.workspace_id)
|
|
823
|
+
.where(sql `deleted_at IS NULL`),
|
|
824
|
+
index('idx_gh_install_account')
|
|
825
|
+
.on(t.account_id)
|
|
826
|
+
.where(sql `deleted_at IS NULL`),
|
|
827
|
+
]);
|
|
828
|
+
// Projection of a workspace's GitHub repositories (mirror of D1 migration 0004).
|
|
829
|
+
// `block_id` links a repo to a board service frame and is owned by the board link
|
|
830
|
+
// (never overwritten by sync). The container executor resolves a run's target repo
|
|
831
|
+
// from the service frame the block sits under.
|
|
832
|
+
export const githubRepos = pgTable('github_repos', {
|
|
833
|
+
workspace_id: text('workspace_id').notNull(),
|
|
834
|
+
github_id: bigint('github_id', { mode: 'number' }).notNull(),
|
|
835
|
+
installation_id: bigint('installation_id', { mode: 'number' }).notNull(),
|
|
836
|
+
owner: text('owner').notNull(),
|
|
837
|
+
name: text('name').notNull(),
|
|
838
|
+
default_branch: text('default_branch'),
|
|
839
|
+
private: integer('private').notNull().default(0),
|
|
840
|
+
block_id: text('block_id'),
|
|
841
|
+
// Whether the repo is a monorepo hosting several services (board-owned, like
|
|
842
|
+
// block_id — sync preserves it). See contracts `GitHubRepo.isMonorepo`.
|
|
843
|
+
is_monorepo: integer('is_monorepo').notNull().default(0),
|
|
844
|
+
etag: text('etag'),
|
|
845
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
846
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
847
|
+
}, (t) => [
|
|
848
|
+
primaryKey({ columns: [t.workspace_id, t.github_id] }),
|
|
849
|
+
index('idx_gh_repos_install').on(t.installation_id),
|
|
850
|
+
]);
|
|
851
|
+
// GitHub projection tables (mirror of D1 migration 0004; sync cursors re-keyed by
|
|
852
|
+
// migration 0032). Local read models of a workspace's repos' branches / PRs / issues /
|
|
853
|
+
// commits / check runs, populated by the inline GitHub sync. `protected`/`merged` are
|
|
854
|
+
// 0/1 to mirror the D1 integer flags; soft-delete tombstones where the D1 tables have one.
|
|
855
|
+
export const githubBranches = pgTable('github_branches', {
|
|
856
|
+
workspace_id: text('workspace_id').notNull(),
|
|
857
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }).notNull(),
|
|
858
|
+
name: text('name').notNull(),
|
|
859
|
+
head_sha: text('head_sha').notNull(),
|
|
860
|
+
protected: integer('protected').notNull().default(0),
|
|
861
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
862
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
863
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.repo_github_id, t.name] })]);
|
|
864
|
+
export const githubPullRequests = pgTable('github_pull_requests', {
|
|
865
|
+
workspace_id: text('workspace_id').notNull(),
|
|
866
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }).notNull(),
|
|
867
|
+
number: integer('number').notNull(),
|
|
868
|
+
github_id: bigint('github_id', { mode: 'number' }).notNull(),
|
|
869
|
+
title: text('title').notNull(),
|
|
870
|
+
state: text('state').notNull(),
|
|
871
|
+
head_ref: text('head_ref'),
|
|
872
|
+
base_ref: text('base_ref'),
|
|
873
|
+
head_sha: text('head_sha'),
|
|
874
|
+
merged: integer('merged').notNull().default(0),
|
|
875
|
+
author: text('author'),
|
|
876
|
+
gh_updated_at: bigint('gh_updated_at', { mode: 'number' }),
|
|
877
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
878
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
879
|
+
}, (t) => [
|
|
880
|
+
primaryKey({ columns: [t.workspace_id, t.repo_github_id, t.number] }),
|
|
881
|
+
index('idx_gh_pr_state').on(t.workspace_id, t.state),
|
|
882
|
+
]);
|
|
883
|
+
export const githubIssues = pgTable('github_issues', {
|
|
884
|
+
workspace_id: text('workspace_id').notNull(),
|
|
885
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }).notNull(),
|
|
886
|
+
number: integer('number').notNull(),
|
|
887
|
+
github_id: bigint('github_id', { mode: 'number' }).notNull(),
|
|
888
|
+
title: text('title').notNull(),
|
|
889
|
+
state: text('state').notNull(),
|
|
890
|
+
author: text('author'),
|
|
891
|
+
labels: text('labels').notNull().default('[]'),
|
|
892
|
+
gh_updated_at: bigint('gh_updated_at', { mode: 'number' }),
|
|
893
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
894
|
+
deleted_at: bigint('deleted_at', { mode: 'number' }),
|
|
895
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.repo_github_id, t.number] })]);
|
|
896
|
+
export const githubCommits = pgTable('github_commits', {
|
|
897
|
+
workspace_id: text('workspace_id').notNull(),
|
|
898
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }).notNull(),
|
|
899
|
+
sha: text('sha').notNull(),
|
|
900
|
+
message: text('message').notNull(),
|
|
901
|
+
author: text('author'),
|
|
902
|
+
authored_at: bigint('authored_at', { mode: 'number' }),
|
|
903
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
904
|
+
}, (t) => [primaryKey({ columns: [t.workspace_id, t.repo_github_id, t.sha] })]);
|
|
905
|
+
export const githubCheckRuns = pgTable('github_check_runs', {
|
|
906
|
+
workspace_id: text('workspace_id').notNull(),
|
|
907
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }).notNull(),
|
|
908
|
+
github_id: bigint('github_id', { mode: 'number' }).notNull(),
|
|
909
|
+
head_sha: text('head_sha').notNull(),
|
|
910
|
+
name: text('name').notNull(),
|
|
911
|
+
status: text('status').notNull(),
|
|
912
|
+
conclusion: text('conclusion'),
|
|
913
|
+
synced_at: bigint('synced_at', { mode: 'number' }).notNull(),
|
|
914
|
+
}, (t) => [
|
|
915
|
+
primaryKey({ columns: [t.workspace_id, t.repo_github_id, t.github_id] }),
|
|
916
|
+
index('idx_gh_checks_sha').on(t.workspace_id, t.repo_github_id, t.head_sha),
|
|
917
|
+
]);
|
|
918
|
+
// Incremental-sync bookkeeping, keyed by (installation, repo, kind) so a repo is
|
|
919
|
+
// fetched once per org and fanned out (mirror of D1 migration 0032).
|
|
920
|
+
export const githubSyncCursors = pgTable('github_sync_cursors', {
|
|
921
|
+
installation_id: bigint('installation_id', { mode: 'number' }).notNull(),
|
|
922
|
+
repo_github_id: bigint('repo_github_id', { mode: 'number' }).notNull(),
|
|
923
|
+
kind: text('kind').notNull(),
|
|
924
|
+
etag: text('etag'),
|
|
925
|
+
last_synced_at: bigint('last_synced_at', { mode: 'number' }),
|
|
926
|
+
since_iso: text('since_iso'),
|
|
927
|
+
}, (t) => [primaryKey({ columns: [t.installation_id, t.repo_github_id, t.kind] })]);
|
|
928
|
+
//# sourceMappingURL=schema.js.map
|