@sixb/pg 0.1.18 → 0.1.20

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.
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAqH3B,OAAO,KAAK,EAAe,GAAG,EAAa,MAAM,aAAa,CAAA;AAG9D,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAE1F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/D,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,wBAAwB,CAAC,CAEzC;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,GACjB,SAAS,eAAe,EAAE,CAQ5B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,wBAAwB,CAAC,CAAA;CAC5D,GAAG,eAAe,CAqBlB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKrD;AAuKD,eAAO,MAAM,yBAAyB,wCAgEpC,CAAA"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAGV,YAAY,EACZ,aAAa,EACb,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAqH3B,OAAO,KAAK,EAAe,GAAG,EAAa,MAAM,aAAa,CAAA;AAG9D,MAAM,WAAW,wBAAwB;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrC;AAED,eAAO,MAAM,2BAA2B,wBAAwB,CAAA;AAEhE,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAAC,wBAAwB,CAAC,CAE1F;AAED,wBAAgB,MAAM,CACpB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAC/D,OAAO,GAAE,oBAAyB,GACjC,aAAa,CAAC,wBAAwB,CAAC,CAEzC;AAED,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,GAAG,EACR,UAAU,EAAE,MAAM,GACjB,SAAS,eAAe,EAAE,CAQ5B;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;IACjB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC,wBAAwB,CAAC,CAAA;CAC5D,GAAG,eAAe,CAqBlB;AAED,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMxF;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE5E;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAKrD;AAuKD,eAAO,MAAM,yBAAyB,wCAkEpC,CAAA"}
@@ -1,22 +1,15 @@
1
- import type { ActionRunRecord, ActionRunStorage, EnterActionRunPhaseInput, ExecutionStorage, FinishActionRunInput, ListActionRunsInput, ListActionRunsResult, LockActionMaterializationRunInput, QueueActionRunInput, RecordActionEffectsInput, RecordActionWritebackInput, StartActionRunInput } from "@sixb/core/storage";
1
+ import type { ActionRunRecord, ActionRunStorage, ExecutionStorage, ListActionRunsInput, ListActionRunsResult, RecordActionEffectsInput, RecordActionRunInput } from "@sixb/core/storage";
2
2
  import { type PgStoreClient } from "./transactions";
3
3
  export declare class PgActionRunStorage implements ActionRunStorage {
4
4
  private readonly sql;
5
5
  private readonly executions;
6
6
  constructor(sql: PgStoreClient, executions: ExecutionStorage);
7
- lockForMaterialization(input: LockActionMaterializationRunInput): Promise<ActionRunRecord>;
8
- queue(input: QueueActionRunInput): Promise<ActionRunRecord>;
9
- private requeueAfterEnqueueFailure;
10
- start(input: StartActionRunInput): Promise<ActionRunRecord>;
11
- enterPhase(input: EnterActionRunPhaseInput): Promise<ActionRunRecord>;
12
- recordWriteback(input: RecordActionWritebackInput): Promise<ActionRunRecord>;
7
+ record(input: RecordActionRunInput): Promise<ActionRunRecord>;
13
8
  recordEffects(input: RecordActionEffectsInput): Promise<ActionRunRecord>;
14
- finish(input: FinishActionRunInput): Promise<ActionRunRecord>;
15
9
  getById(params: {
16
10
  projectId: string;
17
11
  id: string;
18
12
  }): Promise<ActionRunRecord | null>;
19
13
  list(input: ListActionRunsInput): Promise<ListActionRunsResult>;
20
- private requireRunningRun;
21
14
  }
