@sixb/pg 0.1.12 → 0.1.14

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 (41) hide show
  1. package/dist/auth-storage/access-tokens.d.ts.map +1 -1
  2. package/dist/auth-storage/auth-storage.d.ts +2 -1
  3. package/dist/auth-storage/auth-storage.d.ts.map +1 -1
  4. package/dist/auth-storage/device-authorizations.d.ts.map +1 -1
  5. package/dist/auth-storage/rows.d.ts +7 -4
  6. package/dist/auth-storage/rows.d.ts.map +1 -1
  7. package/dist/auth-storage/sessions.d.ts +8 -1
  8. package/dist/auth-storage/sessions.d.ts.map +1 -1
  9. package/dist/auth-storage/shared.d.ts.map +1 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +403 -115
  13. package/dist/index.js.map +16 -15
  14. package/dist/migrations.d.ts.map +1 -1
  15. package/dist/objects/storage.d.ts.map +1 -1
  16. package/dist/ontology-storage/materializations.d.ts +0 -2
  17. package/dist/ontology-storage/materializations.d.ts.map +1 -1
  18. package/dist/ontology-storage/shared.d.ts +2 -1
  19. package/dist/ontology-storage/shared.d.ts.map +1 -1
  20. package/dist/pg-file-upload-session-storage.d.ts +6 -0
  21. package/dist/pg-file-upload-session-storage.d.ts.map +1 -0
  22. package/dist/pg-workflow-intervention-storage.d.ts.map +1 -1
  23. package/package.json +3 -3
  24. package/src/auth-storage/access-tokens.ts +10 -14
  25. package/src/auth-storage/auth-storage.ts +47 -81
  26. package/src/auth-storage/device-authorizations.ts +3 -4
  27. package/src/auth-storage/rows.ts +18 -8
  28. package/src/auth-storage/sessions.ts +82 -4
  29. package/src/auth-storage/shared.ts +8 -2
  30. package/src/index.ts +5 -0
  31. package/src/migrations/046-workflow-intervention-principals.sql +15 -0
  32. package/src/migrations/047-ontology-commit-attribution.sql +50 -0
  33. package/src/migrations/048-file-upload-sessions.sql +34 -0
  34. package/src/migrations/049-native-sessions.sql +19 -0
  35. package/src/migrations/050-directory-group-memberships.sql +5 -0
  36. package/src/migrations.ts +16 -0
  37. package/src/objects/storage.ts +2 -0
  38. package/src/ontology-storage/materializations.ts +15 -58
  39. package/src/ontology-storage/shared.ts +7 -3
  40. package/src/pg-file-upload-session-storage.ts +143 -0
  41. package/src/pg-workflow-intervention-storage.ts +11 -15
package/dist/index.js CHANGED
@@ -1162,7 +1162,14 @@ function rowToSessionRecord(row) {
1162
1162
  revokedAt: row.revoked_at ? toDate(row.revoked_at) : undefined,
1163
1163
  lastSeenAt: row.last_seen_at ? toDate(row.last_seen_at) : undefined,
1164
1164
  userAgent: row.user_agent ?? undefined,
1165
- ipAddress: row.ip_address ?? undefined
1165
+ ipAddress: row.ip_address ?? undefined,
1166
+ bearer: row.refresh_token_hash !== null && row.client_name !== null && row.access_expires_at !== null ? {
1167
+ clientName: row.client_name,
1168
+ accessExpiresAt: toDate(row.access_expires_at),
1169
+ refreshTokenHash: row.refresh_token_hash,
1170
+ previousRefreshTokenHash: row.previous_refresh_token_hash ?? undefined,
1171
+ refreshedAt: row.refreshed_at ? toDate(row.refreshed_at) : undefined
1172
+ } : undefined
1166
1173
  };
1167
1174
  }
