@sixb/pg 0.1.0 → 0.1.2

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.
Files changed (99) hide show
  1. package/README.md +7 -6
  2. package/dist/agents/agent-storage.d.ts +2 -1
  3. package/dist/agents/agent-storage.d.ts.map +1 -1
  4. package/dist/agents/rows.d.ts +4 -11
  5. package/dist/agents/rows.d.ts.map +1 -1
  6. package/dist/agents/runs.d.ts +3 -2
  7. package/dist/agents/runs.d.ts.map +1 -1
  8. package/dist/auth-storage/service-accounts.d.ts.map +1 -1
  9. package/dist/auth-storage/shared.d.ts +0 -4
  10. package/dist/auth-storage/shared.d.ts.map +1 -1
  11. package/dist/connector-connection-storage.d.ts +9 -0
  12. package/dist/connector-connection-storage.d.ts.map +1 -0
  13. package/dist/index.d.ts +8 -5
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +3131 -732
  16. package/dist/index.js.map +28 -25
  17. package/dist/latest-run-query.d.ts.map +1 -1
  18. package/dist/migrations.d.ts.map +1 -1
  19. package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
  20. package/dist/ontology-storage/materialization-state.d.ts +0 -2
  21. package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
  22. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
  23. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  24. package/dist/ontology-storage/outbox.d.ts.map +1 -1
  25. package/dist/ontology-storage/shared.d.ts +3 -6
  26. package/dist/ontology-storage/shared.d.ts.map +1 -1
  27. package/dist/pg-action-run-storage.d.ts +3 -2
  28. package/dist/pg-action-run-storage.d.ts.map +1 -1
  29. package/dist/pg-ai-usage-storage.d.ts +16 -0
  30. package/dist/pg-ai-usage-storage.d.ts.map +1 -0
  31. package/dist/pg-execution-storage.d.ts +14 -0
  32. package/dist/pg-execution-storage.d.ts.map +1 -0
  33. package/dist/pg-pipeline-run-storage.d.ts +5 -2
  34. package/dist/pg-pipeline-run-storage.d.ts.map +1 -1
  35. package/dist/pg-projection-run-storage.d.ts +5 -2
  36. package/dist/pg-projection-run-storage.d.ts.map +1 -1
  37. package/dist/pg-sync-run-storage.d.ts +5 -2
  38. package/dist/pg-sync-run-storage.d.ts.map +1 -1
  39. package/dist/pg-webhook-run-storage.d.ts +5 -2
  40. package/dist/pg-webhook-run-storage.d.ts.map +1 -1
  41. package/dist/pg-workflow-run-storage.d.ts +5 -3
  42. package/dist/pg-workflow-run-storage.d.ts.map +1 -1
  43. package/dist/run-list-query.d.ts +1 -1
  44. package/dist/run-list-query.d.ts.map +1 -1
  45. package/dist/testing.d.ts +6 -0
  46. package/dist/testing.d.ts.map +1 -0
  47. package/package.json +5 -2
  48. package/src/agents/agent-storage.ts +3 -2
  49. package/src/agents/rows.ts +12 -43
  50. package/src/agents/runs.ts +29 -15
  51. package/src/auth-storage/service-accounts.ts +14 -15
  52. package/src/auth-storage/shared.ts +0 -8
  53. package/src/connector-connection-storage.ts +760 -0
  54. package/src/index.ts +32 -15
  55. package/src/latest-run-query.ts +7 -1
  56. package/src/migrations/002-workflow-run-output.sql +27 -0
  57. package/src/migrations/003-merge-sync-runs.sql +3 -0
  58. package/src/migrations/004-executions.sql +140 -0
  59. package/src/migrations/005-workflow-executions.sql +10 -0
  60. package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
  61. package/src/migrations/007-split-overrides.sql +153 -0
  62. package/src/migrations/008-action-executions.sql +7 -0
  63. package/src/migrations/009-agent-executions.sql +21 -0
  64. package/src/migrations/010-ai-usage-accounting-foundation.sql +76 -0
  65. package/src/migrations/011-sync-failure-record.sql +23 -0
  66. package/src/migrations/012-pipeline-failure-record.sql +47 -0
  67. package/src/migrations/013-workflow-failure-record.sql +45 -0
  68. package/src/migrations/014-agent-failure-record.sql +56 -0
  69. package/src/migrations/015-projection-failure-record.sql +20 -0
  70. package/src/migrations/016-webhook-run-failure-record.sql +19 -0
  71. package/src/migrations/017-action-failure-record.sql +65 -0
  72. package/src/migrations/018-ontology-outbox-failure-record.sql +29 -0
  73. package/src/migrations/019-webhook-delivery-failure-record.sql +27 -0
  74. package/src/migrations/020-drop-run-usage-projections.sql +8 -0
  75. package/src/migrations/021-sync-pipeline-executions.sql +50 -0
  76. package/src/migrations/022-projection-executions.sql +115 -0
  77. package/src/migrations/023-webhook-executions.sql +30 -0
  78. package/src/migrations/024-ontology-commit-executions.sql +12 -0
  79. package/src/migrations/025-connector-connections.sql +231 -0
  80. package/src/migrations.ts +79 -1
  81. package/src/ontology-storage/materialization-session.ts +25 -28
  82. package/src/ontology-storage/materialization-state.ts +159 -43
  83. package/src/ontology-storage/materialization-writer.ts +146 -75
  84. package/src/ontology-storage/materializations.ts +3 -1
  85. package/src/ontology-storage/outbox.ts +9 -2
  86. package/src/ontology-storage/shared.ts +10 -20
  87. package/src/pg-action-run-storage.ts +43 -67
  88. package/src/pg-ai-usage-storage.ts +290 -0
  89. package/src/pg-execution-storage.ts +147 -0
  90. package/src/pg-pipeline-run-storage.ts +107 -41
  91. package/src/pg-projection-run-storage.ts +159 -91
  92. package/src/pg-sync-run-storage.ts +102 -30
  93. package/src/pg-webhook-run-storage.ts +89 -57
  94. package/src/pg-workflow-run-storage.ts +101 -130
  95. package/src/run-list-query.ts +9 -4
  96. package/src/testing.ts +18 -0
  97. package/dist/pg-webhook-delivery-storage.d.ts +0 -17
  98. package/dist/pg-webhook-delivery-storage.d.ts.map +0 -1
  99. package/src/pg-webhook-delivery-storage.ts +0 -180
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  createWorkflowRunOperationScope,
20
20
  } from "@sixb/core/internal/storage-operation-scope"
