@sixb/sqlite 0.1.11 → 0.1.13

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 (80) hide show
  1. package/README.md +32 -0
  2. package/dist/agents/rows.d.ts +2 -0
  3. package/dist/agents/rows.d.ts.map +1 -1
  4. package/dist/agents/threads.d.ts +2 -1
  5. package/dist/agents/threads.d.ts.map +1 -1
  6. package/dist/auth-storage/access-tokens.d.ts +0 -1
  7. package/dist/auth-storage/access-tokens.d.ts.map +1 -1
  8. package/dist/auth-storage/auth-storage.d.ts.map +1 -1
  9. package/dist/auth-storage/device-authorizations.d.ts.map +1 -1
  10. package/dist/auth-storage/rows.d.ts +6 -3
  11. package/dist/auth-storage/rows.d.ts.map +1 -1
  12. package/dist/auth-storage/sessions.d.ts +8 -1
  13. package/dist/auth-storage/sessions.d.ts.map +1 -1
  14. package/dist/auth-storage/shared.d.ts.map +1 -1
  15. package/dist/file-upload-session-storage.d.ts +6 -0
  16. package/dist/file-upload-session-storage.d.ts.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1062 -137
  20. package/dist/index.js.map +28 -23
  21. package/dist/migrations.d.ts.map +1 -1
  22. package/dist/objects/query-compiler.d.ts +9 -2
  23. package/dist/objects/query-compiler.d.ts.map +1 -1
  24. package/dist/objects/read-scope.d.ts +3 -0
  25. package/dist/objects/read-scope.d.ts.map +1 -1
  26. package/dist/objects/reader.d.ts +1 -0
  27. package/dist/objects/reader.d.ts.map +1 -1
  28. package/dist/objects/rows.d.ts +1 -0
  29. package/dist/objects/rows.d.ts.map +1 -1
  30. package/dist/objects/storage.d.ts.map +1 -1
  31. package/dist/objects/vector-extension.d.ts +4 -0
  32. package/dist/objects/vector-extension.d.ts.map +1 -0
  33. package/dist/ontology-storage/index.d.ts +4 -0
  34. package/dist/ontology-storage/index.d.ts.map +1 -1
  35. package/dist/ontology-storage/materializations.d.ts +1 -2
  36. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  37. package/dist/ontology-storage/shared.d.ts +2 -1
  38. package/dist/ontology-storage/shared.d.ts.map +1 -1
  39. package/dist/ontology-storage/vector-indexing.d.ts +20 -0
  40. package/dist/ontology-storage/vector-indexing.d.ts.map +1 -0
  41. package/dist/ontology-storage/vectors.d.ts +16 -0
  42. package/dist/ontology-storage/vectors.d.ts.map +1 -0
  43. package/dist/vector-encoding.d.ts +3 -0
  44. package/dist/vector-encoding.d.ts.map +1 -0
  45. package/dist/workflow-intervention-storage.d.ts.map +1 -1
  46. package/package.json +8 -4
  47. package/src/agents/rows.ts +12 -0
  48. package/src/agents/threads.ts +41 -2
  49. package/src/ai-cost-storage.ts +4 -0
  50. package/src/auth-storage/access-tokens.ts +11 -18
  51. package/src/auth-storage/auth-storage.ts +5 -7
  52. package/src/auth-storage/device-authorizations.ts +3 -5
  53. package/src/auth-storage/rows.ts +17 -7
  54. package/src/auth-storage/sessions.ts +89 -4
  55. package/src/auth-storage/shared.ts +9 -3
  56. package/src/file-upload-session-storage.ts +136 -0
  57. package/src/index.ts +5 -0
  58. package/src/migrations/041-agent-thread-workspaces.sql +2 -0
  59. package/src/migrations/042-vector-profiles.sql +21 -0
  60. package/src/migrations/043-vector-indexing.sql +197 -0
  61. package/src/migrations/044-vector-batching.sql +3 -0
  62. package/src/migrations/045-agent-thread-sandbox-state.sql +2 -0
  63. package/src/migrations/046-workflow-intervention-principals.sql +15 -0
  64. package/src/migrations/047-ontology-commit-attribution.sql +67 -0
  65. package/src/migrations/048-file-upload-sessions.sql +35 -0
  66. package/src/migrations/049-native-sessions.sql +12 -0
  67. package/src/migrations.ts +26 -0
  68. package/src/objects/query-compiler.ts +164 -11
  69. package/src/objects/read-scope.ts +34 -31
  70. package/src/objects/reader.ts +29 -1
  71. package/src/objects/rows.ts +5 -1
  72. package/src/objects/storage.ts +13 -1
  73. package/src/objects/vector-extension.ts +26 -0
  74. package/src/ontology-storage/index.ts +16 -0
  75. package/src/ontology-storage/materializations.ts +29 -59
  76. package/src/ontology-storage/shared.ts +7 -3
  77. package/src/ontology-storage/vector-indexing.ts +196 -0
  78. package/src/ontology-storage/vectors.ts +182 -0
  79. package/src/vector-encoding.ts +9 -0
  80. package/src/workflow-intervention-storage.ts +9 -9
