@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,72 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { AgentExecutionFailure, RunNotFound, RunTerminal, RuntimeUnavailable, failureMessage, } from "tenetkit/runtime/driver/errors";
4
+ import { isTerminal } from "tenetkit/runtime/driver/run";
5
+ import { StaleClaim } from "tenetkit/runtime/driver/sql/errors";
6
+ import { claimReadyRuns, refreshLease, releaseClaim } from "./claims.js";
7
+ import { RunClaims } from "tenetkit/runtime/driver/sql/run-claims";
8
+ import { afterTerminal, appendEvent, completeRun, loadEventsAfter, loadRun, settleParent } from "./pg-helpers.js";
9
+ import { lockRunHierarchy } from "./locks.js";
10
+ export const makePostgresClaims = (input) => {
11
+ const { hub, run, cancelRun } = input;
12
+ return RunClaims.of({
13
+ claimReadyRuns: (claimInput) => run(Effect.gen(function* () {
14
+ const claimed = yield* claimReadyRuns({
15
+ workerId: claimInput.workerId,
16
+ limit: claimInput.limit,
17
+ lease: claimInput.lease ?? "30 seconds",
18
+ });
19
+ for (const item of claimed) {
20
+ const fresh = (yield* loadRun(item.run.runId));
21
+ const events = yield* loadEventsAfter(item.run.runId, -1);
22
+ const hasAttempt = events.some((event) => event._tag === "RunAttemptStarted" && event.attempt === fresh.attempt);
23
+ if (!hasAttempt && fresh.attempt > 0) {
24
+ yield* appendEvent(hub, fresh, { _tag: "RunAttemptStarted", attempt: fresh.attempt }, "running");
25
+ }
26
+ }
27
+ return claimed;
28
+ })),
29
+ refreshLease: (leaseInput) => run(refreshLease({
30
+ runId: leaseInput.runId,
31
+ workerId: leaseInput.workerId,
32
+ attemptFence: leaseInput.attemptFence,
33
+ lease: leaseInput.lease ?? "30 seconds",
34
+ })),
35
+ releaseClaim: (releaseInput) => run(releaseClaim({
36
+ runId: releaseInput.runId,
37
+ workerId: releaseInput.workerId,
38
+ attemptFence: releaseInput.attemptFence,
39
+ })),
40
+ commitWithClaim: (commitInput) => run(Effect.gen(function* () {
41
+ yield* lockRunHierarchy(commitInput.runId);
42
+ const loaded = yield* loadRun(commitInput.runId);
43
+ if (loaded === undefined ||
44
+ loaded.ownerWorkerId !== commitInput.workerId ||
45
+ loaded.attemptFence !== commitInput.attemptFence) {
46
+ return yield* StaleClaim.make({
47
+ runId: commitInput.runId,
48
+ workerId: commitInput.workerId,
49
+ attemptFence: commitInput.attemptFence,
50
+ });
51
+ }
52
+ if (commitInput.transition === "cancel") {
53
+ yield* cancelRun(commitInput.runId, commitInput.reason);
54
+ return;
55
+ }
56
+ if (isTerminal(loaded.status)) {
57
+ return yield* RunTerminal.make({ runId: loaded.runId, status: loaded.status });
58
+ }
59
+ if (commitInput.transition === "complete") {
60
+ yield* completeRun(hub, loaded, commitInput.result);
61
+ return;
62
+ }
63
+ const event = yield* appendEvent(hub, loaded, {
64
+ _tag: "RunFailed",
65
+ error: AgentExecutionFailure.make({ message: failureMessage(commitInput.error?.message ?? "failed") }),
66
+ }, "failed");
67
+ const settled = (yield* loadRun(loaded.runId));
68
+ yield* settleParent(hub, settled, event.eventId);
69
+ yield* afterTerminal(hub, settled);
70
+ })),
71
+ });
72
+ };
@@ -0,0 +1,21 @@
1
+ import { Effect } from "effect";
2
+ import type { PgClient } from "@effect/sql-pg";
3
+ import type { SqlClient } from "effect/unstable/sql";
4
+ import type { Interface as RunStoreInterface } from "tenetkit/runtime/driver/run-store";
5
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
6
+ import type { WithoutSqlError } from "tenetkit/runtime/driver/sql/sql-effect";
7
+ import type { SqlError } from "effect/unstable/sql/SqlError";
8
+ type SqlR = SqlClient.SqlClient | PgClient.PgClient;
9
+ export type Run = <A, E>(effect: Effect.Effect<A, E | SqlError, SqlR>) => Effect.Effect<A, WithoutSqlError<E | SqlError> | import("tenetkit/runtime/driver/errors").RuntimeUnavailable>;
10
+ export declare const fanOutStoreMethods: (input: {
11
+ readonly sql: SqlClient.SqlClient;
12
+ readonly pg: PgClient.PgClient;
13
+ readonly hub: EventHub;
14
+ readonly run: Run;
15
+ readonly runNoTxn: Run;
16
+ }) => Pick<RunStoreInterface, "admitFanOut" | "inspectFanOut">;
17
+ export declare const cancelOwnedFanOuts: {
18
+ (parentRunId: string): (sql: SqlClient.SqlClient) => Effect.Effect<string[], SqlError, never>;
19
+ (sql: SqlClient.SqlClient, parentRunId: string): Effect.Effect<string[], SqlError, never>;
20
+ };
21
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Effect, Function } from "effect";
2
+ import { admitFanOut, inspectFanOut } from "tenetkit/runtime/driver/sql/store-fan-out";
3
+ import { NOTIFY_CHANNEL } from "./schema.js";
4
+ export const fanOutStoreMethods = (input) => ({
5
+ admitFanOut: (fanOut) => input
6
+ .run(input.sql `SELECT run_id FROM baton_runs WHERE run_id = ${fanOut.parentRunId} FOR UPDATE`.pipe(Effect.andThen(input.sql `SELECT pg_advisory_xact_lock(hashtext(${`fanout:${fanOut.parentRunId}:${fanOut.idempotencyKey}`}))`), Effect.andThen(admitFanOut(input.hub, fanOut))))
7
+ .pipe(Effect.tap((receipt) => input.runNoTxn(Effect.forEach([receipt.parentRunId, ...receipt.childRunIds], (runId) => input.pg.notify(NOTIFY_CHANNEL, runId), { discard: true })))),
8
+ inspectFanOut: (fanOutId) => input.runNoTxn(inspectFanOut(fanOutId)),
9
+ });
10
+ export const cancelOwnedFanOuts = Function.dual(2, (sql, parentRunId) => Effect.gen(function* () {
11
+ const owned = yield* sql `
12
+ SELECT m.child_run_id FROM baton_fan_outs f
13
+ JOIN baton_fan_out_members m ON m.fan_out_id = f.fan_out_id
14
+ WHERE f.parent_run_id = ${parentRunId} AND f.status = 'running' ORDER BY m.ordinal ASC
15
+ `;
16
+ return owned.map((row) => row.child_run_id);
17
+ }));
@@ -0,0 +1,12 @@
1
+ import type { PgClient } from "@effect/sql-pg";
2
+ import type { Interface as RunStoreInterface } from "tenetkit/runtime/driver/run-store";
3
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
4
+ import type { RunFn } from "./store-ops.js";
5
+ import type { Run } from "./store-fan-out.js";
6
+ export declare const inspectionStoreMethods: (deps: {
7
+ readonly hub: EventHub;
8
+ readonly pg: PgClient.PgClient;
9
+ readonly run: Run;
10
+ readonly runNoTxn: Run;
11
+ readonly runInspection: RunFn;
12
+ }) => Pick<RunStoreInterface, "inspect" | "snapshot" | "sessionRoots" | "inspectTree" | "history" | "treeHistory" | "treeChanges">;
@@ -0,0 +1,44 @@
1
+ import { Effect, Stream } from "effect";
2
+ import { CursorExpired } from "tenetkit/runtime/driver/errors";
3
+ import { loadRunSnapshot, loadTreeInspection } from "tenetkit/runtime/driver/sql/inspection";
4
+ import { sessionRoots } from "tenetkit/runtime/driver/sql/session-lifecycle";
5
+ import { loadChildReadiness } from "tenetkit/runtime/driver/sql/store-child-capacity";
6
+ import { loadRunWait } from "tenetkit/runtime/driver/sql/store-helpers";
7
+ import { loadTreeHistory } from "tenetkit/runtime/driver/sql/tree-history";
8
+ import { loadEventsAfter, loadRun, requireRun } from "./pg-helpers.js";
9
+ import { NOTIFY_CHANNEL } from "./schema.js";
10
+ export const inspectionStoreMethods = (deps) => ({
11
+ inspect: (runId) => deps.runNoTxn(Effect.gen(function* () {
12
+ const loaded = yield* requireRun(runId);
13
+ const wait = yield* loadRunWait(runId, loaded.activeWaitId);
14
+ const childReadiness = yield* loadChildReadiness(runId);
15
+ return {
16
+ runId: loaded.runId,
17
+ status: loaded.status,
18
+ executableRef: loaded.executableRef,
19
+ executableManifest: loaded.executableManifest,
20
+ depth: loaded.depth,
21
+ treePolicy: loaded.treePolicy,
22
+ lastSequence: loaded.lastSequence,
23
+ durability: "durable",
24
+ ...(loaded.parentRunId === undefined ? {} : { parentRunId: loaded.parentRunId }),
25
+ ...(childReadiness === undefined ? {} : { childReadiness }),
26
+ ...(wait === undefined ? {} : { wait }),
27
+ };
28
+ })),
29
+ snapshot: (runId) => deps.runInspection(loadRunSnapshot(runId)),
30
+ sessionRoots: (sessionId) => deps.runNoTxn(sessionRoots(sessionId)),
31
+ inspectTree: (rootRunId) => deps.runInspection(loadTreeInspection(rootRunId)),
32
+ history: (input) => deps.runNoTxn(Effect.gen(function* () {
33
+ const loaded = yield* requireRun(input.runId);
34
+ if (input.cursor < -1 || input.cursor > loaded.lastSequence) {
35
+ return yield* CursorExpired.make({ runId: input.runId, cursor: input.cursor, earliestSequence: 0 });
36
+ }
37
+ return (yield* loadEventsAfter(input.runId, input.cursor)).slice(0, input.limit);
38
+ })),
39
+ treeHistory: (input) => deps.runNoTxn(loadTreeHistory(input)),
40
+ treeChanges: (rootRunId) => deps.hub.subscribeTree({
41
+ rootRunId,
42
+ onSubscribed: deps.pg.listen(NOTIFY_CHANNEL).pipe(Stream.runForEach((runId) => deps.runNoTxn(loadRun(String(runId))).pipe(Effect.flatMap((loaded) => (loaded?.rootRunId === rootRunId ? deps.hub.wakeTree(rootRunId) : Effect.void)), Effect.ignore)), Effect.ignore),
43
+ }),
44
+ });
@@ -0,0 +1,14 @@
1
+ import { Effect } from "effect";
2
+ import type { SqlClient } from "effect/unstable/sql";
3
+ import type { Interface as RunStoreInterface } from "tenetkit/runtime/driver/run-store";
4
+ import type { SqlError } from "effect/unstable/sql/SqlError";
5
+ import type { RunFn } from "./store-ops.js";
6
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
7
+ /** The addressed-messaging half of the Postgres RunStore, kept beside the store it completes. */
8
+ export declare const messagingStoreMethods: (input: {
9
+ readonly run: RunFn;
10
+ readonly runNoTxn: RunFn;
11
+ readonly hub: EventHub;
12
+ readonly lockRun: (runId: string) => Effect.Effect<void, SqlError, SqlClient.SqlClient>;
13
+ readonly lockMailbox: (targetSessionId: string) => Effect.Effect<void, SqlError, SqlClient.SqlClient>;
14
+ }) => Pick<RunStoreInterface, "directory" | "resolveAddress" | "registerAgentName" | "listRelated" | "admitMessage" | "pendingMessages" | "deliverPendingMessages">;
@@ -0,0 +1,12 @@
1
+ import { Effect } from "effect";
2
+ import { admitMessage, deliverPendingMessages, directory, listRelated, pendingMessages, registerAgentName, resolveAddress, } from "tenetkit/runtime/driver/sql/store-directory";
3
+ /** The addressed-messaging half of the Postgres RunStore, kept beside the store it completes. */
4
+ export const messagingStoreMethods = (input) => ({
5
+ directory: (runId) => input.runNoTxn(directory(runId)),
6
+ resolveAddress: (address) => input.runNoTxn(resolveAddress(address)),
7
+ registerAgentName: (request) => input.run(input.lockRun(request.runId).pipe(Effect.andThen(registerAgentName(request)))),
8
+ listRelated: (runId) => input.runNoTxn(listRelated(runId)),
9
+ admitMessage: (request) => input.run(input.lockMailbox(request.targetSessionId).pipe(Effect.andThen(admitMessage(request)))),
10
+ pendingMessages: (request) => input.runNoTxn(pendingMessages(request)),
11
+ deliverPendingMessages: (request) => input.run(input.lockRun(request.runId).pipe(Effect.andThen(directory(request.runId)), Effect.flatMap((entry) => input.lockMailbox(entry.sessionId)), Effect.andThen(deliverPendingMessages(input.hub, request)))),
12
+ });
@@ -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 type { SqlError } from "effect/unstable/sql/SqlError";
5
+ import { RuntimeUnavailable, type RunNotFound } from "tenetkit/runtime/driver/errors";
6
+ import type { Interface as RunStoreInterface } from "tenetkit/runtime/driver/run-store";
7
+ import type { DecodedRun } from "tenetkit/runtime/driver/sql/rows";
8
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
9
+ import type { RunFn } from "./store-ops.js";
10
+ type SqlR = SqlClient.SqlClient | PgClient.PgClient;
11
+ export declare const postgresModelResponseOperations: (input: {
12
+ readonly sql: SqlClient.SqlClient;
13
+ readonly hub: EventHub;
14
+ readonly run: RunFn;
15
+ readonly requireRun: (runId: string) => Effect.Effect<DecodedRun, RunNotFound | RuntimeUnavailable | SqlError, SqlR>;
16
+ readonly requireClaim: (claim: import("tenetkit/runtime/driver/run-store").ExecutionClaim) => Effect.Effect<void, import("tenetkit/runtime/driver/sql/errors").StaleClaim | RunNotFound | RuntimeUnavailable | SqlError, SqlR>;
17
+ }) => Pick<RunStoreInterface, "commitModelResponse" | "commitInterruptedModelResponse">;
18
+ export {};
@@ -0,0 +1,156 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
4
+ import { appendEvent, loadEventsAfter, toOperationRecord } from "./pg-helpers.js";
5
+ import { encodeExecutableRef, encodeJson, encodeJsonValue } from "tenetkit/runtime/driver/sql/codecs";
6
+ import { ExecutionCheckpoint } from "tenetkit/runtime/driver/execution-state";
7
+ import { encodeContinuation } from "tenetkit/runtime/driver/steering";
8
+ import { checkpointRef } from "tenetkit/runtime/driver/executable-manifest";
9
+ import { sameModelResponseEvent, validateModelResponseCommit } from "tenetkit/runtime/driver/model-response-commit";
10
+ import { sameInterruptedModelOutcome, sameInterruptedModelResponse, validateInterruptedModelResponse, } from "tenetkit/runtime/driver/model-response-interrupted";
11
+ import { appendCompletedSessionEntry, appendInterruptedSessionEntry, verifyCompletedSessionEntry, verifyInterruptedSessionEntry, } from "./session-store.js";
12
+ export const postgresModelResponseOperations = (input) => {
13
+ const { sql, hub, run, requireRun, requireClaim } = input;
14
+ 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)));
15
+ return {
16
+ commitInterruptedModelResponse: (op) => fenced(op, Effect.gen(function* () {
17
+ const loaded = yield* requireRun(op.runId);
18
+ const rows = yield* sql `
19
+ SELECT * FROM baton_run_operations
20
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
21
+ FOR UPDATE
22
+ `;
23
+ const row = rows[0];
24
+ if (row === undefined)
25
+ return yield* RuntimeUnavailable.make({ message: "operation missing" });
26
+ const current = toOperationRecord(row);
27
+ const validated = validateInterruptedModelResponse({
28
+ runId: op.runId,
29
+ sessionId: loaded.message.sessionId,
30
+ record: current,
31
+ outcome: op.outcome,
32
+ event: op.event,
33
+ });
34
+ if (Schema.is(RuntimeUnavailable)(validated))
35
+ return yield* validated;
36
+ const sessionEntry = validated.entry;
37
+ if (current.status === "failed") {
38
+ if (!sameInterruptedModelOutcome({ left: { _tag: "Failed", error: current.error }, right: op.outcome })) {
39
+ return yield* RuntimeUnavailable.make({
40
+ message: `model operation ${op.operationId} has a divergent interrupted outcome retry`,
41
+ });
42
+ }
43
+ const prior = (yield* loadEventsAfter(op.runId, -1)).filter((event) => event._tag === "ModelResponseInterrupted" && event.operationKey === op.event.operationKey);
44
+ if (prior.length !== 1 ||
45
+ prior[0]?._tag !== "ModelResponseInterrupted" ||
46
+ !sameInterruptedModelResponse({ left: prior[0], right: validated.event })) {
47
+ return yield* RuntimeUnavailable.make({
48
+ message: `model operation ${op.operationId} has a divergent interrupted outbox retry`,
49
+ });
50
+ }
51
+ yield* verifyInterruptedSessionEntry(sessionEntry).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: error.message })));
52
+ return current;
53
+ }
54
+ if (current.status !== "running") {
55
+ return yield* RuntimeUnavailable.make({
56
+ message: `model operation ${op.operationId} cannot commit an interruption from ${current.status}`,
57
+ });
58
+ }
59
+ yield* appendInterruptedSessionEntry(sessionEntry).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: error.message })));
60
+ yield* sql `
61
+ UPDATE baton_run_operations
62
+ SET status = 'failed', error_json = ${encodeJsonValue(op.outcome.error)}, finished_at = NOW()
63
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId} AND status = 'running'
64
+ `;
65
+ yield* appendEvent(hub, yield* requireRun(op.runId), validated.event);
66
+ const completed = yield* sql `
67
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
68
+ `;
69
+ return toOperationRecord(completed[0]);
70
+ })),
71
+ commitModelResponse: (op) => fenced(op, Effect.gen(function* () {
72
+ const loaded = yield* requireRun(op.runId);
73
+ const existing = yield* sql `
74
+ SELECT * FROM baton_run_operations
75
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
76
+ FOR UPDATE
77
+ `;
78
+ const row = existing[0];
79
+ if (row === undefined)
80
+ return yield* RuntimeUnavailable.make({ message: "operation missing" });
81
+ const current = toOperationRecord(row);
82
+ const validated = validateModelResponseCommit({
83
+ record: current,
84
+ input: op,
85
+ sessionId: loaded.message.sessionId,
86
+ });
87
+ if (Schema.is(RuntimeUnavailable)(validated))
88
+ return yield* validated;
89
+ const sessionEntry = validated.entry;
90
+ if (current.status === "succeeded") {
91
+ const priorValidation = validateModelResponseCommit({
92
+ record: current,
93
+ input: {
94
+ ...op,
95
+ outcome: { _tag: "Succeeded", value: current.result },
96
+ },
97
+ sessionId: loaded.message.sessionId,
98
+ });
99
+ if (Schema.is(RuntimeUnavailable)(priorValidation))
100
+ return yield* priorValidation;
101
+ const prior = (yield* loadEventsAfter(op.runId, -1)).filter((event) => event._tag === "ModelResponseCommitted" && event.operationKey === op.event.operationKey);
102
+ if (prior.length !== 1 ||
103
+ prior[0]?._tag !== "ModelResponseCommitted" ||
104
+ !sameModelResponseEvent({ left: prior[0], right: validated.event })) {
105
+ return yield* RuntimeUnavailable.make({
106
+ message: `model operation ${op.operationId} has a divergent outbox retry`,
107
+ });
108
+ }
109
+ yield* verifyCompletedSessionEntry(sessionEntry).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: error.message })));
110
+ return current;
111
+ }
112
+ if (current.status === "failed" || current.status === "unknown") {
113
+ return yield* RuntimeUnavailable.make({
114
+ message: `model operation ${op.operationId} already completed as ${current.status}`,
115
+ });
116
+ }
117
+ for (const entryId of new Set(op.steeringEntryIds ?? [])) {
118
+ const rows = yield* sql `
119
+ SELECT consumed_operation_id FROM baton_run_steering
120
+ WHERE run_id = ${op.runId} AND entry_id = ${entryId}
121
+ `;
122
+ if (rows[0]?.consumed_operation_id !== op.operationId) {
123
+ return yield* RuntimeUnavailable.make({
124
+ message: `steering entry ${entryId} does not belong to operation`,
125
+ });
126
+ }
127
+ }
128
+ const executableRef = yield* Effect.try({
129
+ try: () => checkpointRef(loaded.executableRef, loaded.executableManifest, op.checkpoint),
130
+ catch: (error) => RuntimeUnavailable.make({ message: String(error) }),
131
+ });
132
+ yield* appendCompletedSessionEntry(sessionEntry).pipe(Effect.mapError((error) => RuntimeUnavailable.make({ message: error.message })));
133
+ yield* sql `
134
+ UPDATE baton_run_operations
135
+ SET status = 'succeeded', result_json = ${encodeJsonValue(validated.reference)}, finished_at = NOW()
136
+ WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
137
+ AND status IN ('requested', 'running')
138
+ `;
139
+ yield* sql `
140
+ UPDATE baton_runs SET
141
+ driver_checkpoint_json = COALESCE(${op.checkpoint === undefined ? null : encodeJson(ExecutionCheckpoint, op.checkpoint)}, driver_checkpoint_json),
142
+ executable_ref_json = ${encodeExecutableRef(executableRef)},
143
+ continuation_json = CASE WHEN ${op.continuation === undefined ? 0 : 1} = 1
144
+ THEN ${op.continuation === null || op.continuation === undefined ? null : encodeContinuation(op.continuation)}
145
+ ELSE continuation_json END,
146
+ updated_at = NOW()
147
+ WHERE run_id = ${op.runId}
148
+ `;
149
+ yield* appendEvent(hub, yield* requireRun(op.runId), validated.event);
150
+ const rows = yield* sql `
151
+ SELECT * FROM baton_run_operations WHERE run_id = ${op.runId} AND operation_id = ${op.operationId}
152
+ `;
153
+ return toOperationRecord(rows[0]);
154
+ })),
155
+ };
156
+ };
@@ -0,0 +1,21 @@
1
+ import { Effect } from "effect";
2
+ import type { PgClient } from "@effect/sql-pg";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import type { SqlError } from "effect/unstable/sql/SqlError";
5
+ import { RunNotFound, RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
6
+ import type { Interface as RunStoreInterface } from "tenetkit/runtime/driver/run-store";
7
+ import type { DecodedRun } from "tenetkit/runtime/driver/sql/rows";
8
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
9
+ import type { WithoutSqlError } from "tenetkit/runtime/driver/sql/sql-effect";
10
+ type SqlR = SqlClient.SqlClient | PgClient.PgClient;
11
+ export type RunFn = <A, E>(effect: Effect.Effect<A, E | SqlError, SqlR>) => Effect.Effect<A, WithoutSqlError<E | SqlError> | RuntimeUnavailable>;
12
+ export declare const postgresOperations: (input: {
13
+ readonly sql: SqlClient.SqlClient;
14
+ readonly hub: EventHub;
15
+ readonly run: RunFn;
16
+ readonly runNoTxn: RunFn;
17
+ readonly requireRun: (runId: string) => Effect.Effect<DecodedRun, RunNotFound | RuntimeUnavailable | SqlError, SqlR>;
18
+ readonly requireClaim: (claim: import("tenetkit/runtime/driver/run-store").ExecutionClaim) => Effect.Effect<void, import("tenetkit/runtime/driver/sql/errors").StaleClaim | RunNotFound | RuntimeUnavailable | SqlError, SqlR>;
19
+ readonly nextId: (prefix: string) => Effect.Effect<string>;
20
+ }) => Pick<RunStoreInterface, "recordOperation" | "startOperation" | "completeOperation" | "commitModelResponse" | "commitInterruptedModelResponse" | "expireRunningOperation" | "getOperation" | "getOperationByKey" | "resolveOperation">;
21
+ export {};