@sixb/pg 0.1.1 → 0.1.3

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 (112) hide show
  1. package/README.md +7 -6
  2. package/dist/agents/agent-storage.d.ts +7 -4
  3. package/dist/agents/agent-storage.d.ts.map +1 -1
  4. package/dist/agents/checkpoints.d.ts +12 -0
  5. package/dist/agents/checkpoints.d.ts.map +1 -0
  6. package/dist/agents/index.d.ts +1 -0
  7. package/dist/agents/index.d.ts.map +1 -1
  8. package/dist/agents/messages.d.ts +2 -1
  9. package/dist/agents/messages.d.ts.map +1 -1
  10. package/dist/agents/rows.d.ts +22 -12
  11. package/dist/agents/rows.d.ts.map +1 -1
  12. package/dist/agents/runs.d.ts +3 -2
  13. package/dist/agents/runs.d.ts.map +1 -1
  14. package/dist/auth-storage/service-accounts.d.ts.map +1 -1
  15. package/dist/auth-storage/shared.d.ts +0 -4
  16. package/dist/auth-storage/shared.d.ts.map +1 -1
  17. package/dist/connector-connection-storage.d.ts +9 -0
  18. package/dist/connector-connection-storage.d.ts.map +1 -0
  19. package/dist/index.d.ts +9 -5
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +5010 -1837
  22. package/dist/index.js.map +31 -26
  23. package/dist/latest-run-query.d.ts.map +1 -1
  24. package/dist/migrations.d.ts.map +1 -1
  25. package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
  26. package/dist/ontology-storage/materialization-state.d.ts +0 -2
  27. package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
  28. package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
  29. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  30. package/dist/ontology-storage/outbox.d.ts.map +1 -1
  31. package/dist/ontology-storage/shared.d.ts +3 -6
  32. package/dist/ontology-storage/shared.d.ts.map +1 -1
  33. package/dist/pg-action-run-storage.d.ts +3 -2
  34. package/dist/pg-action-run-storage.d.ts.map +1 -1
  35. package/dist/pg-ai-cost-storage.d.ts +12 -0
  36. package/dist/pg-ai-cost-storage.d.ts.map +1 -0
  37. package/dist/pg-ai-usage-storage.d.ts +16 -0
  38. package/dist/pg-ai-usage-storage.d.ts.map +1 -0
  39. package/dist/pg-execution-storage.d.ts +14 -0
  40. package/dist/pg-execution-storage.d.ts.map +1 -0
  41. package/dist/pg-pipeline-run-storage.d.ts +5 -2
  42. package/dist/pg-pipeline-run-storage.d.ts.map +1 -1
  43. package/dist/pg-projection-run-storage.d.ts +5 -2
  44. package/dist/pg-projection-run-storage.d.ts.map +1 -1
  45. package/dist/pg-sync-run-storage.d.ts +5 -2
  46. package/dist/pg-sync-run-storage.d.ts.map +1 -1
  47. package/dist/pg-webhook-run-storage.d.ts +5 -2
  48. package/dist/pg-webhook-run-storage.d.ts.map +1 -1
  49. package/dist/pg-workflow-run-storage.d.ts +5 -3
  50. package/dist/pg-workflow-run-storage.d.ts.map +1 -1
  51. package/dist/run-list-query.d.ts +1 -1
  52. package/dist/run-list-query.d.ts.map +1 -1
  53. package/dist/testing.d.ts +6 -0
  54. package/dist/testing.d.ts.map +1 -0
  55. package/package.json +3 -3
  56. package/src/agents/agent-storage.ts +9 -5
  57. package/src/agents/checkpoints.ts +175 -0
  58. package/src/agents/index.ts +1 -0
  59. package/src/agents/messages.ts +71 -13
  60. package/src/agents/rows.ts +57 -43
  61. package/src/agents/runs.ts +29 -15
  62. package/src/auth-storage/service-accounts.ts +14 -15
  63. package/src/auth-storage/shared.ts +0 -8
  64. package/src/connector-connection-storage.ts +760 -0
  65. package/src/index.ts +38 -15
  66. package/src/latest-run-query.ts +7 -1
  67. package/src/migrations/004-executions.sql +140 -0
  68. package/src/migrations/005-workflow-executions.sql +10 -0
  69. package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
  70. package/src/migrations/007-split-overrides.sql +153 -0
  71. package/src/migrations/008-action-executions.sql +7 -0
  72. package/src/migrations/009-agent-executions.sql +21 -0
  73. package/src/migrations/010-ai-usage-accounting-foundation.sql +76 -0
  74. package/src/migrations/011-sync-failure-record.sql +23 -0
  75. package/src/migrations/012-pipeline-failure-record.sql +47 -0
  76. package/src/migrations/013-workflow-failure-record.sql +45 -0
  77. package/src/migrations/014-agent-failure-record.sql +56 -0
  78. package/src/migrations/015-projection-failure-record.sql +20 -0
  79. package/src/migrations/016-webhook-run-failure-record.sql +19 -0
  80. package/src/migrations/017-action-failure-record.sql +65 -0
  81. package/src/migrations/018-ontology-outbox-failure-record.sql +29 -0
  82. package/src/migrations/019-webhook-delivery-failure-record.sql +27 -0
  83. package/src/migrations/020-drop-run-usage-projections.sql +8 -0
  84. package/src/migrations/021-sync-pipeline-executions.sql +50 -0
  85. package/src/migrations/022-projection-executions.sql +115 -0
  86. package/src/migrations/023-webhook-executions.sql +30 -0
  87. package/src/migrations/024-ontology-commit-executions.sql +12 -0
  88. package/src/migrations/025-connector-connections.sql +231 -0
  89. package/src/migrations/026-ai-cost-accounting.sql +53 -0
  90. package/src/migrations/027-agent-context-checkpoints.sql +34 -0
  91. package/src/migrations/028-object-override-edit-times.sql +3 -0
  92. package/src/migrations.ts +82 -0
  93. package/src/ontology-storage/materialization-session.ts +25 -28
  94. package/src/ontology-storage/materialization-state.ts +161 -43
  95. package/src/ontology-storage/materialization-writer.ts +149 -76
  96. package/src/ontology-storage/materializations.ts +3 -1
  97. package/src/ontology-storage/outbox.ts +9 -2
  98. package/src/ontology-storage/shared.ts +10 -20
  99. package/src/pg-action-run-storage.ts +43 -67
  100. package/src/pg-ai-cost-storage.ts +707 -0
  101. package/src/pg-ai-usage-storage.ts +290 -0
  102. package/src/pg-execution-storage.ts +147 -0
  103. package/src/pg-pipeline-run-storage.ts +107 -41
  104. package/src/pg-projection-run-storage.ts +159 -91
  105. package/src/pg-sync-run-storage.ts +100 -28
  106. package/src/pg-webhook-run-storage.ts +89 -57
  107. package/src/pg-workflow-run-storage.ts +97 -130
  108. package/src/run-list-query.ts +9 -4
  109. package/src/testing.ts +18 -0
  110. package/dist/pg-webhook-delivery-storage.d.ts +0 -17
  111. package/dist/pg-webhook-delivery-storage.d.ts.map +0 -1
  112. package/src/pg-webhook-delivery-storage.ts +0 -180