@@ -73,6 +73,11 @@ export interface SqliteAuthSessionRow {
73
73
  readonly last_seen_at: string | null
74
74
  readonly user_agent: string | null
75
75
  readonly ip_address: string | null
76
+ readonly client_name: string | null
77
+ readonly access_expires_at: string | null
78
+ readonly refresh_token_hash: string | null
79
+ readonly previous_refresh_token_hash: string | null
80
+ readonly refreshed_at: string | null
76
81
  }
77
82
 
78
83
  export interface SqliteAuthAccessTokenRow {
@@ -80,7 +85,7 @@ export interface SqliteAuthAccessTokenRow {
80
85
  readonly id: string
81
86
  readonly name: string
82
87
  readonly kind: AccessTokenRecord["kind"]
83
- readonly subject_type: AccessTokenRecord["subjectType"]
88
+ readonly subject_type: AccessTokenRecord["subject"]["type"]
84
89
  readonly subject_id: string
85
90
  readonly token_hash: string
86
91
  readonly group_ids: string | null
@@ -152,8 +157,6 @@ export interface SqliteAuthDeviceAuthorizationRow {
152
157
  readonly device_code_hash: string
153
158
  readonly user_code: string
154
159
  readonly client_name: string
155
- readonly token_name: string
156
- readonly token_expires_at: string
157
160
  readonly status: DeviceAuthorizationRecord["status"]
158
161
  readonly approved_user_id: string | null
159
162
  readonly approved_session_id: string | null
@@ -236,6 +239,16 @@ export function rowToSessionRecord(row: SqliteAuthSessionRow): SessionRecord {
236
239
  lastSeenAt: row.last_seen_at ? new Date(row.last_seen_at) : undefined,
237
240
  userAgent: row.user_agent ?? undefined,
238
241
  ipAddress: row.ip_address ?? undefined,
242
+ bearer:
243
+ row.refresh_token_hash !== null && row.client_name !== null && row.access_expires_at !== null
244
+ ? {
245
+ clientName: row.client_name,
246
+ accessExpiresAt: new Date(row.access_expires_at),
247
+ refreshTokenHash: row.refresh_token_hash,
248
+ previousRefreshTokenHash: row.previous_refresh_token_hash ?? undefined,
249
+ refreshedAt: row.refreshed_at ? new Date(row.refreshed_at) : undefined,
250
+ }
251
+ : undefined,
239
252
  }
240
253
  }
241
254
 
@@ -245,8 +258,7 @@ export function rowToAccessTokenRecord(row: SqliteAuthAccessTokenRow): AccessTok
245
258
  projectId: row.project_id,
246
259
  name: row.name,
247
260
  kind: row.kind,
248
- subjectType: row.subject_type,
249
- subjectId: row.subject_id,
261
+ subject: { type: row.subject_type, id: row.subject_id },
250
262
  tokenHash: row.token_hash,
251
263
  groupIds: parseOptionalStringArray(row.group_ids),
252
264
  createdByPrincipal:
@@ -347,8 +359,6 @@ export function rowToDeviceAuthorizationRecord(
347
359
  deviceCodeHash: row.device_code_hash,
348
360
  userCode: row.user_code,
349
361
  clientName: row.client_name,
350
- tokenName: row.token_name,
351
- tokenExpiresAt: new Date(row.token_expires_at),
352
362
  status: row.status,
353
363
  approvedUserId: row.approved_user_id ?? undefined,
354
364
  approvedSessionId: row.approved_session_id ?? undefined,
@@ -1,6 +1,11 @@
1
1
  import type { Database } from "bun:sqlite"
2
2
  import type { AuthSessionAudience } from "@sixb/core"
3
- import type { AuthSessionStore, CreateAuthSessionInput, SessionRecord } from "@sixb/core/storage"
3
+ import type {
4
+ AuthSessionStore,
5
+ CreateAuthSessionInput,
6
+ RotateSessionRefreshTokenResult,
7
+ SessionRecord,
8
+ } from "@sixb/core/storage"
4
9
  import { AuthStorageError } from "@sixb/core/storage"
5
10
  import { runImmediateTransaction } from "../transactions"
6
11
  import type { SqliteAuthSessionRow } from "./rows"
@@ -99,11 +104,10 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
99
104
 
100
105
  if (
101
106
  !row ||
107
+ row.refresh_token_hash !== null ||
102
108
  row.audience !== params.audience ||
103
109
  row.token_hash !== params.tokenHash ||
104
- row.revoked_at ||
105
- new Date(row.expires_at) <= params.now ||
106
- (row.absolute_expires_at !== null && new Date(row.absolute_expires_at) <= params.now)
110
+ !isLiveSessionRow(row, params.now)
107
111
  ) {
108
112
  return null
109
113
  }
@@ -111,6 +115,78 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
111
115
  return rowToSessionRecord(row)
112
116
  }
113
117
 
118
+ async findValidByAccessTokenHash(params: {
119
+ readonly projectId: string
120
+ readonly id: string
121
+ readonly tokenHash: string
122
+ readonly now: Date
123
+ }): Promise<SessionRecord | null> {
124
+ const row = getSessionRowById(this.db, params)
125
+ if (
126
+ !row ||
127
+ row.access_expires_at === null ||
128
+ row.token_hash !== params.tokenHash ||
129
+ new Date(row.access_expires_at) <= params.now ||
130
+ !isLiveSessionRow(row, params.now)
131
+ ) {
132
+ return null
133
+ }
134
+
135
+ return rowToSessionRecord(row)
136
+ }
137
+
138
+ async rotateRefreshToken(
139
+ params: Parameters<AuthSessionStore["rotateRefreshToken"]>[0]
140
+ ): Promise<RotateSessionRefreshTokenResult> {
141
+ return runImmediateTransaction(this.db, (): RotateSessionRefreshTokenResult => {
142
+ const row = getSessionRowById(this.db, params)
143
+ if (!row?.refresh_token_hash || !isLiveSessionRow(row, params.now)) {
144
+ return { status: "invalid" }
145
+ }
146
+
147
+ const replacedRecently =
148
+ row.previous_refresh_token_hash === params.refreshTokenHash &&
149
+ row.refreshed_at !== null &&
150
+ params.now.getTime() - new Date(row.refreshed_at).getTime() <= params.reuseGraceMs
151
+ if (row.refresh_token_hash !== params.refreshTokenHash && !replacedRecently) {
152
+ if (row.previous_refresh_token_hash !== params.refreshTokenHash) {
153
+ return { status: "invalid" }
154
+ }
155
+ this.db
156
+ .query("UPDATE auth_sessions SET revoked_at = ? WHERE project_id = ? AND id = ?")
157
+ .run(toIso(params.now), params.projectId, params.id)
158
+ return { status: "reused" }
159
+ }
160
+
161
+ const expiresAt = toIso(params.next.expiresAt)
162
+ this.db
163
+ .query(`
164
+ UPDATE auth_sessions
165
+ SET token_hash = ?,
166
+ access_expires_at = ?,
167
+ previous_refresh_token_hash = refresh_token_hash,
168
+ refresh_token_hash = ?,
169
+ refreshed_at = ?,
170
+ last_seen_at = ?,
171
+ expires_at = MAX(expires_at, MIN(?, COALESCE(absolute_expires_at, ?)))
172
+ WHERE project_id = ? AND id = ?
173
+ `)
174
+ .run(
175
+ params.next.tokenHash,
176
+ toIso(params.next.accessExpiresAt),
177
+ params.next.refreshTokenHash,
178
+ toIso(params.now),
179
+ toIso(params.now),
180
+ expiresAt,
181
+ expiresAt,
182
+ params.projectId,
183
+ params.id
184
+ )
185
+ const session = getSessionById(this.db, params)
186
+ return session ? { status: "rotated", session } : { status: "invalid" }
187
+ })
188
+ }
189
+
114
190
  async renewIfValid(params: {
115
191
  readonly projectId: string
116
192
  readonly id: string
@@ -135,6 +211,7 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
135
211
  AND id = ?
136
212
  AND audience = ?
137
213
  AND token_hash = ?
214
+ AND refresh_token_hash IS NULL
138
215
  AND revoked_at IS NULL
139
216
  AND expires_at > ?
140
217
  AND (absolute_expires_at IS NULL OR absolute_expires_at > ?)
@@ -228,3 +305,11 @@ export class SqliteAuthSessionStore implements AuthSessionStore {
228
305
  })
229
306
  }
230
307
  }
308
+
309
+ function isLiveSessionRow(row: SqliteAuthSessionRow, now: Date): boolean {
310
+ return (
311
+ row.revoked_at === null &&
312
+ new Date(row.expires_at) > now &&
313
+ (row.absolute_expires_at === null || new Date(row.absolute_expires_at) > now)
314
+ )
315
+ }
@@ -425,8 +425,11 @@ export function createSession(
425
425
  expires_at,
426
426
  absolute_expires_at,
427
427
  user_agent,
428
- ip_address
429
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
428
+ ip_address,
429
+ client_name,
430
+ access_expires_at,
431
+ refresh_token_hash
432
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
430
433
  `
431
434
  ).run(
432
435
  projectId,
@@ -439,7 +442,10 @@ export function createSession(
439
442
  toIso(input.expiresAt),
440
443
  input.absoluteExpiresAt ? toIso(input.absoluteExpiresAt) : null,
441
444
  input.userAgent ?? null,
442
- input.ipAddress ?? null
445
+ input.ipAddress ?? null,
446
+ input.bearer ? assertNonEmpty(input.bearer.clientName, "Session client name") : null,
447
+ input.bearer ? toIso(input.bearer.accessExpiresAt) : null,
448
+ input.bearer ? assertNonEmpty(input.bearer.refreshTokenHash, "Refresh token hash") : null
443
449
  )
444
450
 
445
451
  const session = getSessionById(db, { projectId, id })
@@ -0,0 +1,136 @@
1
+ import type { Database } from "bun:sqlite"
2
+ import {
3
+ DurableFileUploadSessions,
4
+ type FileUploadSessionPersistence,
5
+ type FileUploadSessionPersistenceBackend,
6
+ fileUploadSessionReapAt,
7
+ parseFileUploadSessionRow,
8
+ } from "@sixb/core/internal/file-upload-session-storage-provider"
9
+ import type { FileUploadSession, ListAbandonedFileUploadSessionsInput } from "@sixb/core/storage"
10
+ import { isUniqueConstraintError } from "./storage-errors"
11
+ import { runImmediateTransactionAsync, type SqliteStoreConnection } from "./transactions"
12
+
13
+ export class SqliteFileUploadSessionStorage extends DurableFileUploadSessions {
14
+ constructor(connection: SqliteStoreConnection) {
15
+ super(new SqliteFileUploadSessionBackend(connection.db))
16
+ }
17
+ }
18
+
19
+ class SqliteFileUploadSessionBackend implements FileUploadSessionPersistenceBackend {
20
+ private readonly persistence: FileUploadSessionPersistence
21
+
22
+ constructor(private readonly db: Database) {
23
+ this.persistence = new SqliteFileUploadSessionPersistence(db)
24
+ }
25
+
26
+ read<T>(run: (persistence: FileUploadSessionPersistence) => Promise<T>): Promise<T> {
27
+ return run(this.persistence)
28
+ }
29
+
30
+ transaction<T>(run: (persistence: FileUploadSessionPersistence) => Promise<T>): Promise<T> {
31
+ return runImmediateTransactionAsync(this.db, () => run(this.persistence))
32
+ }
33
+ }
34
+
35
+ class SqliteFileUploadSessionPersistence implements FileUploadSessionPersistence {
36
+ constructor(private readonly db: Database) {}
37
+
38
+ async now(): Promise<Date> {
39
+ const row = this.db.query("SELECT strftime('%Y-%m-%dT%H:%M:%fZ', 'now') AS now").get() as {
40
+ readonly now: string
41
+ }
42
+ return new Date(row.now)
43
+ }
44
+
45
+ async insert(session: FileUploadSession): Promise<boolean> {
46
+ try {
47
+ this.db
48
+ .query(
49
+ `INSERT INTO file_upload_sessions (
50
+ id, project_id, owner_principal_type, owner_principal_id, strategy, status, file_name,
51
+ media_type, logical_path, expected_size_bytes, expected_digest, provider_upload, signed_parts, file_ref,
52
+ created_at, expires_at, completed_at, aborted_at, reap_at
53
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
54
+ )
55
+ .run(
56
+ session.id,
57
+ session.projectId,
58
+ session.principal.type,
59
+ session.principal.id,
60
+ session.strategy,
61
+ session.status,
62
+ session.fileName ?? null,
63
+ session.mediaType ?? null,
64
+ session.logicalPath ?? null,
65
+ session.expectedSizeBytes ?? null,
66
+ session.expectedDigest ?? null,
67
+ ...mutableColumns(session)
68
+ )
69
+ return true
70
+ } catch (error) {
71
+ if (isUniqueConstraintError(error)) return false
72
+ throw error
73
+ }
74
+ }
75
+
76
+ async get(uploadId: string): Promise<FileUploadSession | null> {
77
+ const row = this.db.query(`${SELECT_SESSION} WHERE id = ?`).get(uploadId)
78
+ return row ? parseFileUploadSessionRow(row as Record<string, unknown>) : null
79
+ }
80
+
81
+ async update(session: FileUploadSession): Promise<void> {
82
+ this.db
83
+ .query(
84
+ `UPDATE file_upload_sessions
85
+ SET status = ?, provider_upload = ?, signed_parts = ?, file_ref = ?, created_at = ?,
86
+ expires_at = ?, completed_at = ?, aborted_at = ?, reap_at = ?
87
+ WHERE id = ?`
88
+ )
89
+ .run(session.status, ...mutableColumns(session), session.id)
90
+ }
91
+
92
+ async listAbandoned(
93
+ input: ListAbandonedFileUploadSessionsInput
94
+ ): Promise<readonly FileUploadSession[]> {
95
+ const rows = this.db
96
+ .query(
97
+ `${SELECT_SESSION}
98
+ WHERE project_id = ? AND status = 'pending' AND provider_upload IS NOT NULL
99
+ AND expires_at <= ?
100
+ ORDER BY expires_at, id
101
+ LIMIT ?`
102
+ )
103
+ .all(input.projectId, input.now.toISOString(), input.limit)
104
+ return rows.map((row) => parseFileUploadSessionRow(row as Record<string, unknown>))
105
+ }
106
+
107
+ async deleteReapable(now: Date): Promise<number> {
108
+ return this.db
109
+ .query("DELETE FROM file_upload_sessions WHERE reap_at IS NOT NULL AND reap_at <= ?")
110
+ .run(now.toISOString()).changes
111
+ }
112
+ }
113
+
114
+ const SELECT_SESSION = `
115
+ SELECT id, project_id AS projectId, owner_principal_type AS ownerPrincipalType,
116
+ owner_principal_id AS ownerPrincipalId, strategy, status,
117
+ file_name AS fileName, media_type AS mediaType, logical_path AS logicalPath,
118
+ expected_size_bytes AS expectedSizeBytes, expected_digest AS expectedDigest,
119
+ provider_upload AS providerUpload, signed_parts AS signedParts, file_ref AS fileRef,
120
+ created_at AS createdAt, expires_at AS expiresAt, completed_at AS completedAt,
121
+ aborted_at AS abortedAt
122
+ FROM file_upload_sessions`
123
+
124
+ /** Columns a transition may change, in insert/update order after `expected_digest`/`status`. */
125
+ function mutableColumns(session: FileUploadSession) {
126
+ return [
127
+ session.providerUpload === undefined ? null : JSON.stringify(session.providerUpload),
128
+ JSON.stringify(session.signedParts),
129
+ session.fileRef === undefined ? null : JSON.stringify(session.fileRef),
130
+ session.createdAt.toISOString(),
131
+ session.expiresAt.toISOString(),
132
+ session.completedAt?.toISOString() ?? null,
133
+ session.abortedAt?.toISOString() ?? null,
134
+ fileUploadSessionReapAt(session)?.toISOString() ?? null,
135
+ ] as const
136
+ }
package/src/index.ts CHANGED
@@ -39,6 +39,7 @@ import { SqliteAiUsageStorage } from "./ai-usage-storage"
39
39
  import { SqliteAuthStorage } from "./auth-storage"
