@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,388 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { Session } from "tenetkit";
3
+ import { SqlClient } from "effect/unstable/sql";
4
+ import { handoffPayload } from "tenetkit/runtime/driver/handoff-session";
5
+ import {} from "tenetkit/runtime/driver/sql/session-store";
6
+ import { PostgresSessionStorage } from "./session-storage.js";
7
+ const { advanceSession, entryPayloadEquivalence, insertEntry, loadEntries, lockSession, pathFromRows, requireActive, storeError, toEntry, } = PostgresSessionStorage;
8
+ const appendMatches = (entry, input, parentId) => entry.parentId === parentId && entryPayloadEquivalence(entry, input);
9
+ const completedPayload = (input) => ({
10
+ _tag: "ModelResponse",
11
+ content: input.content,
12
+ metadata: { modelResponseDigest: input.digest },
13
+ });
14
+ const interruptedPayload = (input) => ({
15
+ _tag: "ModelResponse",
16
+ content: input.content,
17
+ metadata: { interruptionDigest: input.digest },
18
+ });
19
+ /** Append or verify one exact completed assistant projection in the caller's PostgreSQL transaction. */
20
+ export const appendCompletedSessionEntry = (input) => Effect.gen(function* () {
21
+ const sql = yield* SqlClient.SqlClient;
22
+ const session = yield* lockSession(input.sessionId);
23
+ const rows = yield* sql `
24
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
25
+ WHERE session_id = ${input.sessionId} AND entry_id = ${input.entryId}
26
+ `;
27
+ const existing = rows[0];
28
+ const payload = completedPayload(input);
29
+ if (existing !== undefined) {
30
+ if (existing.parent_id !== input.parentId ||
31
+ !entryPayloadEquivalence(toEntry(existing), payload)) {
32
+ return yield* Session.SessionConflict.make({
33
+ reason: "entry-id-reused",
34
+ message: `Session entry id ${input.entryId} does not match the completed response`,
35
+ });
36
+ }
37
+ const conflict = requireActive(yield* loadEntries(input.sessionId), session.leaf_id, input.entryId);
38
+ if (conflict !== undefined)
39
+ return yield* conflict;
40
+ return toEntry(existing);
41
+ }
42
+ if (session.leaf_id !== input.parentId) {
43
+ return yield* Session.SessionConflict.make({
44
+ reason: "stale-leaf",
45
+ message: `Expected Session leaf ${String(input.parentId)} but found ${String(session.leaf_id)}`,
46
+ });
47
+ }
48
+ yield* insertEntry({
49
+ sessionId: input.sessionId,
50
+ id: input.entryId,
51
+ parentId: input.parentId,
52
+ seq: Number(session.next_seq),
53
+ tag: "ModelResponse",
54
+ payload: payload,
55
+ });
56
+ yield* advanceSession({
57
+ sessionId: input.sessionId,
58
+ leafId: input.entryId,
59
+ nextSeq: Number(session.next_seq) + 1,
60
+ });
61
+ return { ...payload, id: input.entryId, parentId: input.parentId };
62
+ });
63
+ /** Verify an exact completed assistant projection in the caller's PostgreSQL transaction. */
64
+ export const verifyCompletedSessionEntry = (input) => Effect.gen(function* () {
65
+ const sql = yield* SqlClient.SqlClient;
66
+ const session = yield* lockSession(input.sessionId);
67
+ const rows = yield* sql `
68
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
69
+ WHERE session_id = ${input.sessionId} AND entry_id = ${input.entryId}
70
+ `;
71
+ const existing = rows[0];
72
+ if (existing === undefined ||
73
+ existing.parent_id !== input.parentId ||
74
+ !entryPayloadEquivalence(toEntry(existing), completedPayload(input))) {
75
+ return yield* Session.SessionConflict.make({
76
+ reason: "entry-id-reused",
77
+ message: `Session entry id ${input.entryId} does not match the completed response`,
78
+ });
79
+ }
80
+ const conflict = requireActive(yield* loadEntries(input.sessionId), session.leaf_id, input.entryId);
81
+ if (conflict !== undefined)
82
+ return yield* conflict;
83
+ });
84
+ /** Append or verify one exact nonempty interrupted assistant projection in the caller's transaction. */
85
+ export const appendInterruptedSessionEntry = (input) => Effect.gen(function* () {
86
+ const sql = yield* SqlClient.SqlClient;
87
+ const session = yield* lockSession(input.sessionId);
88
+ const rows = yield* sql `
89
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
90
+ WHERE session_id = ${input.sessionId} AND entry_id = ${input.entryId}
91
+ `;
92
+ const existing = rows[0];
93
+ const payload = interruptedPayload(input);
94
+ if (existing !== undefined) {
95
+ if (existing.parent_id !== input.parentId ||
96
+ !entryPayloadEquivalence(toEntry(existing), payload)) {
97
+ return yield* Session.SessionConflict.make({
98
+ reason: "entry-id-reused",
99
+ message: `Session entry id ${input.entryId} was reused with different interrupted response content`,
100
+ });
101
+ }
102
+ const conflict = requireActive(yield* loadEntries(input.sessionId), session.leaf_id, input.entryId);
103
+ if (conflict !== undefined)
104
+ return yield* conflict;
105
+ return toEntry(existing);
106
+ }
107
+ if (session.leaf_id !== input.parentId) {
108
+ return yield* Session.SessionConflict.make({
109
+ reason: "stale-leaf",
110
+ message: `Expected Session leaf ${String(input.parentId)} but found ${String(session.leaf_id)}`,
111
+ });
112
+ }
113
+ yield* insertEntry({
114
+ sessionId: input.sessionId,
115
+ id: input.entryId,
116
+ parentId: input.parentId,
117
+ seq: Number(session.next_seq),
118
+ tag: "ModelResponse",
119
+ payload: payload,
120
+ });
121
+ yield* advanceSession({
122
+ sessionId: input.sessionId,
123
+ leafId: input.entryId,
124
+ nextSeq: Number(session.next_seq) + 1,
125
+ });
126
+ return { ...payload, id: input.entryId, parentId: input.parentId };
127
+ });
128
+ export const verifyInterruptedSessionEntry = (input) => Effect.gen(function* () {
129
+ const sql = yield* SqlClient.SqlClient;
130
+ const session = yield* lockSession(input.sessionId);
131
+ const rows = yield* sql `
132
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
133
+ WHERE session_id = ${input.sessionId} AND entry_id = ${input.entryId}
134
+ `;
135
+ const existing = rows[0];
136
+ if (existing === undefined ||
137
+ existing.parent_id !== input.parentId ||
138
+ !entryPayloadEquivalence(toEntry(existing), interruptedPayload(input))) {
139
+ return yield* Session.SessionConflict.make({
140
+ reason: "entry-id-reused",
141
+ message: `Session entry id ${input.entryId} does not match the interrupted response`,
142
+ });
143
+ }
144
+ const conflict = requireActive(yield* loadEntries(input.sessionId), session.leaf_id, input.entryId);
145
+ if (conflict !== undefined)
146
+ return yield* conflict;
147
+ });
148
+ /** Append or verify one exact handoff projection in the caller's PostgreSQL transaction. */
149
+ export const appendHandoffSessionEntry = (input) => Effect.gen(function* () {
150
+ const sql = yield* SqlClient.SqlClient;
151
+ const session = yield* lockSession(input.sessionId);
152
+ const rows = yield* sql `
153
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
154
+ WHERE session_id = ${input.sessionId} AND entry_id = ${input.entryId}
155
+ `;
156
+ const existing = rows[0];
157
+ const payload = handoffPayload(input);
158
+ if (existing !== undefined) {
159
+ if (existing.parent_id !== input.parentId ||
160
+ !entryPayloadEquivalence(toEntry(existing), payload)) {
161
+ return yield* Session.SessionConflict.make({
162
+ reason: "entry-id-reused",
163
+ message: `Session entry id ${input.entryId} does not match the handoff projection`,
164
+ });
165
+ }
166
+ const conflict = requireActive(yield* loadEntries(input.sessionId), session.leaf_id, input.entryId);
167
+ if (conflict !== undefined)
168
+ return yield* conflict;
169
+ return toEntry(existing);
170
+ }
171
+ if (session.leaf_id !== input.parentId) {
172
+ return yield* Session.SessionConflict.make({
173
+ reason: "stale-leaf",
174
+ message: `Expected Session leaf ${String(input.parentId)} but found ${String(session.leaf_id)}`,
175
+ });
176
+ }
177
+ yield* insertEntry({
178
+ sessionId: input.sessionId,
179
+ id: input.entryId,
180
+ parentId: input.parentId,
181
+ seq: Number(session.next_seq),
182
+ tag: "Handoff",
183
+ payload,
184
+ });
185
+ yield* advanceSession({
186
+ sessionId: input.sessionId,
187
+ leafId: input.entryId,
188
+ nextSeq: Number(session.next_seq) + 1,
189
+ });
190
+ return { ...payload, id: input.entryId, parentId: input.parentId };
191
+ });
192
+ export const verifyHandoffSessionEntry = (input) => Effect.gen(function* () {
193
+ const sql = yield* SqlClient.SqlClient;
194
+ const session = yield* lockSession(input.sessionId);
195
+ const rows = yield* sql `
196
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
197
+ WHERE session_id = ${input.sessionId} AND entry_id = ${input.entryId}
198
+ `;
199
+ const existing = rows[0];
200
+ if (existing === undefined ||
201
+ existing.parent_id !== input.parentId ||
202
+ !entryPayloadEquivalence(toEntry(existing), handoffPayload(input))) {
203
+ return yield* Session.SessionConflict.make({
204
+ reason: "entry-id-reused",
205
+ message: `Session entry id ${input.entryId} does not match the handoff projection`,
206
+ });
207
+ }
208
+ const conflict = requireActive(yield* loadEntries(input.sessionId), session.leaf_id, input.entryId);
209
+ if (conflict !== undefined)
210
+ return yield* conflict;
211
+ });
212
+ const mapSessionError = (effect) => Effect.mapError(effect, (error) => Schema.is(Session.SessionConflict)(error) || Schema.is(Session.SessionStoreError)(error)
213
+ ? error
214
+ : storeError(String(error)));
215
+ const mapReadError = (effect) => Effect.mapError(effect, (error) => (Schema.is(Session.SessionStoreError)(error) ? error : storeError(String(error))));
216
+ /** Dialect-native durable PostgreSQL Session authority bound to one session identity. */
217
+ export const makePostgresSessionStore = (options) => {
218
+ const { sessionId, run, runNoTxn } = options;
219
+ const append = (entry, appendOptions) => Effect.gen(function* () {
220
+ const sql = yield* SqlClient.SqlClient;
221
+ const session = yield* lockSession(sessionId);
222
+ if (appendOptions?.id !== undefined) {
223
+ const rows = yield* sql `
224
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
225
+ WHERE session_id = ${sessionId} AND entry_id = ${appendOptions.id}
226
+ `;
227
+ const existing = rows[0];
228
+ if (existing !== undefined) {
229
+ const persisted = toEntry(existing);
230
+ if (!appendMatches(persisted, entry, appendOptions.expectedLeafId)) {
231
+ return yield* Session.SessionConflict.make({
232
+ reason: "entry-id-reused",
233
+ message: `Session entry id ${appendOptions.id} was reused with different parent or content`,
234
+ });
235
+ }
236
+ const conflict = requireActive(yield* loadEntries(sessionId), session.leaf_id, persisted.id);
237
+ if (conflict !== undefined)
238
+ return yield* conflict;
239
+ return persisted;
240
+ }
241
+ }
242
+ if (appendOptions?.expectedLeafId !== undefined && appendOptions.expectedLeafId !== session.leaf_id) {
243
+ return yield* Session.SessionConflict.make({
244
+ reason: "stale-leaf",
245
+ message: `Expected Session leaf ${String(appendOptions.expectedLeafId)} but found ${String(session.leaf_id)}`,
246
+ });
247
+ }
248
+ let generatedSequence = Number(session.next_seq);
249
+ if (appendOptions?.id === undefined) {
250
+ while (true) {
251
+ const collision = yield* sql `
252
+ SELECT entry_id FROM baton_session_entries
253
+ WHERE session_id = ${sessionId} AND entry_id = ${String(generatedSequence)}
254
+ `;
255
+ if (collision[0] === undefined)
256
+ break;
257
+ generatedSequence += 1;
258
+ }
259
+ }
260
+ const id = appendOptions?.id ?? String(generatedSequence);
261
+ yield* insertEntry({
262
+ sessionId,
263
+ id,
264
+ parentId: session.leaf_id,
265
+ seq: Number(session.next_seq),
266
+ tag: entry._tag,
267
+ payload: entry,
268
+ });
269
+ yield* advanceSession({
270
+ sessionId,
271
+ leafId: id,
272
+ nextSeq: appendOptions?.id === undefined ? generatedSequence + 1 : Number(session.next_seq) + 1,
273
+ ...(appendOptions?.ownerToken === undefined ? {} : { ownerToken: appendOptions.ownerToken }),
274
+ });
275
+ return { ...entry, id, parentId: session.leaf_id };
276
+ });
277
+ const appendCheckpoint = (prepared) => Effect.gen(function* () {
278
+ const sql = yield* SqlClient.SqlClient;
279
+ const session = yield* lockSession(sessionId);
280
+ const rows = yield* sql `
281
+ SELECT entry_id, parent_id, seq, tag, payload_json FROM baton_session_entries
282
+ WHERE session_id = ${sessionId} AND entry_id = ${prepared.id}
283
+ `;
284
+ const existing = rows[0];
285
+ if (existing !== undefined) {
286
+ const entry = toEntry(existing);
287
+ if (entry._tag !== "Compaction" || !Session.checkpointMatches(entry, prepared)) {
288
+ return yield* Session.SessionConflict.make({
289
+ reason: "checkpoint-id-reused",
290
+ message: `Session checkpoint id ${prepared.id} was reused with different content`,
291
+ });
292
+ }
293
+ const conflict = requireActive(yield* loadEntries(sessionId), session.leaf_id, prepared.id, "checkpoint-not-on-active-path");
294
+ if (conflict !== undefined)
295
+ return yield* conflict;
296
+ return {
297
+ _tag: "AlreadyPresent",
298
+ checkpoint: entry,
299
+ leafId: session.leaf_id ?? entry.id,
300
+ };
301
+ }
302
+ if (prepared.compactionCommit !== undefined && prepared.compactionCommit.checkpointId !== prepared.id) {
303
+ return yield* Session.SessionConflict.make({
304
+ reason: "checkpoint-id-reused",
305
+ message: `Compaction commit checkpoint id ${prepared.compactionCommit.checkpointId} does not match ${prepared.id}`,
306
+ });
307
+ }
308
+ if (prepared.parentId !== session.leaf_id) {
309
+ return yield* Session.SessionConflict.make({
310
+ reason: "stale-leaf",
311
+ message: `Expected Session leaf ${String(prepared.parentId)} but found ${String(session.leaf_id)}`,
312
+ });
313
+ }
314
+ const checkpoint = {
315
+ _tag: "Compaction",
316
+ id: prepared.id,
317
+ parentId: prepared.parentId,
318
+ projectedHistory: prepared.projectedHistory,
319
+ telemetry: prepared.telemetry,
320
+ ...(prepared.compactionCommit === undefined ? {} : { compactionCommit: prepared.compactionCommit }),
321
+ ...(prepared.summary === undefined ? {} : { summary: prepared.summary }),
322
+ };
323
+ yield* insertEntry({
324
+ sessionId,
325
+ id: checkpoint.id,
326
+ parentId: checkpoint.parentId,
327
+ seq: Number(session.next_seq),
328
+ tag: "Compaction",
329
+ payload: checkpoint,
330
+ });
331
+ yield* advanceSession({
332
+ sessionId,
333
+ leafId: checkpoint.id,
334
+ nextSeq: Number(session.next_seq) + 1,
335
+ ...(prepared.ownerToken === undefined ? {} : { ownerToken: prepared.ownerToken }),
336
+ });
337
+ return { _tag: "Appended", checkpoint, leafId: checkpoint.id };
338
+ });
339
+ return Session.SessionStore.of({
340
+ reserveEntryId: run(Effect.gen(function* () {
341
+ const sql = yield* SqlClient.SqlClient;
342
+ const session = yield* lockSession(sessionId);
343
+ let sequence = Number(session.next_seq);
344
+ while (true) {
345
+ const collision = yield* sql `
346
+ SELECT entry_id FROM baton_session_entries
347
+ WHERE session_id = ${sessionId} AND entry_id = ${String(sequence)}
348
+ `;
349
+ if (collision[0] === undefined)
350
+ break;
351
+ sequence += 1;
352
+ }
353
+ yield* advanceSession({ sessionId, leafId: session.leaf_id, nextSeq: sequence + 1 });
354
+ return String(sequence);
355
+ })).pipe(mapReadError),
356
+ append: (entry, appendOptions) => run(append(entry, appendOptions)).pipe(mapSessionError),
357
+ appendCheckpoint: (prepared) => run(appendCheckpoint(prepared)).pipe(mapSessionError),
358
+ path: (leaf) => runNoTxn(Effect.gen(function* () {
359
+ const sql = yield* SqlClient.SqlClient;
360
+ const sessions = yield* sql `
361
+ SELECT leaf_id, next_seq, owner_token FROM baton_sessions WHERE session_id = ${sessionId}
362
+ `;
363
+ return { target: leaf ?? sessions[0]?.leaf_id ?? null, rows: yield* loadEntries(sessionId) };
364
+ }).pipe(Effect.flatMap(({ rows, target }) => {
365
+ const path = pathFromRows(rows, target);
366
+ return Schema.is(Session.SessionStoreError)(path) ? path : Effect.succeed(path);
367
+ }))).pipe(mapReadError),
368
+ setLeaf: (id) => run(Effect.gen(function* () {
369
+ const sql = yield* SqlClient.SqlClient;
370
+ yield* lockSession(sessionId);
371
+ if (id !== null) {
372
+ const rows = yield* sql `
373
+ SELECT entry_id FROM baton_session_entries WHERE session_id = ${sessionId} AND entry_id = ${id}
374
+ `;
375
+ if (rows[0] === undefined)
376
+ return yield* storeError(`Session entry ${id} does not exist`);
377
+ }
378
+ yield* sql `UPDATE baton_sessions SET leaf_id = ${id}, updated_at = NOW() WHERE session_id = ${sessionId}`;
379
+ })).pipe(mapReadError),
380
+ leaf: Effect.orDie(runNoTxn(Effect.gen(function* () {
381
+ const sql = yield* SqlClient.SqlClient;
382
+ const rows = yield* sql `
383
+ SELECT leaf_id, next_seq, owner_token FROM baton_sessions WHERE session_id = ${sessionId}
384
+ `;
385
+ return rows[0]?.leaf_id ?? null;
386
+ }))),
387
+ });
388
+ };
@@ -0,0 +1,21 @@
1
+ import { Effect } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { AddressNotFound, IdempotencyConflict, RunIdConflict, TreePolicyInvalid } from "tenetkit/runtime/driver/errors";
4
+ import type { PinnedExecutable } from "tenetkit/runtime/driver/executable-manifest";
5
+ import type { AdmitSendInput } from "tenetkit/runtime/driver/run-store";
6
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
7
+ /** Exact addressed admission for the PostgreSQL store. */
8
+ export declare const admitSend: {
9
+ (addressBindings: ReadonlyMap<string, PinnedExecutable>, nextId: (prefix: string) => Effect.Effect<string>, input: AdmitSendInput): (hub: EventHub) => Effect.Effect<{
10
+ runId: string;
11
+ messageId: string;
12
+ acceptedSequence: number;
13
+ duplicate: boolean;
14
+ }, AddressNotFound | IdempotencyConflict | RunIdConflict | TreePolicyInvalid | import("tenetkit/runtime/driver/errors").RuntimeUnavailable | import("effect/unstable/sql/SqlError").SqlError, SqlClient.SqlClient>;
15
+ (hub: EventHub, addressBindings: ReadonlyMap<string, PinnedExecutable>, nextId: (prefix: string) => Effect.Effect<string>, input: AdmitSendInput): Effect.Effect<{
16
+ runId: string;
17
+ messageId: string;
18
+ acceptedSequence: number;
19
+ duplicate: boolean;
20
+ }, AddressNotFound | IdempotencyConflict | RunIdConflict | TreePolicyInvalid | import("tenetkit/runtime/driver/errors").RuntimeUnavailable | import("effect/unstable/sql/SqlError").SqlError, SqlClient.SqlClient>;
21
+ };
@@ -0,0 +1,87 @@
1
+ import { Effect, Function } from "effect";
2
+ import { SqlClient } from "effect/unstable/sql";
3
+ import { AddressNotFound, IdempotencyConflict, RunIdConflict, TreePolicyInvalid } from "tenetkit/runtime/driver/errors";
4
+ import { equals } from "tenetkit/runtime/driver/executable-manifest";
5
+ import { rootDigest } from "tenetkit/runtime/driver/memory/digest";
6
+ import { appendEvent, enqueueLane, insertRun, loadRun } from "./pg-helpers.js";
7
+ import { associateRegistrations, persistRegistrations } from "tenetkit/runtime/driver/sql/executable-registrations";
8
+ import { decodePinnedEffect, decodeStoredPinnedEffect } from "tenetkit/runtime/driver/sql/codecs";
9
+ import { normalize as normalizeTreePolicy } from "tenetkit/runtime/driver/tree-policy";
10
+ /** Exact addressed admission for the PostgreSQL store. */
11
+ export const admitSend = Function.dual(4, (hub, addressBindings, nextId, input) => Effect.gen(function* () {
12
+ const sql = yield* SqlClient.SqlClient;
13
+ const bound = addressBindings.get(input.message.to);
14
+ if (bound === undefined)
15
+ return yield* AddressNotFound.make({ address: input.message.to });
16
+ const admitted = yield* decodePinnedEffect({
17
+ ref: input.executableRef,
18
+ manifest: input.executableManifest,
19
+ });
20
+ const binding = yield* decodePinnedEffect(bound);
21
+ if (!equals(binding, admitted)) {
22
+ return yield* AddressNotFound.make({ address: input.message.to });
23
+ }
24
+ yield* sql `SELECT pg_advisory_xact_lock(hashtext(${`admit:${input.message.to}:${input.message.sessionId}:${input.message.idempotencyKey}`}))`;
25
+ if (input.runId !== undefined) {
26
+ yield* sql `SELECT pg_advisory_xact_lock(hashtext(${`run:${input.runId}`}))`;
27
+ }
28
+ const treePolicy = yield* normalizeTreePolicy(input.treePolicy);
29
+ const digest = rootDigest(input.message, treePolicy);
30
+ const existing = yield* sql `
31
+ SELECT * FROM baton_runs
32
+ WHERE address = ${input.message.to}
33
+ AND session_id = ${input.message.sessionId}
34
+ AND idempotency_key = ${input.message.idempotencyKey}
35
+ `;
36
+ const prior = existing[0];
37
+ if (prior !== undefined) {
38
+ if (input.runId !== undefined && input.runId !== prior.run_id) {
39
+ return yield* RunIdConflict.make({ runId: input.runId, existingRunId: prior.run_id });
40
+ }
41
+ const priorExecutable = yield* decodeStoredPinnedEffect(prior.executable_ref_json, prior.executable_manifest_json);
42
+ if (prior.message_digest !== digest || !equals(priorExecutable, admitted)) {
43
+ return yield* IdempotencyConflict.make({
44
+ address: input.message.to,
45
+ sessionId: input.message.sessionId,
46
+ idempotencyKey: input.message.idempotencyKey,
47
+ existingRunId: prior.run_id,
48
+ });
49
+ }
50
+ return {
51
+ runId: prior.run_id,
52
+ messageId: prior.message_id,
53
+ acceptedSequence: Number(prior.accepted_sequence),
54
+ duplicate: true,
55
+ };
56
+ }
57
+ if (input.runId !== undefined) {
58
+ const byId = yield* sql `SELECT * FROM baton_runs WHERE run_id = ${input.runId}`;
59
+ if (byId[0] !== undefined)
60
+ return yield* RunIdConflict.make({ runId: input.runId, existingRunId: byId[0].run_id });
61
+ }
62
+ const runId = input.runId ?? (yield* nextId("run"));
63
+ const enqueued = yield* enqueueLane(input.message.to, input.message.sessionId, runId);
64
+ yield* insertRun({
65
+ runId,
66
+ status: "queued",
67
+ message: input.message,
68
+ digest,
69
+ executableRef: input.executableRef,
70
+ executableManifest: input.executableManifest,
71
+ rootRunId: runId,
72
+ depth: 0,
73
+ treePolicy,
74
+ acceptedSequence: enqueued.acceptedSequence,
75
+ });
76
+ yield* sql `SELECT pg_advisory_xact_lock(hashtext('tenetkit:executable-registrations'))`;
77
+ yield* persistRegistrations(input.registrations);
78
+ yield* associateRegistrations(runId, input.registrations);
79
+ const loaded = (yield* loadRun(runId));
80
+ yield* appendEvent(hub, loaded, { _tag: "RunAccepted", messageId: input.message.id, address: input.message.to }, "queued");
81
+ return {
82
+ runId,
83
+ messageId: input.message.id,
84
+ acceptedSequence: enqueued.acceptedSequence,
85
+ duplicate: false,
86
+ };
87
+ }));
@@ -0,0 +1,14 @@
1
+ import { Effect } from "effect";
2
+ import type { PgClient } from "@effect/sql-pg";
3
+ import type { 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 { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
7
+ export declare const deferCancelledFanOutParent: {
8
+ (runId: string): (sql: SqlClient.SqlClient) => Effect.Effect<boolean, SqlError, SqlClient.SqlClient>;
9
+ (sql: SqlClient.SqlClient, runId: string): Effect.Effect<boolean, SqlError, SqlClient.SqlClient>;
10
+ };
11
+ export declare const makeCancelRun: (input: {
12
+ readonly sql: SqlClient.SqlClient;
13
+ readonly hub: EventHub;
14
+ }) => (runId: string, reason: string | undefined) => Effect.Effect<void, RunNotFound | RuntimeUnavailable | SqlError, SqlClient.SqlClient | PgClient.PgClient>;
@@ -0,0 +1,73 @@
1
+ import { Effect, Function } from "effect";
2
+ import { RunNotFound, RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
3
+ import { isTerminal } from "tenetkit/runtime/driver/run";
4
+ import { hasUnsettledChild } from "tenetkit/runtime/driver/sql/store-child-settlement";
5
+ import { afterTerminal, appendEvent, loadRun, settleParent } from "./pg-helpers.js";
6
+ import { cancelOwnedFanOuts } from "./store-fan-out.js";
7
+ import { reconcileProgramCancellation } from "tenetkit/runtime/driver/sql/store-program";
8
+ export const deferCancelledFanOutParent = Function.dual(2, (sql, runId) => Effect.gen(function* () {
9
+ const running = yield* sql `
10
+ SELECT fan_out_id FROM baton_fan_outs WHERE parent_run_id = ${runId} AND status = 'running' LIMIT 1
11
+ `;
12
+ if (running.length === 0 && !(yield* hasUnsettledChild(runId)))
13
+ return false;
14
+ yield* sql `UPDATE baton_runs SET owner_worker_id = NULL, lease_expires_at = NULL WHERE run_id = ${runId}`;
15
+ return true;
16
+ }));
17
+ export const makeCancelRun = (input) => {
18
+ const cancelRun = (runId, reason) => Effect.gen(function* () {
19
+ let current = yield* loadRun(runId).pipe(Effect.flatMap((run) => (run === undefined ? RunNotFound.make({ runId }) : Effect.succeed(run))));
20
+ const terminal = isTerminal(current.status);
21
+ const needsResolution = current.status === "needs-resolution";
22
+ const executing = current.ownerWorkerId !== undefined && (current.status === "running" || current.status === "cancelling");
23
+ if (!terminal && !current.cancellationRequested) {
24
+ yield* appendEvent(input.hub, current, { _tag: "RunCancellationRequested", ...(reason === undefined ? {} : { reason }) }, needsResolution ? "needs-resolution" : "cancelling");
25
+ current = (yield* loadRun(runId));
26
+ }
27
+ if (!terminal)
28
+ yield* reconcileProgramCancellation(runId, reason ?? current.cancelReason);
29
+ yield* input.sql `
30
+ UPDATE baton_run_waits SET status = 'cancelled', closed_at = NOW()
31
+ WHERE run_id = ${runId} AND status = 'open'
32
+ `;
33
+ const linked = yield* input.sql `
34
+ SELECT l.child_run_id FROM baton_run_links l
35
+ LEFT JOIN baton_fan_out_members m ON m.child_run_id = l.child_run_id
36
+ WHERE l.parent_run_id = ${runId} AND m.child_run_id IS NULL
37
+ ORDER BY l.child_run_id ASC
38
+ `;
39
+ for (const link of linked) {
40
+ const child = yield* loadRun(link.child_run_id);
41
+ if (child !== undefined && !isTerminal(child.status))
42
+ yield* cancelRun(child.runId, reason ?? "parent cancelled");
43
+ }
44
+ if (linked.length > 0)
45
+ current = (yield* loadRun(runId));
46
+ const owned = yield* cancelOwnedFanOuts(input.sql, runId);
47
+ for (const childRunId of owned) {
48
+ const child = yield* loadRun(childRunId);
49
+ if (child !== undefined && !isTerminal(child.status))
50
+ yield* cancelRun(child.runId, reason ?? "parent cancelled");
51
+ }
52
+ if (owned.length > 0)
53
+ current = (yield* loadRun(runId));
54
+ if (terminal)
55
+ return;
56
+ if (executing)
57
+ return;
58
+ if (isTerminal(current.status))
59
+ return;
60
+ const running = yield* input.sql `
61
+ SELECT fan_out_id FROM baton_fan_outs WHERE parent_run_id = ${runId} AND status = 'running' LIMIT 1
62
+ `;
63
+ if (running.length > 0)
64
+ return;
65
+ if (yield* hasUnsettledChild(runId))
66
+ return;
67
+ const event = yield* appendEvent(input.hub, current, { _tag: "RunCancelled", ...(reason === undefined ? {} : { reason }) }, "cancelled");
68
+ const settled = (yield* loadRun(runId));
69
+ yield* settleParent(input.hub, settled, event.eventId);
70
+ yield* afterTerminal(input.hub, settled);
71
+ });
72
+ return cancelRun;
73
+ };
@@ -0,0 +1,17 @@
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, RunTerminal, RuntimeUnavailable } from "tenetkit/runtime/driver/errors";
6
+ import type { EventHub } from "tenetkit/runtime/driver/sql/subscribers";
7
+ import { type Interface as ClaimsInterface } from "tenetkit/runtime/driver/sql/run-claims";
8
+ import type { WithoutSqlError } from "tenetkit/runtime/driver/sql/sql-effect";
9
+ type SqlR = SqlClient.SqlClient | PgClient.PgClient;
10
+ export type RunFn = <A, E>(effect: Effect.Effect<A, E | SqlError, SqlR>) => Effect.Effect<A, WithoutSqlError<E | SqlError> | RuntimeUnavailable>;
11
+ export declare const makePostgresClaims: (input: {
12
+ readonly sql: SqlClient.SqlClient;
13
+ readonly hub: EventHub;
14
+ readonly run: RunFn;
15
+ readonly cancelRun: (runId: string, reason: string | undefined) => Effect.Effect<void, RunNotFound | RunTerminal | RuntimeUnavailable | SqlError, SqlR>;
16
+ }) => ClaimsInterface;
17
+ export {};