@sixb/pg 0.1.6 → 0.1.8

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 (88) hide show
  1. package/dist/agents/rows.d.ts +8 -5
  2. package/dist/agents/rows.d.ts.map +1 -1
  3. package/dist/agents/runs.d.ts +3 -2
  4. package/dist/agents/runs.d.ts.map +1 -1
  5. package/dist/agents/threads.d.ts.map +1 -1
  6. package/dist/ai-cost-groups.d.ts +5 -0
  7. package/dist/ai-cost-groups.d.ts.map +1 -0
  8. package/dist/auth-storage/auth-storage.d.ts +4 -1
  9. package/dist/auth-storage/auth-storage.d.ts.map +1 -1
  10. package/dist/auth-storage/device-authorizations.d.ts +29 -0
  11. package/dist/auth-storage/device-authorizations.d.ts.map +1 -0
  12. package/dist/auth-storage/rows.d.ts +19 -1
  13. package/dist/auth-storage/rows.d.ts.map +1 -1
  14. package/dist/auth-storage/shared.d.ts +1 -1
  15. package/dist/auth-storage/shared.d.ts.map +1 -1
  16. package/dist/index.d.ts +6 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +8327 -5838
  19. package/dist/index.js.map +28 -19
  20. package/dist/migrations.d.ts.map +1 -1
  21. package/dist/objects/index.d.ts +2 -0
  22. package/dist/objects/index.d.ts.map +1 -0
  23. package/dist/{pg-object-query-compiler.d.ts → objects/query-compiler.d.ts} +25 -4
  24. package/dist/objects/query-compiler.d.ts.map +1 -0
  25. package/dist/objects/read-scope.d.ts +17 -0
  26. package/dist/objects/read-scope.d.ts.map +1 -0
  27. package/dist/objects/reader.d.ts +50 -0
  28. package/dist/objects/reader.d.ts.map +1 -0
  29. package/dist/objects/rows.d.ts +47 -0
  30. package/dist/objects/rows.d.ts.map +1 -0
  31. package/dist/objects/selected-reader.d.ts +8 -0
  32. package/dist/objects/selected-reader.d.ts.map +1 -0
  33. package/dist/{pg-object-storage.d.ts → objects/storage.d.ts} +12 -20
  34. package/dist/objects/storage.d.ts.map +1 -0
  35. package/dist/pg-ai-cost-storage.d.ts +2 -1
  36. package/dist/pg-ai-cost-storage.d.ts.map +1 -1
  37. package/dist/pg-ai-limit-storage.d.ts +34 -0
  38. package/dist/pg-ai-limit-storage.d.ts.map +1 -0
  39. package/dist/pg-ai-usage-storage.d.ts +28 -0
  40. package/dist/pg-ai-usage-storage.d.ts.map +1 -1
  41. package/dist/pg-execution-storage.d.ts +3 -2
  42. package/dist/pg-execution-storage.d.ts.map +1 -1
  43. package/dist/share-grant-storage.d.ts +11 -0
  44. package/dist/share-grant-storage.d.ts.map +1 -0
  45. package/dist/share-session-storage.d.ts +11 -0
  46. package/dist/share-session-storage.d.ts.map +1 -0
  47. package/dist/storage-errors.d.ts +2 -0
  48. package/dist/storage-errors.d.ts.map +1 -1
  49. package/dist/transactions.d.ts +9 -2
  50. package/dist/transactions.d.ts.map +1 -1
  51. package/package.json +4 -4
  52. package/src/agents/rows.ts +38 -9
  53. package/src/agents/runs.ts +148 -11
  54. package/src/agents/threads.ts +1 -17
  55. package/src/ai-cost-groups.ts +102 -0
  56. package/src/auth-storage/auth-storage.ts +84 -0
  57. package/src/auth-storage/device-authorizations.ts +131 -0
  58. package/src/auth-storage/rows.ts +41 -0
  59. package/src/auth-storage/shared.ts +1 -0
  60. package/src/index.ts +24 -4
  61. package/src/migrations/029-model-accounting.sql +24 -0
  62. package/src/migrations/030-ai-usage-limits.sql +94 -0
  63. package/src/migrations/031-subagent-runs.sql +97 -0
  64. package/src/migrations/032-conversation-run-spec.sql +23 -0
  65. package/src/migrations/033-retire-agent-definitions.sql +28 -0
  66. package/src/migrations/034-device-authorizations.sql +24 -0
  67. package/src/migrations/035-share-grants.sql +57 -0
  68. package/src/migrations/036-share-sessions.sql +173 -0
  69. package/src/migrations.ts +22 -0
  70. package/src/objects/index.ts +1 -0
  71. package/src/{pg-object-query-compiler.ts → objects/query-compiler.ts} +185 -100
  72. package/src/objects/read-scope.ts +324 -0
  73. package/src/objects/reader.ts +604 -0
  74. package/src/objects/rows.ts +146 -0
  75. package/src/objects/selected-reader.ts +118 -0
  76. package/src/objects/storage.ts +305 -0
  77. package/src/pg-ai-cost-storage.ts +71 -24
  78. package/src/pg-ai-limit-storage.ts +901 -0
  79. package/src/pg-ai-usage-storage.ts +34 -0
  80. package/src/pg-execution-storage.ts +21 -2
  81. package/src/pg-workflow-run-storage.ts +8 -8
  82. package/src/share-grant-storage.ts +236 -0
  83. package/src/share-session-storage.ts +127 -0
  84. package/src/storage-errors.ts +6 -0
  85. package/src/transactions.ts +46 -5
  86. package/dist/pg-object-query-compiler.d.ts.map +0 -1
  87. package/dist/pg-object-storage.d.ts.map +0 -1
  88. package/src/pg-object-storage.ts +0 -734
