@tenetkit/pg 0.28.0

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 (48) hide show
  1. package/README.md +3 -0
  2. package/dist/postgres/claims.d.ts +26 -0
  3. package/dist/postgres/claims.js +95 -0
  4. package/dist/postgres/event-stream.d.ts +20 -0
  5. package/dist/postgres/event-stream.js +16 -0
  6. package/dist/postgres/index.d.ts +3 -0
  7. package/dist/postgres/index.js +3 -0
  8. package/dist/postgres/locks.d.ts +18 -0
  9. package/dist/postgres/locks.js +51 -0
  10. package/dist/postgres/pg-helpers.d.ts +91 -0
  11. package/dist/postgres/pg-helpers.js +359 -0
  12. package/dist/postgres/run-schema.d.ts +23 -0
  13. package/dist/postgres/run-schema.js +92 -0
  14. package/dist/postgres/runtime-layer.d.ts +15 -0
  15. package/dist/postgres/runtime-layer.js +28 -0
  16. package/dist/postgres/schema.d.ts +6 -0
  17. package/dist/postgres/schema.js +274 -0
  18. package/dist/postgres/session-cancellation.d.ts +11 -0
  19. package/dist/postgres/session-cancellation.js +15 -0
  20. package/dist/postgres/session-storage.d.ts +32 -0
  21. package/dist/postgres/session-storage.js +63 -0
  22. package/dist/postgres/session-store.d.ts +24 -0
  23. package/dist/postgres/session-store.js +388 -0
  24. package/dist/postgres/store-admit.d.ts +21 -0
  25. package/dist/postgres/store-admit.js +87 -0
  26. package/dist/postgres/store-cancel.d.ts +14 -0
  27. package/dist/postgres/store-cancel.js +73 -0
  28. package/dist/postgres/store-claims.d.ts +17 -0
  29. package/dist/postgres/store-claims.js +72 -0
  30. package/dist/postgres/store-fan-out.d.ts +21 -0
  31. package/dist/postgres/store-fan-out.js +17 -0
  32. package/dist/postgres/store-inspection.d.ts +12 -0
  33. package/dist/postgres/store-inspection.js +44 -0
  34. package/dist/postgres/store-messaging.d.ts +14 -0
  35. package/dist/postgres/store-messaging.js +12 -0
  36. package/dist/postgres/store-model-response.d.ts +18 -0
  37. package/dist/postgres/store-model-response.js +156 -0
  38. package/dist/postgres/store-ops.d.ts +21 -0
  39. package/dist/postgres/store-ops.js +328 -0
  40. package/dist/postgres/store-program.d.ts +18 -0
  41. package/dist/postgres/store-program.js +36 -0
  42. package/dist/postgres/store-suspend.d.ts +13 -0
  43. package/dist/postgres/store-suspend.js +87 -0
  44. package/dist/postgres/store.d.ts +7 -0
  45. package/dist/postgres/store.js +373 -0
  46. package/dist/postgres/transaction-events.d.ts +15 -0
  47. package/dist/postgres/transaction-events.js +23 -0
  48. package/package.json +54 -0
