@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.
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/magic-links.d.ts.map +1 -1
- package/dist/auth-storage/oidc-attempts.d.ts.map +1 -1
- package/dist/auth-storage/rows.d.ts +0 -3
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/auth-storage/sessions.d.ts +0 -10
- package/dist/auth-storage/sessions.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +1 -2
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/index.js +265 -352
- package/dist/index.js.map +10 -10
- package/dist/migrations.d.ts.map +1 -1
- package/dist/pg-action-run-storage.d.ts +2 -9
- package/dist/pg-action-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/auth-storage/auth-storage.ts +1 -22
- package/src/auth-storage/magic-links.ts +0 -4
- package/src/auth-storage/oidc-attempts.ts +0 -4
- package/src/auth-storage/rows.ts +0 -6
- package/src/auth-storage/sessions.ts +1 -33
- package/src/auth-storage/shared.ts +3 -35
- package/src/migrations/061-terminal-action-runs.sql +150 -0
- package/src/migrations/062-drop-auth-audience.sql +19 -0
- package/src/migrations.ts +4 -0
- package/src/pg-action-run-storage.ts +97 -374
package/dist/index.js
CHANGED
|
@@ -1244,7 +1244,6 @@ function rowToSessionRecord(row) {
|
|
|
1244
1244
|
projectId: row.project_id,
|
|
1245
1245
|
userId: row.user_id,
|
|
1246
1246
|
strategyId: row.strategy_id,
|
|
1247
|
-
audience: row.audience,
|
|
1248
1247
|
tokenHash: row.token_hash,
|
|
1249
1248
|
createdAt: toDate(row.created_at),
|
|
1250
1249
|
expiresAt: toDate(row.expires_at),
|
|
@@ -1311,7 +1310,6 @@ function rowToMagicLinkRecord(row) {
|
|
|
1311
1310
|
id: row.id,
|
|
1312
1311
|
projectId: row.project_id,
|
|
1313
1312
|
strategyId: row.strategy_id,
|
|
1314
|
-
audience: row.audience,
|
|
1315
1313
|
email: row.email,
|
|
1316
1314
|
tokenHash: row.token_hash,
|
|
1317
1315
|
returnTo: row.return_to ?? undefined,
|
|
@@ -1326,7 +1324,6 @@ function rowToOidcAuthorizationAttemptRecord(row) {
|
|
|
1326
1324
|
id: row.id,
|
|
1327
1325
|
projectId: row.project_id,
|
|
1328
1326
|
strategyId: row.strategy_id,
|
|
1329
|
-
audience: row.audience,
|
|
1330
1327
|
stateHash: row.state_hash,
|
|
1331
1328
|
nonceHash: row.nonce_hash,
|
|
1332
1329
|
codeVerifier: row.code_verifier,
|
|
@@ -1622,7 +1619,6 @@ async function consumeOidcAttempt(sql, params) {
|
|
|
1622
1619
|
}
|
|
1623
1620
|
async function validateCompleteSessionInput(sql, projectId, session) {
|
|
1624
1621
|
const sessionId = assertNonEmpty(session.id, "Session id");
|
|
1625
|
-
assertNonEmpty(session.audience, "Session audience");
|
|
1626
1622
|
assertNonEmpty(session.tokenHash, "Session token hash");
|
|
1627
1623
|
await assertSessionIdAvailable(sql, projectId, sessionId);
|
|
1628
1624
|
}
|
|
@@ -1636,7 +1632,6 @@ async function createSession(sql, input) {
|
|
|
1636
1632
|
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
1637
1633
|
const userId = assertNonEmpty(input.userId, "User id");
|
|
1638
1634
|
const strategyId = assertNonEmpty(input.strategyId, "Strategy id");
|
|
1639
|
-
const audience = assertNonEmpty(input.audience, "Session audience");
|
|
1640
1635
|
const tokenHash = assertNonEmpty(input.tokenHash, "Session token hash");
|
|
1641
1636
|
await requireUserById(sql, { projectId, id: userId });
|
|
1642
1637
|
await assertSessionIdAvailable(sql, projectId, id);
|
|
@@ -1647,7 +1642,6 @@ async function createSession(sql, input) {
|
|
|
1647
1642
|
id,
|
|
1648
1643
|
user_id,
|
|
1649
1644
|
strategy_id,
|
|
1650
|
-
audience,
|
|
1651
1645
|
token_hash,
|
|
1652
1646
|
created_at,
|
|
1653
1647
|
expires_at,
|
|
@@ -1662,7 +1656,6 @@ async function createSession(sql, input) {
|
|
|
1662
1656
|
${id},
|
|
1663
1657
|
${userId},
|
|
1664
1658
|
${strategyId},
|
|
1665
|
-
${audience},
|
|
1666
1659
|
${tokenHash},
|
|
1667
1660
|
${input.createdAt},
|
|
1668
1661
|
${input.expiresAt},
|
|
@@ -1681,50 +1674,26 @@ async function createSession(sql, input) {
|
|
|
1681
1674
|
}
|
|
1682
1675
|
}
|
|
1683
1676
|
async function revokeActiveSessionsForUser(sql, params) {
|
|
1684
|
-
const rows =
|
|
1685
|
-
SELECT *
|
|
1686
|
-
FROM auth_sessions
|
|
1687
|
-
WHERE project_id = ${params.projectId}
|
|
1688
|
-
AND user_id = ${params.userId}
|
|
1689
|
-
AND revoked_at IS NULL
|
|
1690
|
-
AND expires_at > ${params.revokedAt}
|
|
1691
|
-
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
1692
|
-
ORDER BY created_at ASC, id ASC
|
|
1693
|
-
FOR UPDATE
|
|
1694
|
-
` : await sql`
|
|
1677
|
+
const rows = await sql`
|
|
1695
1678
|
SELECT *
|
|
1696
1679
|
FROM auth_sessions
|
|
1697
1680
|
WHERE project_id = ${params.projectId}
|
|
1698
1681
|
AND user_id = ${params.userId}
|
|
1699
|
-
AND audience = ${params.audience}
|
|
1700
1682
|
AND revoked_at IS NULL
|
|
1701
1683
|
AND expires_at > ${params.revokedAt}
|
|
1702
1684
|
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
1703
1685
|
ORDER BY created_at ASC, id ASC
|
|
1704
1686
|
FOR UPDATE
|
|
1705
1687
|
`;
|
|
1706
|
-
|
|
1707
|
-
await sql`
|
|
1708
|
-
UPDATE auth_sessions
|
|
1709
|
-
SET revoked_at = ${params.revokedAt}
|
|
1710
|
-
WHERE project_id = ${params.projectId}
|
|
1711
|
-
AND user_id = ${params.userId}
|
|
1712
|
-
AND revoked_at IS NULL
|
|
1713
|
-
AND expires_at > ${params.revokedAt}
|
|
1714
|
-
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
1715
|
-
`;
|
|
1716
|
-
} else {
|
|
1717
|
-
await sql`
|
|
1688
|
+
await sql`
|
|
1718
1689
|
UPDATE auth_sessions
|
|
1719
1690
|
SET revoked_at = ${params.revokedAt}
|
|
1720
1691
|
WHERE project_id = ${params.projectId}
|
|
1721
1692
|
AND user_id = ${params.userId}
|
|
1722
|
-
AND audience = ${params.audience}
|
|
1723
1693
|
AND revoked_at IS NULL
|
|
1724
1694
|
AND expires_at > ${params.revokedAt}
|
|
1725
1695
|
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.revokedAt})
|
|
1726
1696
|
`;
|
|
1727
|
-
}
|
|
1728
1697
|
return rows.map((row) => rowToSessionRecord({
|
|
1729
1698
|
...row,
|
|
1730
1699
|
revoked_at: params.revokedAt
|
|
@@ -2384,7 +2353,6 @@ class PgAuthInvitationStore {
|
|
|
2384
2353
|
}
|
|
2385
2354
|
|
|
2386
2355
|
// src/auth-storage/magic-links.ts
|
|
2387
|
-
import { resolveAuthSessionAudience } from "@sixb/core";
|
|
2388
2356
|
import { AuthStorageError as AuthStorageError5 } from "@sixb/core/storage";
|
|
2389
2357
|
class PgAuthMagicLinkStore {
|
|
2390
2358
|
sql;
|
|
@@ -2396,7 +2364,6 @@ class PgAuthMagicLinkStore {
|
|
|
2396
2364
|
const id = assertNonEmpty(input.id, "Magic link id");
|
|
2397
2365
|
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
2398
2366
|
const strategyId = assertNonEmpty(input.strategyId, "Strategy id");
|
|
2399
|
-
const audience = resolveAuthSessionAudience(input.audience);
|
|
2400
2367
|
const email = normalizeEmail(input.email);
|
|
2401
2368
|
const tokenHash = assertNonEmpty(input.tokenHash, "Magic link token hash");
|
|
2402
2369
|
await lockAdvisoryKeys(tx, [authLockKey("magic-links", projectId, email)]);
|
|
@@ -2414,7 +2381,6 @@ class PgAuthMagicLinkStore {
|
|
|
2414
2381
|
project_id,
|
|
2415
2382
|
id,
|
|
2416
2383
|
strategy_id,
|
|
2417
|
-
audience,
|
|
2418
2384
|
email,
|
|
2419
2385
|
token_hash,
|
|
2420
2386
|
return_to,
|
|
@@ -2424,7 +2390,6 @@ class PgAuthMagicLinkStore {
|
|
|
2424
2390
|
${projectId},
|
|
2425
2391
|
${id},
|
|
2426
2392
|
${strategyId},
|
|
2427
|
-
${audience},
|
|
2428
2393
|
${email},
|
|
2429
2394
|
${tokenHash},
|
|
2430
2395
|
${input.returnTo ?? null},
|
|
@@ -2472,7 +2437,6 @@ class PgAuthMagicLinkStore {
|
|
|
2472
2437
|
}
|
|
2473
2438
|
|
|
2474
2439
|
// src/auth-storage/oidc-attempts.ts
|
|
2475
|
-
import { resolveAuthSessionAudience as resolveAuthSessionAudience2 } from "@sixb/core";
|
|
2476
2440
|
import { AuthStorageError as AuthStorageError6 } from "@sixb/core/storage";
|
|
2477
2441
|
class PgAuthOidcAuthorizationAttemptStore {
|
|
2478
2442
|
sql;
|
|
@@ -2483,7 +2447,6 @@ class PgAuthOidcAuthorizationAttemptStore {
|
|
|
2483
2447
|
const id = assertNonEmpty(input.id, "OIDC authorization attempt id");
|
|
2484
2448
|
const projectId = assertNonEmpty(input.projectId, "Project id");
|
|
2485
2449
|
const strategyId = assertNonEmpty(input.strategyId, "Strategy id");
|
|
2486
|
-
const audience = resolveAuthSessionAudience2(input.audience);
|
|
2487
2450
|
const stateHash = assertNonEmpty(input.stateHash, "OIDC state hash");
|
|
2488
2451
|
const nonceHash = assertNonEmpty(input.nonceHash, "OIDC nonce hash");
|
|
2489
2452
|
const codeVerifier = assertNonEmpty(input.codeVerifier, "OIDC code verifier");
|
|
@@ -2496,7 +2459,6 @@ class PgAuthOidcAuthorizationAttemptStore {
|
|
|
2496
2459
|
project_id,
|
|
2497
2460
|
id,
|
|
2498
2461
|
strategy_id,
|
|
2499
|
-
audience,
|
|
2500
2462
|
state_hash,
|
|
2501
2463
|
nonce_hash,
|
|
2502
2464
|
code_verifier,
|
|
@@ -2507,7 +2469,6 @@ class PgAuthOidcAuthorizationAttemptStore {
|
|
|
2507
2469
|
${projectId},
|
|
2508
2470
|
${id},
|
|
2509
2471
|
${strategyId},
|
|
2510
|
-
${audience},
|
|
2511
2472
|
${stateHash},
|
|
2512
2473
|
${nonceHash},
|
|
2513
2474
|
${codeVerifier},
|
|
@@ -2765,21 +2726,6 @@ class PgAuthSessionStore {
|
|
|
2765
2726
|
async getById(params) {
|
|
2766
2727
|
return getSessionById(this.sql, params);
|
|
2767
2728
|
}
|
|
2768
|
-
async getActiveByUserId(params) {
|
|
2769
|
-
const [row] = await this.sql`
|
|
2770
|
-
SELECT *
|
|
2771
|
-
FROM auth_sessions
|
|
2772
|
-
WHERE project_id = ${params.projectId}
|
|
2773
|
-
AND user_id = ${params.userId}
|
|
2774
|
-
AND audience = ${params.audience}
|
|
2775
|
-
AND revoked_at IS NULL
|
|
2776
|
-
AND expires_at > ${params.now}
|
|
2777
|
-
AND (absolute_expires_at IS NULL OR absolute_expires_at > ${params.now})
|
|
2778
|
-
ORDER BY created_at DESC, id DESC
|
|
2779
|
-
LIMIT 1
|
|
2780
|
-
`;
|
|
2781
|
-
return row ? rowToSessionRecord(row) : null;
|
|
2782
|
-
}
|
|
2783
2729
|
async listActiveByUserId(params) {
|
|
2784
2730
|
const rows = await this.sql`
|
|
2785
2731
|
SELECT *
|
|
@@ -2795,7 +2741,7 @@ class PgAuthSessionStore {
|
|
|
2795
2741
|
}
|
|
2796
2742
|
async findValidByTokenHash(params) {
|
|
2797
2743
|
const row = await getSessionRowById(this.sql, params);
|
|
2798
|
-
if (!row || row.refresh_token_hash !== null || row.
|
|
2744
|
+
if (!row || row.refresh_token_hash !== null || row.token_hash !== params.tokenHash || !isLiveSessionRow(row, params.now)) {
|
|
2799
2745
|
return null;
|
|
2800
2746
|
}
|
|
2801
2747
|
return rowToSessionRecord(row);
|
|
@@ -2855,7 +2801,6 @@ class PgAuthSessionStore {
|
|
|
2855
2801
|
last_seen_at = ${params.now}
|
|
2856
2802
|
WHERE project_id = ${params.projectId}
|
|
2857
2803
|
AND id = ${params.id}
|
|
2858
|
-
AND audience = ${params.audience}
|
|
2859
2804
|
AND token_hash = ${params.tokenHash}
|
|
2860
2805
|
AND refresh_token_hash IS NULL
|
|
2861
2806
|
AND revoked_at IS NULL
|
|
@@ -2883,11 +2828,7 @@ class PgAuthSessionStore {
|
|
|
2883
2828
|
}
|
|
2884
2829
|
async revokeActiveForUser(params) {
|
|
2885
2830
|
return runPgTransaction(this.sql, async (tx) => {
|
|
2886
|
-
|
|
2887
|
-
if (params.audience) {
|
|
2888
|
-
locks.push(authLockKey("sessions", params.projectId, params.userId, params.audience));
|
|
2889
|
-
}
|
|
2890
|
-
await lockAdvisoryKeys(tx, locks);
|
|
2831
|
+
await lockAdvisoryKeys(tx, [authLockKey("sessions", params.projectId, params.userId)]);
|
|
2891
2832
|
return revokeActiveSessionsForUser(tx, params);
|
|
2892
2833
|
});
|
|
2893
2834
|
}
|
|
@@ -3146,7 +3087,6 @@ class PgAuthStorage {
|
|
|
3146
3087
|
};
|
|
3147
3088
|
}
|
|
3148
3089
|
await validateCompleteSessionInput(tx, projectId, input.session);
|
|
3149
|
-
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience);
|
|
3150
3090
|
let newUserId;
|
|
3151
3091
|
if (shouldCreateUser) {
|
|
3152
3092
|
newUserId = assertNonEmpty(input.newUserId, "User id");
|
|
@@ -3251,7 +3191,6 @@ class PgAuthStorage {
|
|
|
3251
3191
|
};
|
|
3252
3192
|
}
|
|
3253
3193
|
await validateCompleteSessionInput(tx, projectId, input.session);
|
|
3254
|
-
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience);
|
|
3255
3194
|
let newUserId;
|
|
3256
3195
|
if (shouldCreateUser) {
|
|
3257
3196
|
newUserId = assertNonEmpty(input.newUserId, "User id");
|
|
@@ -3459,16 +3398,12 @@ class PgAuthStorage {
|
|
|
3459
3398
|
return this.applyGroups(sql, { ...input, groupIds, source: "directory" });
|
|
3460
3399
|
}
|
|
3461
3400
|
async createSignInSession(sql, input) {
|
|
3462
|
-
await lockAdvisoryKeys(sql, [
|
|
3463
|
-
authLockKey("sessions", input.projectId, input.userId),
|
|
3464
|
-
authLockKey("sessions", input.projectId, input.userId, input.session.audience)
|
|
3465
|
-
]);
|
|
3401
|
+
await lockAdvisoryKeys(sql, [authLockKey("sessions", input.projectId, input.userId)]);
|
|
3466
3402
|
return createSession(sql, {
|
|
3467
3403
|
id: input.session.id,
|
|
3468
3404
|
projectId: input.projectId,
|
|
3469
3405
|
userId: input.userId,
|
|
3470
3406
|
strategyId: input.strategyId,
|
|
3471
|
-
audience: input.session.audience,
|
|
3472
3407
|
tokenHash: input.session.tokenHash,
|
|
3473
3408
|
createdAt: input.session.createdAt,
|
|
3474
3409
|
expiresAt: input.session.expiresAt,
|
|
@@ -3514,12 +3449,6 @@ class PgAuthStorage {
|
|
|
3514
3449
|
return rowToIdentityRecord(row);
|
|
3515
3450
|
}
|
|
3516
3451
|
}
|
|
3517
|
-
function assertSignInSessionAudience(projectId, sessionAudience, storedAudience) {
|
|
3518
|
-
if (sessionAudience === storedAudience) {
|
|
3519
|
-
return;
|
|
3520
|
-
}
|
|
3521
|
-
throw new AuthStorageError11("invalid_input", `[Sixb] Sign-in session audience '${sessionAudience}' does not match stored auth audience '${storedAudience}' for project '${projectId}'.`);
|
|
3522
|
-
}
|
|
3523
3452
|
function assertCompletableDeviceAuthorization(authorization, input) {
|
|
3524
3453
|
if (authorization.deviceCodeHash !== input.deviceCodeHash) {
|
|
3525
3454
|
throw new AuthStorageError11("invalid_device_authorization", "[Sixb] Invalid device authorization.");
|
|
@@ -8015,6 +7944,181 @@ CREATE INDEX idx_ontology_sources_drained
|
|
|
8015
7944
|
WHERE status = 'superseded' AND root_count = 0;
|
|
8016
7945
|
`;
|
|
8017
7946
|
|
|
7947
|
+
// src/migrations/061-terminal-action-runs.sql
|
|
7948
|
+
var _061_terminal_action_runs_default = `-- Actions run in the request that asks for them and record their run once, when it ends. No run
|
|
7949
|
+
-- is queued, running, or cancelled any more, and every run has started and finished.
|
|
7950
|
+
|
|
7951
|
+
-- A run left running after its edits committed, as a crash between the commit and the end of the
|
|
7952
|
+
-- run left it, succeeded: its edits and their outbox facts are in the commit. A run that stopped
|
|
7953
|
+
-- during its effects keeps that phase, and records effects that never ended.
|
|
7954
|
+
UPDATE action_runs AS run
|
|
7955
|
+
SET
|
|
7956
|
+
status = 'succeeded',
|
|
7957
|
+
error = NULL,
|
|
7958
|
+
finished_at = committed.committed_at,
|
|
7959
|
+
phase = CASE WHEN run.phase = 'effects' THEN 'effects' ELSE 'commit' END,
|
|
7960
|
+
effects_status = CASE
|
|
7961
|
+
WHEN run.phase = 'effects' AND run.effects_status IS NULL THEN 'failed'
|
|
7962
|
+
ELSE run.effects_status
|
|
7963
|
+
END,
|
|
7964
|
+
effects_completed_at = CASE
|
|
7965
|
+
WHEN run.phase = 'effects' AND run.effects_status IS NULL THEN now()
|
|
7966
|
+
ELSE run.effects_completed_at
|
|
7967
|
+
END,
|
|
7968
|
+
effects_error = CASE
|
|
7969
|
+
WHEN run.phase = 'effects' AND run.effects_status IS NULL THEN jsonb_build_object(
|
|
7970
|
+
'code', 'internal.unexpected',
|
|
7971
|
+
'message', 'Abandoned: Actions now run synchronously; these effects never finished.',
|
|
7972
|
+
'retryable', false,
|
|
7973
|
+
'at', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
7974
|
+
'details', jsonb_build_object('actionId', run.action_id, 'runId', run.id, 'phase', 'effects')
|
|
7975
|
+
)
|
|
7976
|
+
ELSE run.effects_error
|
|
7977
|
+
END
|
|
7978
|
+
FROM ontology_commits AS committed
|
|
7979
|
+
WHERE run.status IN ('queued', 'running')
|
|
7980
|
+
AND committed.origin_kind = 'action'
|
|
7981
|
+
AND committed.project_id = run.project_id
|
|
7982
|
+
AND committed.origin_run_id = run.id;
|
|
7983
|
+
|
|
7984
|
+
-- Any other run left queued or running never finished, and nothing will resume it.
|
|
7985
|
+
UPDATE action_runs
|
|
7986
|
+
SET
|
|
7987
|
+
status = 'failed',
|
|
7988
|
+
finished_at = now(),
|
|
7989
|
+
error = jsonb_build_object(
|
|
7990
|
+
'code', 'internal.unexpected',
|
|
7991
|
+
'message', 'Abandoned: Actions now run synchronously; this run never finished.',
|
|
7992
|
+
'retryable', false,
|
|
7993
|
+
'at', to_char(now() AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
7994
|
+
'details', jsonb_build_object('actionId', action_id, 'runId', id)
|
|
7995
|
+
)
|
|
7996
|
+
WHERE status IN ('queued', 'running');
|
|
7997
|
+
|
|
7998
|
+
-- A cancelled run failed: it keeps the failure it recorded.
|
|
7999
|
+
UPDATE action_runs SET status = 'failed' WHERE status = 'cancelled';
|
|
8000
|
+
|
|
8001
|
+
-- Every failed run carries a failure. One recorded before failures were kept explains itself.
|
|
8002
|
+
UPDATE action_runs
|
|
8003
|
+
SET error = jsonb_build_object(
|
|
8004
|
+
'code', 'internal.unexpected',
|
|
8005
|
+
'message', 'This run failed before Sixb recorded why.',
|
|
8006
|
+
'retryable', false,
|
|
8007
|
+
'at', to_char(COALESCE(finished_at, now()) AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
8008
|
+
'details', jsonb_build_object('actionId', action_id, 'runId', id)
|
|
8009
|
+
)
|
|
8010
|
+
WHERE status = 'failed' AND error IS NULL;
|
|
8011
|
+
|
|
8012
|
+
-- The phases \`request\`, \`enqueue\`, and \`cancelled\` are gone: a run that stopped in one of them
|
|
8013
|
+
-- never got past validation. A run starts when it was queued at the latest.
|
|
8014
|
+
UPDATE action_runs
|
|
8015
|
+
SET
|
|
8016
|
+
phase = CASE
|
|
8017
|
+
WHEN phase IN ('validation', 'writeback', 'edits', 'commit', 'effects') THEN phase
|
|
8018
|
+
ELSE 'validation'
|
|
8019
|
+
END,
|
|
8020
|
+
started_at = COALESCE(started_at, queued_at),
|
|
8021
|
+
finished_at = COALESCE(finished_at, now()),
|
|
8022
|
+
writeback_completed_at = CASE
|
|
8023
|
+
WHEN writeback_status IS NULL THEN NULL
|
|
8024
|
+
ELSE COALESCE(writeback_completed_at, finished_at, now())
|
|
8025
|
+
END,
|
|
8026
|
+
effects_completed_at = CASE
|
|
8027
|
+
WHEN effects_status IS NULL THEN NULL
|
|
8028
|
+
ELSE COALESCE(effects_completed_at, finished_at, now())
|
|
8029
|
+
END
|
|
8030
|
+
WHERE phase IS NULL
|
|
8031
|
+
OR phase NOT IN ('validation', 'writeback', 'edits', 'commit', 'effects')
|
|
8032
|
+
OR started_at IS NULL
|
|
8033
|
+
OR finished_at IS NULL
|
|
8034
|
+
OR (writeback_status IS NOT NULL AND writeback_completed_at IS NULL)
|
|
8035
|
+
OR (effects_status IS NOT NULL AND effects_completed_at IS NULL);
|
|
8036
|
+
|
|
8037
|
+
-- A failure names the phase its run ended in, and only Action codes: an enqueue failure, which no
|
|
8038
|
+
-- run can meet any more, reads as the unexpected error it now is.
|
|
8039
|
+
UPDATE action_runs
|
|
8040
|
+
SET error = jsonb_set(
|
|
8041
|
+
CASE
|
|
8042
|
+
WHEN error->>'code' = 'queue.enqueue_failed'
|
|
8043
|
+
THEN error || '{"code": "internal.unexpected", "retryable": false}'::jsonb
|
|
8044
|
+
ELSE error
|
|
8045
|
+
END,
|
|
8046
|
+
'{details,phase}',
|
|
8047
|
+
to_jsonb(phase)
|
|
8048
|
+
)
|
|
8049
|
+
WHERE error IS NOT NULL
|
|
8050
|
+
AND (
|
|
8051
|
+
error->'details'->>'phase' IS DISTINCT FROM phase
|
|
8052
|
+
OR error->>'code' = 'queue.enqueue_failed'
|
|
8053
|
+
);
|
|
8054
|
+
|
|
8055
|
+
-- The status and phase checks were unnamed. Drop exactly the two that admit the removed values.
|
|
8056
|
+
DO $$
|
|
8057
|
+
DECLARE lifecycle_constraint RECORD;
|
|
8058
|
+
BEGIN
|
|
8059
|
+
FOR lifecycle_constraint IN
|
|
8060
|
+
SELECT conname
|
|
8061
|
+
FROM pg_constraint
|
|
8062
|
+
WHERE conrelid = 'action_runs'::regclass
|
|
8063
|
+
AND contype = 'c'
|
|
8064
|
+
AND (
|
|
8065
|
+
pg_get_constraintdef(oid) LIKE '%''queued''%'
|
|
8066
|
+
OR pg_get_constraintdef(oid) LIKE '%''enqueue''%'
|
|
8067
|
+
)
|
|
8068
|
+
LOOP
|
|
8069
|
+
EXECUTE format('ALTER TABLE action_runs DROP CONSTRAINT %I', lifecycle_constraint.conname);
|
|
8070
|
+
END LOOP;
|
|
8071
|
+
END $$;
|
|
8072
|
+
|
|
8073
|
+
-- The listing indexes ordered runs by \`COALESCE(started_at, queued_at)\`.
|
|
8074
|
+
DROP INDEX idx_action_runs_project_started;
|
|
8075
|
+
DROP INDEX idx_action_runs_project_action_started;
|
|
8076
|
+
DROP INDEX idx_action_runs_project_object_started;
|
|
8077
|
+
DROP INDEX idx_action_runs_project_status_started;
|
|
8078
|
+
|
|
8079
|
+
ALTER TABLE action_runs
|
|
8080
|
+
DROP COLUMN queued_at,
|
|
8081
|
+
ALTER COLUMN phase SET NOT NULL,
|
|
8082
|
+
ALTER COLUMN started_at SET NOT NULL,
|
|
8083
|
+
ALTER COLUMN finished_at SET NOT NULL,
|
|
8084
|
+
ADD CONSTRAINT action_runs_status_check CHECK (status IN ('succeeded', 'failed')),
|
|
8085
|
+
ADD CONSTRAINT action_runs_phase_check CHECK (
|
|
8086
|
+
phase IN ('validation', 'writeback', 'edits', 'commit', 'effects')
|
|
8087
|
+
),
|
|
8088
|
+
ADD CONSTRAINT action_runs_failure_check CHECK ((status = 'failed') = (error IS NOT NULL));
|
|
8089
|
+
|
|
8090
|
+
CREATE INDEX idx_action_runs_project_started
|
|
8091
|
+
ON action_runs (project_id, started_at DESC);
|
|
8092
|
+
CREATE INDEX idx_action_runs_project_action_started
|
|
8093
|
+
ON action_runs (project_id, action_id, started_at DESC);
|
|
8094
|
+
CREATE INDEX idx_action_runs_project_object_started
|
|
8095
|
+
ON action_runs (project_id, object_type_id, primary_id, started_at DESC);
|
|
8096
|
+
CREATE INDEX idx_action_runs_project_status_started
|
|
8097
|
+
ON action_runs (project_id, status, started_at DESC);
|
|
8098
|
+
`;
|
|
8099
|
+
|
|
8100
|
+
// src/migrations/062-drop-auth-audience.sql
|
|
8101
|
+
var _062_drop_auth_audience_default = `-- A project has one browser app, so sessions, magic links and sign-in attempts no longer name the
|
|
8102
|
+
-- one they belong to. Atlas's browser sessions end here: their cookie is no longer read. Native
|
|
8103
|
+
-- sessions carried Atlas's name only because the column needed a value, so they stay.
|
|
8104
|
+
UPDATE auth_sessions
|
|
8105
|
+
SET revoked_at = now()
|
|
8106
|
+
WHERE audience = 'atlas'
|
|
8107
|
+
AND refresh_token_hash IS NULL
|
|
8108
|
+
AND revoked_at IS NULL;
|
|
8109
|
+
|
|
8110
|
+
-- Dropping the column drops the two indexes that name it.
|
|
8111
|
+
ALTER TABLE auth_sessions DROP COLUMN audience;
|
|
8112
|
+
CREATE INDEX idx_auth_sessions_user_active
|
|
8113
|
+
ON auth_sessions (project_id, user_id, expires_at DESC, created_at DESC, id DESC)
|
|
8114
|
+
WHERE revoked_at IS NULL;
|
|
8115
|
+
CREATE INDEX idx_auth_sessions_user_created
|
|
8116
|
+
ON auth_sessions (project_id, user_id, created_at DESC, id DESC);
|
|
8117
|
+
|
|
8118
|
+
ALTER TABLE auth_magic_links DROP COLUMN audience;
|
|
8119
|
+
ALTER TABLE auth_oidc_authorization_attempts DROP COLUMN audience;
|
|
8120
|
+
`;
|
|
8121
|
+
|
|
8018
8122
|
// src/migrations.ts
|
|
8019
8123
|
var POSTGRES_STORAGE_ADAPTER_ID = "SixbPostgresStorage";
|
|
8020
8124
|
function pgSql(id, sqlText) {
|
|
@@ -8241,7 +8345,9 @@ var postgresStorageMigrations = defineMigrations({
|
|
|
8241
8345
|
pgSql("057-audio-transcription", _057_audio_transcription_default),
|
|
8242
8346
|
pgSql("058-reranking-model-kind", _058_reranking_model_kind_default),
|
|
8243
8347
|
pgSql("059-file-download-grants", _059_file_download_grants_default),
|
|
8244
|
-
pgSql("060-drained-source-versions", _060_drained_source_versions_default)
|
|
8348
|
+
pgSql("060-drained-source-versions", _060_drained_source_versions_default),
|
|
8349
|
+
pgSql("061-terminal-action-runs", _061_terminal_action_runs_default),
|
|
8350
|
+
pgSql("062-drop-auth-audience", _062_drop_auth_audience_default)
|
|
8245
8351
|
]
|
|
8246
8352
|
});
|
|
8247
8353
|
|
|
@@ -15681,12 +15787,8 @@ import {
|
|
|
15681
15787
|
serializeActionRunFailure
|
|
15682
15788
|
} from "@sixb/core/internal/action-run-storage";
|
|
15683
15789
|
import { assertActionRunExecution } from "@sixb/core/internal/action-run-storage-provider";
|
|
15684
|
-
import {
|
|
15685
|
-
|
|
15686
|
-
canRequeueActionRunAfterEnqueueFailure,
|
|
15687
|
-
finishActionRunPhase
|
|
15688
|
-
} from "@sixb/core/internal/storage";
|
|
15689
|
-
import { ActionRunError, isTerminalActionRun } from "@sixb/core/storage";
|
|
15790
|
+
import { normalizeRecordActionRunInput, resolveActionRunEffects } from "@sixb/core/internal/storage";
|
|
15791
|
+
import { ActionRunError } from "@sixb/core/storage";
|
|
15690
15792
|
class PgActionRunStorage {
|
|
15691
15793
|
sql;
|
|
15692
15794
|
executions;
|
|
@@ -15694,227 +15796,83 @@ class PgActionRunStorage {
|
|
|
15694
15796
|
this.sql = sql;
|
|
15695
15797
|
this.executions = executions;
|
|
15696
15798
|
}
|
|
15697
|
-
async
|
|
15698
|
-
const
|
|
15699
|
-
SELECT *
|
|
15700
|
-
FROM action_runs
|
|
15701
|
-
WHERE project_id = ${input.projectId} AND id = ${input.runId}
|
|
15702
|
-
FOR UPDATE
|
|
15703
|
-
`;
|
|
15704
|
-
if (!row) {
|
|
15705
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.runId}' not found for project '${input.projectId}'.`);
|
|
15706
|
-
}
|
|
15707
|
-
if (row.action_id !== input.actionId) {
|
|
15708
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.runId}' does not belong to action '${input.actionId}'.`);
|
|
15709
|
-
}
|
|
15710
|
-
if (row.status !== "running") {
|
|
15711
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.runId}' cannot materialize from status '${row.status}'.`);
|
|
15712
|
-
}
|
|
15713
|
-
return rowToActionRunRecord(row);
|
|
15714
|
-
}
|
|
15715
|
-
async queue(input) {
|
|
15799
|
+
async record(input) {
|
|
15800
|
+
const record = normalizeRecordActionRunInput(input);
|
|
15716
15801
|
await assertActionRunExecution({
|
|
15717
15802
|
executions: this.executions,
|
|
15718
|
-
projectId:
|
|
15719
|
-
executionId:
|
|
15720
|
-
runId:
|
|
15721
|
-
actionId:
|
|
15722
|
-
});
|
|
15723
|
-
try {
|
|
15724
|
-
const [row] = await this.sql`
|
|
15725
|
-
INSERT INTO action_runs (
|
|
15726
|
-
project_id,
|
|
15727
|
-
id,
|
|
15728
|
-
execution_id,
|
|
15729
|
-
action_id,
|
|
15730
|
-
subject_kind,
|
|
15731
|
-
object_type_id,
|
|
15732
|
-
primary_id,
|
|
15733
|
-
status,
|
|
15734
|
-
phase,
|
|
15735
|
-
queued_at,
|
|
15736
|
-
started_at,
|
|
15737
|
-
finished_at,
|
|
15738
|
-
params,
|
|
15739
|
-
idempotency_key,
|
|
15740
|
-
writeback_status,
|
|
15741
|
-
writeback_completed_at,
|
|
15742
|
-
writeback_result,
|
|
15743
|
-
writeback_error,
|
|
15744
|
-
effects_status,
|
|
15745
|
-
effects_completed_at,
|
|
15746
|
-
effects_error,
|
|
15747
|
-
error
|
|
15748
|
-
) VALUES (
|
|
15749
|
-
${input.projectId},
|
|
15750
|
-
${input.id},
|
|
15751
|
-
${input.executionId},
|
|
15752
|
-
${input.actionId},
|
|
15753
|
-
${input.subject.kind},
|
|
15754
|
-
${input.subject.kind === "object" ? input.subject.objectTypeId : null},
|
|
15755
|
-
${input.subject.kind === "object" ? input.subject.primaryId : null},
|
|
15756
|
-
${"queued"},
|
|
15757
|
-
${"request"},
|
|
15758
|
-
${input.queuedAt ?? new Date},
|
|
15759
|
-
${null},
|
|
15760
|
-
${null},
|
|
15761
|
-
${JSON.stringify(input.params)}::text::jsonb,
|
|
15762
|
-
${input.idempotencyKey},
|
|
15763
|
-
${null},
|
|
15764
|
-
${null},
|
|
15765
|
-
${null},
|
|
15766
|
-
${null},
|
|
15767
|
-
${null},
|
|
15768
|
-
${null},
|
|
15769
|
-
${null},
|
|
15770
|
-
${null}
|
|
15771
|
-
)
|
|
15772
|
-
RETURNING *
|
|
15773
|
-
`;
|
|
15774
|
-
return rowToActionRunRecord(row);
|
|
15775
|
-
} catch (error) {
|
|
15776
|
-
if (isUniqueViolation(error)) {
|
|
15777
|
-
return this.requeueAfterEnqueueFailure(input);
|
|
15778
|
-
}
|
|
15779
|
-
throw error;
|
|
15780
|
-
}
|
|
15781
|
-
}
|
|
15782
|
-
async requeueAfterEnqueueFailure(input) {
|
|
15783
|
-
return runPgTransaction(this.sql, async (tx) => {
|
|
15784
|
-
const [existing] = await tx`
|
|
15785
|
-
SELECT * FROM action_runs
|
|
15786
|
-
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15787
|
-
FOR UPDATE
|
|
15788
|
-
`;
|
|
15789
|
-
if (!existing || !canRequeueActionRunAfterEnqueueFailure(rowToActionRunRecord(existing), input)) {
|
|
15790
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.id}' already exists for project '${input.projectId}'.`);
|
|
15791
|
-
}
|
|
15792
|
-
const [updated] = await tx`
|
|
15793
|
-
UPDATE action_runs
|
|
15794
|
-
SET
|
|
15795
|
-
status = ${"queued"},
|
|
15796
|
-
phase = ${"request"},
|
|
15797
|
-
queued_at = ${input.queuedAt ?? new Date},
|
|
15798
|
-
started_at = ${null},
|
|
15799
|
-
finished_at = ${null},
|
|
15800
|
-
writeback_status = ${null},
|
|
15801
|
-
writeback_completed_at = ${null},
|
|
15802
|
-
writeback_result = ${null},
|
|
15803
|
-
writeback_error = ${null},
|
|
15804
|
-
effects_status = ${null},
|
|
15805
|
-
effects_completed_at = ${null},
|
|
15806
|
-
effects_error = ${null},
|
|
15807
|
-
error = ${null}
|
|
15808
|
-
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15809
|
-
RETURNING *
|
|
15810
|
-
`;
|
|
15811
|
-
return rowToActionRunRecord(updated);
|
|
15803
|
+
projectId: record.projectId,
|
|
15804
|
+
executionId: record.executionId,
|
|
15805
|
+
runId: record.id,
|
|
15806
|
+
actionId: record.actionId
|
|
15812
15807
|
});
|
|
15813
|
-
|
|
15814
|
-
|
|
15815
|
-
|
|
15816
|
-
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
|
|
15824
|
-
|
|
15808
|
+
const [row] = await this.sql`
|
|
15809
|
+
INSERT INTO action_runs (
|
|
15810
|
+
project_id,
|
|
15811
|
+
id,
|
|
15812
|
+
execution_id,
|
|
15813
|
+
action_id,
|
|
15814
|
+
subject_kind,
|
|
15815
|
+
object_type_id,
|
|
15816
|
+
primary_id,
|
|
15817
|
+
status,
|
|
15818
|
+
phase,
|
|
15819
|
+
started_at,
|
|
15820
|
+
finished_at,
|
|
15821
|
+
params,
|
|
15822
|
+
idempotency_key,
|
|
15823
|
+
writeback_status,
|
|
15824
|
+
writeback_completed_at,
|
|
15825
|
+
writeback_result,
|
|
15826
|
+
writeback_error,
|
|
15827
|
+
error
|
|
15828
|
+
) VALUES (
|
|
15829
|
+
${record.projectId},
|
|
15830
|
+
${record.id},
|
|
15831
|
+
${record.executionId},
|
|
15832
|
+
${record.actionId},
|
|
15833
|
+
${record.subject.kind},
|
|
15834
|
+
${record.subject.kind === "object" ? record.subject.objectTypeId : null},
|
|
15835
|
+
${record.subject.kind === "object" ? record.subject.primaryId : null},
|
|
15836
|
+
${record.status},
|
|
15837
|
+
${record.phase},
|
|
15838
|
+
${record.startedAt},
|
|
15839
|
+
${record.finishedAt},
|
|
15840
|
+
${JSON.stringify(record.params)}::text::jsonb,
|
|
15841
|
+
${record.idempotencyKey},
|
|
15842
|
+
${record.writeback?.status ?? null},
|
|
15843
|
+
${record.writeback?.completedAt ?? null},
|
|
15844
|
+
${record.writeback?.status === "succeeded" ? JSON.stringify(record.writeback.result) : null}::text::jsonb,
|
|
15845
|
+
${record.writeback?.status === "failed" ? serializeActionRunFailure(record.writeback.error, "writeback") : null}::text::jsonb,
|
|
15846
|
+
${record.status === "failed" ? serializeActionRunFailure(record.error) : null}::text::jsonb
|
|
15847
|
+
)
|
|
15848
|
+
ON CONFLICT DO NOTHING
|
|
15825
15849
|
RETURNING *
|
|
15826
15850
|
`;
|
|
15827
|
-
if (
|
|
15828
|
-
return rowToActionRunRecord(
|
|
15829
|
-
|
|
15830
|
-
|
|
15831
|
-
|
|
15832
|
-
|
|
15833
|
-
}
|
|
15834
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.id}' cannot start from status '${existing.status}'.`);
|
|
15835
|
-
}
|
|
15836
|
-
async enterPhase(input) {
|
|
15837
|
-
return runPgTransaction(this.sql, async (tx) => {
|
|
15838
|
-
await this.requireRunningRun(tx, input.projectId, input.id, "transition phase");
|
|
15839
|
-
const [updated] = await tx`
|
|
15840
|
-
UPDATE action_runs
|
|
15841
|
-
SET phase = ${input.phase}
|
|
15842
|
-
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15843
|
-
RETURNING *
|
|
15844
|
-
`;
|
|
15845
|
-
return rowToActionRunRecord(updated);
|
|
15846
|
-
});
|
|
15847
|
-
}
|
|
15848
|
-
async recordWriteback(input) {
|
|
15849
|
-
return runPgTransaction(this.sql, async (tx) => {
|
|
15850
|
-
const existing = await this.requireRunningRun(tx, input.projectId, input.id, "record writeback");
|
|
15851
|
-
const nextWriteback = toWritebackRecord(input, new Date(input.completedAt ?? new Date));
|
|
15852
|
-
const currentWriteback = toActionRunWritebackRecord(existing);
|
|
15853
|
-
if (currentWriteback) {
|
|
15854
|
-
if (actionRunPhaseRecordsEqual(currentWriteback, nextWriteback)) {
|
|
15855
|
-
return rowToActionRunRecord(existing);
|
|
15856
|
-
}
|
|
15857
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.id}' already has a different writeback record.`);
|
|
15858
|
-
}
|
|
15859
|
-
const [updated] = await tx`
|
|
15860
|
-
UPDATE action_runs
|
|
15861
|
-
SET
|
|
15862
|
-
phase = ${"writeback"},
|
|
15863
|
-
writeback_status = ${input.status},
|
|
15864
|
-
writeback_completed_at = ${nextWriteback.completedAt},
|
|
15865
|
-
writeback_result = ${input.status === "succeeded" ? JSON.stringify(input.result) : null}::text::jsonb,
|
|
15866
|
-
writeback_error = ${input.status === "failed" ? serializeActionRunFailure(input.error, "writeback") : null}::text::jsonb
|
|
15867
|
-
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15868
|
-
RETURNING *
|
|
15869
|
-
`;
|
|
15870
|
-
return rowToActionRunRecord(updated);
|
|
15871
|
-
});
|
|
15851
|
+
if (row)
|
|
15852
|
+
return rowToActionRunRecord(row);
|
|
15853
|
+
const [conflict] = await this.sql`
|
|
15854
|
+
SELECT id FROM action_runs
|
|
15855
|
+
WHERE project_id = ${record.projectId} AND id = ${record.id}
|
|
15856
|
+
`;
|
|
15857
|
+
throw new ActionRunError(conflict ? `[SixbPg] Action run '${record.id}' is already recorded for project '${record.projectId}'.` : `[SixbPg] Execution '${record.executionId}' already belongs to another Action run.`);
|
|
15872
15858
|
}
|
|
15873
15859
|
async recordEffects(input) {
|
|
15874
|
-
return runPgTransaction(this.sql, async (tx) => {
|
|
15875
|
-
const existing = await this.requireRunningRun(tx, input.projectId, input.id, "record effects");
|
|
15876
|
-
const nextEffects = toEffectsRecord(input, new Date(input.completedAt ?? new Date));
|
|
15877
|
-
const currentEffects = toActionRunEffectsRecord(existing);
|
|
15878
|
-
if (currentEffects) {
|
|
15879
|
-
if (actionRunPhaseRecordsEqual(currentEffects, nextEffects)) {
|
|
15880
|
-
return rowToActionRunRecord(existing);
|
|
15881
|
-
}
|
|
15882
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.id}' already has a different effects record.`);
|
|
15883
|
-
}
|
|
15884
|
-
const [updated] = await tx`
|
|
15885
|
-
UPDATE action_runs
|
|
15886
|
-
SET
|
|
15887
|
-
phase = ${"effects"},
|
|
15888
|
-
effects_status = ${input.status},
|
|
15889
|
-
effects_completed_at = ${nextEffects.completedAt},
|
|
15890
|
-
effects_error = ${input.status === "failed" ? serializeActionRunFailure(input.error, "effects") : null}::text::jsonb
|
|
15891
|
-
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15892
|
-
RETURNING *
|
|
15893
|
-
`;
|
|
15894
|
-
return rowToActionRunRecord(updated);
|
|
15895
|
-
});
|
|
15896
|
-
}
|
|
15897
|
-
async finish(input) {
|
|
15898
15860
|
return runPgTransaction(this.sql, async (tx) => {
|
|
15899
15861
|
const [existing] = await tx`
|
|
15900
15862
|
SELECT * FROM action_runs
|
|
15901
15863
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15902
15864
|
FOR UPDATE
|
|
15903
15865
|
`;
|
|
15904
|
-
|
|
15905
|
-
|
|
15906
|
-
|
|
15907
|
-
if (isTerminalActionRun({ status: existing.status })) {
|
|
15908
|
-
throw new ActionRunError(`[SixbPg] Action run '${input.id}' cannot finish from terminal status '${existing.status}'.`);
|
|
15909
|
-
}
|
|
15910
|
-
const phase = finishActionRunPhase(input, existing.phase);
|
|
15866
|
+
const { run, effects } = resolveActionRunEffects(existing ? rowToActionRunRecord(existing) : null, input);
|
|
15867
|
+
if (!effects)
|
|
15868
|
+
return run;
|
|
15911
15869
|
const [updated] = await tx`
|
|
15912
15870
|
UPDATE action_runs
|
|
15913
15871
|
SET
|
|
15914
|
-
|
|
15915
|
-
|
|
15916
|
-
|
|
15917
|
-
|
|
15872
|
+
phase = ${"effects"},
|
|
15873
|
+
effects_status = ${effects.status},
|
|
15874
|
+
effects_completed_at = ${effects.completedAt},
|
|
15875
|
+
effects_error = ${effects.status === "failed" ? serializeActionRunFailure(effects.error, "effects") : null}::text::jsonb
|
|
15918
15876
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15919
15877
|
RETURNING *
|
|
15920
15878
|
`;
|
|
@@ -15987,11 +15945,11 @@ class PgActionRunStorage {
|
|
|
15987
15945
|
params.push(...input.statuses);
|
|
15988
15946
|
}
|
|
15989
15947
|
if (input.startedAfter) {
|
|
15990
|
-
whereClauses.push(`
|
|
15948
|
+
whereClauses.push(`started_at >= $${index++}`);
|
|
15991
15949
|
params.push(input.startedAfter);
|
|
15992
15950
|
}
|
|
15993
15951
|
if (input.startedBefore) {
|
|
15994
|
-
whereClauses.push(`
|
|
15952
|
+
whereClauses.push(`started_at <= $${index++}`);
|
|
15995
15953
|
params.push(input.startedBefore);
|
|
15996
15954
|
}
|
|
15997
15955
|
const where = `WHERE ${whereClauses.join(" AND ")}`;
|
|
@@ -16002,7 +15960,7 @@ class PgActionRunStorage {
|
|
|
16002
15960
|
let query = `
|
|
16003
15961
|
SELECT * FROM action_runs
|
|
16004
15962
|
${where}
|
|
16005
|
-
ORDER BY
|
|
15963
|
+
ORDER BY started_at ${order}, id ${order}
|
|
16006
15964
|
`;
|
|
16007
15965
|
if (input.limit !== undefined) {
|
|
16008
15966
|
query += ` LIMIT $${index++} OFFSET $${index++}`;
|
|
@@ -16020,32 +15978,12 @@ class PgActionRunStorage {
|
|
|
16020
15978
|
total
|
|
16021
15979
|
};
|
|
16022
15980
|
}
|
|
16023
|
-
async requireRunningRun(runner, projectId, id, operation) {
|
|
16024
|
-
const [existing] = await runner`
|
|
16025
|
-
SELECT * FROM action_runs
|
|
16026
|
-
WHERE project_id = ${projectId} AND id = ${id}
|
|
16027
|
-
FOR UPDATE
|
|
16028
|
-
`;
|
|
16029
|
-
if (!existing) {
|
|
16030
|
-
throw new ActionRunError(`[SixbPg] Action run '${id}' not found for project '${projectId}'.`);
|
|
16031
|
-
}
|
|
16032
|
-
if (existing.status !== "running") {
|
|
16033
|
-
throw new ActionRunError(`[SixbPg] Action run '${id}' cannot ${operation} from status '${existing.status}'.`);
|
|
16034
|
-
}
|
|
16035
|
-
return existing;
|
|
16036
|
-
}
|
|
16037
|
-
}
|
|
16038
|
-
function toActionRunFailure(row) {
|
|
16039
|
-
return row.error === null ? undefined : parseActionRunFailure(row.error);
|
|
16040
|
-
}
|
|
16041
|
-
function parsePhaseFailure(value, expectedPhase) {
|
|
16042
|
-
return parseActionRunFailure(value, expectedPhase);
|
|
16043
15981
|
}
|
|
16044
15982
|
function toActionRunWritebackRecord(row) {
|
|
16045
15983
|
if (!row.writeback_status) {
|
|
16046
15984
|
return;
|
|
16047
15985
|
}
|
|
16048
|
-
const completedAt =
|
|
15986
|
+
const completedAt = new Date(row.writeback_completed_at ?? row.finished_at);
|
|
16049
15987
|
if (row.writeback_status === "succeeded") {
|
|
16050
15988
|
return {
|
|
16051
15989
|
status: "succeeded",
|
|
@@ -16056,14 +15994,14 @@ function toActionRunWritebackRecord(row) {
|
|
|
16056
15994
|
return {
|
|
16057
15995
|
status: "failed",
|
|
16058
15996
|
completedAt,
|
|
16059
|
-
error:
|
|
15997
|
+
error: parseActionRunFailure(row.writeback_error, "writeback")
|
|
16060
15998
|
};
|
|
16061
15999
|
}
|
|
16062
16000
|
function toActionRunEffectsRecord(row) {
|
|
16063
16001
|
if (!row.effects_status) {
|
|
16064
16002
|
return;
|
|
16065
16003
|
}
|
|
16066
|
-
const completedAt =
|
|
16004
|
+
const completedAt = new Date(row.effects_completed_at ?? row.finished_at);
|
|
16067
16005
|
if (row.effects_status === "succeeded") {
|
|
16068
16006
|
return {
|
|
16069
16007
|
status: "succeeded",
|
|
@@ -16073,54 +16011,29 @@ function toActionRunEffectsRecord(row) {
|
|
|
16073
16011
|
return {
|
|
16074
16012
|
status: "failed",
|
|
16075
16013
|
completedAt,
|
|
16076
|
-
error:
|
|
16077
|
-
};
|
|
16078
|
-
}
|
|
16079
|
-
function toWritebackRecord(input, completedAt) {
|
|
16080
|
-
if (input.status === "succeeded") {
|
|
16081
|
-
return {
|
|
16082
|
-
status: "succeeded",
|
|
16083
|
-
completedAt,
|
|
16084
|
-
result: input.result
|
|
16085
|
-
};
|
|
16086
|
-
}
|
|
16087
|
-
return {
|
|
16088
|
-
status: "failed",
|
|
16089
|
-
completedAt,
|
|
16090
|
-
error: input.error
|
|
16091
|
-
};
|
|
16092
|
-
}
|
|
16093
|
-
function toEffectsRecord(input, completedAt) {
|
|
16094
|
-
if (input.status === "succeeded") {
|
|
16095
|
-
return {
|
|
16096
|
-
status: "succeeded",
|
|
16097
|
-
completedAt
|
|
16098
|
-
};
|
|
16099
|
-
}
|
|
16100
|
-
return {
|
|
16101
|
-
status: "failed",
|
|
16102
|
-
completedAt,
|
|
16103
|
-
error: input.error
|
|
16014
|
+
error: parseActionRunFailure(row.effects_error, "effects")
|
|
16104
16015
|
};
|
|
16105
16016
|
}
|
|
16106
16017
|
function rowToActionRunRecord(row) {
|
|
16107
|
-
|
|
16018
|
+
const writeback = toActionRunWritebackRecord(row);
|
|
16019
|
+
const effects = toActionRunEffectsRecord(row);
|
|
16020
|
+
const fields = {
|
|
16108
16021
|
id: row.id,
|
|
16109
16022
|
projectId: row.project_id,
|
|
16110
16023
|
executionId: row.execution_id,
|
|
16111
16024
|
actionId: row.action_id,
|
|
16112
16025
|
subject: rowToActionSubject(row),
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
16117
|
-
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
16026
|
+
phase: row.phase,
|
|
16027
|
+
startedAt: new Date(row.started_at),
|
|
16028
|
+
finishedAt: new Date(row.finished_at),
|
|
16118
16029
|
params: row.params,
|
|
16119
16030
|
idempotencyKey: row.idempotency_key,
|
|
16120
|
-
writeback:
|
|
16121
|
-
effects:
|
|
16122
|
-
error: toActionRunFailure(row)
|
|
16031
|
+
...writeback === undefined ? {} : { writeback },
|
|
16032
|
+
...effects === undefined ? {} : { effects }
|
|
16123
16033
|
};
|
|
16034
|
+
if (row.status === "succeeded")
|
|
16035
|
+
return { ...fields, status: "succeeded" };
|
|
16036
|
+
return { ...fields, status: "failed", error: parseActionRunFailure(row.error) };
|
|
16124
16037
|
}
|
|
16125
16038
|
function rowToActionSubject(row) {
|
|
16126
16039
|
if (row.subject_kind === "none") {
|
|
@@ -21065,4 +20978,4 @@ export {
|
|
|
21065
20978
|
migratePostgresStorage
|
|
21066
20979
|
};
|
|
21067
20980
|
|
|
21068
|
-
//# debugId=
|
|
20981
|
+
//# debugId=971E14D4D5198C8B64756E2164756E21
|