1168
1175
  function rowToAccessTokenRecord(row) {
@@ -1171,8 +1178,7 @@ function rowToAccessTokenRecord(row) {
1171
1178
  projectId: row.project_id,
1172
1179
  name: row.name,
1173
1180
  kind: row.kind,
1174
- subjectType: row.subject_type,
1175
- subjectId: row.subject_id,
1181
+ subject: { type: row.subject_type, id: row.subject_id },
1176
1182
  tokenHash: row.token_hash,
1177
1183
  groupIds: parseOptionalStringArray(row.group_ids),
1178
1184
  createdByPrincipal: principalFromColumns2(row),
@@ -1247,8 +1253,6 @@ function rowToDeviceAuthorizationRecord(row) {
1247
1253
  deviceCodeHash: row.device_code_hash,
1248
1254
  userCode: row.user_code,
1249
1255
  clientName: row.client_name,
1250
- tokenName: row.token_name,
1251
- tokenExpiresAt: toDate(row.token_expires_at),
1252
1256
  status: row.status,
1253
1257
  approvedUserId: row.approved_user_id ?? undefined,
1254
1258
  approvedSessionId: row.approved_session_id ?? undefined,
@@ -1559,7 +1563,10 @@ async function createSession(sql, input) {
1559
1563
  expires_at,
1560
1564
  absolute_expires_at,
1561
1565
  user_agent,
1562
- ip_address
1566
+ ip_address,
1567
+ client_name,
1568
+ access_expires_at,
1569
+ refresh_token_hash
1563
1570
  ) VALUES (
1564
1571
  ${projectId},
1565
1572
  ${id},
@@ -1571,7 +1578,10 @@ async function createSession(sql, input) {
1571
1578
  ${input.expiresAt},
1572
1579
  ${input.absoluteExpiresAt ?? null},
1573
1580
  ${input.userAgent ?? null},
1574
- ${input.ipAddress ?? null}
1581
+ ${input.ipAddress ?? null},
1582
+ ${input.bearer ? assertNonEmpty(input.bearer.clientName, "Session client name") : null},
1583
+ ${input.bearer?.accessExpiresAt ?? null},
1584
+ ${input.bearer ? assertNonEmpty(input.bearer.refreshTokenHash, "Refresh token hash") : null}
1575
1585
  )
1576
1586
  RETURNING *
1577
1587
  `;
@@ -1763,10 +1773,10 @@ class PgAuthAccessTokenStore {
1763
1773
  const id = assertNonEmpty(input.id, "Access token id");
1764
1774
  const projectId = assertNonEmpty(input.projectId, "Project id");
1765
1775
  const name = assertNonEmpty(input.name, "Access token name");
1766
- const subjectId = assertNonEmpty(input.subjectId, "Access token subject id");
1776
+ const subjectId = assertNonEmpty(input.subject.id, "Access token subject id");
1767
1777
  const tokenHash = assertNonEmpty(input.tokenHash, "Access token hash");
1768
1778
  assertKindMatchesSubject(input);
1769
- await this.assertSubjectExists(projectId, input.subjectType, subjectId);
1779
+ await this.assertSubjectExists(projectId, input.subject.type, subjectId);
1770
1780
  const groupIds = input.groupIds === undefined ? undefined : normalizeGroupIds(input.groupIds);
1771
1781
  try {
1772
1782
  const [row] = await this.sql`
@@ -1790,7 +1800,7 @@ class PgAuthAccessTokenStore {
1790
1800
  ${id},
1791
1801
  ${name},
1792
1802
  ${input.kind},
1793
- ${input.subjectType},
1803
+ ${input.subject.type},
1794
1804
  ${subjectId},
1795
1805
  ${tokenHash},
1796
1806
  ${serializeOptionalStringArray(groupIds)},
@@ -1825,13 +1835,9 @@ class PgAuthAccessTokenStore {
1825
1835
  whereClauses.push(`kind = $${nextIndex++}`);
1826
1836
  params.push(input.kind);
1827
1837
  }
1828
- if (input.subjectType) {
1829
- whereClauses.push(`subject_type = $${nextIndex++}`);
1830
- params.push(input.subjectType);
1831
- }
1832
- if (input.subjectId) {
1833
- whereClauses.push(`subject_id = $${nextIndex++}`);
1834
- params.push(input.subjectId);
1838
+ if (input.subject) {
1839
+ whereClauses.push(`subject_type = $${nextIndex++}`, `subject_id = $${nextIndex++}`);
1840
+ params.push(input.subject.type, input.subject.id);
1835
1841
  }
1836
1842
  if (!input.includeRevoked) {
1837
1843
  whereClauses.push("revoked_at IS NULL");
@@ -1919,10 +1925,10 @@ class PgAuthAccessTokenStore {
1919
1925
  }
1920
1926
  }
1921
1927
  function assertKindMatchesSubject(input) {
1922
- if (input.kind === "personal" && input.subjectType === "user" || input.kind === "serviceAccount" && input.subjectType === "serviceAccount") {
1928
+ if (input.kind === "personal" && input.subject.type === "user" || input.kind === "serviceAccount" && input.subject.type === "serviceAccount") {
1923
1929
  return;
1924
1930
  }
1925
- throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.subjectType}'.`);
1931
+ throw new AuthStorageError2("invalid_input", `[Sixb] Access token kind '${input.kind}' cannot target subject type '${input.subject.type}'.`);
1926
1932
  }
1927
1933
 
1928
1934
  // src/auth-storage/device-authorizations.ts
@@ -1949,16 +1955,15 @@ class PgAuthDeviceAuthorizationStore {
1949
1955
  try {
1950
1956
  const [row] = await tx`
1951
1957
  INSERT INTO auth_device_authorizations (
1952
- project_id, id, device_code_hash, user_code, client_name, token_name,
1953
- token_expires_at, status, created_at, expires_at
1958
+ project_id, id, device_code_hash, user_code, client_name, status, created_at,
1959
+ expires_at
1954
1960
  ) VALUES (
1955
1961
  ${assertNonEmpty(input.projectId, "Project id")},
1956
1962
  ${assertNonEmpty(input.id, "Device authorization id")},
1957
1963
  ${assertNonEmpty(input.deviceCodeHash, "Device code hash")},
1958
1964
  ${assertNonEmpty(input.userCode, "User code")},
1959
1965
  ${assertNonEmpty(input.clientName, "Client name")},
1960
- ${assertNonEmpty(input.tokenName, "Token name")},
1961
- ${input.tokenExpiresAt}, 'pending', ${input.createdAt}, ${input.expiresAt}
1966
+ 'pending', ${input.createdAt}, ${input.expiresAt}
1962
1967
  ) RETURNING *
1963
1968
  `;
1964
1969
  return rowToDeviceAuthorizationRecord(row);
@@ -2700,11 +2705,53 @@ class PgAuthSessionStore {
2700
2705
  }
2701
2706
  async findValidByTokenHash(params) {
2702
2707
  const row = await getSessionRowById(this.sql, params);
2703
- if (!row || row.audience !== params.audience || row.token_hash !== params.tokenHash || row.revoked_at || new Date(row.expires_at) <= params.now || row.absolute_expires_at !== null && new Date(row.absolute_expires_at) <= params.now) {
2708
+ if (!row || row.refresh_token_hash !== null || row.audience !== params.audience || row.token_hash !== params.tokenHash || !isLiveSessionRow(row, params.now)) {
2709
+ return null;
2710
+ }
2711
+ return rowToSessionRecord(row);
2712
+ }
2713
+ async findValidByAccessTokenHash(params) {
2714
+ const row = await getSessionRowById(this.sql, params);
2715
+ if (!row || row.access_expires_at === null || row.token_hash !== params.tokenHash || new Date(row.access_expires_at) <= params.now || !isLiveSessionRow(row, params.now)) {
2704
2716
  return null;
2705
2717
  }
2706
2718
  return rowToSessionRecord(row);
2707
2719
  }
2720
+ async rotateRefreshToken(params) {
2721
+ return runPgTransaction(this.sql, async (tx) => {
2722
+ const row = await getSessionRowById(tx, params, { forUpdate: true });
2723
+ if (!row?.refresh_token_hash || !isLiveSessionRow(row, params.now)) {
2724
+ return { status: "invalid" };
2725
+ }
2726
+ const replacedRecently = row.previous_refresh_token_hash === params.refreshTokenHash && row.refreshed_at !== null && params.now.getTime() - new Date(row.refreshed_at).getTime() <= params.reuseGraceMs;
2727
+ if (row.refresh_token_hash !== params.refreshTokenHash && !replacedRecently) {
2728
+ if (row.previous_refresh_token_hash !== params.refreshTokenHash) {
2729
+ return { status: "invalid" };
2730
+ }
2731
+ await tx`
2732
+ UPDATE auth_sessions SET revoked_at = ${params.now}
2733
+ WHERE project_id = ${params.projectId} AND id = ${params.id}
2734
+ `;
2735
+ return { status: "reused" };
2736
+ }
2737
+ const [rotated] = await tx`
2738
+ UPDATE auth_sessions
2739
+ SET token_hash = ${params.next.tokenHash},
2740
+ access_expires_at = ${params.next.accessExpiresAt},
2741
+ previous_refresh_token_hash = refresh_token_hash,
2742
+ refresh_token_hash = ${params.next.refreshTokenHash},
2743
+ refreshed_at = ${params.now},
2744
+ last_seen_at = ${params.now},
2745
+ expires_at = GREATEST(
2746
+ expires_at,
2747
+ LEAST(${params.next.expiresAt}, COALESCE(absolute_expires_at, ${params.next.expiresAt}))
2748
+ )
2749
+ WHERE project_id = ${params.projectId} AND id = ${params.id}
2750
+ RETURNING *
2751
+ `;
2752
+ return rotated ? { status: "rotated", session: rowToSessionRecord(rotated) } : { status: "invalid" };
2753
+ });
2754
+ }
2708
2755
  async renewIfValid(params) {
2709
2756
  const [row] = await this.sql`
2710
2757
  UPDATE auth_sessions
@@ -2720,6 +2767,7 @@ class PgAuthSessionStore {
2720
2767
  AND id = ${params.id}
2721
2768
  AND audience = ${params.audience}
2722
2769
  AND token_hash = ${params.tokenHash}
2770
+ AND refresh_token_hash IS NULL
2723
2771
  AND revoked_at IS NULL
2724
2772
  AND expires_at > ${params.now}
2725
2773
  AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.now})
@@ -2767,6 +2815,9 @@ class PgAuthSessionStore {
2767
2815
  });
2768
2816
  }
2769
2817
  }
2818
+ function isLiveSessionRow(row, now) {
2819
+ return row.revoked_at === null && new Date(row.expires_at) > now && (row.absolute_expires_at === null || new Date(row.absolute_expires_at) > now);
2820
+ }
2770
2821
 
2771
2822
  // src/auth-storage/users.ts
2772
2823
  import { AuthStorageError as AuthStorageError10 } from "@sixb/core/storage";
@@ -2928,7 +2979,7 @@ class PgAuthStorage {
2928
2979
  throw new AuthStorageError11("missing_device_authorization", `[Sixb] Device authorization '${input.id}' not found for project '${input.projectId}'.`);
2929
2980
  }
2930
2981
  assertCompletableDeviceAuthorization(authorization, input);
2931
- const accessToken = await new PgAuthAccessTokenStore(tx).create(input.accessToken);
2982
+ const session = await createSession(tx, input.session);
2932
2983
  const [consumed] = await tx`
2933
2984
  UPDATE auth_device_authorizations
2934
2985
  SET status = 'consumed', consumed_at = ${input.completedAt}
@@ -2944,7 +2995,7 @@ class PgAuthStorage {
2944
2995
  status: "consumed",
2945
2996
  consumedAt: new Date(input.completedAt)
2946
2997
  },
2947
- accessToken
2998
+ session
2948
2999
  };
2949
3000
  });
2950
3001
  }
@@ -3004,17 +3055,6 @@ class PgAuthStorage {
3004
3055
  error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
3005
3056
  };
3006
3057
  }
3007
- if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && await hasActiveUsers(tx, projectId)) {
3008
- await consumeMagicLink(tx, {
3009
- projectId,
3010
- id: input.magicLinkId,
3011
- tokenHash: input.tokenHash,
3012
- consumedAt: completedAt
3013
- });
3014
- return {
3015
- error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
3016
- };
3017
- }
3018
3058
  await validateCompleteSessionInput(tx, projectId, input.session);
3019
3059
  assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience);
3020
3060
  let newUserId;
@@ -3046,8 +3086,9 @@ class PgAuthStorage {
3046
3086
  projectId,
3047
3087
  user
3048
3088
  });
3049
- const groupMemberships = await this.applyManualGroups(tx, {
3089
+ const groupMemberships = await this.applyGroups(tx, {
3050
3090
  completedAt,
3091
+ source: "manual",
3051
3092
  existing: invitation.groupMemberships,
3052
3093
  groupIds: manualGroupIds,
3053
3094
  projectId,
@@ -3107,36 +3148,18 @@ class PgAuthStorage {
3107
3148
  error: new AuthStorageError11("missing_user", `[Sixb] User '${identity.user_id}' not found for linked OIDC identity.`)
3108
3149
  };
3109
3150
  }
3110
- if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
3111
- await this.consumeOidcAttempt(input, completedAt, projectId, tx);
3112
- return {
3113
- error: new AuthStorageError11("email_link_not_allowed", `[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`)
3114
- };
3115
- }
3116
3151
  if (userRow?.status === "suspended") {
3117
3152
  await this.consumeOidcAttempt(input, completedAt, projectId, tx);
3118
3153
  return {
3119
3154
  error: new AuthStorageError11("suspended_user", `[Sixb] User '${userRow.id}' is suspended for project '${projectId}'.`)
3120
3155
  };
3121
3156
  }
3122
- if (shouldCreateUser && !input.emailVerified) {
3123
- await this.consumeOidcAttempt(input, completedAt, projectId, tx);
3124
- return {
3125
- error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
3126
- };
3127
- }
3128
3157
  if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
3129
3158
  await this.consumeOidcAttempt(input, completedAt, projectId, tx);
3130
3159
  return {
3131
3160
  error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
3132
3161
  };
3133
3162
  }
3134
- if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && await hasActiveUsers(tx, projectId)) {
3135
- await this.consumeOidcAttempt(input, completedAt, projectId, tx);
3136
- return {
3137
- error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
3138
- };
3139
- }
3140
3163
  await validateCompleteSessionInput(tx, projectId, input.session);
3141
3164
  assertSignInSessionAudience(projectId, input.session.audience, attempt.audience);
3142
3165
  let newUserId;
@@ -3179,10 +3202,17 @@ class PgAuthStorage {
3179
3202
  projectId,
3180
3203
  user
3181
3204
  });
3182
- const groupMemberships = await this.applyManualGroups(tx, {
3205
+ const groupMemberships = await this.syncDirectoryGroups(tx, {
3183
3206
  completedAt,
3184
- existing: invitation.groupMemberships,
3185
- groupIds: manualGroupIds,
3207
+ existing: await this.applyGroups(tx, {
3208
+ completedAt,
3209
+ source: "manual",
3210
+ existing: invitation.groupMemberships,
3211
+ groupIds: manualGroupIds,
3212
+ projectId,
3213
+ userId: user.id
3214
+ }),
3215
+ groupIds: input.directoryGroupIds,
3186
3216
  projectId,
3187
3217
  userId: user.id
3188
3218
  });
@@ -3311,19 +3341,33 @@ class PgAuthStorage {
3311
3341
  }
3312
3342
  return { invitation, groupMemberships };
3313
3343
  }
3314
- async applyManualGroups(sql, input) {
3344
+ async applyGroups(sql, input) {
3315
3345
  const groupMemberships = [...input.existing];
3316
3346
  for (const groupId of input.groupIds) {
3317
3347
  groupMemberships.push(await upsertGroupMembership(sql, {
3318
3348
  projectId: input.projectId,
3319
3349
  userId: input.userId,
3320
3350
  groupId,
3321
- source: "manual",
3351
+ source: input.source,
3322
3352
  createdAt: input.completedAt
3323
3353
  }));
3324
3354
  }
3325
3355
  return groupMemberships;
3326
3356
  }
3357
+ async syncDirectoryGroups(sql, input) {
3358
+ if (!input.groupIds) {
3359
+ return input.existing;
3360
+ }
3361
+ const groupIds = normalizeGroupIds(input.groupIds);
3362
+ await sql`
3363
+ DELETE FROM auth_group_memberships
3364
+ WHERE project_id = ${input.projectId}
3365
+ AND user_id = ${input.userId}
3366
+ AND source = 'directory'
3367
+ ${groupIds.length > 0 ? sql`AND group_id NOT IN ${sql(groupIds)}` : sql``}
3368
+ `;
3369
+ return this.applyGroups(sql, { ...input, groupIds, source: "directory" });
3370
+ }
3327
3371
  async createSignInSession(sql, input) {
3328
3372
  await lockAdvisoryKeys(sql, [
3329
3373
  authLockKey("sessions", input.projectId, input.userId),
@@ -3380,16 +3424,6 @@ class PgAuthStorage {
3380
3424
  return rowToIdentityRecord(row);
3381
3425
  }
3382
3426
  }
3383
- async function hasActiveUsers(sql, projectId) {
3384
- const rows = await sql`
3385
- SELECT 1 AS active
3386
- FROM auth_users
3387
- WHERE project_id = ${projectId}
3388
- AND status = 'active'
3389
- LIMIT 1
3390
- `;
3391
- return rows.length > 0;
3392
- }
3393
3427
  function assertSignInSessionAudience(projectId, sessionAudience, storedAudience) {
3394
3428
  if (sessionAudience === storedAudience) {
3395
3429
  return;
@@ -3406,7 +3440,7 @@ function assertCompletableDeviceAuthorization(authorization, input) {
3406
3440
  if (authorization.status === "denied") {
3407
3441
  throw new AuthStorageError11("device_authorization_denied", "[Sixb] Device authorization was denied.");
3408
3442
  }
3409
- if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.accessToken.projectId !== input.projectId || input.accessToken.subjectType !== "user" || input.accessToken.subjectId !== authorization.approvedUserId || input.accessToken.createdBySessionId !== authorization.approvedSessionId) {
3443
+ if (authorization.status !== "approved" || authorization.expiresAt <= input.completedAt || !authorization.approvedUserId || !authorization.approvedSessionId || input.session.projectId !== input.projectId || input.session.userId !== authorization.approvedUserId) {
3410
3444
  throw new AuthStorageError11("invalid_device_authorization", "[Sixb] Device authorization is invalid.");
3411
3445
  }
3412
3446
  }
@@ -7244,6 +7278,107 @@ var _045_agent_thread_sandbox_state_default = `ALTER TABLE agent_threads ADD COL
7244
7278
  CHECK (sandbox_state IS NULL OR jsonb_typeof(sandbox_state) = 'object');
7245
7279
  `;
7246
7280
 
7281
+ // src/migrations/046-workflow-intervention-principals.sql
7282
+ var _046_workflow_intervention_principals_default = `-- Intervention actors were stored as { principalType, principalId }. Rewrite them to the
7283
+ -- canonical Principal shape { type, id } used by every other stored principal.
7284
+ UPDATE workflow_interventions
7285
+ SET submitted_by = jsonb_build_object(
7286
+ 'type', submitted_by->>'principalType',
7287
+ 'id', submitted_by->>'principalId'
7288
+ )
7289
+ WHERE submitted_by->>'principalType' IS NOT NULL;
7290
+
7291
+ UPDATE workflow_interventions
7292
+ SET cancelled_by = jsonb_build_object(
7293
+ 'type', cancelled_by->>'principalType',
7294
+ 'id', cancelled_by->>'principalId'
7295
+ )
7296
+ WHERE cancelled_by->>'principalType' IS NOT NULL;
7297
+ `;
7298
+
7299
+ // src/migrations/047-ontology-commit-attribution.sql
7300
+ var _047_ontology_commit_attribution_default = `-- Commits and their events name who requested the write and what ran it, replacing the
7301
+ -- authority-only \`actor\`. Both are copied from the commit's execution, which stays canonical and
7302
+ -- is immutable, so the backfill is exact.
7303
+ ALTER TABLE ontology_commits
7304
+ ADD COLUMN requested_by JSONB,
7305
+ ADD COLUMN executor JSONB;
7306
+
7307
+ UPDATE ontology_commits c SET
7308
+ requested_by = CASE
7309
+ WHEN e.requested_by_user_id IS NOT NULL
7310
+ THEN jsonb_build_object('type', 'user', 'id', e.requested_by_user_id)
7311
+ WHEN e.requested_by_service_account_id IS NOT NULL
7312
+ THEN jsonb_build_object('type', 'serviceAccount', 'id', e.requested_by_service_account_id)
7313
+ END,
7314
+ executor = CASE e.executor_kind
7315
+ WHEN 'request' THEN jsonb_build_object('type', 'request', 'requestId', e.executor_id)
7316
+ WHEN 'agent' THEN jsonb_build_object('type', 'agent', 'runId', e.executor_id)
7317
+ WHEN 'kernel' THEN jsonb_build_object(
7318
+ 'type', 'kernel',
7319
+ 'operation',
7320
+ CASE e.authority_kernel_operation
7321
+ WHEN 'ontology.recover'
7322
+ THEN jsonb_build_object('type', 'ontology.recover', 'recoveryId', e.executor_id)
7323
+ ELSE jsonb_build_object('type', 'ontology.indexVectors', 'indexingId', e.executor_id)
7324
+ END
7325
+ )
7326
+ ELSE jsonb_build_object(
7327
+ 'type', 'primitive',
7328
+ 'kind', e.executor_kind,
7329
+ 'id', e.authority_primitive_id,
7330
+ 'runId', e.executor_id
7331
+ )
7332
+ END
7333
+ FROM executions e
7334
+ WHERE e.project_id = c.project_id AND e.id = c.execution_id;
7335
+
7336
+ ALTER TABLE ontology_commits ALTER COLUMN executor SET NOT NULL;
7337
+
7338
+ -- Events not yet published would otherwise reach consumers in the retired shape.
7339
+ UPDATE ontology_outbox o SET envelope =
7340
+ (o.envelope - 'actor')
7341
+ || jsonb_build_object('executor', c.executor)
7342
+ || CASE
7343
+ WHEN c.requested_by IS NULL THEN '{}'::jsonb
7344
+ ELSE jsonb_build_object('requestedBy', c.requested_by)
7345
+ END
7346
+ FROM ontology_commits c
7347
+ WHERE c.project_id = o.project_id AND c.id = o.commit_id;
7348
+
7349
+ ALTER TABLE ontology_commits DROP COLUMN actor;
7350
+ `;
7351
+
7352
+ // src/migrations/049-native-sessions.sql
7353
+ var _049_native_sessions_default = `-- Native clients, such as the CLI, hold their session as bearer tokens: a
7354
+ -- short-lived access token, whose hash is \`token_hash\`, and a rotating refresh token. Browser
7355
+ -- sessions leave these columns NULL; \`refresh_token_hash IS NULL\` is what marks a cookie session.
7356
+ ALTER TABLE auth_sessions
7357
+ ADD COLUMN client_name TEXT,
7358
+ ADD COLUMN access_expires_at TIMESTAMPTZ,
7359
+ ADD COLUMN refresh_token_hash TEXT,
7360
+ ADD COLUMN previous_refresh_token_hash TEXT,
7361
+ ADD COLUMN refreshed_at TIMESTAMPTZ,
7362
+ ADD CONSTRAINT auth_sessions_bearer_state CHECK (
7363
+ (refresh_token_hash IS NULL) = (client_name IS NULL)
7364
+ AND (refresh_token_hash IS NULL) = (access_expires_at IS NULL)
7365
+ AND (refresh_token_hash IS NOT NULL OR previous_refresh_token_hash IS NULL)
7366
+ );
7367
+
7368
+ -- The device flow now starts a session instead of issuing a personal access token.
7369
+ ALTER TABLE auth_device_authorizations
7370
+ DROP COLUMN token_name,
7371
+ DROP COLUMN token_expires_at;
7372
+ `;
7373
+
7374
+ // src/migrations/050-directory-group-memberships.sql
7375
+ var _050_directory_group_memberships_default = `-- A membership the identity provider owns is recorded with source 'directory' and synced to the
7376
+ -- provider's groups on every sign-in. Service accounts never sign in, so their table is unchanged.
7377
+ ALTER TABLE auth_group_memberships DROP CONSTRAINT auth_group_memberships_source_check;
7378
+ ALTER TABLE auth_group_memberships ADD CONSTRAINT auth_group_memberships_source_check
7379
+ CHECK (source IN ('invitation', 'manual', 'agent', 'directory'));
7380
+ `;
7381
+
7247
7382
  // src/migrations/037-execution-requester-groups.sql
7248
7383
  var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids JSONB NOT NULL DEFAULT '[]'::jsonb;
7249
7384
 
@@ -7282,6 +7417,43 @@ ALTER TABLE connector_authorization_attempts
7282
7417
  ALTER COLUMN code_verifier DROP NOT NULL;
7283
7418
  `;
7284
7419
 
7420
+ // src/migrations/048-file-upload-sessions.sql
7421
+ var _048_file_upload_sessions_default = `-- Durable staged-upload sessions, so an upload survives restarts and spans API replicas.
7422
+ CREATE TABLE file_upload_sessions (
7423
+ id TEXT PRIMARY KEY CHECK (length(trim(id)) > 0),
7424
+ project_id TEXT NOT NULL CHECK (length(trim(project_id)) > 0),
7425
+ owner_principal_type TEXT NOT NULL
7426
+ CHECK (owner_principal_type IN ('user', 'serviceAccount', 'system')),
7427
+ owner_principal_id TEXT NOT NULL CHECK (length(owner_principal_id) > 0),
7428
+ strategy TEXT NOT NULL CHECK (strategy IN ('server', 'direct-put', 'multipart')),
7429
+ status TEXT NOT NULL CHECK (status IN ('pending', 'completed', 'aborted')),
7430
+ file_name TEXT,
7431
+ media_type TEXT,
7432
+ logical_path TEXT,
7433
+ expected_size_bytes BIGINT CHECK (expected_size_bytes IS NULL OR expected_size_bytes >= 0),
7434
+ expected_digest TEXT,
7435
+ provider_upload JSONB CHECK (provider_upload IS NULL OR jsonb_typeof(provider_upload) = 'object'),
7436
+ signed_parts JSONB NOT NULL CHECK (jsonb_typeof(signed_parts) = 'array'),
7437
+ file_ref JSONB CHECK (file_ref IS NULL OR jsonb_typeof(file_ref) = 'object'),
7438
+ created_at TIMESTAMPTZ NOT NULL,
7439
+ expires_at TIMESTAMPTZ NOT NULL,
7440
+ completed_at TIMESTAMPTZ,
7441
+ aborted_at TIMESTAMPTZ,
7442
+ -- Derived by core (fileUploadSessionReapAt); NULL while an abandoned upload awaits its abort.
7443
+ reap_at TIMESTAMPTZ,
7444
+ CHECK ((status = 'completed') = (completed_at IS NOT NULL)),
7445
+ CHECK ((status = 'aborted') = (aborted_at IS NOT NULL))
7446
+ );
7447
+
7448
+ CREATE INDEX idx_file_upload_sessions_reap
7449
+ ON file_upload_sessions (reap_at)
7450
+ WHERE reap_at IS NOT NULL;
7451
+
7452
+ CREATE INDEX idx_file_upload_sessions_abandoned
7453
+ ON file_upload_sessions (project_id, expires_at)
7454
+ WHERE status = 'pending' AND provider_upload IS NOT NULL;
7455
+ `;
7456
+
7285
7457
  // src/migrations.ts
7286
7458
  var POSTGRES_STORAGE_ADAPTER_ID = "SixbPostgresStorage";
7287
7459
  function pgSql(id, sqlText) {
@@ -7501,7 +7673,12 @@ var postgresStorageMigrations = defineMigrations({
7501
7673
  pgSql("042-vector-profiles", _042_vector_profiles_default),
7502
7674
  pgSql("043-vector-indexing", _043_vector_indexing_default),
7503
7675
  pgSql("044-vector-batching", _044_vector_batching_default),
7504
- pgSql("045-agent-thread-sandbox-state", _045_agent_thread_sandbox_state_default)
7676
+ pgSql("045-agent-thread-sandbox-state", _045_agent_thread_sandbox_state_default),
7677
+ pgSql("046-workflow-intervention-principals", _046_workflow_intervention_principals_default),
7678
+ pgSql("047-ontology-commit-attribution", _047_ontology_commit_attribution_default),
7679
+ pgSql("048-file-upload-sessions", _048_file_upload_sessions_default),
7680
+ pgSql("049-native-sessions", _049_native_sessions_default),
7681
+ pgSql("050-directory-group-memberships", _050_directory_group_memberships_default)
7505
7682
  ]
7506
7683
  });
7507
7684
 
@@ -9625,7 +9802,8 @@ var PG_OBJECT_QUERY_CAPABILITIES = {
9625
9802
  double: { equality: true, ordering: true },
9626
9803
  decimal: { equality: true, ordering: true },
9627
9804
  date: { equality: true, ordering: true },
9628
- timestamp: { equality: true, ordering: true }
9805
+ timestamp: { equality: true, ordering: true },
9806
+ userRef: { equality: true }
9629
9807
  },
9630
9808
  limits: {
9631
9809
  totalCount: true,
@@ -9828,9 +10006,10 @@ function commitRecord(row) {
9828
10006
  requestHash: row.request_hash,
9829
10007
  executionId: row.execution_id,
9830
10008
  origin: structuredClone(row.origin),
9831
- ...row.actor === null ? {} : {
9832
- actor: structuredClone(row.actor)
10009
+ ...row.requested_by === null ? {} : {
10010
+ requestedBy: structuredClone(row.requested_by)
9833
10011
  },
10012
+ executor: structuredClone(row.executor),
9834
10013
  ontologyRevision: row.ontology_revision,
9835
10014
  ...row.projection_revision === null ? {} : { projectionRevision: row.projection_revision },
9836
10015
  ...row.ownership_hash === null ? {} : { ownershipHash: row.ownership_hash },
@@ -9979,6 +10158,9 @@ import {
9979
10158
  telemetryPointSortKey as telemetryPointSortKey2
9980
10159
  } from "@sixb/core/internal/materialization";
9981
10160
  import {
10161
+ assertExpectedLinkRevision,
10162
+ assertExpectedLinkScopeRevision,
10163
+ assertExpectedObjectRevision,
9982
10164
  assertMaterializationFinalizationCorrelation,
9983
10165
  assertMaterializationHeader,
9984
10166
  assertMaterializationLaneCompletion,
@@ -11915,17 +12097,15 @@ class PgOntologyMaterializationStorage {
11915
12097
  await this.assertSources(input.expected.sources, input.commit.projectId);
11916
12098
  const objectRevisions = await reader.effectiveObjectRevisions(input.expected.objects.map((expected) => expected.ref), true);
11917
12099
  for (const expected of input.expected.objects) {
11918
- this.assertObject(objectRevisions.get(objectRefKey3(expected.ref)) ?? null, expected);
12100
+ assertExpectedObjectRevision(objectRevisions.get(objectRefKey3(expected.ref)) ?? null, expected);
11919
12101
  }
11920
12102
  const linkRevisions = await reader.effectiveLinkLastCommits(input.expected.links.map((expected) => expected.ref), true);
11921
12103
  for (const expected of input.expected.links) {
11922
- this.assertLink(linkRevisions.get(linkRefKey3(expected.ref)), expected);
12104
+ assertExpectedLinkRevision(linkRevisions.get(linkRefKey3(expected.ref)), expected);
11923
12105
  }
11924
12106
  const linkScopeRevisions = await reader.linkScopeRevisions(input.expected.linkScopes);
11925
12107
  for (const [index, expected] of input.expected.linkScopes.entries()) {
11926
- if (linkScopeRevisions[index]?.fingerprint !== expected.fingerprint) {
11927
- throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
11928
- }
12108
+ assertExpectedLinkScopeRevision(linkScopeRevisions[index]?.fingerprint, expected);
11929
12109
  }
11930
12110
  const points = await reader.exactPoints(input.expected.points, true);
11931
12111
  const pointRevisions = new Map(points.map((point) => [telemetryPointKey3(point.series, point.at), point.lastCommitId]));
@@ -12185,28 +12365,6 @@ class PgOntologyMaterializationStorage {
12185
12365
  throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
12186
12366
  }
12187
12367
  }
12188
- assertObject(row, expected) {
12189
- if (!expected.exists) {
12190
- if (row) {
12191
- throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
12192
- }
12193
- return;
12194
- }
12195
- if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
12196
- throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
12197
- }
12198
- }
12199
- assertLink(lastCommitId, expected) {
12200
- if (!expected.exists) {
12201
- if (lastCommitId !== undefined) {
12202
- throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
12203
- }
12204
- return;
12205
- }
12206
- if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
12207
- throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
12208
- }
12209
- }
12210
12368
  async assertFinalization(session, input) {
12211
12369
  const { commit } = session.header;
12212
12370
  const { result } = input.finalization;
@@ -12442,18 +12600,19 @@ class PgOntologyMaterializationStorage {
12442
12600
  async insertCommit(header, input) {
12443
12601
  const { commit } = header;
12444
12602
  const origin = originColumns(commit.origin);
12445
- const actor = commit.actor === undefined ? null : jsonParameter2(this.sql, commit.actor);
12603
+ const requestedBy = commit.requestedBy === undefined ? null : jsonParameter2(this.sql, commit.requestedBy);
12446
12604
  const rows = await this.sql`
12447
12605
  INSERT INTO ontology_commits (
12448
12606
  project_id, id, idempotency_key, request_hash, execution_id,
12449
- origin_kind, origin_run_id, origin_batch_ordinal, origin, actor,
12607
+ origin_kind, origin_run_id, origin_batch_ordinal, origin, requested_by, executor,
12450
12608
  ontology_revision, projection_revision, ownership_hash,
12451
12609
  intent, result, committed_at
12452
12610
  ) VALUES (
12453
12611
  ${commit.projectId}, ${commit.id}, ${commit.idempotencyKey}, ${commit.requestHash},
12454
12612
  ${commit.executionId},
12455
12613
  ${origin.kind}, ${origin.runId}, ${origin.batchOrdinal},
12456
- ${jsonParameter2(this.sql, commit.origin)}, ${actor}, ${commit.ontologyRevision},
12614
+ ${jsonParameter2(this.sql, commit.origin)}, ${requestedBy},
12615
+ ${jsonParameter2(this.sql, commit.executor)}, ${commit.ontologyRevision},
12457
12616
  ${commit.projectionRevision ?? null}, ${commit.ownershipHash ?? null},
12458
12617
  ${jsonParameter2(this.sql, commit.intent)},
12459
12618
  ${jsonParameter2(this.sql, input.finalization.result)}, ${commit.committedAt}
@@ -15547,6 +15706,132 @@ function toStorageRow(row) {
15547
15706
  };
15548
15707
  }
15549
15708
 
15709
+ // src/pg-file-upload-session-storage.ts
15710
+ import {
15711
+ DurableFileUploadSessions,
15712
+ fileUploadSessionReapAt,
15713
+ parseFileUploadSessionRow
15714
+ } from "@sixb/core/internal/file-upload-session-storage-provider";
15715
+ class PgFileUploadSessionStorage extends DurableFileUploadSessions {
15716
+ constructor(sql) {
15717
+ super(new PgFileUploadSessionBackend(sql));
15718
+ }
15719
+ }
15720
+
15721
+ class PgFileUploadSessionBackend {
15722
+ sql;
15723
+ constructor(sql) {
15724
+ this.sql = sql;
15725
+ }
15726
+ read(run) {
15727
+ return run(new PgFileUploadSessionPersistence(this.sql, false));
15728
+ }
15729
+ transaction(run) {
15730
+ return runPgTransaction(this.sql, (tx) => run(new PgFileUploadSessionPersistence(tx, true)));
15731
+ }
15732
+ }
15733
+
15734
+ class PgFileUploadSessionPersistence {
15735
+ sql;
15736
+ locking;
15737
+ constructor(sql, locking) {
15738
+ this.sql = sql;
15739
+ this.locking = locking;
15740
+ }
15741
+ async now() {
15742
+ const [row] = await this.sql`SELECT clock_timestamp() AS now`;
15743
+ if (!row)
15744
+ throw new Error("[SixbPg] Database clock returned no row.");
15745
+ return new Date(row.now);
15746
+ }
15747
+ async insert(session) {
15748
+ const rows = await this.sql`
15749
+ INSERT INTO file_upload_sessions (
15750
+ id, project_id, owner_principal_type, owner_principal_id, strategy, status, file_name,
15751
+ media_type, logical_path, expected_size_bytes, expected_digest, provider_upload,
15752
+ signed_parts, file_ref, created_at, expires_at, completed_at, aborted_at, reap_at
15753
+ ) VALUES (
15754
+ ${session.id}, ${session.projectId}, ${session.principal.type}, ${session.principal.id},
15755
+ ${session.strategy}, ${session.status}, ${session.fileName ?? null}, ${session.mediaType ?? null},
15756
+ ${session.logicalPath ?? null}, ${session.expectedSizeBytes ?? null},
15757
+ ${session.expectedDigest ?? null}, ${jsonText(session.providerUpload)}::text::jsonb,
15758
+ ${JSON.stringify(session.signedParts)}::text::jsonb, ${jsonText(session.fileRef)}::text::jsonb,
15759
+ ${session.createdAt}, ${session.expiresAt}, ${session.completedAt ?? null},
15760
+ ${session.abortedAt ?? null}, ${fileUploadSessionReapAt(session)}
15761
+ )
15762
+ ON CONFLICT (id) DO NOTHING
15763
+ RETURNING id
15764
+ `;
15765
+ return rows.length === 1;
15766
+ }
15767
+ async get(uploadId) {
15768
+ const [row] = this.locking ? await this.sql`
15769
+ SELECT * FROM file_upload_sessions WHERE id = ${uploadId} FOR UPDATE
15770
+ ` : await this.sql`
15771
+ SELECT * FROM file_upload_sessions WHERE id = ${uploadId}
15772
+ `;
15773
+ return row ? sessionFromRow(row) : null;
15774
+ }
15775
+ async update(session) {
15776
+ await this.sql`
15777
+ UPDATE file_upload_sessions
15778
+ SET status = ${session.status},
15779
+ provider_upload = ${jsonText(session.providerUpload)}::text::jsonb,
15780
+ signed_parts = ${JSON.stringify(session.signedParts)}::text::jsonb,
15781
+ file_ref = ${jsonText(session.fileRef)}::text::jsonb,
15782
+ created_at = ${session.createdAt},
15783
+ expires_at = ${session.expiresAt},
15784
+ completed_at = ${session.completedAt ?? null},
15785
+ aborted_at = ${session.abortedAt ?? null},
15786
+ reap_at = ${fileUploadSessionReapAt(session)}
15787
+ WHERE id = ${session.id}
15788
+ `;
15789
+ }
15790
+ async listAbandoned(input) {
15791
+ const rows = await this.sql`
15792
+ SELECT * FROM file_upload_sessions
15793
+ WHERE project_id = ${input.projectId} AND status = 'pending'
15794
+ AND provider_upload IS NOT NULL AND expires_at <= ${input.now}
15795
+ ORDER BY expires_at, id COLLATE "C"
15796
+ LIMIT ${input.limit}
15797
+ `;
15798
+ return rows.map(sessionFromRow);
15799
+ }
15800
+ async deleteReapable(now) {
15801
+ const rows = await this.sql`
15802
+ DELETE FROM file_upload_sessions
15803
+ WHERE reap_at IS NOT NULL AND reap_at <= ${now}
15804
+ RETURNING id
15805
+ `;
15806
+ return rows.length;
15807
+ }
15808
+ }
15809
+ function sessionFromRow(row) {
15810
+ return parseFileUploadSessionRow({
15811
+ id: row.id,
15812
+ projectId: row.project_id,
15813
+ ownerPrincipalType: row.owner_principal_type,
15814
+ ownerPrincipalId: row.owner_principal_id,
15815
+ strategy: row.strategy,
15816
+ status: row.status,
15817
+ fileName: row.file_name,
15818
+ mediaType: row.media_type,
15819
+ logicalPath: row.logical_path,
15820
+ expectedSizeBytes: row.expected_size_bytes,
15821
+ expectedDigest: row.expected_digest,
15822
+ providerUpload: row.provider_upload,
15823
+ signedParts: row.signed_parts,
15824
+ fileRef: row.file_ref,
15825
+ createdAt: row.created_at,
15826
+ expiresAt: row.expires_at,
15827
+ completedAt: row.completed_at,
15828
+ abortedAt: row.aborted_at
15829
+ });
15830
+ }
15831
+ function jsonText(value) {
15832
+ return value === undefined ? null : JSON.stringify(value);
15833
+ }
15834
+
15550
15835
  // src/pg-pipeline-run-storage.ts
15551
15836
  import { parseSixbFailure as parseSixbFailure4, serializeSixbFailure as serializeSixbFailure3 } from "@sixb/core/internal/errors";
15552
15837
  import { assertPipelineRunExecution } from "@sixb/core/internal/pipeline-run-storage-provider";
@@ -17218,7 +17503,7 @@ class PgWorkflowInterventionStorage {
17218
17503
  SET
17219
17504
  status = ${"submitted"},
17220
17505
  submitted_at = ${input.submittedAt ?? new Date},
17221
- submitted_by = ${serializeActor(input.submittedBy)}::text::jsonb,
17506
+ submitted_by = ${serializePrincipal(input.submittedBy)}::text::jsonb,
17222
17507
  response = ${serializeRecord(input.response)}::text::jsonb
17223
17508
  WHERE project_id = ${input.projectId} AND id = ${input.id}
17224
17509
  RETURNING *
@@ -17234,7 +17519,7 @@ class PgWorkflowInterventionStorage {
17234
17519
  SET
17235
17520
  status = ${"cancelled"},
17236
17521
  cancelled_at = ${input.cancelledAt ?? new Date},
17237
- cancelled_by = ${serializeActor(input.cancelledBy)}::text::jsonb
17522
+ cancelled_by = ${serializePrincipal(input.cancelledBy)}::text::jsonb
17238
17523
  WHERE project_id = ${input.projectId} AND id = ${input.id}
17239
17524
  RETURNING *
17240
17525
  `;
@@ -17361,10 +17646,10 @@ function serializeRecord(value) {
17361
17646
  function parseRecord(value) {
17362
17647
  return typeof value === "string" ? JSON.parse(value) : value;
17363
17648
  }
17364
- function serializeActor(actor) {
17365
- return actor ? JSON.stringify(actor) : null;
17649
+ function serializePrincipal(principal) {
17650
+ return principal ? JSON.stringify(principal) : null;
17366
17651
  }
17367
- function parseActor(value) {
17652
+ function parsePrincipal(value) {
17368
17653
  return typeof value === "string" ? JSON.parse(value) : value ?? undefined;
17369
17654
  }
17370
17655
  function rowToWorkflowInterventionRecord(row) {
@@ -17384,10 +17669,10 @@ function rowToWorkflowInterventionRecord(row) {
17384
17669
  requestedAt: new Date(row.requested_at),
17385
17670
  expiresAt: row.expires_at ? new Date(row.expires_at) : undefined,
17386
17671
  submittedAt: row.submitted_at ? new Date(row.submitted_at) : undefined,
17387
- submittedBy: parseActor(row.submitted_by),
17672
+ submittedBy: parsePrincipal(row.submitted_by),
17388
17673
  response: row.response ? parseRecord(row.response) : undefined,
17389
17674
  cancelledAt: row.cancelled_at ? new Date(row.cancelled_at) : undefined,
17390
- cancelledBy: parseActor(row.cancelled_by),
17675
+ cancelledBy: parsePrincipal(row.cancelled_by),
17391
17676
  expiredAt: row.expired_at ? new Date(row.expired_at) : undefined
17392
17677
  };
17393
17678
  }
@@ -18455,6 +18740,7 @@ class PostgresStorage {
18455
18740
  connectorConnections;
18456
18741
  shareGrants;
18457
18742
  shareSessions;
18743
+ fileUploadSessions;
18458
18744
  migrators;
18459
18745
  sql;
18460
18746
  schemaName;
@@ -18508,6 +18794,7 @@ class PostgresStorage {
18508
18794
  this.connectorConnections = createOperationScopedFacade(stores.connectorConnections, scope);
18509
18795
  this.shareGrants = createOperationScopedFacade(stores.shareGrants, scope);
18510
18796
  this.shareSessions = createOperationScopedFacade(stores.shareSessions, scope);
18797
+ this.fileUploadSessions = createOperationScopedFacade(stores.fileUploadSessions, scope);
18511
18798
  registerPostgresStorageTestingAdapter(this, (durationMs) => stores.connectorConnections.advanceTimeForTesting(durationMs));
18512
18799
  }
18513
18800
  async ping() {
@@ -18602,7 +18889,8 @@ function createPostgresStores(sql, options) {
18602
18889
  rules: new PgRulesStorage(sql),
18603
18890
  connectorConnections: new PgConnectorConnectionStorage(sql),
18604
18891
  shareGrants,
18605
- shareSessions
18892
+ shareSessions,
18893
+ fileUploadSessions: new PgFileUploadSessionStorage(sql)
18606
18894
  };
18607
18895
  }
18608
18896
  function resolveTimeoutMillis(value, label) {
@@ -18619,4 +18907,4 @@ export {
18619
18907
  migratePostgresStorage
18620
18908
  };
18621
18909
 
18622
- //# debugId=C35F49117A3070E564756E2164756E21
18910
+ //# debugId=5427C0EF9727207C64756E2164756E21