40
40
  import { SqliteConnectorConnectionStorage } from "./connector-connection-storage"
41
41
  import { SqliteExecutionStorage } from "./execution-storage"
42
+ import { SqliteFileUploadSessionStorage } from "./file-upload-session-storage"
42
43
  import {
43
44
  createSqliteStorageMigrators,
44
45
  installFreshSqliteSchema,
@@ -106,6 +107,7 @@ export class SqliteStorage implements MigrationCapableStorage {
106
107
  readonly shareGrants: ShareGrantStorage
107
108
  readonly shareSessions: ShareSessionStorage
108
109
  readonly connectorConnections: SqliteConnectorConnectionStorage
110
+ readonly fileUploadSessions: SqliteFileUploadSessionStorage
109
111
  readonly migrators: readonly StorageMigrator[]
110
112
 
111
113
  private readonly connection: SqliteStoreConnection
@@ -172,6 +174,7 @@ export class SqliteStorage implements MigrationCapableStorage {
172
174
  this.shareGrants = createOperationScopedFacade(stores.shareGrants, scope)
173
175
  this.shareSessions = createOperationScopedFacade(stores.shareSessions, scope)
174
176
  this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope)
177
+ this.fileUploadSessions = createOperationScopedFacade(stores.fileUploadSessions, scope)
175
178
  this.migrators = options.path ? createSqliteStorageMigrators(options.path) : []
176
179
  registerSqliteStorageTestingAdapter(this, (durationMs) =>
177
180
  stores.connectorConnections.advanceTimeForTesting(durationMs)
@@ -333,6 +336,7 @@ function createSqliteStores(
333
336
  shareGrants,
334
337
  shareSessions,
335
338
  connectorConnections: new SqliteConnectorConnectionStorage(connection),
339
+ fileUploadSessions: new SqliteFileUploadSessionStorage(connection),
336
340
  }
337
341
  }
338
342
 
@@ -357,6 +361,7 @@ interface SqliteStoreSet {
357
361
  readonly shareGrants: SqliteShareGrantStorage
358
362
  readonly shareSessions: SqliteShareSessionStorage
359
363
  readonly connectorConnections: SqliteConnectorConnectionStorage
364
+ readonly fileUploadSessions: SqliteFileUploadSessionStorage
360
365
  }
361
366
 
362
367
  export { migrateSqliteStorage } from "./migrations"
@@ -0,0 +1,2 @@
1
+ ALTER TABLE agent_threads ADD COLUMN sandbox_params TEXT
2
+ CHECK (sandbox_params IS NULL OR (json_valid(sandbox_params) AND json_type(sandbox_params) = 'object'));
@@ -0,0 +1,21 @@
1
+ CREATE TABLE object_vectors (
2
+ project_id TEXT NOT NULL,
3
+ object_type_id TEXT NOT NULL,
4
+ primary_id TEXT NOT NULL,
5
+ profile TEXT NOT NULL CHECK (length(trim(profile)) > 0),
6
+ configuration TEXT NOT NULL,
7
+ source TEXT NOT NULL CHECK (
8
+ json_valid(source) AND json_type(source) = 'array' AND json_array_length(source) > 0
9
+ ),
10
+ source_fingerprint TEXT NOT NULL,
11
+ embedding BLOB NOT NULL CHECK (
12
+ typeof(embedding) = 'blob' AND length(embedding) BETWEEN 4 AND 64000
13
+ AND length(embedding) % 4 = 0
14
+ ),
15
+ last_commit_id TEXT NOT NULL,
16
+ PRIMARY KEY (project_id, object_type_id, primary_id, profile),
17
+ FOREIGN KEY (project_id, object_type_id, primary_id)
18
+ REFERENCES objects (project_id, object_type_id, primary_id) ON DELETE CASCADE,
19
+ FOREIGN KEY (project_id, last_commit_id)
20
+ REFERENCES ontology_commits (project_id, id) DEFERRABLE INITIALLY DEFERRED
21
+ );
@@ -0,0 +1,197 @@
1
+ CREATE TABLE object_vector_indexing (
2
+ project_id TEXT NOT NULL,
3
+ id TEXT NOT NULL,
4
+ object_type_id TEXT NOT NULL,
5
+ primary_id TEXT NOT NULL,
6
+ profile TEXT NOT NULL,
7
+ request TEXT NOT NULL,
8
+ status TEXT NOT NULL CHECK (status IN ('pending', 'running', 'ready', 'failed')),
9
+ available_at TEXT NOT NULL,
10
+ dispatch_at TEXT NOT NULL,
11
+ values_json TEXT,
12
+ error TEXT,
13
+ PRIMARY KEY (project_id, id),
14
+ UNIQUE (project_id, object_type_id, primary_id, profile)
15
+ );
16
+ CREATE INDEX object_vector_indexing_due ON object_vector_indexing (project_id, dispatch_at, id)
17
+ WHERE status <> 'failed';
18
+
19
+ CREATE TABLE executions_v2 (
20
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
21
+ id TEXT NOT NULL CHECK (length(trim(id)) > 0),
22
+ executor_kind TEXT NOT NULL CHECK (
23
+ executor_kind IN (
24
+ 'request', 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow',
25
+ 'agent', 'kernel'
26
+ )
27
+ ),
28
+ executor_id TEXT NOT NULL CHECK (length(trim(executor_id)) > 0),
29
+ source_kind TEXT NOT NULL CHECK (
30
+ source_kind IN ('http', 'webhook', 'schedule', 'event', 'datasetVersion', 'execution', 'ontologyCommit')
31
+ ),
32
+ source_id TEXT NOT NULL CHECK (length(trim(source_id)) > 0),
33
+ requested_by_user_id TEXT,
34
+ requested_by_service_account_id TEXT,
35
+ correlation_id TEXT NOT NULL CHECK (length(trim(correlation_id)) > 0),
36
+ parent_execution_id TEXT,
37
+ authority_kind TEXT NOT NULL CHECK (
38
+ authority_kind IN ('principal', 'trustedPrimitive', 'delegated', 'kernel', 'disabled')
39
+ ),
40
+ authority_user_id TEXT,
41
+ authority_service_account_id TEXT,
42
+ authority_session_id TEXT,
43
+ authority_access_token_id TEXT,
44
+ authority_primitive_kind TEXT CHECK (
45
+ authority_primitive_kind IS NULL
46
+ OR authority_primitive_kind IN (
47
+ 'action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow'
48
+ )
49
+ ),
50
+ authority_primitive_id TEXT,
51
+ authority_kernel_operation TEXT CHECK (
52
+ authority_kernel_operation IS NULL OR authority_kernel_operation IN ('ontology.recover', 'ontology.indexVectors')
53
+ ),
54
+ authority_delegation_kind TEXT CHECK (
55
+ authority_delegation_kind IS NULL OR authority_delegation_kind = 'share'
56
+ ),
57
+ authority_delegation_id TEXT,
58
+ authority_delegation_session_id TEXT,
59
+ requester_group_ids TEXT NOT NULL DEFAULT '[]',
60
+ created_at TEXT NOT NULL,
61
+ PRIMARY KEY (project_id, id),
62
+ FOREIGN KEY (project_id, parent_execution_id) REFERENCES executions (project_id, id),
63
+ FOREIGN KEY (project_id, requested_by_user_id) REFERENCES auth_users (project_id, id),
64
+ FOREIGN KEY (project_id, requested_by_service_account_id)
65
+ REFERENCES auth_service_accounts (project_id, id),
66
+ FOREIGN KEY (project_id, authority_user_id) REFERENCES auth_users (project_id, id),
67
+ FOREIGN KEY (project_id, authority_service_account_id)
68
+ REFERENCES auth_service_accounts (project_id, id),
69
+ FOREIGN KEY (project_id, authority_session_id) REFERENCES auth_sessions (project_id, id),
70
+ FOREIGN KEY (project_id, authority_access_token_id)
71
+ REFERENCES auth_access_tokens (project_id, id),
72
+ FOREIGN KEY (
73
+ project_id, authority_delegation_session_id, authority_delegation_id
74
+ ) REFERENCES share_sessions (project_id, id, grant_id),
75
+ CHECK ((requested_by_user_id IS NOT NULL) + (requested_by_service_account_id IS NOT NULL) <= 1),
76
+ CHECK (
77
+ (source_kind = 'execution' AND parent_execution_id = source_id)
78
+ OR (source_kind <> 'execution' AND parent_execution_id IS NULL)
79
+ ),
80
+ CHECK (
81
+ (
82
+ authority_kind = 'principal'
83
+ AND (authority_user_id IS NOT NULL) + (authority_service_account_id IS NOT NULL) = 1
84
+ AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) <= 1
85
+ AND (authority_session_id IS NULL OR authority_user_id IS NOT NULL)
86
+ AND authority_primitive_kind IS NULL
87
+ AND authority_primitive_id IS NULL
88
+ AND authority_kernel_operation IS NULL
89
+ AND authority_delegation_kind IS NULL
90
+ AND authority_delegation_id IS NULL
91
+ AND authority_delegation_session_id IS NULL
92
+ )
93
+ OR (
94
+ authority_kind = 'trustedPrimitive'
95
+ AND authority_user_id IS NULL
96
+ AND authority_service_account_id IS NULL
97
+ AND authority_session_id IS NULL
98
+ AND authority_access_token_id IS NULL
99
+ AND authority_primitive_kind IS NOT NULL
100
+ AND authority_primitive_id IS NOT NULL
101
+ AND length(trim(authority_primitive_id)) > 0
102
+ AND authority_kernel_operation IS NULL
103
+ AND authority_delegation_kind IS NULL
104
+ AND authority_delegation_id IS NULL
105
+ AND authority_delegation_session_id IS NULL
106
+ )
107
+ OR (
108
+ authority_kind = 'delegated'
109
+ AND authority_user_id IS NULL
110
+ AND authority_service_account_id IS NULL
111
+ AND authority_session_id IS NULL
112
+ AND authority_access_token_id IS NULL
113
+ AND authority_primitive_kind IS NULL
114
+ AND authority_primitive_id IS NULL
115
+ AND authority_kernel_operation IS NULL
116
+ AND authority_delegation_kind IS NOT NULL
117
+ AND authority_delegation_kind = 'share'
118
+ AND authority_delegation_id IS NOT NULL
119
+ AND length(trim(authority_delegation_id)) > 0
120
+ AND authority_delegation_session_id IS NOT NULL
121
+ AND length(trim(authority_delegation_session_id)) > 0
122
+ )
123
+ OR (
124
+ authority_kind = 'kernel'
125
+ AND authority_user_id IS NULL
126
+ AND authority_service_account_id IS NULL
127
+ AND authority_session_id IS NULL
128
+ AND authority_access_token_id IS NULL
129
+ AND authority_primitive_kind IS NULL
130
+ AND authority_primitive_id IS NULL
131
+ AND authority_kernel_operation IS NOT NULL
132
+ AND authority_delegation_kind IS NULL
133
+ AND authority_delegation_id IS NULL
134
+ AND authority_delegation_session_id IS NULL
135
+ )
136
+ OR (
137
+ authority_kind = 'disabled'
138
+ AND authority_user_id IS NULL
139
+ AND authority_service_account_id IS NULL
140
+ AND authority_session_id IS NULL
141
+ AND authority_access_token_id IS NULL
142
+ AND authority_primitive_kind IS NULL
143
+ AND authority_primitive_id IS NULL
144
+ AND authority_kernel_operation IS NULL
145
+ AND authority_delegation_kind IS NULL
146
+ AND authority_delegation_id IS NULL
147
+ AND authority_delegation_session_id IS NULL
148
+ )
149
+ ),
150
+ CHECK (
151
+ (
152
+ executor_kind = 'request'
153
+ AND source_kind = 'http'
154
+ AND executor_id = source_id
155
+ AND (
156
+ (
157
+ authority_kind = 'principal'
158
+ AND requested_by_user_id IS authority_user_id
159
+ AND requested_by_service_account_id IS authority_service_account_id
160
+ )
161
+ OR (
162
+ authority_kind IN ('delegated', 'disabled')
163
+ AND requested_by_user_id IS NULL
164
+ AND requested_by_service_account_id IS NULL
165
+ )
166
+ )
167
+ )
168
+ OR (
169
+ executor_kind IN ('action', 'pipeline', 'projection', 'rule', 'sync', 'webhook', 'workflow')
170
+ AND authority_kind = 'trustedPrimitive'
171
+ AND executor_kind = authority_primitive_kind
172
+ )
173
+ OR (
174
+ executor_kind = 'agent'
175
+ AND source_kind = 'execution'
176
+ AND (
177
+ (
178
+ authority_kind = 'principal'
179
+ AND authority_service_account_id IS NOT NULL
180
+ AND authority_session_id IS NULL
181
+ AND authority_access_token_id IS NULL
182
+ )
183
+ OR (
184
+ authority_kind = 'principal'
185
+ AND authority_user_id IS NOT NULL
186
+ AND (authority_session_id IS NOT NULL) + (authority_access_token_id IS NOT NULL) = 1
187
+ )
188
+ OR authority_kind = 'disabled'
189
+ )
190
+ )
191
+ OR (executor_kind = 'kernel' AND authority_kind = 'kernel')
192
+ )
193
+ );
194
+
195
+ INSERT INTO executions_v2 (project_id, id, executor_kind, executor_id, source_kind, source_id, requested_by_user_id, requested_by_service_account_id, correlation_id, parent_execution_id, authority_kind, authority_user_id, authority_service_account_id, authority_session_id, authority_access_token_id, authority_primitive_kind, authority_primitive_id, authority_kernel_operation, authority_delegation_kind, authority_delegation_id, authority_delegation_session_id, requester_group_ids, created_at) SELECT project_id, id, executor_kind, executor_id, source_kind, source_id, requested_by_user_id, requested_by_service_account_id, correlation_id, parent_execution_id, authority_kind, authority_user_id, authority_service_account_id, authority_session_id, authority_access_token_id, authority_primitive_kind, authority_primitive_id, authority_kernel_operation, authority_delegation_kind, authority_delegation_id, authority_delegation_session_id, requester_group_ids, created_at FROM executions;
196
+ DROP TABLE executions;
197
+ ALTER TABLE executions_v2 RENAME TO executions;
@@ -0,0 +1,3 @@
1
+ ALTER TABLE object_vector_indexing ADD COLUMN batch_id TEXT;
2
+ CREATE INDEX object_vector_indexing_batch ON object_vector_indexing (project_id, batch_id, id)
3
+ WHERE batch_id IS NOT NULL;
@@ -0,0 +1,2 @@
1
+ ALTER TABLE agent_threads ADD COLUMN sandbox_state TEXT
2
+ CHECK (sandbox_state IS NULL OR (json_valid(sandbox_state) AND json_type(sandbox_state) = 'object'));
@@ -0,0 +1,15 @@
1
+ -- Intervention actors were stored as { principalType, principalId }. Rewrite them to the
2
+ -- canonical Principal shape { type, id } used by every other stored principal.
3
+ UPDATE workflow_interventions
4
+ SET submitted_by = json_object(
5
+ 'type', json_extract(submitted_by, '$.principalType'),
6
+ 'id', json_extract(submitted_by, '$.principalId')
7
+ )
8
+ WHERE json_extract(submitted_by, '$.principalType') IS NOT NULL;
9
+
10
+ UPDATE workflow_interventions
11
+ SET cancelled_by = json_object(
12
+ 'type', json_extract(cancelled_by, '$.principalType'),
13
+ 'id', json_extract(cancelled_by, '$.principalId')
14
+ )
15
+ WHERE json_extract(cancelled_by, '$.principalType') IS NOT NULL;