@@ -0,0 +1,328 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { OperationResolutionConflict, RunNotFound, RunTerminal, RuntimeUnavailable, } from "tenetkit/runtime/driver/errors";
4
+ import { OperationResolution, digest as resolutionDigest } from "tenetkit/runtime/driver/operation-resolution";
5
+ import { isTerminal } from "tenetkit/runtime/driver/run";
6
+ import { ExecutionCheckpoint } from "tenetkit/runtime/driver/execution-state";
7
+ import { decodeJson, encodeExecutableRef, encodeJson, encodeJsonValue } from "tenetkit/runtime/driver/sql/codecs";
8
+ import { canBlindRetry } from "tenetkit/runtime/driver/sql/operations";
9
+ import { appendEvent, toOperationRecord } from "./pg-helpers.js";
10
+ import { encodeContinuation } from "tenetkit/runtime/driver/steering";
11
+ import { checkpointRef } from "tenetkit/runtime/driver/executable-manifest";
12
+ import { getProgramOperation, resolveProgramOperation } from "tenetkit/runtime/driver/sql/store-program";
13
+ import { settleAdmittedCancellation } from "tenetkit/runtime/driver/sql/store-control";
14
+ import { postgresModelResponseOperations } from "./store-model-response.js";
15
+ import { appendHandoffSessionEntry, verifyHandoffSessionEntry } from "./session-store.js";
16
+ import { handoffSessionEntry, isHandoffCommit, sameHandoffCheckpoint, sameHandoffCommit, } from "tenetkit/runtime/driver/handoff-session";
17
+ import { lockRunHierarchy } from "./locks.js";
18
+ export const postgresOperations = (input) => {
19
+ const { sql, hub, run, runNoTxn, requireRun, requireClaim, nextId } = input;
20
+ const fenced = (claim, effect) => run(sql `SELECT run_id FROM baton_runs WHERE run_id = ${claim.runId} FOR UPDATE`.pipe(Effect.andThen(requireClaim(claim)), Effect.andThen(effect)));
21
+ return {
22
+ recordOperation: (op) => fenced(op, Effect.gen(function* () {
23
+ const loaded = yield* requireRun(op.runId);
24
+ if (isTerminal(loaded.status)) {
25
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
26
+ }
27
+ const existing = yield* sql `
28
+ SELECT * FROM baton_run_operations
29
+ WHERE run_id = ${op.runId} AND operation_key = ${op.operationKey}
30
+ `;
31
+ const prior = existing[0];
32
+ if (prior !== undefined) {
33
+ const consumed = yield* sql `
34
+ SELECT entry_id FROM baton_run_steering
35
+ WHERE run_id = ${op.runId} AND consumed_operation_id = ${prior.operation_id}
36
+ ORDER BY sequence
37
+ `;
38
+ const retried = op.steeringEntryIds ?? [];
39
+ if (consumed.length !== retried.length ||
40
+ consumed.some((entry, index) => entry.entry_id !== retried[index])) {
41
+ return yield* RuntimeUnavailable.make({ message: "steering consumption does not match operation" });
42
+ }
43
+ return toOperationRecord(prior);
44
+ }
45
+ const steeringEntryIds = op.steeringEntryIds ?? [];
46
+ const pending = yield* sql `
47
+ SELECT entry_id FROM baton_run_steering
48
+ WHERE run_id = ${op.runId} AND consumed_operation_id IS NULL AND discarded_reason IS NULL
49
+ ORDER BY sequence
50
+ `;
51
+ const selected = pending.slice(0, steeringEntryIds.length);
52
+ if (selected.length !== steeringEntryIds.length ||
53
+ selected.some((entry, index) => entry.entry_id !== steeringEntryIds[index])) {
54
+ return yield* RuntimeUnavailable.make({ message: "steering entries are not the pending prefix" });
55
+ }
56
+ const operationId = yield* nextId("op");
57
+ const executableRef = yield* Effect.try({
58
+ try: () => checkpointRef(loaded.executableRef, loaded.executableManifest, op.checkpoint),
59
+ catch: (error) => RuntimeUnavailable.make({ message: String(error) }),
60
+ });
61
+ yield* sql `
62
+ INSERT INTO baton_run_operations (
63
+ run_id, operation_id, operation_key, kind, status, input_digest, input_json,
64
+ result_json, error_json, replay_policy, attempt, owner_worker_id, lease_expires_at, started_at, finished_at
65
+ ) VALUES (
66
+ ${op.runId}, ${operationId}, ${op.operationKey}, ${op.kind}, 'requested',
67
+ ${op.inputDigest}, ${encodeJsonValue(op.input)}, NULL, NULL, ${op.replayPolicy},
68
+ ${op.attempt}, NULL, NULL, NULL, NULL
69
+ )
70
+ `;
71
+ if (op.checkpoint !== undefined || op.continuation !== undefined) {
72
+ yield* sql `
73
+ UPDATE baton_runs SET
74
+ driver_checkpoint_json = COALESCE(${op.checkpoint === undefined ? null : encodeJson(ExecutionCheckpoint, op.checkpoint)}, driver_checkpoint_json),
75
+ executable_ref_json = ${encodeExecutableRef(executableRef)},
76
+ continuation_json = CASE WHEN ${op.continuation === undefined ? 0 : 1} = 1
77
+ THEN ${op.continuation === null || op.continuation === undefined ? null : encodeContinuation(op.continuation)}
78
+ ELSE continuation_json END
79
+ WHERE run_id = ${op.runId}
80
+ `;
81
+ }
82
+ for (const entryId of steeringEntryIds) {
83
+ yield* sql `
84
+ UPDATE baton_run_steering SET consumed_operation_id = ${operationId}
85
+ WHERE run_id = ${op.runId} AND entry_id = ${entryId}
86
+ AND consumed_operation_id IS NULL AND discarded_reason IS NULL
87
+ `;
88
+ }
89
+ if (steeringEntryIds.length > 0) {
90
+ yield* appendEvent(hub, yield* requireRun(op.runId), {
91
+ _tag: "SteeringConsumed",
92
+ entryIds: steeringEntryIds,
93
+ operationId,
94
+ });
95
+ }
96
+ for (const event of op.steeringEvents ?? []) {
97
+ yield* appendEvent(hub, yield* requireRun(op.runId), event);
98
+ }
99
+ const rows = yield* sql `
100
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${operationId}
101
+ `;
102
+ return toOperationRecord(rows[0]);
103
+ })),
104
+ startOperation: (op) => fenced(op, Effect.gen(function* () {
105
+ yield* requireRun(op.runId);
106
+ yield* sql `
107
+ UPDATE baton_run_operations
108
+ SET status = 'running', started_at = NOW()
109
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'requested'
110
+ `;
111
+ const rows = yield* sql `
112
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
113
+ `;
114
+ const row = rows[0];
115
+ if (row === undefined)
116
+ return yield* RuntimeUnavailable.make({ message: "operation missing" });
117
+ return toOperationRecord(row);
118
+ })),
119
+ completeOperation: (op) => fenced(op, Effect.gen(function* () {
120
+ const loaded = yield* requireRun(op.runId);
121
+ const existing = yield* sql `
122
+ SELECT * FROM baton_run_operations
123
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
124
+ FOR UPDATE
125
+ `;
126
+ const row = existing[0];
127
+ if (row === undefined)
128
+ return yield* RuntimeUnavailable.make({ message: "operation missing" });
129
+ const current = toOperationRecord(row);
130
+ if (row.status === "succeeded" || row.status === "failed" || row.status === "unknown") {
131
+ if (current.kind === "handoff" && current.status === "succeeded" && isHandoffCommit(current.result)) {
132
+ if (op.outcome._tag !== "Succeeded" ||
133
+ !sameHandoffCommit(current.result, op.outcome.value) ||
134
+ !sameHandoffCheckpoint(loaded.driverCheckpoint, op.checkpoint)) {
135
+ return yield* RuntimeUnavailable.make({ message: "handoff operation has a divergent completion retry" });
136
+ }
137
+ const entry = handoffSessionEntry({
138
+ sessionId: loaded.message.sessionId,
139
+ operationKey: current.operationKey,
140
+ value: op.outcome.value,
141
+ });
142
+ if (Schema.is(RuntimeUnavailable)(entry))
143
+ return yield* entry;
144
+ yield* verifyHandoffSessionEntry(entry).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: error.message })));
145
+ }
146
+ return current;
147
+ }
148
+ for (const entryId of new Set(op.steeringEntryIds ?? [])) {
149
+ const rows = yield* sql `
150
+ SELECT consumed_operation_id FROM baton_run_steering
151
+ WHERE run_id = ${op.runId} AND entry_id = ${entryId}
152
+ `;
153
+ if (rows[0]?.consumed_operation_id !== op.operationId) {
154
+ return yield* RuntimeUnavailable.make({
155
+ message: `steering entry ${entryId} does not belong to operation`,
156
+ });
157
+ }
158
+ }
159
+ const executableRef = yield* Effect.try({
160
+ try: () => checkpointRef(loaded.executableRef, loaded.executableManifest, op.checkpoint),
161
+ catch: (error) => RuntimeUnavailable.make({ message: String(error) }),
162
+ });
163
+ if (row.kind === "handoff" && op.outcome._tag === "Succeeded" && isHandoffCommit(op.outcome.value)) {
164
+ const entry = handoffSessionEntry({
165
+ sessionId: loaded.message.sessionId,
166
+ operationKey: row.operation_key,
167
+ value: op.outcome.value,
168
+ });
169
+ if (Schema.is(RuntimeUnavailable)(entry))
170
+ return yield* entry;
171
+ yield* appendHandoffSessionEntry(entry).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: error.message })));
172
+ }
173
+ if (op.outcome._tag === "Succeeded") {
174
+ yield* sql `
175
+ UPDATE baton_run_operations
176
+ SET status = 'succeeded', result_json = ${encodeJsonValue(op.outcome.value)}, finished_at = NOW()
177
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
178
+ AND status IN ('requested', 'running')
179
+ `;
180
+ }
181
+ else if (op.outcome._tag === "Failed") {
182
+ yield* sql `
183
+ UPDATE baton_run_operations
184
+ SET status = 'failed', error_json = ${encodeJsonValue(op.outcome.error)}, finished_at = NOW()
185
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
186
+ AND status IN ('requested', 'running')
187
+ `;
188
+ }
189
+ else {
190
+ yield* sql `
191
+ UPDATE baton_run_operations SET status = 'unknown', finished_at = NOW()
192
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
193
+ AND status IN ('requested', 'running')
194
+ `;
195
+ }
196
+ yield* sql `
197
+ UPDATE baton_runs SET
198
+ driver_checkpoint_json = COALESCE(${op.checkpoint === undefined ? null : encodeJson(ExecutionCheckpoint, op.checkpoint)}, driver_checkpoint_json),
199
+ executable_ref_json = ${encodeExecutableRef(executableRef)},
200
+ continuation_json = CASE WHEN ${op.continuation === undefined ? 0 : 1} = 1
201
+ THEN ${op.continuation === null || op.continuation === undefined ? null : encodeContinuation(op.continuation)}
202
+ ELSE continuation_json END,
203
+ updated_at = NOW()
204
+ WHERE run_id = ${op.runId}
205
+ `;
206
+ if (op.outcome._tag === "Unknown") {
207
+ yield* appendEvent(hub, yield* requireRun(op.runId), { _tag: "OperationUnknown", operationId: op.operationId }, loaded.cancellationRequested ? "cancelling" : "needs-resolution");
208
+ }
209
+ const rows = yield* sql `
210
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
211
+ `;
212
+ return toOperationRecord(rows[0]);
213
+ })),
214
+ ...postgresModelResponseOperations(input),
215
+ expireRunningOperation: (op) => fenced(op, Effect.gen(function* () {
216
+ const loaded = yield* requireRun(op.runId);
217
+ const rows = yield* sql `
218
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
219
+ `;
220
+ const row = rows[0];
221
+ if (row === undefined)
222
+ return yield* RuntimeUnavailable.make({ message: "operation missing" });
223
+ if (row.status !== "running") {
224
+ return { record: toOperationRecord(row), outcome: row.status };
225
+ }
226
+ if (canBlindRetry(row.replay_policy)) {
227
+ yield* sql `
228
+ UPDATE baton_run_operations
229
+ SET status = 'requested', started_at = NULL, finished_at = NULL, owner_worker_id = NULL, lease_expires_at = NULL
230
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'running'
231
+ `;
232
+ const next = yield* sql `
233
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
234
+ `;
235
+ return { record: toOperationRecord(next[0]), outcome: "retried" };
236
+ }
237
+ yield* sql `
238
+ UPDATE baton_run_operations SET status = 'unknown', finished_at = NOW()
239
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'running'
240
+ `;
241
+ yield* appendEvent(hub, loaded, { _tag: "OperationUnknown", operationId: op.operationId }, loaded.cancellationRequested ? "cancelling" : "needs-resolution");
242
+ const next = yield* sql `
243
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
244
+ `;
245
+ return { record: toOperationRecord(next[0]), outcome: "unknown" };
246
+ })),
247
+ getOperation: (op) => runNoTxn(Effect.gen(function* () {
248
+ yield* requireRun(op.runId);
249
+ const rows = yield* sql `
250
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
251
+ `;
252
+ const row = rows[0];
253
+ if (row === undefined)
254
+ return yield* RuntimeUnavailable.make({ message: "operation missing" });
255
+ return toOperationRecord(row);
256
+ })),
257
+ getOperationByKey: (op) => runNoTxn(Effect.gen(function* () {
258
+ yield* requireRun(op.runId);
259
+ const rows = yield* sql `
260
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_key = ${op.operationKey}
261
+ `;
262
+ return rows[0] === undefined ? undefined : toOperationRecord(rows[0]);
263
+ })),
264
+ resolveOperation: (op) => run(Effect.gen(function* () {
265
+ yield* lockRunHierarchy(op.runId);
266
+ const loaded = yield* requireRun(op.runId);
267
+ const program = yield* getProgramOperation({ runId: op.runId, operation: op.operationId });
268
+ if (program !== undefined) {
269
+ yield* resolveProgramOperation(op, "queued", true);
270
+ yield* settleAdmittedCancellation(hub, op.runId);
271
+ return;
272
+ }
273
+ const rows = yield* sql `
274
+ SELECT * FROM baton_run_operations
275
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
276
+ FOR UPDATE
277
+ `;
278
+ const row = rows[0];
279
+ const resolutionJson = encodeJsonValue(op.resolution);
280
+ const conflict = () => OperationResolutionConflict.make({
281
+ runId: op.runId,
282
+ operationId: op.operationId,
283
+ idempotencyKey: op.idempotencyKey,
284
+ });
285
+ if (row === undefined)
286
+ return yield* conflict();
287
+ if (row.resolution_idempotency_key !== null) {
288
+ const priorResolution = row.resolution_json === null ? undefined : decodeJson(OperationResolution, row.resolution_json);
289
+ if (row.resolution_idempotency_key === op.idempotencyKey &&
290
+ priorResolution !== undefined &&
291
+ resolutionDigest(priorResolution) === resolutionDigest(op.resolution))
292
+ return;
293
+ return yield* conflict();
294
+ }
295
+ if (loaded.status !== "needs-resolution" || row.status !== "unknown")
296
+ return yield* conflict();
297
+ if (op.resolution._tag === "Succeeded") {
298
+ yield* sql `
299
+ UPDATE baton_run_operations SET status = 'succeeded', result_json = ${encodeJsonValue(op.resolution.value)},
300
+ resolution_idempotency_key = ${op.idempotencyKey}, resolution_json = ${resolutionJson},
301
+ owner_worker_id = NULL, lease_expires_at = NULL, finished_at = NOW()
302
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'unknown'
303
+ `;
304
+ }
305
+ else if (op.resolution._tag === "Failed") {
306
+ yield* sql `
307
+ UPDATE baton_run_operations SET status = 'failed', error_json = ${encodeJsonValue(op.resolution.error)},
308
+ resolution_idempotency_key = ${op.idempotencyKey}, resolution_json = ${resolutionJson},
309
+ owner_worker_id = NULL, lease_expires_at = NULL, finished_at = NOW()
310
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'unknown'
311
+ `;
312
+ }
313
+ else {
314
+ yield* sql `
315
+ UPDATE baton_run_operations SET status = 'requested', result_json = NULL, error_json = NULL,
316
+ resolution_idempotency_key = ${op.idempotencyKey}, resolution_json = ${resolutionJson},
317
+ owner_worker_id = NULL, lease_expires_at = NULL, started_at = NULL, finished_at = NULL
318
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'unknown'
319
+ `;
320
+ }
321
+ yield* sql `
322
+ UPDATE baton_runs SET status = CASE WHEN cancellation_requested THEN 'cancelling' ELSE 'queued' END, owner_worker_id = NULL, lease_expires_at = NULL, updated_at = NOW()
323
+ WHERE run_id = ${op.runId} AND status = 'needs-resolution'
324
+ `;
325
+ yield* settleAdmittedCancellation(hub, op.runId);
326
+ })),
327
+ };
328
+ };
@@ -0,0 +1,18 @@
1
+ import { Effect } from "effect";
2
+ import type { PgClient } from "@effect/sql-pg";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import { RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
5
+ import type { Interface as RunStore } from "tenetkit/runtime/driver/run-store";
6
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
7
+ import type { WithoutSqlError } from "tenetkit/runtime/driver/sql/sql-effect";
8
+ import type { SqlError } from "effect/unstable/sql/SqlError";
9
+ type SqlR = SqlClient.SqlClient | PgClient.PgClient;
10
+ export type Run = <A, E>(effect: Effect.Effect<A, E | SqlError, SqlR>) => Effect.Effect<A, WithoutSqlError<E | SqlError> | RuntimeUnavailable>;
11
+ export declare const programStoreMethods: (input: {
12
+ readonly sql: SqlClient.SqlClient;
13
+ readonly hub: EventHub;
14
+ readonly run: Run;
15
+ readonly runNoTxn: Run;
16
+ readonly lockRunHierarchy: (runId: string) => Effect.Effect<void, SqlError, SqlClient.SqlClient>;
17
+ }) => Pick<RunStore, "reserveProgramOperation" | "admitProgramChild" | "admitProgramChildAndSuspend" | "admitProgramAgents" | "settleProgramOperation" | "suspendProgramOperation" | "startProgramOperation" | "loadProgramState" | "completeProgram" | "getProgramOperation" | "commitProgramLog">;
18
+ export {};
@@ -0,0 +1,36 @@
1
+ import { Effect } from "effect";
2
+ import { ProgramCapabilities } from "tenetkit";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import { RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
5
+ import { requireExecutionClaim } from "tenetkit/runtime/driver/sql/store-execution";
6
+ import { loadProgramState, getProgramOperation, reserveProgramOperation, admitProgramAgents, commitProgramLog, settleProgramOperation, suspendProgramOperation, startProgramOperation, } from "tenetkit/runtime/driver/sql/store-program";
7
+ import { completeRun, requireRun } from "./pg-helpers.js";
8
+ import { suspend } from "./store-suspend.js";
9
+ import { admitProgramChild } from "tenetkit/runtime/driver/sql/store-admit";
10
+ export const programStoreMethods = (input) => {
11
+ const fenced = (claim, effect) => input.run(input.sql `SELECT run_id FROM baton_runs WHERE run_id = ${claim.runId} FOR UPDATE`.pipe(Effect.andThen(requireExecutionClaim(claim)), Effect.andThen(effect)));
12
+ return {
13
+ admitProgramChild: (operation) => fenced(operation, admitProgramChild(input.hub, operation)),
14
+ admitProgramChildAndSuspend: (operation) => fenced(operation, admitProgramChild(input.hub, operation).pipe(Effect.tap(() => suspend(input.hub, operation)))),
15
+ reserveProgramOperation: (operation) => fenced(operation, reserveProgramOperation(operation)),
16
+ admitProgramAgents: (operation) => fenced(operation, admitProgramAgents(input.hub, operation, suspend)),
17
+ suspendProgramOperation: (operation) => fenced(operation, suspendProgramOperation(input.hub, operation, suspend)),
18
+ settleProgramOperation: (operation) => fenced(operation, settleProgramOperation(input.hub, operation)),
19
+ startProgramOperation: (operation) => fenced(operation, startProgramOperation(operation)),
20
+ loadProgramState: (runId) => input.runNoTxn(requireRun(runId).pipe(Effect.andThen(loadProgramState(runId)))),
21
+ getProgramOperation: (operation) => input.runNoTxn(requireRun(operation.runId).pipe(Effect.andThen(getProgramOperation(operation)))),
22
+ commitProgramLog: (operation) => fenced(operation, commitProgramLog(input.hub, operation)),
23
+ completeProgram: (operation) => input.run(Effect.gen(function* () {
24
+ yield* input.lockRunHierarchy(operation.runId);
25
+ yield* requireExecutionClaim(operation);
26
+ if (operation.outputBytes > operation.outputLimit)
27
+ return yield* ProgramCapabilities.ProgramBudgetExhausted.make({
28
+ dimension: "outputBytes",
29
+ limit: operation.outputLimit,
30
+ });
31
+ const loaded = yield* requireRun(operation.runId);
32
+ yield* completeRun(input.hub, loaded, { _tag: "Program", value: operation.output });
33
+ return { _tag: "Completed" };
34
+ })),
35
+ };
36
+ };
@@ -0,0 +1,13 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { RunNotFound, RunTerminal, RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
4
+ import { StaleClaim } from "tenetkit/runtime/driver/sql/errors";
5
+ import type { SqlError } from "effect/unstable/sql/SqlError";
6
+ import type { Interface as RunStoreInterface } from "tenetkit/runtime/driver/run-store";
7
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
8
+ type SuspendEffect = Effect.Effect<undefined, RunNotFound | RunTerminal | RuntimeUnavailable | SqlError | StaleClaim, SqlClient.SqlClient>;
9
+ export declare const suspend: {
10
+ (input: Parameters<RunStoreInterface["suspend"]>[0]): (hub: EventHub) => SuspendEffect;
11
+ (hub: EventHub, input: Parameters<RunStoreInterface["suspend"]>[0]): SuspendEffect;
12
+ };
13
+ export {};
@@ -0,0 +1,87 @@
1
+ import { Effect, Function, Option } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { RunNotFound, RunTerminal, RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
4
+ import { StaleClaim } from "tenetkit/runtime/driver/sql/errors";
5
+ import { checkpointRef } from "tenetkit/runtime/driver/executable-manifest";
6
+ import { isTerminal } from "tenetkit/runtime/driver/run";
7
+ import { encodeContinuation } from "tenetkit/runtime/driver/steering";
8
+ import { encodeExecutableRef, encodeJson } from "tenetkit/runtime/driver/sql/codecs";
9
+ import { encodeReason, WaitResolution } from "tenetkit/runtime/driver/run-wait";
10
+ import { lockRun } from "./locks.js";
11
+ import { appendEvent, requireRun } from "./pg-helpers.js";
12
+ import { requireExecutionClaim } from "tenetkit/runtime/driver/sql/store-execution";
13
+ import { groupIdFromSuspension, resultFromInspection } from "tenetkit/runtime/driver/child-group";
14
+ import { inspectFanOut } from "tenetkit/runtime/driver/sql/store-fan-out";
15
+ import { ExecutionCheckpoint, ExecutionSuspension } from "tenetkit/runtime/driver/execution-state";
16
+ import { loadTerminalEvent, reconcileChildWaitWith } from "tenetkit/runtime/driver/sql/store-child-settlement";
17
+ export const suspend = Function.dual(2, (hub, input) => Effect.gen(function* () {
18
+ const sql = yield* SqlClient.SqlClient;
19
+ yield* lockRun(input.runId);
20
+ yield* requireExecutionClaim(input);
21
+ const loaded = yield* requireRun(input.runId);
22
+ if (isTerminal(loaded.status)) {
23
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
24
+ }
25
+ if (loaded.cancellationRequested)
26
+ return;
27
+ const executableRef = yield* Effect.try({
28
+ try: () => checkpointRef(loaded.executableRef, loaded.executableManifest, input.checkpoint),
29
+ catch: (error) => RuntimeUnavailable.make({ message: String(error) }),
30
+ });
31
+ yield* sql `
32
+ UPDATE baton_runs SET
33
+ driver_checkpoint_json = COALESCE(${input.checkpoint === undefined ? null : encodeJson(ExecutionCheckpoint, input.checkpoint)}, driver_checkpoint_json),
34
+ executable_ref_json = ${encodeExecutableRef(executableRef)},
35
+ suspension_json = ${encodeJson(ExecutionSuspension, input.suspension)},
36
+ continuation_json = CASE WHEN ${input.continuation === undefined ? 0 : 1} = 1
37
+ THEN ${input.continuation === null || input.continuation === undefined ? null : encodeContinuation(input.continuation)}
38
+ ELSE continuation_json END,
39
+ updated_at = NOW()
40
+ WHERE run_id = ${input.runId}
41
+ `;
42
+ yield* sql `
43
+ INSERT INTO baton_run_waits (
44
+ run_id, wait_id, reason, status, response_json, due_at, owner_worker_id, lease_expires_at, opened_at, closed_at
45
+ ) VALUES (
46
+ ${loaded.runId}, ${input.wait.waitId}, ${encodeReason(input.wait.reason)}, 'open', NULL, NULL, NULL, NULL, NOW(), NULL
47
+ )
48
+ ON CONFLICT (run_id, wait_id) DO UPDATE SET
49
+ status = 'open', reason = EXCLUDED.reason, response_json = NULL, opened_at = EXCLUDED.opened_at, closed_at = NULL
50
+ `;
51
+ yield* appendEvent(hub, loaded, { _tag: "RunWaiting", wait: input.wait }, "waiting");
52
+ const child = typeof input.suspension.token === "string"
53
+ ? yield* requireRun(input.suspension.token).pipe(Effect.option)
54
+ : Option.none();
55
+ if (child._tag === "Some" && child.value.terminalEventId !== undefined) {
56
+ const terminalEvent = yield* loadTerminalEvent(child.value.terminalEventId);
57
+ if (terminalEvent !== undefined) {
58
+ yield* reconcileChildWaitWith({
59
+ hub,
60
+ parent: yield* requireRun(loaded.runId),
61
+ child: child.value,
62
+ event: terminalEvent,
63
+ append: appendEvent,
64
+ });
65
+ }
66
+ }
67
+ const groupId = groupIdFromSuspension(input.suspension);
68
+ if (groupId !== undefined) {
69
+ const rows = yield* sql `
70
+ SELECT parent_run_id, status FROM baton_fan_outs WHERE fan_out_id = ${groupId}
71
+ `;
72
+ const group = rows[0];
73
+ if (group?.parent_run_id === loaded.runId && group.status !== "running") {
74
+ const resolution = {
75
+ _tag: "Signal",
76
+ name: input.wait.waitId,
77
+ payload: resultFromInspection(yield* inspectFanOut(groupId).pipe(Effect.mapError(() => RuntimeUnavailable.make({ message: `child group ${groupId} disappeared` })))),
78
+ };
79
+ yield* sql `
80
+ UPDATE baton_run_waits SET status = 'signaled', response_json = ${encodeJson(WaitResolution, resolution)}, closed_at = NOW()
81
+ WHERE run_id = ${loaded.runId} AND wait_id = ${input.wait.waitId} AND status = 'open'
82
+ `;
83
+ yield* appendEvent(hub, yield* requireRun(loaded.runId), { _tag: "RunResumed", waitId: input.wait.waitId, resolution }, "running");
84
+ }
85
+ }
86
+ yield* sql `UPDATE baton_runs SET owner_worker_id = NULL, lease_expires_at = NULL WHERE run_id = ${loaded.runId}`;
87
+ }));
@@ -0,0 +1,7 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import type { PostgresStoreOptions } from "./runtime-layer.js";
4
+ export declare const makePostgresServices: (options: PostgresStoreOptions) => Effect.Effect<{
5
+ store: import("tenetkit/runtime/driver/run-store").Interface;
6
+ claims: import("tenetkit/runtime/driver/sql/run-claims").Interface;
7
+ }, import("tenetkit/runtime/driver/sql/errors").SchemaChecksumMismatch | import("tenetkit/runtime/driver/sql/errors").SchemaDirty | import("tenetkit/runtime/driver/sql/errors").SchemaMigrationFailed | import("tenetkit/runtime/driver/sql/errors").SchemaUpgradeRequired | import("tenetkit/runtime/driver/sql/errors").SchemaVersionUnsupported | import("effect/unstable/sql/SqlError").SqlError, import("effect/Scope").Scope | SqlClient.SqlClient>;