22
15
  //# sourceMappingURL=pg-action-run-storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pg-action-run-storage.d.ts","sourceRoot":"","sources":["../src/pg-action-run-storage.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAKV,eAAe,EACf,gBAAgB,EAEhB,wBAAwB,EACxB,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,iCAAiC,EACjC,mBAAmB,EACnB,wBAAwB,EACxB,0BAA0B,EAC1B,mBAAmB,EACpB,MAAM,oBAAoB,CAAA;AAI3B,OAAO,EAAE,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAA;AAErE,qBAAa,kBAAmB,YAAW,gBAAgB;IAEvD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,gBAAgB;IAGzC,sBAAsB,CAAC,KAAK,EAAE,iCAAiC,GAAG,OAAO,CAAC,eAAe,CAAC;IAyB1F,KAAK,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;YAuEnD,0BAA0B;IAyClC,KAAK,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,eAAe,CAAC;IA8B3D,UAAU,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,eAAe,CAAC;IAerE,eAAe,CAAC,KAAK,EAAE,0BAA0B,GAAG,OAAO,CAAC,eAAe,CAAC;IAqC5E,aAAa,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,eAAe,CAAC;IA+BxE,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAqC7D,OAAO,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IASnF,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;YAkHvD,iBAAiB;CAwBhC"}