21
21
  import {
22
+ type AiUsageStorage,
22
23
  createTransactionStorageProxy,
23
24
  type ObjectStorage,
24
25
  StorageTransactionError,
@@ -26,21 +27,24 @@ import {
26
27
  } from "@sixb/core/storage"
27
28
  import { PgAgentStorage } from "./agents"
28
29
  import { PgAuthStorage } from "./auth-storage"
30
+ import { PgConnectorConnectionStorage } from "./connector-connection-storage"
29
31
  import { createPostgresStorageMigrators, dropSchema } from "./migrations"
30
32
  import { PgOntologyStorage, type PgOntologyTransactionContext } from "./ontology-storage"
31
33
  import { PgActionRunStorage } from "./pg-action-run-storage"
34
+ import { PgAiUsageStorage } from "./pg-ai-usage-storage"
32
35
  import { createPgClient, type SQL, type SQLClient } from "./pg-client"
36
+ import { PgExecutionStorage } from "./pg-execution-storage"
33
37
  import { PgObjectStorage } from "./pg-object-storage"
34
38
  import { PgPipelineRunStorage } from "./pg-pipeline-run-storage"
35
39
  import { PgProjectionRunStorage } from "./pg-projection-run-storage"
36
40
  import { PgRulesStorage } from "./pg-rules-storage"
37
41
  import { PgSyncRunStorage } from "./pg-sync-run-storage"
38
42
  import { PgTimeseriesStorage } from "./pg-timeseries-storage"
39
- import { PgWebhookDeliveryStorage } from "./pg-webhook-delivery-storage"
40
43
  import { PgWebhookRunStorage } from "./pg-webhook-run-storage"
41
44
  import { PgWorkflowInterventionStorage } from "./pg-workflow-intervention-storage"
42
45
  import { PgWorkflowRunStorage } from "./pg-workflow-run-storage"
43
46
  import { isRetryableTransactionConflict } from "./storage-errors"
47
+ import { registerPostgresStorageTestingAdapter } from "./testing"
44
48
  import { type PgStoreClient, runPgTransaction } from "./transactions"
45
49
 
46
50
  export interface PostgresStorageOptions {
@@ -106,8 +110,8 @@ export interface PostgresStorageOptions {
106
110
  /**
107
111
  * PostgreSQL storage provider for Sixb.
108
112
  *
109
- * Bundles Sixb storage adapters backed by a shared PostgreSQL connection pool (porsager
110
- * `postgres`), which reliably reclaims connections under load.
113
+ * Bundles Sixb storage adapters, including the AI usage ledger, behind a shared PostgreSQL
114
+ * connection pool (porsager `postgres`), which reliably reclaims connections under load.
111
115
  *
112
116
  * The storage exposes a core `StorageMigrator`. Sixb CLI startup and
113
117
  * `sixb db migrate` run it automatically through `migrateStorage(storage)`.
@@ -128,7 +132,9 @@ export class PostgresStorage implements MigrationCapableStorage {
128
132
  readonly objects: ObjectStorage
129
133
  readonly ontology: PgOntologyStorage
130
134
  readonly auth: PgAuthStorage
135
+ readonly executions: PgExecutionStorage
131
136
  readonly agents: PgAgentStorage
137
+ readonly aiUsage: AiUsageStorage
132
138
  readonly actionRuns: PgActionRunStorage
133
139
  readonly pipelineRuns: PgPipelineRunStorage
134
140
  readonly workflowRuns: PgWorkflowRunStorage
@@ -136,9 +142,9 @@ export class PostgresStorage implements MigrationCapableStorage {
136
142
  readonly syncRuns: PgSyncRunStorage
137
143
  readonly projectionRuns: PgProjectionRunStorage
138
144
  readonly timeseries: Storage["timeseries"]
139
- readonly webhookDeliveries: PgWebhookDeliveryStorage
140
145
  readonly webhookRuns: PgWebhookRunStorage
141
146
  readonly rules: PgRulesStorage
147
+ readonly connectorConnections: PgConnectorConnectionStorage
142
148
  readonly migrators: readonly StorageMigrator[]
143
149
 
144
150
  private readonly sql: SQL
@@ -199,7 +205,9 @@ export class PostgresStorage implements MigrationCapableStorage {
199
205
  this.objects = createOperationScopedFacade(stores.objects, scope)
200
206
  this.ontology = createOntologyOperationScope(stores.ontology, scope)
201
207
  this.auth = createAuthOperationScope(stores.auth, scope)
208
+ this.executions = createOperationScopedFacade(stores.executions, scope)
202
209
  this.agents = createAgentOperationScope(stores.agents, scope)
210
+ this.aiUsage = createOperationScopedFacade(stores.aiUsage, scope)
203
211
  this.actionRuns = createOperationScopedFacade(stores.actionRuns, scope)
204
212
  this.pipelineRuns = createOperationScopedFacade(stores.pipelineRuns, scope)
205
213
  this.workflowRuns = createWorkflowRunOperationScope(stores.workflowRuns, scope)
@@ -207,9 +215,12 @@ export class PostgresStorage implements MigrationCapableStorage {
207
215
  this.syncRuns = createOperationScopedFacade(stores.syncRuns, scope)
208
216
  this.projectionRuns = createOperationScopedFacade(stores.projectionRuns, scope)
209
217
  this.timeseries = createOperationScopedFacade(stores.timeseries, scope)
210
- this.webhookDeliveries = createOperationScopedFacade(stores.webhookDeliveries, scope)
211
218
  this.webhookRuns = createOperationScopedFacade(stores.webhookRuns, scope)
212
219
  this.rules = createOperationScopedFacade(stores.rules, scope)
220
+ this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope)
221
+ registerPostgresStorageTestingAdapter(this, (durationMs) =>
222
+ stores.connectorConnections.advanceTimeForTesting(durationMs)
223
+ )
213
224
  }
214
225
 
215
226
  async ping(): Promise<void> {
@@ -313,6 +324,8 @@ function createPostgresStores(
313
324
  readonly transactionContext: PgOntologyTransactionContext | null
314
325
  }
315
326
  ): PostgresStoreSet {
327
+ const auth = new PgAuthStorage({ sql })
328
+ const executions = new PgExecutionStorage(sql, auth)
316
329
  return {
317
330
  objects: new PgObjectStorage(sql),
318
331
  ontology: new PgOntologyStorage({
@@ -320,18 +333,20 @@ function createPostgresStores(
320
333
  runRootOperation: options.runOntologyOperation,
321
334
  transactionContext: options.transactionContext,
322
335
  }),
323
- auth: new PgAuthStorage({ sql }),
324
- agents: new PgAgentStorage({ sql }),
325
- actionRuns: new PgActionRunStorage(sql),
326
- pipelineRuns: new PgPipelineRunStorage(sql),
327
- workflowRuns: new PgWorkflowRunStorage(sql),
336
+ auth,
337
+ executions,
338
+ agents: new PgAgentStorage({ sql, executions }),
339
+ aiUsage: new PgAiUsageStorage(sql),
340
+ actionRuns: new PgActionRunStorage(sql, executions),
341
+ pipelineRuns: new PgPipelineRunStorage(sql, executions),
342
+ workflowRuns: new PgWorkflowRunStorage(sql, executions),
328
343
  workflowInterventions: new PgWorkflowInterventionStorage(sql),
329
- syncRuns: new PgSyncRunStorage(sql),
330
- projectionRuns: new PgProjectionRunStorage(sql),
344
+ syncRuns: new PgSyncRunStorage(sql, executions),
345
+ projectionRuns: new PgProjectionRunStorage(sql, executions),
331
346
  timeseries: new PgTimeseriesStorage(sql),
332
- webhookDeliveries: new PgWebhookDeliveryStorage(sql),
333
- webhookRuns: new PgWebhookRunStorage(sql),
347
+ webhookRuns: new PgWebhookRunStorage(sql, executions),
334
348
  rules: new PgRulesStorage(sql),
349
+ connectorConnections: new PgConnectorConnectionStorage(sql),
335
350
  }
336
351
  }
337
352
 
@@ -339,7 +354,9 @@ interface PostgresStoreSet {
339
354
  readonly objects: PgObjectStorage
340
355
  readonly ontology: PgOntologyStorage
341
356
  readonly auth: PgAuthStorage
357
+ readonly executions: PgExecutionStorage
342
358
  readonly agents: PgAgentStorage
359
+ readonly aiUsage: PgAiUsageStorage
343
360
  readonly actionRuns: PgActionRunStorage
344
361
  readonly pipelineRuns: PgPipelineRunStorage
345
362
  readonly workflowRuns: PgWorkflowRunStorage
@@ -347,9 +364,9 @@ interface PostgresStoreSet {
347
364
  readonly syncRuns: PgSyncRunStorage
348
365
  readonly projectionRuns: PgProjectionRunStorage
349
366
  readonly timeseries: PgTimeseriesStorage
350
- readonly webhookDeliveries: PgWebhookDeliveryStorage
351
367
  readonly webhookRuns: PgWebhookRunStorage
352
368
  readonly rules: PgRulesStorage
369
+ readonly connectorConnections: PgConnectorConnectionStorage
353
370
  }
354
371
 
355
372
  function resolveTimeoutMillis(value: number | undefined, label: string): number | undefined {
@@ -23,12 +23,18 @@ export async function queryLatestRunsByOwnerId<TRow>(
23
23
  }
24
24
 
25
25
  const placeholders = ownerIds.map((_, index) => `$${index + 2}`).join(", ")
26
+ const orderColumn =
27
+ input.tableName === "sync_runs" ||
28
+ input.tableName === "pipeline_runs" ||
29
+ input.tableName === "projection_runs"
30
+ ? "COALESCE(started_at, queued_at)"
31
+ : "started_at"
26
32
  const rows = await input.sql.unsafe<TRow[]>(
27
33
  `
28
34
  SELECT DISTINCT ON (${input.ownerColumn}) ${input.selectList ?? "*"}
29
35
  FROM ${input.tableName}
30
36
  WHERE project_id = $1 AND ${input.ownerColumn} IN (${placeholders})
31
- ORDER BY ${input.ownerColumn}, started_at DESC, id DESC
37
+ ORDER BY ${input.ownerColumn}, ${orderColumn} DESC, id DESC
32
38
  `,
33
39
  [input.projectId, ...ownerIds] as SqlParameter[]
34
40
  )
@@ -0,0 +1,27 @@
1
+ ALTER TABLE workflow_runs ADD COLUMN output JSONB;
2
+
3
+ UPDATE workflow_runs AS run
4
+ SET output = CASE
5
+ WHEN EXISTS (
6
+ SELECT 1
7
+ FROM workflow_node_runs AS node
8
+ WHERE node.project_id = run.project_id
9
+ AND node.workflow_run_id = run.id
10
+ AND node.status = 'succeeded'
11
+ AND node.node_type <> 'action'
12
+ ) THEN COALESCE(
13
+ (
14
+ SELECT node.output
15
+ FROM workflow_node_runs AS node
16
+ WHERE node.project_id = run.project_id
17
+ AND node.workflow_run_id = run.id
18
+ AND node.status = 'succeeded'
19
+ AND node.node_type <> 'action'
20
+ ORDER BY node.node_index DESC, node.id DESC
21
+ LIMIT 1
22
+ ),
23
+ '{}'::jsonb
24
+ )
25
+ ELSE run.input
26
+ END
27
+ WHERE run.status = 'succeeded';
@@ -0,0 +1,3 @@
1
+ ALTER TABLE sync_runs
2
+ DROP CONSTRAINT sync_runs_mode_check,
3
+ ADD CONSTRAINT sync_runs_mode_check CHECK (mode IN ('snapshot', 'append', 'merge'));
@@ -0,0 +1,140 @@
1
+ CREATE TABLE executions (
2
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
3
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
4
+
5
+ executor_kind TEXT NOT NULL CHECK (
6
+ executor_kind IN (
7
+ 'request', 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow',
8
+ 'agent', 'kernel'
9
+ )
10
+ ),
11
+ executor_id TEXT NOT NULL CHECK (length(trim(executor_id)) > 0),
12
+ source_kind TEXT NOT NULL CHECK (
13
+ source_kind IN ('http', 'webhook', 'schedule', 'event', 'execution')
14
+ ),
15
+ source_id TEXT NOT NULL CHECK (length(trim(source_id)) > 0),
16
+
17
+ requested_by_user_id TEXT,
18
+ requested_by_service_account_id TEXT,
19
+ correlation_id TEXT NOT NULL CHECK (length(trim(correlation_id)) > 0),
20
+ parent_execution_id TEXT,
21
+
22
+ authority_kind TEXT NOT NULL CHECK (
23
+ authority_kind IN ('principal', 'trustedPrimitive', 'kernel', 'disabled')
24
+ ),
25
+ authority_user_id TEXT,
26
+ authority_service_account_id TEXT,
27
+ authority_session_id TEXT,
28
+ authority_access_token_id TEXT,
29
+ authority_primitive_kind TEXT CHECK (
30
+ authority_primitive_kind IS NULL
31
+ OR authority_primitive_kind IN (
32
+ 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow'
33
+ )
34
+ ),
35
+ authority_primitive_id TEXT,
36
+ authority_kernel_operation TEXT CHECK (
37
+ authority_kernel_operation IS NULL OR authority_kernel_operation = 'ontology.recover'
38
+ ),
39
+
40
+ created_at TIMESTAMPTZ NOT NULL,
41
+
42
+ PRIMARY KEY (project_id, id),
43
+ FOREIGN KEY (project_id, parent_execution_id)
44
+ REFERENCES executions (project_id, id),
45
+ FOREIGN KEY (project_id, requested_by_user_id)
46
+ REFERENCES auth_users (project_id, id),
47
+ FOREIGN KEY (project_id, requested_by_service_account_id)
48
+ REFERENCES auth_service_accounts (project_id, id),
49
+ FOREIGN KEY (project_id, authority_user_id)
50
+ REFERENCES auth_users (project_id, id),
51
+ FOREIGN KEY (project_id, authority_service_account_id)
52
+ REFERENCES auth_service_accounts (project_id, id),
53
+ FOREIGN KEY (project_id, authority_session_id)
54
+ REFERENCES auth_sessions (project_id, id),
55
+ FOREIGN KEY (project_id, authority_access_token_id)
56
+ REFERENCES auth_access_tokens (project_id, id),
57
+
58
+ CHECK (num_nonnulls(requested_by_user_id, requested_by_service_account_id) <= 1),
59
+ CHECK (
60
+ (source_kind = 'execution' AND parent_execution_id = source_id)
61
+ OR (source_kind <> 'execution' AND parent_execution_id IS NULL)
62
+ ),
63
+ CHECK (
64
+ (
65
+ authority_kind = 'principal'
66
+ AND num_nonnulls(authority_user_id, authority_service_account_id) = 1
67
+ AND num_nonnulls(authority_session_id, authority_access_token_id) <= 1
68
+ AND (authority_session_id IS NULL OR authority_user_id IS NOT NULL)
69
+ AND authority_primitive_kind IS NULL
70
+ AND authority_primitive_id IS NULL
71
+ AND authority_kernel_operation IS NULL
72
+ )
73
+ OR (
74
+ authority_kind = 'trustedPrimitive'
75
+ AND authority_user_id IS NULL
76
+ AND authority_service_account_id IS NULL
77
+ AND authority_session_id IS NULL
78
+ AND authority_access_token_id IS NULL
79
+ AND authority_primitive_kind IS NOT NULL
80
+ AND authority_primitive_id IS NOT NULL
81
+ AND length(trim(authority_primitive_id)) > 0
82
+ AND authority_kernel_operation IS NULL
83
+ )
84
+ OR (
85
+ authority_kind = 'kernel'
86
+ AND authority_user_id IS NULL
87
+ AND authority_service_account_id IS NULL
88
+ AND authority_session_id IS NULL
89
+ AND authority_access_token_id IS NULL
90
+ AND authority_primitive_kind IS NULL
91
+ AND authority_primitive_id IS NULL
92
+ AND authority_kernel_operation IS NOT NULL
93
+ )
94
+ OR (
95
+ authority_kind = 'disabled'
96
+ AND authority_user_id IS NULL
97
+ AND authority_service_account_id IS NULL
98
+ AND authority_session_id IS NULL
99
+ AND authority_access_token_id IS NULL
100
+ AND authority_primitive_kind IS NULL
101
+ AND authority_primitive_id IS NULL
102
+ AND authority_kernel_operation IS NULL
103
+ )
104
+ ),
105
+ CHECK (
106
+ (
107
+ executor_kind = 'request'
108
+ AND source_kind = 'http'
109
+ AND executor_id = source_id
110
+ AND (
111
+ (
112
+ authority_kind = 'principal'
113
+ AND requested_by_user_id IS NOT DISTINCT FROM authority_user_id
114
+ AND requested_by_service_account_id IS NOT DISTINCT FROM authority_service_account_id
115
+ )
116
+ OR (
117
+ authority_kind = 'disabled'
118
+ AND requested_by_user_id IS NULL
119
+ AND requested_by_service_account_id IS NULL
120
+ )
121
+ )
122
+ )
123
+ OR (
124
+ executor_kind IN ('action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow')
125
+ AND authority_kind = 'trustedPrimitive'
126
+ AND executor_kind = authority_primitive_kind
127
+ )
128
+ OR (
129
+ executor_kind = 'agent'
130
+ AND authority_kind = 'principal'
131
+ AND authority_service_account_id IS NOT NULL
132
+ AND authority_session_id IS NULL
133
+ AND authority_access_token_id IS NULL
134
+ )
135
+ OR (
136
+ executor_kind = 'kernel'
137
+ AND authority_kind = 'kernel'
138
+ )
139
+ )
140
+ );
@@ -0,0 +1,10 @@
1
+ ALTER TABLE workflow_runs
2
+ ADD COLUMN execution_id TEXT NOT NULL,
3
+ DROP COLUMN source,
4
+ DROP COLUMN requested_by_principal_type,
5
+ DROP COLUMN requested_by_principal_id,
6
+ ADD CONSTRAINT fk_workflow_runs_execution
7
+ FOREIGN KEY (project_id, execution_id)
8
+ REFERENCES executions (project_id, id)
9
+ ON DELETE RESTRICT,
10
+ ADD CONSTRAINT uq_workflow_runs_execution UNIQUE (project_id, execution_id);
@@ -0,0 +1,4 @@
1
+ DROP INDEX idx_ontology_source_rows_root;
2
+
3
+ CREATE INDEX idx_ontology_source_rows_root
4
+ ON ontology_source_rows (project_id, source_id, materialization_id, root_sort_key);
@@ -0,0 +1,153 @@
1
+ CREATE TABLE ontology_object_overrides (
2
+ project_id TEXT NOT NULL,
3
+ object_type_id TEXT NOT NULL,
4
+ primary_id TEXT NOT NULL,
5
+ value JSONB NOT NULL CHECK (jsonb_typeof(value) = 'object'),
6
+ last_commit_id TEXT NOT NULL,
7
+ updated_at TIMESTAMPTZ NOT NULL,
8
+ PRIMARY KEY (project_id, object_type_id, primary_id),
9
+ CHECK (COALESCE(value->>'kind' IN ('create', 'patch', 'delete'), false))
10
+ );
11
+
12
+ CREATE TABLE ontology_link_overrides (
13
+ project_id TEXT NOT NULL,
14
+ identity_kind TEXT NOT NULL CHECK (identity_kind IN ('edge', 'slot')),
15
+ identity_key JSONB NOT NULL,
16
+ source_type_id TEXT NOT NULL,
17
+ source_primary_id TEXT NOT NULL,
18
+ link_id TEXT NOT NULL,
19
+ target_type_id TEXT NOT NULL,
20
+ target_primary_id TEXT NOT NULL,
21
+ value JSONB NOT NULL CHECK (jsonb_typeof(value) = 'object'),
22
+ last_commit_id TEXT NOT NULL,
23
+ updated_at TIMESTAMPTZ NOT NULL,
24
+ PRIMARY KEY (project_id, identity_kind, identity_key),
25
+ CHECK (
26
+ identity_key = CASE identity_kind
27
+ WHEN 'edge' THEN jsonb_build_array(
28
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
29
+ )
30
+ WHEN 'slot' THEN jsonb_build_array(source_type_id, source_primary_id, link_id)
31
+ END
32
+ ),
33
+ CHECK (
34
+ (identity_kind = 'edge' AND COALESCE(value->>'kind' IN ('upsert', 'delete'), false))
35
+ OR
36
+ (identity_kind = 'slot'
37
+ AND COALESCE(value->>'kind' IN ('set', 'clear', 'legacy-conflict'), false)
38
+ AND (
39
+ value->>'kind' = 'legacy-conflict'
40
+ OR (
41
+ target_type_id = value->'target'->>'objectTypeId'
42
+ AND target_primary_id = value->'target'->>'primaryId'
43
+ )
44
+ ))
45
+ )
46
+ );
47
+
48
+ CREATE INDEX idx_ontology_link_overrides_source
49
+ ON ontology_link_overrides (
50
+ project_id, identity_kind, source_type_id, source_primary_id, link_id,
51
+ target_type_id, target_primary_id
52
+ );
53
+
54
+ CREATE INDEX idx_ontology_link_overrides_target
55
+ ON ontology_link_overrides (project_id, target_type_id, target_primary_id);
56
+
57
+ INSERT INTO ontology_object_overrides (
58
+ project_id, object_type_id, primary_id, value, last_commit_id, updated_at
59
+ )
60
+ SELECT project_id, object_type_id, primary_id, value, last_commit_id, updated_at
61
+ FROM ontology_overrides
62
+ WHERE entity_kind = 'object';
63
+
64
+ INSERT INTO ontology_link_overrides (
65
+ project_id, identity_kind, identity_key,
66
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id,
67
+ value, last_commit_id, updated_at
68
+ )
69
+ SELECT project_id, 'edge', jsonb_build_array(
70
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
71
+ ),
72
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id,
73
+ value, last_commit_id, updated_at
74
+ FROM ontology_overrides
75
+ WHERE entity_kind = 'link';
76
+
77
+ -- Storage migrations do not have access to the ontology cardinality. Derive a slot candidate for
78
+ -- every legacy link scope; the runtime selects edge or slot authority from the registered ontology.
79
+ -- A single upsert is an unambiguous set and a delete-only scope is an unambiguous clear. Multiple
80
+ -- upserts are preserved as an explicit conflict instead of selecting a target arbitrarily.
81
+ WITH scopes AS (
82
+ SELECT project_id, source_type_id, source_primary_id, link_id,
83
+ COUNT(*) FILTER (WHERE value->>'kind' = 'upsert') AS upsert_count
84
+ FROM ontology_overrides
85
+ WHERE entity_kind = 'link'
86
+ GROUP BY project_id, source_type_id, source_primary_id, link_id
87
+ ), selected AS (
88
+ SELECT scopes.*, COALESCE(upsert.entity_key, anchor.entity_key) AS selected_key
89
+ FROM scopes
90
+ LEFT JOIN LATERAL (
91
+ SELECT candidate.entity_key
92
+ FROM ontology_overrides AS candidate
93
+ WHERE candidate.project_id = scopes.project_id
94
+ AND candidate.entity_kind = 'link'
95
+ AND candidate.source_type_id = scopes.source_type_id
96
+ AND candidate.source_primary_id = scopes.source_primary_id
97
+ AND candidate.link_id = scopes.link_id
98
+ AND candidate.value->>'kind' = 'upsert'
99
+ ORDER BY candidate.updated_at DESC, candidate.entity_sort_key DESC
100
+ LIMIT 1
101
+ ) AS upsert ON true
102
+ LEFT JOIN LATERAL (
103
+ SELECT candidate.entity_key
104
+ FROM ontology_overrides AS candidate
105
+ WHERE candidate.project_id = scopes.project_id
106
+ AND candidate.entity_kind = 'link'
107
+ AND candidate.source_type_id = scopes.source_type_id
108
+ AND candidate.source_primary_id = scopes.source_primary_id
109
+ AND candidate.link_id = scopes.link_id
110
+ ORDER BY candidate.updated_at DESC, candidate.entity_sort_key DESC
111
+ LIMIT 1
112
+ ) AS anchor ON true
113
+ )
114
+ INSERT INTO ontology_link_overrides (
115
+ project_id, identity_kind, identity_key,
116
+ source_type_id, source_primary_id, link_id, target_type_id, target_primary_id,
117
+ value, last_commit_id, updated_at
118
+ )
119
+ SELECT selected.project_id, 'slot',
120
+ jsonb_build_array(selected.source_type_id, selected.source_primary_id, selected.link_id),
121
+ selected.source_type_id, selected.source_primary_id, selected.link_id,
122
+ overrides.target_type_id, overrides.target_primary_id,
123
+ CASE
124
+ WHEN selected.upsert_count > 1 THEN jsonb_build_object('kind', 'legacy-conflict')
125
+ WHEN selected.upsert_count = 1 THEN
126
+ jsonb_build_object(
127
+ 'kind', 'set',
128
+ 'target', jsonb_build_object(
129
+ 'objectTypeId', overrides.target_type_id,
130
+ 'primaryId', overrides.target_primary_id
131
+ )
132
+ ) || CASE
133
+ WHEN overrides.value ? 'properties'
134
+ THEN jsonb_build_object('properties', overrides.value->'properties')
135
+ ELSE '{}'::jsonb
136
+ END
137
+ ELSE jsonb_build_object(
138
+ 'kind', 'clear',
139
+ 'target', jsonb_build_object(
140
+ 'objectTypeId', overrides.target_type_id,
141
+ 'primaryId', overrides.target_primary_id
142
+ )
143
+ )
144
+ END,
145
+ overrides.last_commit_id,
146
+ overrides.updated_at
147
+ FROM selected
148
+ JOIN ontology_overrides AS overrides
149
+ ON overrides.project_id = selected.project_id
150
+ AND overrides.entity_kind = 'link'
151
+ AND overrides.entity_key = selected.selected_key;
152
+
153
+ DROP TABLE ontology_overrides;
@@ -0,0 +1,7 @@
1
+ ALTER TABLE action_runs
2
+ ADD COLUMN execution_id TEXT NOT NULL,
3
+ ADD CONSTRAINT fk_action_runs_execution
4
+ FOREIGN KEY (project_id, execution_id)
5
+ REFERENCES executions (project_id, id)
6
+ ON DELETE RESTRICT,
7
+ ADD CONSTRAINT uq_action_runs_execution UNIQUE (project_id, execution_id);
@@ -0,0 +1,21 @@
1
+ ALTER TABLE agent_runs
2
+ ADD COLUMN execution_id TEXT NOT NULL,
3
+ ADD CONSTRAINT fk_agent_runs_execution
4
+ FOREIGN KEY (project_id, execution_id)
5
+ REFERENCES executions (project_id, id)
6
+ ON DELETE RESTRICT,
7
+ ADD CONSTRAINT uq_agent_runs_execution UNIQUE (project_id, execution_id),
8
+ DROP COLUMN requested_by_principal_type,
9
+ DROP COLUMN requested_by_principal_id,
10
+ DROP COLUMN execution_principal_type,
11
+ DROP COLUMN execution_principal_id;
12
+
13
+ ALTER TABLE workflow_agent_node_runs
14
+ ADD COLUMN execution_id TEXT NOT NULL,
15
+ ADD CONSTRAINT fk_workflow_agent_node_runs_execution
16
+ FOREIGN KEY (project_id, execution_id)
17
+ REFERENCES executions (project_id, id)
18
+ ON DELETE RESTRICT,
19
+ ADD CONSTRAINT uq_workflow_agent_node_runs_execution UNIQUE (project_id, execution_id),
20
+ DROP COLUMN execution_principal_type,
21
+ DROP COLUMN execution_principal_id;
@@ -0,0 +1,76 @@
1
+ ALTER TABLE agent_runs
2
+ ADD COLUMN requester_group_ids JSONB NOT NULL DEFAULT '[]'::jsonb;
3
+ ALTER TABLE agent_runs
4
+ ADD CONSTRAINT agent_runs_requester_group_ids_array
5
+ CHECK (jsonb_typeof(requester_group_ids) = 'array');
6
+
7
+ ALTER TABLE workflow_runs
8
+ ADD COLUMN requester_group_ids JSONB NOT NULL DEFAULT '[]'::jsonb;
9
+ ALTER TABLE workflow_runs
10
+ ADD CONSTRAINT workflow_runs_requester_group_ids_array
11
+ CHECK (jsonb_typeof(requester_group_ids) = 'array');
12
+
13
+ CREATE TABLE ai_model_call_usage (
14
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
15
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
16
+ execution_id TEXT NOT NULL CHECK (length(trim(execution_id)) > 0),
17
+ attempt BIGINT NOT NULL CHECK (attempt >= 1),
18
+ call_id TEXT NOT NULL CHECK (length(trim(call_id)) > 0),
19
+ provider_id TEXT NOT NULL CHECK (length(trim(provider_id)) > 0),
20
+ requested_model_id TEXT NOT NULL CHECK (length(trim(requested_model_id)) > 0),
21
+ response_model_id TEXT CHECK (
22
+ response_model_id IS NULL OR length(trim(response_model_id)) > 0
23
+ ),
24
+ response_id TEXT NOT NULL CHECK (length(trim(response_id)) > 0),
25
+ input_tokens BIGINT CHECK (input_tokens IS NULL OR input_tokens >= 0),
26
+ output_tokens BIGINT CHECK (output_tokens IS NULL OR output_tokens >= 0),
27
+ total_tokens BIGINT CHECK (total_tokens IS NULL OR total_tokens >= 0),
28
+ uncached_input_tokens BIGINT CHECK (
29
+ uncached_input_tokens IS NULL OR uncached_input_tokens >= 0
30
+ ),
31
+ cache_read_input_tokens BIGINT CHECK (
32
+ cache_read_input_tokens IS NULL OR cache_read_input_tokens >= 0
33
+ ),
34
+ cache_write_input_tokens BIGINT CHECK (
35
+ cache_write_input_tokens IS NULL OR cache_write_input_tokens >= 0
36
+ ),
37
+ text_output_tokens BIGINT CHECK (
38
+ text_output_tokens IS NULL OR text_output_tokens >= 0
39
+ ),
40
+ reasoning_output_tokens BIGINT CHECK (
41
+ reasoning_output_tokens IS NULL OR reasoning_output_tokens >= 0
42
+ ),
43
+ reporting_status TEXT NOT NULL CHECK (
44
+ reporting_status IN ('complete', 'partial', 'unavailable')
45
+ ),
46
+ raw_usage JSONB CHECK (raw_usage IS NULL OR jsonb_typeof(raw_usage) = 'object'),
47
+ occurred_at TIMESTAMPTZ NOT NULL,
48
+ recorded_at TIMESTAMPTZ NOT NULL,
49
+ PRIMARY KEY (project_id, id),
50
+ FOREIGN KEY (project_id, execution_id)
51
+ REFERENCES executions(project_id, id)
52
+ ON DELETE RESTRICT
53
+ );
54
+
55
+ CREATE UNIQUE INDEX idx_ai_model_call_usage_idempotency
56
+ ON ai_model_call_usage (project_id, execution_id, attempt, call_id, response_id);
57
+ CREATE INDEX idx_ai_model_call_usage_execution
58
+ ON ai_model_call_usage (project_id, execution_id, occurred_at, id);
59
+ CREATE INDEX idx_ai_model_call_usage_project_time
60
+ ON ai_model_call_usage (project_id, occurred_at, id);
61
+ CREATE INDEX idx_ai_model_call_usage_provider_response
62
+ ON ai_model_call_usage (project_id, provider_id, response_id);
63
+
64
+ CREATE TABLE ai_model_call_usage_groups (
65
+ project_id TEXT NOT NULL,
66
+ usage_record_id TEXT NOT NULL,
67
+ group_id TEXT NOT NULL,
68
+ occurred_at TIMESTAMPTZ NOT NULL,
69
+ PRIMARY KEY (project_id, usage_record_id, group_id),
70
+ FOREIGN KEY (project_id, usage_record_id)
71
+ REFERENCES ai_model_call_usage(project_id, id)
72
+ ON DELETE CASCADE
73
+ );
74
+
75
+ CREATE INDEX idx_ai_model_call_usage_groups_group_time
76
+ ON ai_model_call_usage_groups (project_id, group_id, occurred_at, usage_record_id);
@@ -0,0 +1,23 @@
1
+ ALTER TABLE sync_runs ADD COLUMN error JSONB;
2
+
3
+ UPDATE sync_runs
4
+ SET error = jsonb_build_object(
5
+ 'code', CASE WHEN status = 'cancelled' THEN 'runtime.cancelled' ELSE 'internal.unexpected' END,
6
+ 'message', CASE
7
+ WHEN status = 'cancelled' THEN 'Execution was cancelled.'
8
+ ELSE 'An unexpected internal error occurred.'
9
+ END,
10
+ 'retryable', false,
11
+ 'at', to_char(COALESCE(finished_at, started_at) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
12
+ 'details', jsonb_build_object(
13
+ 'syncId', sync_id,
14
+ 'runId', id,
15
+ 'datasetId', dataset_id,
16
+ 'migratedFromLegacyError', true
17
+ )
18
+ )
19
+ WHERE error_name IS NOT NULL OR error_message IS NOT NULL;
20
+
21
+ ALTER TABLE sync_runs
22
+ DROP COLUMN error_name,
23
+ DROP COLUMN error_message;