@@ -1,10 +1,17 @@
1
- import type { Principal, WorkflowRunSource } from "@sixb/core"
1
+ import type { JsonValue } from "@sixb/core"
2
+ import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth"
3
+ import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
4
+ import {
5
+ assertWorkflowAgentNodeRunExecution,
6
+ assertWorkflowRunExecution,
7
+ } from "@sixb/core/internal/workflow-run-storage-provider"
2
8
  import type { WorkflowIOSnapshot } from "@sixb/core/internal/workflows"
3
9
  import type {
4
10
  CancelWorkflowAgentNodeRunInput,
5
11
  ConfirmWorkflowAgentNodeRunExecutionOwnershipInput,
6
12
  ConfirmWorkflowRunExecutionOwnershipInput,
7
13
  CreateWorkflowAgentNodeRunInput,
14
+ ExecutionStorage,
8
15
  FinishWorkflowAgentNodeRunInput,
9
16
  FinishWorkflowNodeRunInput,
10
17
  FinishWorkflowRunInput,
@@ -32,7 +39,11 @@ import type {
32
39
  WorkflowRunRecord,
33
40
  WorkflowRunStorage,
34
41
  } from "@sixb/core/storage"
35
- import { WorkflowRunError } from "@sixb/core/storage"
42
+ import {
43
+ AGENT_RUN_FAILURE_CODES,
44
+ WORKFLOW_RUN_FAILURE_CODES,
45
+ WorkflowRunError,
46
+ } from "@sixb/core/storage"
36
47
  import { queryLatestRunsByOwnerId } from "./latest-run-query"
37
48
  import type { SqlParameter } from "./pg-client"
38
49
  import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "./run-list-query"
@@ -43,39 +54,47 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
43
54
  readonly nodes: PgWorkflowNodeRunStorage
44
55
  readonly agentNodes: PgWorkflowAgentNodeRunStorage
45
56
 
46
- constructor(private readonly sql: PgStoreClient) {
57
+ constructor(
58
+ private readonly sql: PgStoreClient,
59
+ private readonly executions: ExecutionStorage
60
+ ) {
47
61
  this.nodes = new PgWorkflowNodeRunStorage(sql)
48
- this.agentNodes = new PgWorkflowAgentNodeRunStorage(sql)
62
+ this.agentNodes = new PgWorkflowAgentNodeRunStorage(sql, executions)
49
63
  }
50
64
 
51
65
  async queue(input: QueueWorkflowRunInput): Promise<WorkflowRunRecord> {
52
66
  const queuedAt = input.queuedAt ?? new Date()
67
+ await assertWorkflowRunExecution({
68
+ executions: this.executions,
69
+ projectId: input.projectId,
70
+ executionId: input.executionId,
71
+ runId: input.id,
72
+ workflowId: input.workflowId,
73
+ })
53
74
 
54
75
  try {
55
76
  const [row] = await this.sql<WorkflowRunDatabaseRow[]>`
56
77
  INSERT INTO workflow_runs (
57
78
  project_id,
58
79
  id,
80
+ execution_id,
59
81
  workflow_id,
60
82
  status,
61
83
  input,
62
84
  queued_at,
63
85
  started_at,
64
- source,
65
- requested_by_principal_type,
66
- requested_by_principal_id,
86
+ requester_group_ids,
67
87
  attempt
68
88
  ) VALUES (
69
89
  ${input.projectId},
70
90
  ${input.id},
91
+ ${input.executionId},
71
92
  ${input.workflowId},
72
93
  ${"queued"},
73
94
  ${serializeRecord(input.input)}::text::jsonb,
74
95
  ${queuedAt},
75
96
  ${queuedAt},
76
- ${input.source ? JSON.stringify(input.source) : null}::text::jsonb,
77
- ${input.requestedByPrincipal?.type ?? "system"},
78
- ${input.requestedByPrincipal?.id ?? "system"},
97
+ ${JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds))}::text::jsonb,
79
98
  ${0}
80
99
  )
81
100
  RETURNING *
@@ -85,7 +104,7 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
85
104
  } catch (error) {
86
105
  if (isUniqueViolation(error)) {
87
106
  throw new WorkflowRunError(
88
- `[SixbPg] Workflow run '${input.id}' already exists for project '${input.projectId}'.`
107
+ `[SixbPg] Workflow run '${input.id}' or execution '${input.executionId}' is already linked for project '${input.projectId}'.`
89
108
  )
90
109
  }
91
110
 
@@ -102,83 +121,29 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
102
121
  FOR UPDATE
103
122
  `
104
123
 
105
- if (existing) {
106
- if (existing.status !== "queued") {
107
- throw new WorkflowRunError(
108
- `[SixbPg] Workflow run '${input.id}' already exists for project '${input.projectId}'.`
109
- )
110
- }
111
-
112
- if (existing.workflow_id !== input.workflowId) {
113
- throw new WorkflowRunError(
114
- `[SixbPg] Workflow run '${input.id}' workflow '${input.workflowId}' does not match existing workflow '${existing.workflow_id}'.`
115
- )
116
- }
117
-
118
- const [updated] = await tx<WorkflowRunDatabaseRow[]>`
119
- UPDATE workflow_runs
120
- SET
121
- status = ${"running"},
122
- input = ${serializeRecord(input.input)}::text::jsonb,
123
- started_at = ${startedAt},
124
- finished_at = ${null},
125
- error = ${null},
126
- source = COALESCE(
127
- source,
128
- ${input.source ? JSON.stringify(input.source) : null}::text::jsonb
129
- ),
130
- attempt = attempt + 1,
131
- execution_token = ${input.execution?.token ?? null},
132
- execution_queue_lease_expires_at = ${input.execution?.queueLeaseExpiresAt ?? null}
133
- WHERE project_id = ${input.projectId} AND id = ${input.id}
134
- RETURNING *
135
- `
136
-
137
- return rowToWorkflowRunRecord(updated)
124
+ if (!existing || existing.status !== "queued") {
125
+ throw new WorkflowRunError(
126
+ existing
127
+ ? `[SixbPg] Workflow run '${input.id}' cannot start from status '${existing.status}'.`
128
+ : `[SixbPg] Workflow run '${input.id}' not found for project '${input.projectId}'.`
129
+ )
138
130
  }
139
131
 
140
- try {
141
- const [row] = await tx<WorkflowRunDatabaseRow[]>`
142
- INSERT INTO workflow_runs (
143
- project_id,
144
- id,
145
- workflow_id,
146
- status,
147
- input,
148
- started_at,
149
- source,
150
- requested_by_principal_type,
151
- requested_by_principal_id,
152
- attempt,
153
- execution_token,
154
- execution_queue_lease_expires_at
155
- ) VALUES (
156
- ${input.projectId},
157
- ${input.id},
158
- ${input.workflowId},
159
- ${"running"},
160
- ${serializeRecord(input.input)}::text::jsonb,
161
- ${startedAt},
162
- ${input.source ? JSON.stringify(input.source) : null}::text::jsonb,
163
- ${input.requestedByPrincipal?.type ?? "system"},
164
- ${input.requestedByPrincipal?.id ?? "system"},
165
- ${1},
166
- ${input.execution?.token ?? null},
167
- ${input.execution?.queueLeaseExpiresAt ?? null}
168
- )
169
- RETURNING *
170
- `
171
-
172
- return rowToWorkflowRunRecord(row)
173
- } catch (error) {
174
- if (isUniqueViolation(error)) {
175
- throw new WorkflowRunError(
176
- `[SixbPg] Workflow run '${input.id}' already exists for project '${input.projectId}'.`
177
- )
178
- }
132
+ const [updated] = await tx<WorkflowRunDatabaseRow[]>`
133
+ UPDATE workflow_runs
134
+ SET
135
+ status = ${"running"},
136
+ started_at = ${startedAt},
137
+ finished_at = ${null},
138
+ error = ${null},
139
+ attempt = attempt + 1,
140
+ execution_token = ${input.execution?.token ?? null},
141
+ execution_queue_lease_expires_at = ${input.execution?.queueLeaseExpiresAt ?? null}
142
+ WHERE project_id = ${input.projectId} AND id = ${input.id}
143
+ RETURNING *
144
+ `
179
145
 
180
- throw error
181
- }
146
+ return rowToWorkflowRunRecord(updated)
182
147
  })
183
148
  }
184
149
 
@@ -258,7 +223,7 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
258
223
  status = ${input.status},
259
224
  finished_at = ${input.finishedAt ?? new Date()},
260
225
  output = ${null},
261
- error = ${input.error ?? null},
226
+ error = ${input.error === undefined ? null : serializeSixbFailure(input.error, WORKFLOW_RUN_FAILURE_CODES)}::text::jsonb,
262
227
  execution_token = ${null},
263
228
  execution_queue_lease_expires_at = ${null}
264
229
  WHERE project_id = ${input.projectId} AND id = ${input.id}
@@ -416,16 +381,43 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
416
381
  }
417
382
 
418
383
  export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorage {
419
- constructor(private readonly sql: PgStoreClient) {}
384
+ constructor(
385
+ private readonly sql: PgStoreClient,
386
+ private readonly executions: ExecutionStorage
387
+ ) {}
420
388
 
421
389
  async create(input: CreateWorkflowAgentNodeRunInput): Promise<WorkflowAgentNodeRunRecord> {
390
+ const [parent] = await this.sql<{ execution_id: string }[]>`
391
+ SELECT workflow_runs.execution_id
392
+ FROM workflow_node_runs
393
+ JOIN workflow_runs
394
+ ON workflow_runs.project_id = workflow_node_runs.project_id
395
+ AND workflow_runs.id = workflow_node_runs.workflow_run_id
396
+ WHERE workflow_node_runs.project_id = ${input.projectId}
397
+ AND workflow_node_runs.id = ${input.nodeRunId}
398
+ AND workflow_node_runs.node_type = ${"agent"}
399
+ `
400
+ if (!parent) {
401
+ throw new WorkflowRunError(
402
+ `[SixbPg] Agent workflow node run '${input.nodeRunId}' was not found.`
403
+ )
404
+ }
405
+ await assertWorkflowAgentNodeRunExecution({
406
+ executions: this.executions,
407
+ projectId: input.projectId,
408
+ executionId: input.executionId,
409
+ nodeRunId: input.nodeRunId,
410
+ agentId: input.agentId,
411
+ workflowExecutionId: parent.execution_id,
412
+ })
413
+
422
414
  try {
423
415
  const [row] = await this.sql<WorkflowAgentNodeRunDatabaseRow[]>`
424
416
  INSERT INTO workflow_agent_node_runs (
425
- project_id, node_run_id, agent_id, status, prompt, attempt, created_at
417
+ project_id, node_run_id, execution_id, agent_id, status, prompt, attempt, created_at
426
418
  ) VALUES (
427
- ${input.projectId}, ${input.nodeRunId}, ${input.agentId}, ${"queued"},
428
- ${input.prompt}, ${0}, ${input.createdAt ?? new Date()}
419
+ ${input.projectId}, ${input.nodeRunId}, ${input.executionId}, ${input.agentId},
420
+ ${"queued"}, ${input.prompt}, ${0}, ${input.createdAt ?? new Date()}
429
421
  ) RETURNING *
430
422
  `
431
423
  return rowToWorkflowAgentNodeRunRecord(row)
@@ -445,8 +437,6 @@ export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorag
445
437
  const [updated] = await tx<WorkflowAgentNodeRunDatabaseRow[]>`
446
438
  UPDATE workflow_agent_node_runs SET
447
439
  status = ${"running"},
448
- execution_principal_type = ${input.executionPrincipal?.type ?? null},
449
- execution_principal_id = ${input.executionPrincipal?.id ?? null},
450
440
  model_id = ${input.modelId ?? null},
451
441
  attempt = ${1},
452
442
  execution_token = ${input.execution.token},
@@ -504,10 +494,9 @@ export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorag
504
494
  status = ${input.status},
505
495
  model_id = COALESCE(${input.modelId ?? null}, model_id),
506
496
  finish_reason = ${input.finishReason ?? null},
507
- usage = ${input.usage ? JSON.stringify(input.usage) : null}::text::jsonb,
508
497
  trace = ${input.trace ? JSON.stringify(input.trace) : null}::text::jsonb,
509
498
  diagnostics = ${input.diagnostics ? JSON.stringify(input.diagnostics) : null}::text::jsonb,
510
- error = ${input.status === "succeeded" ? null : (input.error ?? null)},
499
+ error = ${input.status === "succeeded" || input.error === undefined ? null : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES)}::text::jsonb,
511
500
  execution_token = ${null},
512
501
  execution_queue_lease_expires_at = ${null},
513
502
  completed_at = ${input.completedAt ?? new Date()}
@@ -538,7 +527,7 @@ export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorag
538
527
  const [updated] = await tx<WorkflowAgentNodeRunDatabaseRow[]>`
539
528
  UPDATE workflow_agent_node_runs SET
540
529
  status = ${"cancelled"},
541
- error = ${input.error ?? null},
530
+ error = ${input.error === undefined ? null : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES)}::text::jsonb,
542
531
  execution_token = ${null},
543
532
  execution_queue_lease_expires_at = ${null},
544
533
  completed_at = ${input.completedAt ?? new Date()}
@@ -698,7 +687,7 @@ export class PgWorkflowNodeRunStorage implements WorkflowNodeRunStorage {
698
687
  status = ${input.status},
699
688
  finished_at = ${input.finishedAt ?? new Date()},
700
689
  output = ${null},
701
- error = ${input.error ?? null}
690
+ error = ${input.error === undefined ? null : serializeSixbFailure(input.error, WORKFLOW_RUN_FAILURE_CODES)}::text::jsonb
702
691
  WHERE project_id = ${input.projectId} AND id = ${input.id}
703
692
  RETURNING *
704
693
  `
@@ -824,6 +813,7 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
824
813
  return {
825
814
  id: row.id,
826
815
  projectId: row.project_id,
816
+ executionId: row.execution_id,
827
817
  workflowId: row.workflow_id,
828
818
  status: row.status,
829
819
  input: parseRecord(row.input),
@@ -831,12 +821,8 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
831
821
  queuedAt: row.queued_at ? new Date(row.queued_at) : undefined,
832
822
  startedAt: new Date(row.started_at),
833
823
  finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
834
- error: row.error ?? undefined,
835
- source: parseSource(row.source),
836
- requestedByPrincipal: {
837
- type: row.requested_by_principal_type,
838
- id: row.requested_by_principal_id,
839
- },
824
+ error: row.error === null ? undefined : parseSixbFailure(row.error, WORKFLOW_RUN_FAILURE_CODES),
825
+ requesterGroupIds: parseJson(row.requester_group_ids),
840
826
  attempt: Number(row.attempt),
841
827
  ...(row.execution_token && row.execution_queue_lease_expires_at
842
828
  ? {
@@ -849,14 +835,6 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
849
835
  }
850
836
  }
851
837
 
852
- function parseSource(value: WorkflowRunSource | string | null): WorkflowRunSource | undefined {
853
- if (value === null || value === undefined) {
854
- return undefined
855
- }
856
-
857
- return typeof value === "string" ? (JSON.parse(value) as WorkflowRunSource) : value
858
- }
859
-
860
838
  function rowToWorkflowNodeRunRecord(row: WorkflowNodeRunDatabaseRow): WorkflowNodeRunRecord {
861
839
  return {
862
840
  id: row.id,
@@ -872,7 +850,7 @@ function rowToWorkflowNodeRunRecord(row: WorkflowNodeRunDatabaseRow): WorkflowNo
872
850
  startedAt: new Date(row.started_at),
873
851
  finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
874
852
  output: row.output ? parseRecord(row.output) : undefined,
875
- error: row.error ?? undefined,
853
+ error: row.error === null ? undefined : parseSixbFailure(row.error, WORKFLOW_RUN_FAILURE_CODES),
876
854
  }
877
855
  }
878
856
 
@@ -896,6 +874,7 @@ function canFinishWorkflowRun(
896
874
  interface WorkflowRunDatabaseRow {
897
875
  project_id: string
898
876
  id: string
877
+ execution_id: string
899
878
  workflow_id: string
900
879
  status: WorkflowRunRecord["status"]
901
880
  input: WorkflowIOSnapshot | string
@@ -903,10 +882,8 @@ interface WorkflowRunDatabaseRow {
903
882
  queued_at: Date | string | null
904
883
  started_at: Date | string
905
884
  finished_at: Date | string | null
906
- error: string | null
907
- source: WorkflowRunSource | string | null
908
- requested_by_principal_type: Principal["type"]
909
- requested_by_principal_id: string
885
+ error: JsonValue | null
886
+ requester_group_ids: string[] | string
910
887
  attempt: number | string
911
888
  execution_token: string | null
912
889
  execution_queue_lease_expires_at: Date | string | null
@@ -926,23 +903,21 @@ interface WorkflowNodeRunDatabaseRow {
926
903
  started_at: Date | string
927
904
  finished_at: Date | string | null
928
905
  output: WorkflowIOSnapshot | string | null
929
- error: string | null
906
+ error: JsonValue | null
930
907
  }
931
908
 
932
909
  interface WorkflowAgentNodeRunDatabaseRow {
933
910
  project_id: string
934
911
  node_run_id: string
912
+ execution_id: string
935
913
  agent_id: string
936
914
  status: WorkflowAgentNodeRunRecord["status"]
937
915
  prompt: string
938
- execution_principal_type: "serviceAccount" | null
939
- execution_principal_id: string | null
940
916
  model_id: string | null
941
917
  finish_reason: WorkflowAgentNodeRunRecord["finishReason"] | null
942
- usage: WorkflowAgentNodeRunRecord["usage"] | string | null
943
918
  trace: WorkflowAgentNodeRunRecord["trace"] | string | null
944
919
  diagnostics: WorkflowAgentNodeRunRecord["diagnostics"] | string | null
945
- error: string | null
920
+ error: JsonValue | null
946
921
  attempt: number | string
947
922
  execution_token: string | null
948
923
  execution_queue_lease_expires_at: Date | string | null
@@ -992,23 +967,15 @@ function rowToWorkflowAgentNodeRunRecord(
992
967
  return {
993
968
  projectId: row.project_id,
994
969
  nodeRunId: row.node_run_id,
970
+ executionId: row.execution_id,
995
971
  agentId: row.agent_id,
996
972
  status: row.status,
997
973
  prompt: row.prompt,
998
- ...(row.execution_principal_type && row.execution_principal_id
999
- ? {
1000
- executionPrincipal: {
1001
- type: row.execution_principal_type,
1002
- id: row.execution_principal_id,
1003
- },
1004
- }
1005
- : {}),
1006
974
  ...(row.model_id ? { modelId: row.model_id } : {}),
1007
975
  ...(row.finish_reason ? { finishReason: row.finish_reason } : {}),
1008
- ...(row.usage ? { usage: parseJson(row.usage) } : {}),
1009
976
  ...(row.trace ? { trace: parseJson(row.trace) } : {}),
1010
977
  ...(row.diagnostics ? { diagnostics: parseJson(row.diagnostics) } : {}),
1011
- ...(row.error ? { error: row.error } : {}),
978
+ ...(row.error ? { error: parseSixbFailure(row.error, AGENT_RUN_FAILURE_CODES) } : {}),
1012
979
  attempt: Number(row.attempt),
1013
980
  ...(row.execution_token && row.execution_queue_lease_expires_at
1014
981
  ? {
@@ -12,7 +12,10 @@ type PgRunListTable =
12
12
 
13
13
  /** Column projection — `sync_runs` needs the derived `checkpoint_present` flag. */
14
14
  type PgRunListSelectList = "*" | "*, checkpoint IS NOT NULL AS checkpoint_present"
15
- type StartedAtExpression = "started_at" | "COALESCE(started_at, created_at)"
15
+ type StartedAtExpression =
16
+ | "started_at"
17
+ | "COALESCE(started_at, created_at)"
18
+ | "COALESCE(started_at, queued_at)"
16
19
 
17
20
  export function hasEmptyStatuses(input: { readonly statuses?: readonly unknown[] }): boolean {
18
21
  return input.statuses !== undefined && input.statuses.length === 0
@@ -78,9 +81,11 @@ export async function queryRunList<TRow>(input: {
78
81
  const orderColumn =
79
82
  input.tableName === "agent_runs"
80
83
  ? "COALESCE(started_at, created_at)"
81
- : input.tableName === "workflow_agent_node_runs"
82
- ? "created_at"
83
- : "started_at"
84
+ : input.tableName === "sync_runs" || input.tableName === "pipeline_runs"
85
+ ? "COALESCE(started_at, queued_at)"
86
+ : input.tableName === "workflow_agent_node_runs"
87
+ ? "created_at"
88
+ : "started_at"
84
89
  const idColumn = input.tableName === "workflow_agent_node_runs" ? "node_run_id" : "id"
85
90
  let query = `
86
91
  SELECT ${input.selectList ?? "*"} FROM ${input.tableName}
package/src/testing.ts ADDED
@@ -0,0 +1,18 @@
1
+ export interface PostgresStorageTestingAdapter {
2
+ advanceConnectorConnectionTime(durationMs: number): void
3
+ }
4
+
5
+ const adapters = new WeakMap<object, PostgresStorageTestingAdapter>()
6
+
7
+ export function registerPostgresStorageTestingAdapter(
8
+ storage: object,
9
+ advanceConnectorConnectionTime: (durationMs: number) => void
10
+ ): void {
11
+ adapters.set(storage, { advanceConnectorConnectionTime })
12
+ }
13
+
14
+ export function getPostgresStorageTestingAdapter(storage: object): PostgresStorageTestingAdapter {
15
+ const adapter = adapters.get(storage)
16
+ if (!adapter) throw new Error("[SixbPg] Storage testing adapter is unavailable.")
17
+ return adapter
18
+ }
@@ -1,17 +0,0 @@
1
- import type { WebhookDeliveryClaimRecord, WebhookDeliveryKey, WebhookDeliveryRecord, WebhookDeliveryStorage } from "@sixb/core/storage";
2
- import { type PgStoreClient } from "./transactions";
3
- export declare class PgWebhookDeliveryStorage implements WebhookDeliveryStorage {
4
- private readonly sql;
5
- constructor(sql: PgStoreClient);
6
- claim(input: WebhookDeliveryKey & {
7
- receivedAt: string;
8
- }): Promise<WebhookDeliveryClaimRecord>;
9
- complete(input: WebhookDeliveryKey & {
10
- completedAt: string;
11
- }): Promise<WebhookDeliveryRecord>;
12
- fail(input: WebhookDeliveryKey & {
13
- failedAt: string;
14
- error: string;
15
- }): Promise<WebhookDeliveryRecord>;
16
- }
17
- //# sourceMappingURL=pg-webhook-delivery-storage.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pg-webhook-delivery-storage.d.ts","sourceRoot":"","sources":["../src/pg-webhook-delivery-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAE1B,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAA;AAErE,qBAAa,wBAAyB,YAAW,sBAAsB;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAEzC,KAAK,CACT,KAAK,EAAE,kBAAkB,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,0BAA0B,CAAC;IAyEhC,QAAQ,CACZ,KAAK,EAAE,kBAAkB,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAClD,OAAO,CAAC,qBAAqB,CAAC;IAuB3B,IAAI,CACR,KAAK,EAAE,kBAAkB,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC9D,OAAO,CAAC,qBAAqB,CAAC;CAqBlC"}
@@ -1,180 +0,0 @@
1
- import type {
2
- WebhookDeliveryClaimRecord,
3
- WebhookDeliveryClaimResult,
4
- WebhookDeliveryKey,
5
- WebhookDeliveryRecord,
6
- WebhookDeliveryStorage,
7
- } from "@sixb/core/storage"
8
- import { type PgStoreClient, runPgTransaction } from "./transactions"
9
-
10
- export class PgWebhookDeliveryStorage implements WebhookDeliveryStorage {
11
- constructor(private readonly sql: PgStoreClient) {}
12
-
13
- async claim(
14
- input: WebhookDeliveryKey & { receivedAt: string }
15
- ): Promise<WebhookDeliveryClaimRecord> {
16
- return runPgTransaction(this.sql, async (tx) => {
17
- // Atomic first claim: only one transaction can insert this scoped delivery key.
18
- const [inserted] = await tx<WebhookDeliveryRow[]>`
19
- INSERT INTO webhook_deliveries (
20
- project_id,
21
- connector_id,
22
- webhook_id,
23
- idempotency_key,
24
- status,
25
- received_at
26
- ) VALUES (
27
- ${input.projectId},
28
- ${input.connectorId},
29
- ${input.webhookId},
30
- ${input.idempotencyKey},
31
- ${"in_progress"},
32
- ${input.receivedAt}
33
- )
34
- ON CONFLICT (project_id, connector_id, webhook_id, idempotency_key) DO NOTHING
35
- RETURNING *
36
- `
37
-
38
- if (inserted) {
39
- return toWebhookDeliveryClaimRecord(inserted, "claimed")
40
- }
41
-
42
- const [existing] = await tx<WebhookDeliveryRow[]>`
43
- SELECT * FROM webhook_deliveries
44
- WHERE project_id = ${input.projectId}
45
- AND connector_id = ${input.connectorId}
46
- AND webhook_id = ${input.webhookId}
47
- AND idempotency_key = ${input.idempotencyKey}
48
- FOR UPDATE
49
- `
50
-
51
- if (existing?.status === "completed") {
52
- return toWebhookDeliveryClaimRecord(existing, "duplicate")
53
- }
54
-
55
- if (existing?.status === "in_progress") {
56
- return toWebhookDeliveryClaimRecord(existing, "in_progress")
57
- }
58
-
59
- if (existing?.status !== "failed") {
60
- throw new Error("[SixbPg] Webhook delivery row disappeared during claim.")
61
- }
62
-
63
- // Failed deliveries are intentionally claimable so provider retries can run again.
64
- const [updated] = await tx<WebhookDeliveryRow[]>`
65
- UPDATE webhook_deliveries
66
- SET status = ${"in_progress"},
67
- received_at = ${input.receivedAt},
68
- completed_at = ${null},
69
- failed_at = ${null},
70
- error = ${null}
71
- WHERE project_id = ${input.projectId}
72
- AND connector_id = ${input.connectorId}
73
- AND webhook_id = ${input.webhookId}
74
- AND idempotency_key = ${input.idempotencyKey}
75
- RETURNING *
76
- `
77
-
78
- if (!updated) {
79
- throw new Error(
80
- `[SixbPg] Missing webhook delivery row after claim for ${serializeKey(input)}.`
81
- )
82
- }
83
-
84
- return toWebhookDeliveryClaimRecord(updated, "claimed")
85
- })
86
- }
87
-
88
- async complete(
89
- input: WebhookDeliveryKey & { completedAt: string }
90
- ): Promise<WebhookDeliveryRecord> {
91
- const [updated] = await this.sql<WebhookDeliveryRow[]>`
92
- UPDATE webhook_deliveries
93
- SET status = ${"completed"},
94
- completed_at = ${input.completedAt},
95
- failed_at = ${null},
96
- error = ${null}
97
- WHERE project_id = ${input.projectId}
98
- AND connector_id = ${input.connectorId}
99
- AND webhook_id = ${input.webhookId}
100
- AND idempotency_key = ${input.idempotencyKey}
101
- RETURNING *
102
- `
103
-
104
- if (!updated) {
105
- throw new Error(
106
- `[SixbPg] Missing webhook delivery row after complete for ${serializeKey(input)}.`
107
- )
108
- }
109
-
110
- return toWebhookDeliveryRecord(updated)
111
- }
112
-
113
- async fail(
114
- input: WebhookDeliveryKey & { failedAt: string; error: string }
115
- ): Promise<WebhookDeliveryRecord> {
116
- const [updated] = await this.sql<WebhookDeliveryRow[]>`
117
- UPDATE webhook_deliveries
118
- SET status = ${"failed"},
119
- failed_at = ${input.failedAt},
120
- error = ${input.error}
121
- WHERE project_id = ${input.projectId}
122
- AND connector_id = ${input.connectorId}
123
- AND webhook_id = ${input.webhookId}
124
- AND idempotency_key = ${input.idempotencyKey}
125
- RETURNING *
126
- `
127
-
128
- if (!updated) {
129
- throw new Error(
130
- `[SixbPg] Missing webhook delivery row after fail for ${serializeKey(input)}.`
131
- )
132
- }
133
-
134
- return toWebhookDeliveryRecord(updated)
135
- }
136
- }
137
-
138
- function toWebhookDeliveryClaimRecord(
139
- row: WebhookDeliveryRow,
140
- claimResult: WebhookDeliveryClaimResult
141
- ): WebhookDeliveryClaimRecord {
142
- return {
143
- ...toWebhookDeliveryRecord(row),
144
- claimResult,
145
- }
146
- }
147
-
148
- function toWebhookDeliveryRecord(row: WebhookDeliveryRow): WebhookDeliveryRecord {
149
- return {
150
- projectId: row.project_id,
151
- connectorId: row.connector_id,
152
- webhookId: row.webhook_id,
153
- idempotencyKey: row.idempotency_key,
154
- status: row.status,
155
- receivedAt: toIsoString(row.received_at),
156
- completedAt: row.completed_at ? toIsoString(row.completed_at) : undefined,
157
- failedAt: row.failed_at ? toIsoString(row.failed_at) : undefined,
158
- error: row.error ?? undefined,
159
- }
160
- }
161
-
162
- function toIsoString(value: Date | string): string {
163
- return value instanceof Date ? value.toISOString() : new Date(value).toISOString()
164
- }
165
-
166
- function serializeKey(key: WebhookDeliveryKey): string {
167
- return JSON.stringify([key.projectId, key.connectorId, key.webhookId, key.idempotencyKey])
168
- }
169
-
170
- interface WebhookDeliveryRow {
171
- readonly project_id: string
172
- readonly connector_id: string
173
- readonly webhook_id: string
174
- readonly idempotency_key: string
175
- readonly status: WebhookDeliveryRecord["status"]
176
- readonly received_at: Date | string
177
- readonly completed_at: Date | string | null
178
- readonly failed_at: Date | string | null
179
- readonly error: string | null
180
- }