1
+ {"version":3,"file":"pg-action-run-storage.d.ts","sourceRoot":"","sources":["../src/pg-action-run-storage.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAIV,eAAe,EAEf,gBAAgB,EAEhB,gBAAgB,EAChB,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,oBAAoB,EACrB,MAAM,oBAAoB,CAAA;AAG3B,OAAO,EAAE,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAA;AAErE,qBAAa,kBAAmB,YAAW,gBAAgB;IAEvD,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;gBADV,GAAG,EAAE,aAAa,EAClB,UAAU,EAAE,gBAAgB;IAGzC,MAAM,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAuE7D,aAAa,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,eAAe,CAAC;IA2BxE,OAAO,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IASnF,IAAI,CAAC,KAAK,EAAE,mBAAmB,GAAG,OAAO,CAAC,oBAAoB,CAAC;CAiHtE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sixb/pg",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "PostgreSQL storage provider for Sixb",
5
5
  "keywords": [
6
6
  "sixb",
@@ -40,10 +40,10 @@
40
40
  "postgres": "^3.4.5"
41
41
  },
42
42
  "peerDependencies": {
43
- "@sixb/core": "0.1.18"
43
+ "@sixb/core": "0.1.20"
44
44
  },
45
45
  "devDependencies": {
46
- "@sixb/core": "0.1.18",
46
+ "@sixb/core": "0.1.20",
47
47
  "@types/bun": "latest",
48
48
  "typescript": "^5.7.0"
49
49
  },
@@ -234,7 +234,6 @@ export class PgAuthStorage implements AuthStorage {
234
234
  }
235
235
 
236
236
  await validateCompleteSessionInput(tx, projectId, input.session)
237
- assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience)
238
237
 
239
238
  let newUserId: string | undefined
240
239
  if (shouldCreateUser) {
@@ -394,7 +393,6 @@ export class PgAuthStorage implements AuthStorage {
394
393
  }
395
394
 
396
395
  await validateCompleteSessionInput(tx, projectId, input.session)
397
- assertSignInSessionAudience(projectId, input.session.audience, attempt.audience)
398
396
 
399
397
  let newUserId: string | undefined
400
398
  if (shouldCreateUser) {
@@ -719,17 +717,13 @@ export class PgAuthStorage implements AuthStorage {
719
717
  readonly userId: string
720
718
  }
721
719
  ): Promise<SessionRecord> {
722
- await lockAdvisoryKeys(sql, [
723
- authLockKey("sessions", input.projectId, input.userId),
724
- authLockKey("sessions", input.projectId, input.userId, input.session.audience),
725
- ])
720
+ await lockAdvisoryKeys(sql, [authLockKey("sessions", input.projectId, input.userId)])
726
721
 
727
722
  return createSession(sql, {
728
723
  id: input.session.id,
729
724
  projectId: input.projectId,
730
725
  userId: input.userId,
731
726
  strategyId: input.strategyId,
732
- audience: input.session.audience,
733
727
  tokenHash: input.session.tokenHash,
734
728
  createdAt: input.session.createdAt,
735
729
  expiresAt: input.session.expiresAt,
@@ -795,21 +789,6 @@ export class PgAuthStorage implements AuthStorage {
795
789
  }
796
790
  }
797
791
 
798
- function assertSignInSessionAudience(
799
- projectId: string,
800
- sessionAudience: string,
801
- storedAudience: string
802
- ): void {
803
- if (sessionAudience === storedAudience) {
804
- return
805
- }
806
-
807
- throw new AuthStorageError(
808
- "invalid_input",
809
- `[Sixb] Sign-in session audience '${sessionAudience}' does not match stored auth audience '${storedAudience}' for project '${projectId}'.`
810
- )
811
- }
812
-
813
792
  function assertCompletableDeviceAuthorization(
814
793
  authorization: DeviceAuthorizationRecord,
815
794
  input: CompleteDeviceAuthorizationInput
@@ -1,4 +1,3 @@
1
- import { resolveAuthSessionAudience } from "@sixb/core"
2
1
  import type {
3
2
  AuthMagicLinkStore,
4
3
  CreateAuthMagicLinkInput,
@@ -31,7 +30,6 @@ export class PgAuthMagicLinkStore implements AuthMagicLinkStore {
31
30
  const id = assertNonEmpty(input.id, "Magic link id")
32
31
  const projectId = assertNonEmpty(input.projectId, "Project id")
33
32
  const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
34
- const audience = resolveAuthSessionAudience(input.audience)
35
33
  const email = normalizeEmail(input.email)
36
34
  const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash")
37
35
 
@@ -56,7 +54,6 @@ export class PgAuthMagicLinkStore implements AuthMagicLinkStore {
56
54
  project_id,
57
55
  id,
58
56
  strategy_id,
59
- audience,
60
57
  email,
61
58
  token_hash,
62
59
  return_to,
@@ -66,7 +63,6 @@ export class PgAuthMagicLinkStore implements AuthMagicLinkStore {
66
63
  ${projectId},
67
64
  ${id},
68
65
  ${strategyId},
69
- ${audience},
70
66
  ${email},
71
67
  ${tokenHash},
72
68
  ${input.returnTo ?? null},
@@ -1,4 +1,3 @@
1
- import { resolveAuthSessionAudience } from "@sixb/core"
2
1
  import type {
3
2
  AuthOidcAuthorizationAttemptStore,
4
3
  CreateOidcAuthorizationAttemptInput,
@@ -25,7 +24,6 @@ export class PgAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizatio
25
24
  const id = assertNonEmpty(input.id, "OIDC authorization attempt id")
26
25
  const projectId = assertNonEmpty(input.projectId, "Project id")
27
26
  const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
28
- const audience = resolveAuthSessionAudience(input.audience)
29
27
  const stateHash = assertNonEmpty(input.stateHash, "OIDC state hash")
30
28
  const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash")
31
29
  const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier")
@@ -43,7 +41,6 @@ export class PgAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizatio
43
41
  project_id,
44
42
  id,
45
43
  strategy_id,
46
- audience,
47
44
  state_hash,
48
45
  nonce_hash,
49
46
  code_verifier,
@@ -54,7 +51,6 @@ export class PgAuthOidcAuthorizationAttemptStore implements AuthOidcAuthorizatio
54
51
  ${projectId},
55
52
  ${id},
56
53
  ${strategyId},
57
- ${audience},
58
54
  ${stateHash},
59
55
  ${nonceHash},
60
56
  ${codeVerifier},
@@ -67,7 +67,6 @@ export interface PgAuthSessionRow {
67
67
  readonly id: string
68
68
  readonly user_id: string
69
69
  readonly strategy_id: string
70
- readonly audience: SessionRecord["audience"]
71
70
  readonly token_hash: string
72
71
  readonly created_at: PgDate
73
72
  readonly expires_at: PgDate
@@ -132,7 +131,6 @@ export interface PgAuthMagicLinkRow {
132
131
  readonly project_id: string
133
132
  readonly id: string
134
133
  readonly strategy_id: string
135
- readonly audience: MagicLinkRecord["audience"]
136
134
  readonly email: string
137
135
  readonly token_hash: string
138
136
  readonly return_to: string | null
@@ -146,7 +144,6 @@ export interface PgAuthOidcAttemptRow {
146
144
  readonly project_id: string
147
145
  readonly id: string
148
146
  readonly strategy_id: string
149
- readonly audience: OidcAuthorizationAttemptRecord["audience"]
150
147
  readonly state_hash: string
151
148
  readonly nonce_hash: string
152
149
  readonly code_verifier: string
@@ -229,7 +226,6 @@ export function rowToSessionRecord(row: PgAuthSessionRow): SessionRecord {
229
226
  projectId: row.project_id,
230
227
  userId: row.user_id,
231
228
  strategyId: row.strategy_id,
232
- audience: row.audience,
233
229
  tokenHash: row.token_hash,
234
230
  createdAt: toDate(row.created_at),
235
231
  expiresAt: toDate(row.expires_at),
@@ -306,7 +302,6 @@ export function rowToMagicLinkRecord(row: PgAuthMagicLinkRow): MagicLinkRecord {
306
302
  id: row.id,
307
303
  projectId: row.project_id,
308
304
  strategyId: row.strategy_id,
309
- audience: row.audience,
310
305
  email: row.email,
311
306
  tokenHash: row.token_hash,
312
307
  returnTo: row.return_to ?? undefined,
@@ -324,7 +319,6 @@ export function rowToOidcAuthorizationAttemptRecord(
324
319
  id: row.id,
325
320
  projectId: row.project_id,
326
321
  strategyId: row.strategy_id,
327
- audience: row.audience,
328
322
  stateHash: row.state_hash,
329
323
  nonceHash: row.nonce_hash,
330
324
  codeVerifier: row.code_verifier,
@@ -1,4 +1,3 @@
1
- import type { AuthSessionAudience } from "@sixb/core"
2
1
  import type {
3
2
  AuthSessionStore,
4
3
  CreateAuthSessionInput,
@@ -38,28 +37,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
38
37
  return getSessionById(this.sql, params)
39
38
  }
40
39
 
41
- async getActiveByUserId(params: {
42
- readonly projectId: string
43
- readonly userId: string
44
- readonly audience: AuthSessionAudience
45
- readonly now: Date
46
- }): Promise<SessionRecord | null> {
47
- const [row] = await this.sql<PgAuthSessionRow[]>`
48
- SELECT *
49
- FROM auth_sessions
50
- WHERE project_id = ${params.projectId}
51
- AND user_id = ${params.userId}
52
- AND audience = ${params.audience}
53
- AND revoked_at IS NULL
54
- AND expires_at > ${params.now}
55
- AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.now})
56
- ORDER BY created_at DESC, id DESC
57
- LIMIT 1
58
- `
59
-
60
- return row ? rowToSessionRecord(row) : null
61
- }
62
-
63
40
  async listActiveByUserId(params: {
64
41
  readonly projectId: string
65
42
  readonly userId: string
@@ -82,7 +59,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
82
59
  async findValidByTokenHash(params: {
83
60
  readonly projectId: string
84
61
  readonly id: string
85
- readonly audience: AuthSessionAudience
86
62
  readonly tokenHash: string
87
63
  readonly now: Date
88
64
  }): Promise<SessionRecord | null> {
@@ -91,7 +67,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
91
67
  if (
92
68
  !row ||
93
69
  row.refresh_token_hash !== null ||
94
- row.audience !== params.audience ||
95
70
  row.token_hash !== params.tokenHash ||
96
71
  !isLiveSessionRow(row, params.now)
97
72
  ) {
@@ -169,7 +144,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
169
144
  async renewIfValid(params: {
170
145
  readonly projectId: string
171
146
  readonly id: string
172
- readonly audience: AuthSessionAudience
173
147
  readonly tokenHash: string
174
148
  readonly now: Date
175
149
  readonly expiresAt: Date
@@ -186,7 +160,6 @@ export class PgAuthSessionStore implements AuthSessionStore {
186
160
  last_seen_at = ${params.now}
187
161
  WHERE project_id = ${params.projectId}
188
162
  AND id = ${params.id}
189
- AND audience = ${params.audience}
190
163
  AND token_hash = ${params.tokenHash}
191
164
  AND refresh_token_hash IS NULL
192
165
  AND revoked_at IS NULL
@@ -228,15 +201,10 @@ export class PgAuthSessionStore implements AuthSessionStore {
228
201
  async revokeActiveForUser(params: {
229
202
  readonly projectId: string
230
203
  readonly userId: string
231
- readonly audience?: AuthSessionAudience
232
204
  readonly revokedAt: Date
233
205
  }): Promise<readonly SessionRecord[]> {
234
206
  return runPgTransaction(this.sql, async (tx) => {
235
- const locks = [authLockKey("sessions", params.projectId, params.userId)]
236
- if (params.audience) {
237
- locks.push(authLockKey("sessions", params.projectId, params.userId, params.audience))
238
- }
239
- await lockAdvisoryKeys(tx, locks)
207
+ await lockAdvisoryKeys(tx, [authLockKey("sessions", params.projectId, params.userId)])
240
208
  return revokeActiveSessionsForUser(tx, params)
241
209
  })
242
210
  }
@@ -1,4 +1,4 @@
1
- import type { AuthSessionAudience, Principal } from "@sixb/core"
1
+ import type { Principal } from "@sixb/core"
2
2
  import type {
3
3
  CompleteAuthSessionInput,
4
4
  CreateAuthSessionInput,
@@ -442,7 +442,6 @@ export async function validateCompleteSessionInput(
442
442
  session: CompleteAuthSessionInput
443
443
  ): Promise<void> {
444
444
  const sessionId = assertNonEmpty(session.id, "Session id")
445
- assertNonEmpty(session.audience, "Session audience")
446
445
  assertNonEmpty(session.tokenHash, "Session token hash")
447
446
  await assertSessionIdAvailable(sql, projectId, sessionId)
448
447
  }
@@ -468,7 +467,6 @@ export async function createSession(
468
467
  const projectId = assertNonEmpty(input.projectId, "Project id")
469
468
  const userId = assertNonEmpty(input.userId, "User id")
470
469
  const strategyId = assertNonEmpty(input.strategyId, "Strategy id")
471
- const audience = assertNonEmpty(input.audience, "Session audience")
472
470
  const tokenHash = assertNonEmpty(input.tokenHash, "Session token hash")
473
471
 
474
472
  await requireUserById(sql, { projectId, id: userId })
@@ -481,7 +479,6 @@ export async function createSession(
481
479
  id,
482
480
  user_id,
483
481
  strategy_id,
484
- audience,
485
482
  token_hash,
486
483
  created_at,
487
484
  expires_at,
@@ -496,7 +493,6 @@ export async function createSession(
496
493
  ${id},
497
494
  ${userId},
498
495
  ${strategyId},
499
- ${audience},
500
496
  ${tokenHash},
501
497
  ${input.createdAt},
502
498
  ${input.expiresAt},
@@ -525,13 +521,10 @@ export async function revokeActiveSessionsForUser(
525
521
  params: {
526
522
  readonly projectId: string
527
523
  readonly userId: string
528
- readonly audience?: AuthSessionAudience
529
524
  readonly revokedAt: Date
530
525
  }
531
526
  ): Promise<readonly SessionRecord[]> {
532
- const rows =
533
- params.audience === undefined
534
- ? await sql<PgAuthSessionRow[]>`
527
+ const rows = await sql<PgAuthSessionRow[]>`
535
528
  SELECT *
536
529
  FROM auth_sessions
537
530
  WHERE project_id = ${params.projectId}
@@ -542,41 +535,16 @@ export async function revokeActiveSessionsForUser(
542
535
  ORDER BY created_at ASC, id ASC
543
536
  FOR UPDATE
544
537
  `
545
- : await sql<PgAuthSessionRow[]>`
546
- SELECT *
547
- FROM auth_sessions
548
- WHERE project_id = ${params.projectId}
549
- AND user_id = ${params.userId}
550
- AND audience = ${params.audience}
551
- AND revoked_at IS NULL
552
- AND expires_at > ${params.revokedAt}
553
- AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
554
- ORDER BY created_at ASC, id ASC
555
- FOR UPDATE
556
- `
557
538
 
558
- if (params.audience === undefined) {
559
- await sql`
560
- UPDATE auth_sessions
561
- SET revoked_at = ${params.revokedAt}
562
- WHERE project_id = ${params.projectId}
563
- AND user_id = ${params.userId}
564
- AND revoked_at IS NULL
565
- AND expires_at > ${params.revokedAt}
566
- AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
567
- `
568
- } else {
569
- await sql`
539
+ await sql`
570
540
  UPDATE auth_sessions
571
541
  SET revoked_at = ${params.revokedAt}
572
542
  WHERE project_id = ${params.projectId}
573
543
  AND user_id = ${params.userId}
574
- AND audience = ${params.audience}
575
544
  AND revoked_at IS NULL
576
545
  AND expires_at > ${params.revokedAt}
577
546
  AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
578
547
  `
579
- }
580
548
 
581
549
  return rows.map((row) =>
582
550
  rowToSessionRecord({
@@ -0,0 +1,150 @@
1
+ -- Actions run in the request that asks for them and record their run once, when it ends. No run
2
+ -- is queued, running, or cancelled any more, and every run has started and finished.
3
+
4
+ -- A run left running after its edits committed, as a crash between the commit and the end of the
5
+ -- run left it, succeeded: its edits and their outbox facts are in the commit. A run that stopped
6
+ -- during its effects keeps that phase, and records effects that never ended.
7
+ UPDATE action_runs AS run
8
+ SET
9
+ status = 'succeeded',
10
+ error = NULL,
11
+ finished_at = committed.committed_at,
12
+ phase = CASE WHEN run.phase = 'effects' THEN 'effects' ELSE 'commit' END,
13
+ effects_status = CASE
14
+ WHEN run.phase = 'effects' AND run.effects_status IS NULL THEN 'failed'
15
+ ELSE run.effects_status
16
+ END,
17
+ effects_completed_at = CASE
18
+ WHEN run.phase = 'effects' AND run.effects_status IS NULL THEN now()
19
+ ELSE run.effects_completed_at
20
+ END,
21
+ effects_error = CASE
22
+ WHEN run.phase = 'effects' AND run.effects_status IS NULL THEN jsonb_build_object(
23
+ 'code', 'internal.unexpected',
24
+ 'message', 'Abandoned: Actions now run synchronously; these effects never finished.',
25
+ 'retryable', false,
26
+ 'at', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
27
+ 'details', jsonb_build_object('actionId', run.action_id, 'runId', run.id, 'phase', 'effects')
28
+ )
29
+ ELSE run.effects_error
30
+ END
31
+ FROM ontology_commits AS committed
32
+ WHERE run.status IN ('queued', 'running')
33
+ AND committed.origin_kind = 'action'
34
+ AND committed.project_id = run.project_id
35
+ AND committed.origin_run_id = run.id;
36
+
37
+ -- Any other run left queued or running never finished, and nothing will resume it.
38
+ UPDATE action_runs
39
+ SET
40
+ status = 'failed',
41
+ finished_at = now(),
42
+ error = jsonb_build_object(
43
+ 'code', 'internal.unexpected',
44
+ 'message', 'Abandoned: Actions now run synchronously; this run never finished.',
45
+ 'retryable', false,
46
+ 'at', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
47
+ 'details', jsonb_build_object('actionId', action_id, 'runId', id)
48
+ )
49
+ WHERE status IN ('queued', 'running');
50
+
51
+ -- A cancelled run failed: it keeps the failure it recorded.
52
+ UPDATE action_runs SET status = 'failed' WHERE status = 'cancelled';
53
+
54
+ -- Every failed run carries a failure. One recorded before failures were kept explains itself.
55
+ UPDATE action_runs
56
+ SET error = jsonb_build_object(
57
+ 'code', 'internal.unexpected',
58
+ 'message', 'This run failed before Sixb recorded why.',
59
+ 'retryable', false,
60
+ 'at', to_char(COALESCE(finished_at, now()) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
61
+ 'details', jsonb_build_object('actionId', action_id, 'runId', id)
62
+ )
63
+ WHERE status = 'failed' AND error IS NULL;
64
+
65
+ -- The phases `request`, `enqueue`, and `cancelled` are gone: a run that stopped in one of them
66
+ -- never got past validation. A run starts when it was queued at the latest.
67
+ UPDATE action_runs
68
+ SET
69
+ phase = CASE
70
+ WHEN phase IN ('validation', 'writeback', 'edits', 'commit', 'effects') THEN phase
71
+ ELSE 'validation'
72
+ END,
73
+ started_at = COALESCE(started_at, queued_at),
74
+ finished_at = COALESCE(finished_at, now()),
75
+ writeback_completed_at = CASE
76
+ WHEN writeback_status IS NULL THEN NULL
77
+ ELSE COALESCE(writeback_completed_at, finished_at, now())
78
+ END,
79
+ effects_completed_at = CASE
80
+ WHEN effects_status IS NULL THEN NULL
81
+ ELSE COALESCE(effects_completed_at, finished_at, now())
82
+ END
83
+ WHERE phase IS NULL
84
+ OR phase NOT IN ('validation', 'writeback', 'edits', 'commit', 'effects')
85
+ OR started_at IS NULL
86
+ OR finished_at IS NULL
87
+ OR (writeback_status IS NOT NULL AND writeback_completed_at IS NULL)
88
+ OR (effects_status IS NOT NULL AND effects_completed_at IS NULL);
89
+
90
+ -- A failure names the phase its run ended in, and only Action codes: an enqueue failure, which no
91
+ -- run can meet any more, reads as the unexpected error it now is.
92
+ UPDATE action_runs
93
+ SET error = jsonb_set(
94
+ CASE
95
+ WHEN error->>'code' = 'queue.enqueue_failed'
96
+ THEN error || '{"code": "internal.unexpected", "retryable": false}'::jsonb
97
+ ELSE error
98
+ END,
99
+ '{details,phase}',
100
+ to_jsonb(phase)
101
+ )
102
+ WHERE error IS NOT NULL
103
+ AND (
104
+ error->'details'->>'phase' IS DISTINCT FROM phase
105
+ OR error->>'code' = 'queue.enqueue_failed'
106
+ );
107
+
108
+ -- The status and phase checks were unnamed. Drop exactly the two that admit the removed values.
109
+ DO $$
110
+ DECLARE lifecycle_constraint RECORD;
111
+ BEGIN
112
+ FOR lifecycle_constraint IN
113
+ SELECT conname
114
+ FROM pg_constraint
115
+ WHERE conrelid = 'action_runs'::regclass
116
+ AND contype = 'c'
117
+ AND (
118
+ pg_get_constraintdef(oid) LIKE '%''queued''%'
119
+ OR pg_get_constraintdef(oid) LIKE '%''enqueue''%'
120
+ )
121
+ LOOP
122
+ EXECUTE format('ALTER TABLE action_runs DROP CONSTRAINT %I', lifecycle_constraint.conname);
123
+ END LOOP;
124
+ END $$;
125
+
126
+ -- The listing indexes ordered runs by `COALESCE(started_at, queued_at)`.
127
+ DROP INDEX idx_action_runs_project_started;
128
+ DROP INDEX idx_action_runs_project_action_started;
129
+ DROP INDEX idx_action_runs_project_object_started;
130
+ DROP INDEX idx_action_runs_project_status_started;
131
+
132
+ ALTER TABLE action_runs
133
+ DROP COLUMN queued_at,
134
+ ALTER COLUMN phase SET NOT NULL,
135
+ ALTER COLUMN started_at SET NOT NULL,
136
+ ALTER COLUMN finished_at SET NOT NULL,
137
+ ADD CONSTRAINT action_runs_status_check CHECK (status IN ('succeeded', 'failed')),
138
+ ADD CONSTRAINT action_runs_phase_check CHECK (
139
+ phase IN ('validation', 'writeback', 'edits', 'commit', 'effects')
140
+ ),
141
+ ADD CONSTRAINT action_runs_failure_check CHECK ((status = 'failed') = (error IS NOT NULL));
142
+
143
+ CREATE INDEX idx_action_runs_project_started
144
+ ON action_runs (project_id, started_at DESC);
145
+ CREATE INDEX idx_action_runs_project_action_started
146
+ ON action_runs (project_id, action_id, started_at DESC);
147
+ CREATE INDEX idx_action_runs_project_object_started
148
+ ON action_runs (project_id, object_type_id, primary_id, started_at DESC);
149
+ CREATE INDEX idx_action_runs_project_status_started
150
+ ON action_runs (project_id, status, started_at DESC);
@@ -0,0 +1,19 @@
1
+ -- A project has one browser app, so sessions, magic links and sign-in attempts no longer name the
2
+ -- one they belong to. Atlas's browser sessions end here: their cookie is no longer read. Native
3
+ -- sessions carried Atlas's name only because the column needed a value, so they stay.
4
+ UPDATE auth_sessions
5
+ SET revoked_at = now()
6
+ WHERE audience = 'atlas'
7
+ AND refresh_token_hash IS NULL
8
+ AND revoked_at IS NULL;
9
+
10
+ -- Dropping the column drops the two indexes that name it.
11
+ ALTER TABLE auth_sessions DROP COLUMN audience;
12
+ CREATE INDEX idx_auth_sessions_user_active
13
+ ON auth_sessions (project_id, user_id, expires_at DESC, created_at DESC, id DESC)
14
+ WHERE revoked_at IS NULL;
15
+ CREATE INDEX idx_auth_sessions_user_created
16
+ ON auth_sessions (project_id, user_id, created_at DESC, id DESC);
17
+
18
+ ALTER TABLE auth_magic_links DROP COLUMN audience;
19
+ ALTER TABLE auth_oidc_authorization_attempts DROP COLUMN audience;
package/src/migrations.ts CHANGED
@@ -431,6 +431,8 @@ export const postgresStorageMigrations = defineMigrations<PostgresMigrationConte
431
431
  pgSql("058-reranking-model-kind", rerankingModelKindSql),
432
432
  pgSql("059-file-download-grants", fileDownloadGrantsSql),
433
433
  pgSql("060-drained-source-versions", drainedSourceVersionsSql),
434
+ pgSql("061-terminal-action-runs", terminalActionRunsSql),
435
+ pgSql("062-drop-auth-audience", dropAuthAudienceSql),
434
436
  ],
435
437
  })
436
438
 
@@ -459,3 +461,5 @@ import fileDownloadGrantsSql from "./migrations/059-file-download-grants.sql" wi
459
461
  import drainedSourceVersionsSql from "./migrations/060-drained-source-versions.sql" with {
460
462
  type: "text",
461
463
  }
464
+ import terminalActionRunsSql from "./migrations/061-terminal-action-runs.sql" with { type: "text" }
465
+ import dropAuthAudienceSql from "./migrations/062-drop-auth-audience.sql" with { type: "text" }