@@ -1,6 +1,7 @@
1
1
  import type { Principal } from "@sixb/core"
2
2
  import type {
3
3
  AccessTokenRecord,
4
+ DeviceAuthorizationRecord,
4
5
  GroupMembershipRecord,
5
6
  GroupMembershipSource,
6
7
  InvitationRecord,
@@ -150,6 +151,24 @@ export interface PgAuthOidcAttemptRow {
150
151
  readonly consumed_at: PgDate | null
151
152
  }
152
153
 
154
+ export interface PgAuthDeviceAuthorizationRow {
155
+ readonly project_id: string
156
+ readonly id: string
157
+ readonly device_code_hash: string
158
+ readonly user_code: string
159
+ readonly client_name: string
160
+ readonly token_name: string
161
+ readonly token_expires_at: PgDate
162
+ readonly status: DeviceAuthorizationRecord["status"]
163
+ readonly approved_user_id: string | null
164
+ readonly approved_session_id: string | null
165
+ readonly created_at: PgDate
166
+ readonly expires_at: PgDate
167
+ readonly approved_at: PgDate | null
168
+ readonly denied_at: PgDate | null
169
+ readonly consumed_at: PgDate | null
170
+ }
171
+
153
172
  export function rowToUserRecord(row: PgAuthUserRow): UserRecord {
154
173
  return {
155
174
  id: row.id,
@@ -304,6 +323,28 @@ export function rowToOidcAuthorizationAttemptRecord(
304
323
  }
305
324
  }
306
325
 
326
+ export function rowToDeviceAuthorizationRecord(
327
+ row: PgAuthDeviceAuthorizationRow
328
+ ): DeviceAuthorizationRecord {
329
+ return {
330
+ id: row.id,
331
+ projectId: row.project_id,
332
+ deviceCodeHash: row.device_code_hash,
333
+ userCode: row.user_code,
334
+ clientName: row.client_name,
335
+ tokenName: row.token_name,
336
+ tokenExpiresAt: toDate(row.token_expires_at),
337
+ status: row.status,
338
+ approvedUserId: row.approved_user_id ?? undefined,
339
+ approvedSessionId: row.approved_session_id ?? undefined,
340
+ createdAt: toDate(row.created_at),
341
+ expiresAt: toDate(row.expires_at),
342
+ approvedAt: row.approved_at ? toDate(row.approved_at) : undefined,
343
+ deniedAt: row.denied_at ? toDate(row.denied_at) : undefined,
344
+ consumedAt: row.consumed_at ? toDate(row.consumed_at) : undefined,
345
+ }
346
+ }
347
+
307
348
  export function serializeOptionalRecord(
308
349
  value: Readonly<Record<string, unknown>> | undefined
309
350
  ): string | null {
@@ -761,6 +761,7 @@ export function mapUniqueConstraintError(
761
761
  error: unknown,
762
762
  code:
763
763
  | "duplicate_access_token"
764
+ | "duplicate_device_authorization"
764
765
  | "duplicate_identity"
765
766
  | "duplicate_invitation"
766
767
  | "duplicate_magic_link"
package/src/index.ts CHANGED
@@ -13,6 +13,7 @@ import { ProviderMaterializationTransactionLifecycle } from "@sixb/core/internal
13
13
  import {
14
14
  createAgentOperationScope,
15
15
  createAuthOperationScope,
16
+ createObjectOperationScope,
16
17
  createOntologyOperationScope,
17
18
  createOperationScopedFacade,
18
19
  createStorageOperationScope,
@@ -20,6 +21,7 @@ import {
20
21
  } from "@sixb/core/internal/storage-operation-scope"
21
22
  import {
22
23
  type AiCostStorage,
24
+ type AiLimitStorage,
23
25
  type AiUsageStorage,
24
26
  createTransactionStorageProxy,
25
27
  type ObjectStorage,
@@ -30,13 +32,14 @@ import { PgAgentStorage } from "./agents"
30
32
  import { PgAuthStorage } from "./auth-storage"
31
33
  import { PgConnectorConnectionStorage } from "./connector-connection-storage"
32
34
  import { createPostgresStorageMigrators, dropSchema } from "./migrations"
35
+ import { PgObjectStorage } from "./objects"
33
36
  import { PgOntologyStorage, type PgOntologyTransactionContext } from "./ontology-storage"
34
37
  import { PgActionRunStorage } from "./pg-action-run-storage"
35
38
  import { PgAiCostStorage } from "./pg-ai-cost-storage"
39
+ import { PgAiLimitStorage } from "./pg-ai-limit-storage"
36
40
  import { PgAiUsageStorage } from "./pg-ai-usage-storage"
37
41
  import { createPgClient, type SQL, type SQLClient } from "./pg-client"
38
42
  import { PgExecutionStorage } from "./pg-execution-storage"
39
- import { PgObjectStorage } from "./pg-object-storage"
40
43
  import { PgPipelineRunStorage } from "./pg-pipeline-run-storage"
41
44
  import { PgProjectionRunStorage } from "./pg-projection-run-storage"
42
45
  import { PgRulesStorage } from "./pg-rules-storage"
@@ -45,6 +48,8 @@ import { PgTimeseriesStorage } from "./pg-timeseries-storage"
45
48
  import { PgWebhookRunStorage } from "./pg-webhook-run-storage"
46
49
  import { PgWorkflowInterventionStorage } from "./pg-workflow-intervention-storage"
47
50
  import { PgWorkflowRunStorage } from "./pg-workflow-run-storage"
51
+ import { PgShareGrantStorage } from "./share-grant-storage"
52
+ import { PgShareSessionStorage } from "./share-session-storage"
48
53
  import { isRetryableTransactionConflict } from "./storage-errors"
49
54
  import { registerPostgresStorageTestingAdapter } from "./testing"
50
55
  import { type PgStoreClient, runPgTransaction } from "./transactions"
@@ -138,6 +143,7 @@ export class PostgresStorage implements MigrationCapableStorage {
138
143
  readonly agents: PgAgentStorage
139
144
  readonly aiUsage: AiUsageStorage
140
145
  readonly aiCosts: AiCostStorage
146
+ readonly aiLimits: AiLimitStorage
141
147
  readonly actionRuns: PgActionRunStorage
142
148
  readonly pipelineRuns: PgPipelineRunStorage
143
149
  readonly workflowRuns: PgWorkflowRunStorage
@@ -148,6 +154,8 @@ export class PostgresStorage implements MigrationCapableStorage {
148
154
  readonly webhookRuns: PgWebhookRunStorage
149
155
  readonly rules: PgRulesStorage
150
156
  readonly connectorConnections: PgConnectorConnectionStorage
157
+ readonly shareGrants: PgShareGrantStorage
158
+ readonly shareSessions: PgShareSessionStorage
151
159
  readonly migrators: readonly StorageMigrator[]
152
160
 
153
161
  private readonly sql: SQL
@@ -205,13 +213,14 @@ export class PostgresStorage implements MigrationCapableStorage {
205
213
  },
206
214
  () => this.assertRootOperationAvailable()
207
215
  )
208
- this.objects = createOperationScopedFacade(stores.objects, scope)
216
+ this.objects = createObjectOperationScope(stores.objects, scope)
209
217
  this.ontology = createOntologyOperationScope(stores.ontology, scope)
210
218
  this.auth = createAuthOperationScope(stores.auth, scope)
211
219
  this.executions = createOperationScopedFacade(stores.executions, scope)
212
220
  this.agents = createAgentOperationScope(stores.agents, scope)
213
221
  this.aiUsage = createOperationScopedFacade(stores.aiUsage, scope)
214
222
  this.aiCosts = createOperationScopedFacade(stores.aiCosts, scope)
223
+ this.aiLimits = createOperationScopedFacade(stores.aiLimits, scope)
215
224
  this.actionRuns = createOperationScopedFacade(stores.actionRuns, scope)
216
225
  this.pipelineRuns = createOperationScopedFacade(stores.pipelineRuns, scope)
217
226
  this.workflowRuns = createWorkflowRunOperationScope(stores.workflowRuns, scope)
@@ -222,6 +231,8 @@ export class PostgresStorage implements MigrationCapableStorage {
222
231
  this.webhookRuns = createOperationScopedFacade(stores.webhookRuns, scope)
223
232
  this.rules = createOperationScopedFacade(stores.rules, scope)
224
233
  this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope)
234
+ this.shareGrants = createOperationScopedFacade(stores.shareGrants, scope)
235
+ this.shareSessions = createOperationScopedFacade(stores.shareSessions, scope)
225
236
  registerPostgresStorageTestingAdapter(this, (durationMs) =>
226
237
  stores.connectorConnections.advanceTimeForTesting(durationMs)
227
238
  )
@@ -258,6 +269,7 @@ export class PostgresStorage implements MigrationCapableStorage {
258
269
  transactionContext.materializations.assertCommittable()
259
270
  return result
260
271
  } finally {
272
+ await txStorage.aiLimits.settleOperations()
261
273
  transactionContext.active = false
262
274
  txStorage.ontology.deactivateSessions()
263
275
  transactionContext.materializations.deactivate()
@@ -305,7 +317,7 @@ export class PostgresStorage implements MigrationCapableStorage {
305
317
  private createTransactionStorage(
306
318
  client: PgStoreClient,
307
319
  transactionContext: PgOntologyTransactionContext
308
- ): Storage & { readonly ontology: PgOntologyStorage } {
320
+ ): Storage & { readonly ontology: PgOntologyStorage; readonly aiLimits: PgAiLimitStorage } {
309
321
  return {
310
322
  ...createPostgresStores(client, {
311
323
  runOntologyOperation: async (run) => run(client),
@@ -329,7 +341,9 @@ function createPostgresStores(
329
341
  }
330
342
  ): PostgresStoreSet {
331
343
  const auth = new PgAuthStorage({ sql })
332
- const executions = new PgExecutionStorage(sql, auth)
344
+ const shareGrants = new PgShareGrantStorage(sql)
345
+ const shareSessions = new PgShareSessionStorage(sql)
346
+ const executions = new PgExecutionStorage(sql, auth, shareSessions)
333
347
  return {
334
348
  objects: new PgObjectStorage(sql),
335
349
  ontology: new PgOntologyStorage({
@@ -342,6 +356,7 @@ function createPostgresStores(
342
356
  agents: new PgAgentStorage({ sql, executions }),
343
357
  aiUsage: new PgAiUsageStorage(sql),
344
358
  aiCosts: new PgAiCostStorage(sql),
359
+ aiLimits: new PgAiLimitStorage(sql),
345
360
  actionRuns: new PgActionRunStorage(sql, executions),
346
361
  pipelineRuns: new PgPipelineRunStorage(sql, executions),
347
362
  workflowRuns: new PgWorkflowRunStorage(sql, executions),
@@ -352,6 +367,8 @@ function createPostgresStores(
352
367
  webhookRuns: new PgWebhookRunStorage(sql, executions),
353
368
  rules: new PgRulesStorage(sql),
354
369
  connectorConnections: new PgConnectorConnectionStorage(sql),
370
+ shareGrants,
371
+ shareSessions,
355
372
  }
356
373
  }
357
374
 
@@ -363,6 +380,7 @@ interface PostgresStoreSet {
363
380
  readonly agents: PgAgentStorage
364
381
  readonly aiUsage: PgAiUsageStorage
365
382
  readonly aiCosts: PgAiCostStorage
383
+ readonly aiLimits: PgAiLimitStorage
366
384
  readonly actionRuns: PgActionRunStorage
367
385
  readonly pipelineRuns: PgPipelineRunStorage
368
386
  readonly workflowRuns: PgWorkflowRunStorage
@@ -373,6 +391,8 @@ interface PostgresStoreSet {
373
391
  readonly webhookRuns: PgWebhookRunStorage
374
392
  readonly rules: PgRulesStorage
375
393
  readonly connectorConnections: PgConnectorConnectionStorage
394
+ readonly shareGrants: PgShareGrantStorage
395
+ readonly shareSessions: PgShareSessionStorage
376
396
  }
377
397
 
378
398
  function resolveTimeoutMillis(value: number | undefined, label: string): number | undefined {
@@ -0,0 +1,24 @@
1
+ -- Call-time model configuration and provider identity. Historical identity remains unknown.
2
+ ALTER TABLE ai_model_call_usage
3
+ ADD COLUMN requested_reasoning JSONB
4
+ CHECK (
5
+ requested_reasoning IS NULL OR
6
+ jsonb_typeof(requested_reasoning) IN ('string', 'object')
7
+ );
8
+ ALTER TABLE ai_model_call_usage ADD COLUMN provider_ids JSONB;
9
+
10
+ -- Upgrade the shipped valuation reason while retaining the selected-cost projection.
11
+ ALTER TABLE ai_model_call_valuations
12
+ DROP CONSTRAINT ai_model_call_valuations_reason_check;
13
+
14
+ UPDATE ai_model_call_valuations SET reason = 'missingRateCard'
15
+ WHERE reason = 'missingCatalogEntry';
16
+
17
+ ALTER TABLE ai_model_call_valuations ADD CONSTRAINT ai_model_call_valuations_reason_check
18
+ CHECK (reason IS NULL OR reason IN (
19
+ 'missingBillingIdentity',
20
+ 'missingRateCard',
21
+ 'missingUsageMeter',
22
+ 'unsupportedPricingDimension',
23
+ 'invalidUsageForFormula'
24
+ ));
@@ -0,0 +1,94 @@
1
+ CREATE TABLE ai_usage_limits (
2
+ project_id TEXT NOT NULL CHECK (length(btrim(project_id)) > 0),
3
+ id TEXT NOT NULL CHECK (length(btrim(id)) > 0),
4
+ subject_type TEXT NOT NULL CHECK (
5
+ subject_type IN ('project', 'group', 'user', 'serviceAccount')
6
+ ),
7
+ subject_id TEXT NOT NULL CHECK (
8
+ (subject_type = 'project' AND subject_id = '')
9
+ OR (subject_type <> 'project' AND length(btrim(subject_id)) > 0)
10
+ ),
11
+ meter TEXT NOT NULL CHECK (meter IN ('tokens.total', 'cost.catalogEstimated')),
12
+ currency TEXT NOT NULL CHECK (
13
+ (meter = 'tokens.total' AND currency = '')
14
+ OR (meter = 'cost.catalogEstimated' AND currency = 'USD')
15
+ ),
16
+ limit_amount NUMERIC(78, 0) NOT NULL CHECK (limit_amount >= 0),
17
+ period_kind TEXT NOT NULL CHECK (period_kind = 'calendarMonth'),
18
+ enabled BOOLEAN NOT NULL,
19
+ created_at TIMESTAMPTZ NOT NULL,
20
+ updated_at TIMESTAMPTZ NOT NULL,
21
+ PRIMARY KEY (project_id, id),
22
+ UNIQUE (project_id, subject_type, subject_id, meter, currency)
23
+ );
24
+
25
+ CREATE INDEX idx_ai_usage_limits_project_enabled
26
+ ON ai_usage_limits (project_id, enabled, subject_type, subject_id, meter, currency);
27
+
28
+ CREATE TABLE ai_usage_limit_periods (
29
+ project_id TEXT NOT NULL CHECK (length(btrim(project_id)) > 0),
30
+ subject_type TEXT NOT NULL CHECK (
31
+ subject_type IN ('project', 'group', 'user', 'serviceAccount')
32
+ ),
33
+ subject_id TEXT NOT NULL CHECK (
34
+ (subject_type = 'project' AND subject_id = '')
35
+ OR (subject_type <> 'project' AND length(btrim(subject_id)) > 0)
36
+ ),
37
+ meter TEXT NOT NULL CHECK (meter IN ('tokens.total', 'cost.catalogEstimated')),
38
+ currency TEXT NOT NULL CHECK (
39
+ (meter = 'tokens.total' AND currency = '')
40
+ OR (meter = 'cost.catalogEstimated' AND currency = 'USD')
41
+ ),
42
+ period_start TIMESTAMPTZ NOT NULL,
43
+ period_end TIMESTAMPTZ NOT NULL,
44
+ actual_amount NUMERIC(78, 0) NOT NULL DEFAULT 0 CHECK (actual_amount >= 0),
45
+ reserved_amount NUMERIC(78, 0) NOT NULL DEFAULT 0 CHECK (reserved_amount >= 0),
46
+ unknown_amount NUMERIC(78, 0) NOT NULL DEFAULT 0 CHECK (unknown_amount >= 0),
47
+ accounting_status TEXT NOT NULL DEFAULT 'complete' CHECK (
48
+ accounting_status IN ('complete', 'unavailable')
49
+ ),
50
+ updated_at TIMESTAMPTZ NOT NULL,
51
+ PRIMARY KEY (
52
+ project_id, subject_type, subject_id, meter, currency, period_start
53
+ )
54
+ );
55
+
56
+ CREATE INDEX idx_ai_usage_limit_periods_subject_period
57
+ ON ai_usage_limit_periods (
58
+ project_id, subject_type, subject_id, period_start, period_end, meter, currency
59
+ );
60
+
61
+ CREATE TABLE ai_model_call_reservations (
62
+ project_id TEXT NOT NULL CHECK (length(btrim(project_id)) > 0),
63
+ execution_id TEXT NOT NULL CHECK (length(btrim(execution_id)) > 0),
64
+ attempt BIGINT NOT NULL CHECK (attempt >= 1),
65
+ call_id TEXT NOT NULL CHECK (length(btrim(call_id)) > 0),
66
+ buckets JSONB NOT NULL CHECK (jsonb_typeof(buckets) = 'array'),
67
+ request_key TEXT NOT NULL,
68
+ period_start TIMESTAMPTZ NOT NULL,
69
+ period_end TIMESTAMPTZ NOT NULL,
70
+ state TEXT NOT NULL CHECK (state IN ('active', 'reconciled', 'unknown')),
71
+ usage_record_id TEXT,
72
+ reserved_at TIMESTAMPTZ NOT NULL,
73
+ updated_at TIMESTAMPTZ NOT NULL,
74
+ PRIMARY KEY (project_id, execution_id, attempt, call_id),
75
+ FOREIGN KEY (project_id, execution_id)
76
+ REFERENCES executions(project_id, id)
77
+ ON DELETE RESTRICT,
78
+ FOREIGN KEY (project_id, usage_record_id)
79
+ REFERENCES ai_model_call_usage(project_id, id)
80
+ ON DELETE RESTRICT,
81
+ CHECK (
82
+ (
83
+ state = 'reconciled'
84
+ AND usage_record_id IS NOT NULL
85
+ )
86
+ OR (
87
+ state <> 'reconciled'
88
+ AND usage_record_id IS NULL
89
+ )
90
+ )
91
+ );
92
+
93
+ CREATE INDEX idx_ai_model_call_reservations_active
94
+ ON ai_model_call_reservations (project_id, state, period_end, execution_id, attempt, call_id);
@@ -0,0 +1,97 @@
1
+ -- Agent executions may either use a managed service account or inherit their parent's exact
2
+ -- credentialed-user / auth-disabled authority.
3
+ ALTER TABLE executions
4
+ DROP CONSTRAINT executions_check3,
5
+ ADD CONSTRAINT executions_agent_authority_check CHECK (
6
+ (
7
+ executor_kind = 'request'
8
+ AND source_kind = 'http'
9
+ AND executor_id = source_id
10
+ AND (
11
+ (
12
+ authority_kind = 'principal'
13
+ AND requested_by_user_id IS NOT DISTINCT FROM authority_user_id
14
+ AND requested_by_service_account_id IS NOT DISTINCT FROM authority_service_account_id
15
+ )
16
+ OR (
17
+ authority_kind = 'disabled'
18
+ AND requested_by_user_id IS NULL
19
+ AND requested_by_service_account_id IS NULL
20
+ )
21
+ )
22
+ )
23
+ OR (
24
+ executor_kind IN ('action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow')
25
+ AND authority_kind = 'trustedPrimitive'
26
+ AND executor_kind = authority_primitive_kind
27
+ )
28
+ OR (
29
+ executor_kind = 'agent'
30
+ AND source_kind = 'execution'
31
+ AND (
32
+ (
33
+ authority_kind = 'principal'
34
+ AND authority_service_account_id IS NOT NULL
35
+ AND authority_session_id IS NULL
36
+ AND authority_access_token_id IS NULL
37
+ )
38
+ OR (
39
+ authority_kind = 'principal'
40
+ AND authority_user_id IS NOT NULL
41
+ AND num_nonnulls(authority_session_id, authority_access_token_id) = 1
42
+ )
43
+ OR authority_kind = 'disabled'
44
+ )
45
+ )
46
+ OR (executor_kind = 'kernel' AND authority_kind = 'kernel')
47
+ );
48
+
49
+ ALTER TABLE agent_runs
50
+ ADD COLUMN kind TEXT NOT NULL DEFAULT 'conversation'
51
+ CHECK (kind IN ('conversation', 'subagent')),
52
+ ADD COLUMN parent_run_id TEXT,
53
+ ADD COLUMN spawn_key TEXT,
54
+ ADD COLUMN spec JSONB,
55
+ ADD COLUMN result JSONB,
56
+ ALTER COLUMN thread_id DROP NOT NULL,
57
+ ALTER COLUMN agent_id DROP NOT NULL,
58
+ ALTER COLUMN trigger_message_id DROP NOT NULL,
59
+ ADD CONSTRAINT fk_agent_runs_parent
60
+ FOREIGN KEY (project_id, parent_run_id)
61
+ REFERENCES agent_runs (project_id, id)
62
+ ON DELETE RESTRICT,
63
+ ADD CONSTRAINT agent_runs_kind_fields CHECK (
64
+ (
65
+ kind = 'conversation'
66
+ AND thread_id IS NOT NULL
67
+ AND agent_id IS NOT NULL
68
+ AND trigger_message_id IS NOT NULL
69
+ AND parent_run_id IS NULL
70
+ AND spawn_key IS NULL
71
+ AND spec IS NULL
72
+ AND result IS NULL
73
+ )
74
+ OR
75
+ (
76
+ kind = 'subagent'
77
+ AND thread_id IS NULL
78
+ AND agent_id IS NULL
79
+ AND trigger_message_id IS NULL
80
+ AND parent_run_id IS NOT NULL
81
+ AND spawn_key IS NOT NULL
82
+ AND spec IS NOT NULL
83
+ )
84
+ ),
85
+ ADD CONSTRAINT agent_runs_subagent_result CHECK (
86
+ kind <> 'subagent' OR status <> 'succeeded' OR result IS NOT NULL
87
+ ),
88
+ ADD CONSTRAINT uq_agent_runs_parent_spawn_key UNIQUE (project_id, parent_run_id, spawn_key);
89
+
90
+ CREATE INDEX idx_agent_runs_project_parent_started
91
+ ON agent_runs (
92
+ project_id,
93
+ parent_run_id,
94
+ status,
95
+ COALESCE(started_at, created_at) DESC,
96
+ id DESC
97
+ );
@@ -0,0 +1,23 @@
1
+ ALTER TABLE agent_runs
2
+ DROP CONSTRAINT agent_runs_kind_fields,
3
+ ADD CONSTRAINT agent_runs_kind_fields CHECK (
4
+ (
5
+ kind = 'conversation'
6
+ AND thread_id IS NOT NULL
7
+ AND agent_id IS NOT NULL
8
+ AND trigger_message_id IS NOT NULL
9
+ AND parent_run_id IS NULL
10
+ AND spawn_key IS NULL
11
+ AND result IS NULL
12
+ )
13
+ OR
14
+ (
15
+ kind = 'subagent'
16
+ AND thread_id IS NULL
17
+ AND agent_id IS NULL
18
+ AND trigger_message_id IS NULL
19
+ AND parent_run_id IS NOT NULL
20
+ AND spawn_key IS NOT NULL
21
+ AND spec IS NOT NULL
22
+ )
23
+ );
@@ -0,0 +1,28 @@
1
+ DROP INDEX IF EXISTS idx_agent_threads_project_agent_activity;
2
+ ALTER TABLE workflow_agent_node_runs RENAME COLUMN agent_id TO actor_id;
3
+ DROP INDEX IF EXISTS idx_agent_runs_project_agent_started;
4
+
5
+ ALTER TABLE agent_threads DROP COLUMN agent_id;
6
+
7
+ ALTER TABLE agent_runs
8
+ DROP CONSTRAINT agent_runs_kind_fields,
9
+ DROP COLUMN agent_id,
10
+ ADD CONSTRAINT agent_runs_kind_fields CHECK (
11
+ (
12
+ kind = 'conversation'
13
+ AND thread_id IS NOT NULL
14
+ AND trigger_message_id IS NOT NULL
15
+ AND parent_run_id IS NULL
16
+ AND spawn_key IS NULL
17
+ AND result IS NULL
18
+ )
19
+ OR
20
+ (
21
+ kind = 'subagent'
22
+ AND thread_id IS NULL
23
+ AND trigger_message_id IS NULL
24
+ AND parent_run_id IS NOT NULL
25
+ AND spawn_key IS NOT NULL
26
+ AND spec IS NOT NULL
27
+ )
28
+ );
@@ -0,0 +1,24 @@
1
+ CREATE TABLE auth_device_authorizations (
2
+ project_id TEXT NOT NULL,
3
+ id TEXT NOT NULL,
4
+ device_code_hash TEXT NOT NULL,
5
+ user_code TEXT NOT NULL,
6
+ client_name TEXT NOT NULL,
7
+ token_name TEXT NOT NULL,
8
+ token_expires_at TIMESTAMPTZ NOT NULL,
9
+ status TEXT NOT NULL CHECK (status IN ('pending', 'approved', 'denied', 'consumed')),
10
+ approved_user_id TEXT,
11
+ approved_session_id TEXT,
12
+ created_at TIMESTAMPTZ NOT NULL,
13
+ expires_at TIMESTAMPTZ NOT NULL,
14
+ approved_at TIMESTAMPTZ,
15
+ denied_at TIMESTAMPTZ,
16
+ consumed_at TIMESTAMPTZ,
17
+ PRIMARY KEY (project_id, id),
18
+ UNIQUE (project_id, user_code),
19
+ FOREIGN KEY (project_id, approved_user_id) REFERENCES auth_users (project_id, id),
20
+ FOREIGN KEY (project_id, approved_session_id) REFERENCES auth_sessions (project_id, id)
21
+ );
22
+
23
+ CREATE INDEX idx_auth_device_authorizations_pending
24
+ ON auth_device_authorizations (project_id, status, expires_at DESC);
@@ -0,0 +1,57 @@
1
+ CREATE TABLE share_grants (
2
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
3
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
4
+ definition_id TEXT NOT NULL CHECK (length(trim(definition_id)) > 0),
5
+ target_object_type_id TEXT NOT NULL CHECK (length(trim(target_object_type_id)) > 0),
6
+ target_primary_id TEXT NOT NULL CHECK (length(trim(target_primary_id)) > 0),
7
+ issued_by_type TEXT NOT NULL CHECK (issued_by_type IN ('user', 'serviceAccount')),
8
+ issued_by_id TEXT NOT NULL CHECK (length(trim(issued_by_id)) > 0),
9
+ authority_version INTEGER NOT NULL CHECK (authority_version > 0),
10
+ authority_snapshot JSONB NOT NULL CHECK (
11
+ jsonb_typeof(authority_snapshot) = 'object'
12
+ AND jsonb_typeof(authority_snapshot -> 'version') = 'number'
13
+ AND (authority_snapshot ->> 'version')::integer = authority_version
14
+ AND jsonb_typeof(authority_snapshot -> 'access') = 'object'
15
+ ),
16
+ authority_digest TEXT NOT NULL CHECK (authority_digest ~ '^[0-9a-f]{64}$'),
17
+ token_hash TEXT NOT NULL CHECK (token_hash ~ '^[0-9a-f]{64}$'),
18
+ destination_path TEXT NOT NULL CHECK (
19
+ left(destination_path, 1) = '/'
20
+ AND position('?' IN destination_path) = 0
21
+ AND position('#' IN destination_path) = 0
22
+ AND position(chr(92) IN destination_path) = 0
23
+ ),
24
+ created_at TIMESTAMPTZ NOT NULL,
25
+ expires_at TIMESTAMPTZ NOT NULL,
26
+ revoked_at TIMESTAMPTZ,
27
+ revoked_by_type TEXT CHECK (
28
+ revoked_by_type IS NULL OR revoked_by_type IN ('user', 'serviceAccount', 'system')
29
+ ),
30
+ revoked_by_id TEXT CHECK (
31
+ revoked_by_id IS NULL OR length(trim(revoked_by_id)) > 0
32
+ ),
33
+ PRIMARY KEY (project_id, id),
34
+ UNIQUE (project_id, token_hash),
35
+ CHECK (expires_at > created_at),
36
+ CHECK (revoked_at IS NULL OR revoked_at >= created_at),
37
+ CHECK (
38
+ (revoked_at IS NULL AND revoked_by_type IS NULL AND revoked_by_id IS NULL)
39
+ OR
40
+ (revoked_at IS NOT NULL AND revoked_by_type IS NOT NULL AND revoked_by_id IS NOT NULL)
41
+ )
42
+ );
43
+
44
+ CREATE INDEX idx_share_grants_project_created
45
+ ON share_grants (project_id, created_at DESC, id DESC);
46
+ CREATE INDEX idx_share_grants_definition_target
47
+ ON share_grants (
48
+ project_id,
49
+ definition_id,
50
+ target_object_type_id,
51
+ target_primary_id,
52
+ created_at DESC,
53
+ id DESC
54
+ );
55
+ CREATE INDEX idx_share_grants_expiry
56
+ ON share_grants (project_id, expires_at)
57
+ WHERE revoked_at IS NULL;