@effect-agent/storage-sqlite 0.1.0-beta.44 → 0.1.0-beta.46

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 (58) hide show
  1. package/dist/SqliteActivityStore.d.mts +14 -0
  2. package/dist/SqliteActivityStore.mjs +310 -0
  3. package/dist/SqliteActivityStore.mjs.map +1 -0
  4. package/dist/SqliteScheduleStore.d.mts +14 -0
  5. package/dist/SqliteScheduleStore.mjs +255 -0
  6. package/dist/SqliteScheduleStore.mjs.map +1 -0
  7. package/dist/SqliteStorageConfig.d.mts +34 -0
  8. package/dist/SqliteStorageConfig.mjs +39 -0
  9. package/dist/SqliteStorageConfig.mjs.map +1 -0
  10. package/dist/{sqlite-storage-failpoint-Cr0SXflP.d.mts → SqliteStorageError-W1oM5ka3.d.mts} +6 -15
  11. package/dist/SqliteStorageError.d.mts +2 -0
  12. package/dist/SqliteStorageError.mjs +144 -0
  13. package/dist/SqliteStorageError.mjs.map +1 -0
  14. package/dist/SqliteStorageFailpoint.d.mts +17 -0
  15. package/dist/{sqlite-storage-failpoint-DIwWk5dw.mjs → SqliteStorageFailpoint.mjs} +5 -3
  16. package/dist/SqliteStorageFailpoint.mjs.map +1 -0
  17. package/dist/{testing.d.mts → SqliteStorageFailpointTesting.d.mts} +3 -3
  18. package/dist/{testing.mjs → SqliteStorageFailpointTesting.mjs} +3 -3
  19. package/dist/SqliteStorageFailpointTesting.mjs.map +1 -0
  20. package/dist/SqliteStorageVersion-DwQbn4bw.d.mts +9 -0
  21. package/dist/SqliteStorageVersion.d.mts +2 -0
  22. package/dist/SqliteStorageVersion.mjs +8 -0
  23. package/dist/SqliteStorageVersion.mjs.map +1 -0
  24. package/dist/SqliteSubmissionLedger.d.mts +23 -0
  25. package/dist/SqliteSubmissionLedger.mjs +1656 -0
  26. package/dist/SqliteSubmissionLedger.mjs.map +1 -0
  27. package/dist/SqliteSubscriptionStore.d.mts +13 -0
  28. package/dist/SqliteSubscriptionStore.mjs +596 -0
  29. package/dist/SqliteSubscriptionStore.mjs.map +1 -0
  30. package/dist/SqliteThreadStore.d.mts +49 -0
  31. package/dist/SqliteThreadStore.mjs +422 -0
  32. package/dist/SqliteThreadStore.mjs.map +1 -0
  33. package/dist/index.d.mts +10 -108
  34. package/dist/index.mjs +10 -4265
  35. package/dist/migrations-B82C9A3r.mjs +293 -0
  36. package/dist/migrations-B82C9A3r.mjs.map +1 -0
  37. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  38. package/dist/sqlite-journal-C5SZnSdC.mjs +656 -0
  39. package/dist/sqlite-journal-C5SZnSdC.mjs.map +1 -0
  40. package/package.json +1 -1
  41. package/src/{sqlite-activity-store.ts → SqliteActivityStore.ts} +2 -2
  42. package/src/{sqlite-schedule-store.ts → SqliteScheduleStore.ts} +9 -7
  43. package/src/{errors.ts → SqliteStorageError.ts} +1 -1
  44. package/src/{sqlite-storage-failpoint.ts → SqliteStorageFailpoint.ts} +4 -1
  45. package/src/{sqlite-storage-failpoint-testing.ts → SqliteStorageFailpointTesting.ts} +1 -1
  46. package/src/SqliteStorageVersion.ts +2 -0
  47. package/src/{sqlite-ledger.ts → SqliteSubmissionLedger.ts} +17 -15
  48. package/src/{sqlite-subscription-store.ts → SqliteSubscriptionStore.ts} +14 -12
  49. package/src/{sqlite-thread-store.ts → SqliteThreadStore.ts} +19 -21
  50. package/src/index.ts +9 -10
  51. package/src/{sqlite-journal.ts → internal/sqlite-journal.ts} +5 -5
  52. package/dist/index.mjs.map +0 -1
  53. package/dist/sqlite-storage-failpoint-DIwWk5dw.mjs.map +0 -1
  54. package/dist/testing.mjs.map +0 -1
  55. package/src/sqlite-memory-store.ts +0 -7
  56. package/src/testing.ts +0 -2
  57. /package/src/{sqlite-storage-config.ts → SqliteStorageConfig.ts} +0 -0
  58. /package/src/{migrations.ts → internal/migrations.ts} +0 -0
@@ -0,0 +1,1656 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { SqliteStorageConfig } from "./SqliteStorageConfig.mjs";
3
+ import { SqliteLedgerError, SqliteStorageCorruptionError, SqliteStorageError, SqliteWriteContention } from "./SqliteStorageError.mjs";
4
+ import { SqliteStorageFailpoint } from "./SqliteStorageFailpoint.mjs";
5
+ import { a as initializeSqliteJournal, i as decodeRows } from "./sqlite-journal-C5SZnSdC.mjs";
6
+ import { storageConfigLayer, storageFailpointLayer } from "./SqliteThreadStore.mjs";
7
+ import { ApprovalDecision, CanonicalSequence, DefinitionDigests, Digest, PersistedJson, ProducerEpoch, RecordEnvelope, SettlementOutcome } from "@effect-agent/thread/Records";
8
+ import { Clock, Context, Crypto, DateTime, Effect, Layer, Option, Schema, Stream } from "effect";
9
+ import * as SqlClientService from "effect/unstable/sql/SqlClient";
10
+ import { SqliteClient } from "@effect/sql-sqlite-node";
11
+ import { EMPTY_TAIL_DIGEST } from "@effect-agent/thread/Digest";
12
+ import { AbortCommand, AbortIntent, AdmissionAdmitted, AdmissionConflict, AdmissionNotAdmitted, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildReservationStatus, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, ParentLinkage, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionState, SuspendRequest, SuspensionReason, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, settlementFailureFromRecord, submissionAbortRecordId } from "@effect-agent/thread/SubmissionLedger";
13
+ import { NodeCrypto } from "@effect/platform-node";
14
+ //#region src/SqliteSubmissionLedger.ts
15
+ var SqliteSubmissionLedger_exports = /* @__PURE__ */ __exportAll({
16
+ ledgerLayer: () => ledgerLayer,
17
+ submissionLedgerLayer: () => submissionLedgerLayer
18
+ });
19
+ const BoundedStoredText = Schema.String.check(Schema.isMaxLength(16777216));
20
+ const BoundedIdentifier = Schema.NonEmptyString.check(Schema.isMaxLength(1024));
21
+ const BoundedTimestamp = Schema.NonEmptyString.check(Schema.isMaxLength(128));
22
+ const SCAN_PAGE_SIZE = 256;
23
+ const EPOCH_ZERO = Schema.decodeSync(ProducerEpoch)(0);
24
+ const RESUME_IMMEDIATELY = "resume-immediately";
25
+ const SUSPENDED = "suspended";
26
+ const NOT_WAITING = "not-waiting";
27
+ const STILL_WAITING = "still-waiting";
28
+ const WOKEN = "woken";
29
+ var SubmissionRow = class extends Schema.Class("SubmissionRow")({
30
+ submission_id: BoundedIdentifier,
31
+ thread_id: BoundedIdentifier,
32
+ queue_sequence: QueueSequence,
33
+ principal: BoundedIdentifier,
34
+ idempotency_key: BoundedIdentifier,
35
+ agent_id: BoundedIdentifier,
36
+ agent_digests_json: BoundedStoredText,
37
+ deployment_id: BoundedIdentifier,
38
+ input_json: BoundedStoredText,
39
+ input_digest: Digest,
40
+ receipt_id: BoundedIdentifier,
41
+ state: SubmissionState,
42
+ settled_outcome: Schema.NullOr(SettlementOutcome),
43
+ created_at: BoundedTimestamp,
44
+ ready_at: Schema.NullOr(BoundedTimestamp),
45
+ input_applied_record_id: Schema.NullOr(BoundedIdentifier),
46
+ input_applied_sequence: Schema.NullOr(CanonicalSequence),
47
+ joined_host_submission_id: Schema.NullOr(BoundedIdentifier),
48
+ suspended_reason_json: Schema.NullOr(BoundedStoredText),
49
+ suspended_at: Schema.NullOr(BoundedTimestamp),
50
+ unknown_reason: Schema.NullOr(BoundedStoredText),
51
+ unknown_tool_call_ids_json: Schema.NullOr(BoundedStoredText),
52
+ parent_submission_id: Schema.NullOr(BoundedIdentifier),
53
+ parent_tool_call_id: Schema.NullOr(BoundedIdentifier)
54
+ }) {};
55
+ var ChildReservationRow = class extends Schema.Class("ChildReservationRow")({
56
+ reservation_id: BoundedIdentifier,
57
+ parent_submission_id: BoundedIdentifier,
58
+ parent_tool_call_id: BoundedIdentifier,
59
+ child_submission_id: Schema.NullOr(BoundedIdentifier),
60
+ status: ChildReservationStatus,
61
+ allocation_json: BoundedStoredText,
62
+ allocation_digest: Digest,
63
+ accounting_json: Schema.NullOr(BoundedStoredText),
64
+ reserved_at: BoundedTimestamp,
65
+ release_began_at: Schema.NullOr(BoundedTimestamp),
66
+ released_at: Schema.NullOr(BoundedTimestamp)
67
+ }) {};
68
+ var ApprovalDecisionRow = class extends Schema.Class("ApprovalDecisionRow")({
69
+ submission_id: BoundedIdentifier,
70
+ tool_call_id: BoundedIdentifier,
71
+ decision: ApprovalDecision,
72
+ resolver: BoundedIdentifier,
73
+ reason: BoundedStoredText,
74
+ decided_at: BoundedTimestamp
75
+ }) {};
76
+ var UnknownResolutionRow = class extends Schema.Class("UnknownResolutionRow")({
77
+ submission_id: BoundedIdentifier,
78
+ tool_call_id: BoundedIdentifier,
79
+ author: BoundedIdentifier,
80
+ reason: BoundedStoredText,
81
+ resolution_json: BoundedStoredText,
82
+ resolved_at: BoundedTimestamp
83
+ }) {};
84
+ var OwnershipRow = class extends Schema.Class("OwnershipRow")({
85
+ submission_id: BoundedIdentifier,
86
+ attempt_id: BoundedIdentifier,
87
+ ownership_token: BoundedIdentifier,
88
+ producer_epoch: ProducerEpoch,
89
+ owner_producer_id: BoundedIdentifier,
90
+ lease_expires_at: BoundedTimestamp
91
+ }) {};
92
+ var ReservationRow = class extends Schema.Class("ReservationRow")({
93
+ submission_id: BoundedIdentifier,
94
+ settlement_id: BoundedIdentifier,
95
+ outcome: SettlementOutcome,
96
+ record_id: BoundedIdentifier,
97
+ record_json: BoundedStoredText,
98
+ record_digest: Digest,
99
+ reserved_at: BoundedTimestamp,
100
+ finalized_at: Schema.NullOr(BoundedTimestamp)
101
+ }) {};
102
+ var AbortIntentRow = class extends Schema.Class("AbortIntentRow")({
103
+ submission_id: BoundedIdentifier,
104
+ author: BoundedIdentifier,
105
+ reason: BoundedStoredText,
106
+ requested_at: BoundedTimestamp,
107
+ canonical_record_id: Schema.NullOr(BoundedIdentifier)
108
+ }) {};
109
+ var MaxQueueSequenceRow = class extends Schema.Class("MaxQueueSequenceRow")({ max_queue_sequence: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)) }) {};
110
+ var CanonicalRecordIdRow = class extends Schema.Class("CanonicalRecordIdRow")({ record_id: BoundedIdentifier }) {};
111
+ const SUBMISSION_COLUMNS = `
112
+ submission_id,
113
+ thread_id,
114
+ queue_sequence,
115
+ principal,
116
+ idempotency_key,
117
+ agent_id,
118
+ agent_digests_json,
119
+ deployment_id,
120
+ input_json,
121
+ input_digest,
122
+ receipt_id,
123
+ state,
124
+ settled_outcome,
125
+ created_at,
126
+ ready_at,
127
+ input_applied_record_id,
128
+ input_applied_sequence,
129
+ joined_host_submission_id,
130
+ suspended_reason_json,
131
+ suspended_at,
132
+ unknown_reason,
133
+ unknown_tool_call_ids_json,
134
+ parent_submission_id,
135
+ parent_tool_call_id
136
+ `;
137
+ const CHILD_RESERVATION_COLUMNS = `
138
+ reservation_id,
139
+ parent_submission_id,
140
+ parent_tool_call_id,
141
+ child_submission_id,
142
+ status,
143
+ allocation_json,
144
+ allocation_digest,
145
+ accounting_json,
146
+ reserved_at,
147
+ release_began_at,
148
+ released_at
149
+ `;
150
+ /** The branded ToolCallId schema, reached through the thread port so no core import is needed. */
151
+ const ToolCallIdSchema = ApprovalDecisionCommand.fields.toolCallId;
152
+ const ToolCallIdList = Schema.Array(ToolCallIdSchema);
153
+ const encodePersistedJsonText = Schema.encodeEffect(Schema.fromJsonString(PersistedJson));
154
+ const encodeDefinitionDigestsText = Schema.encodeEffect(Schema.fromJsonString(DefinitionDigests));
155
+ const encodeRecordEnvelopeText = Schema.encodeEffect(Schema.fromJsonString(RecordEnvelope));
156
+ const decodeRecordEnvelopeText = Schema.decodeEffect(Schema.fromJsonString(RecordEnvelope));
157
+ const encodeSuspensionReasonText = Schema.encodeEffect(Schema.fromJsonString(SuspensionReason));
158
+ const encodeUnknownResolutionText = Schema.encodeEffect(Schema.fromJsonString(UnknownResolution));
159
+ const encodeToolCallIdsText = Schema.encodeEffect(Schema.fromJsonString(ToolCallIdList));
160
+ const decodeToolCallIdsText = Schema.decodeEffect(Schema.fromJsonString(ToolCallIdList));
161
+ const parseStoredJsonText = Schema.decodeEffect(Schema.fromJsonString(Schema.Json));
162
+ const decodeAdmissionResult = Schema.decodeUnknownEffect(AdmissionResult);
163
+ const decodeClaim = Schema.decodeUnknownEffect(Claim);
164
+ const decodeOwnershipRenewal = Schema.decodeUnknownEffect(OwnershipRenewal);
165
+ const decodeSettlement = Schema.decodeUnknownEffect(Settlement);
166
+ const decodeAbortIntent = Schema.decodeUnknownEffect(AbortIntent);
167
+ const decodeOwnershipSnapshot = Schema.decodeUnknownEffect(OwnershipSnapshot);
168
+ const decodeInputAppliedMarker = Schema.decodeUnknownEffect(InputAppliedMarker);
169
+ const decodeSubmissionSnapshotUnknown = Schema.decodeUnknownEffect(SubmissionSnapshot);
170
+ const decodeSubmissionId = Schema.decodeUnknownEffect(SubmissionSnapshot.fields.submissionId);
171
+ const decodeQueueSequence = Schema.decodeUnknownEffect(QueueSequence);
172
+ const decodeUtcInstant = Schema.decodeUnknownEffect(Schema.DateTimeUtcFromString);
173
+ const decodeJoiningClaim = Schema.decodeUnknownEffect(JoiningClaim);
174
+ const decodeJoinSnapshot = Schema.decodeUnknownEffect(JoinSnapshot);
175
+ const decodeSuspensionSnapshot = Schema.decodeUnknownEffect(SuspensionSnapshot);
176
+ const decodeApprovalDecisionIntent = Schema.decodeUnknownEffect(ApprovalDecisionIntent);
177
+ const decodeUnknownResolutionIntent = Schema.decodeUnknownEffect(UnknownResolutionIntent);
178
+ const decodeParentLinkage = Schema.decodeUnknownEffect(ParentLinkage);
179
+ const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(ChildBudgetReservationSnapshot);
180
+ const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
181
+ const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
182
+ const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
183
+ const isSqliteTransactionFailure = Schema.is(Schema.Union([SqliteStorageError, SqliteWriteContention]));
184
+ /** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
185
+ const internalFailure = (operation) => (error) => LedgerError.make({
186
+ operation,
187
+ message: error.message,
188
+ cause: error
189
+ });
190
+ /** Classify raw SQL failures: write-lock timeouts stay retryable typed contention. */
191
+ const sqlFailure = (operation) => (error) => {
192
+ const internal = error.reason._tag === "LockTimeoutError" ? SqliteWriteContention.make({
193
+ cause: error,
194
+ operation,
195
+ message: `Another producer holds the SQLite write lock; ${operation} is safe to retry.`
196
+ }) : SqliteLedgerError.make({
197
+ cause: error,
198
+ operation,
199
+ message: error.message
200
+ });
201
+ return internalFailure(operation)(internal);
202
+ };
203
+ const corruptionFailure = (operation, table, rowKey, message) => internalFailure(operation)(SqliteStorageCorruptionError.make({
204
+ table,
205
+ rowKey,
206
+ message
207
+ }));
208
+ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function* () {
209
+ const config = yield* SqliteStorageConfig;
210
+ const failpoint = yield* SqliteStorageFailpoint;
211
+ const sql = yield* SqlClientService.SqlClient;
212
+ const crypto = yield* Crypto.Crypto;
213
+ const journal = yield* initializeSqliteJournal();
214
+ const hitFailpoint = (location, operation) => failpoint.hit(location).pipe(Effect.mapError((error) => internalFailure(operation)(error)));
215
+ /**
216
+ * Run one ledger mutation under the journal's `BEGIN IMMEDIATE` write transaction so
217
+ * ownership-token and epoch checks are atomic with their writes (DUR-006). Transaction
218
+ * acquisition failures surface as LedgerError carrying the typed retryable
219
+ * SqliteWriteContention (or SqliteStorageError) as cause.
220
+ */
221
+ const inWriteTransaction = (operation, effect) => journal.withWriteTransaction(operation)(effect).pipe(Effect.mapError((error) => isSqliteTransactionFailure(error) ? internalFailure(operation)(error) : error));
222
+ const mintIdentifier = (prefix, operation) => crypto.randomUUIDv7.pipe(Effect.map((uuid) => `${prefix}-${uuid}`), Effect.mapError((error) => internalFailure(operation)(error)));
223
+ const currentInstant = Effect.map(Clock.currentTimeMillis, (millis) => ({
224
+ millis,
225
+ iso: new Date(millis).toISOString()
226
+ }));
227
+ const timestampMillis = (operation, rowKey) => (timestamp) => decodeUtcInstant(timestamp).pipe(Effect.map(DateTime.toEpochMillis), Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submission_ownership", rowKey, error.message)));
228
+ const decodeSubmissionRows = (operation, rowKey, rows) => decodeRows(Schema.Array(SubmissionRow), "effect_agent_submissions", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
229
+ const readSubmission = Effect.fn("SqliteSubmissionLedger.readSubmission")(function* (operation, submissionId) {
230
+ const rows = yield* sql`
231
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
232
+ FROM effect_agent_submissions
233
+ WHERE submission_id = ${submissionId}
234
+ `.pipe(Effect.mapError(sqlFailure(operation)));
235
+ const decoded = yield* decodeSubmissionRows(operation, submissionId, rows);
236
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", submissionId, "A submission primary key returned more than one row.");
237
+ return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
238
+ });
239
+ const requireSubmission = Effect.fn("SqliteSubmissionLedger.requireSubmission")(function* (operation, submissionId) {
240
+ const submission = yield* readSubmission(operation, submissionId);
241
+ if (Option.isNone(submission)) return yield* LedgerError.make({
242
+ operation,
243
+ message: `Unknown submission ${submissionId}.`
244
+ });
245
+ return submission.value;
246
+ });
247
+ const readOwnership = Effect.fn("SqliteSubmissionLedger.readOwnership")(function* (operation, submissionId) {
248
+ const rows = yield* sql`
249
+ SELECT
250
+ submission_id,
251
+ attempt_id,
252
+ ownership_token,
253
+ producer_epoch,
254
+ owner_producer_id,
255
+ lease_expires_at
256
+ FROM effect_agent_submission_ownership
257
+ WHERE submission_id = ${submissionId}
258
+ `.pipe(Effect.mapError(sqlFailure(operation)));
259
+ const decoded = yield* decodeRows(Schema.Array(OwnershipRow), "effect_agent_submission_ownership", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
260
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submission_ownership", submissionId, "An ownership primary key returned more than one row.");
261
+ return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
262
+ });
263
+ const threadEpoch = Effect.fn("SqliteSubmissionLedger.threadEpoch")(function* (operation, threadId) {
264
+ const threads = yield* journal.getThread(threadId).pipe(Effect.mapError(internalFailure(operation)));
265
+ return threads.length === 0 ? EPOCH_ZERO : threads[0].producer_epoch;
266
+ });
267
+ /**
268
+ * Verify inside the surrounding write transaction that the presented token still owns the
269
+ * Submission's lane; a superseded or missing token fails with OwnershipLost carrying the
270
+ * Thread's current producer epoch (DUR-006).
271
+ */
272
+ const requireOwnership = Effect.fn("SqliteSubmissionLedger.requireOwnership")(function* (operation, submission, ownershipToken) {
273
+ const ownership = yield* readOwnership(operation, submission.submission_id);
274
+ if (Option.isNone(ownership) || ownership.value.ownership_token !== ownershipToken) {
275
+ const actualEpoch = yield* threadEpoch(operation, submission.thread_id);
276
+ const submissionId = yield* Schema.decodeUnknownEffect(SubmissionSnapshot.fields.submissionId)(submission.submission_id).pipe(Effect.mapError(internalFailure(operation)));
277
+ return yield* OwnershipLost.make({
278
+ submissionId,
279
+ actualEpoch
280
+ });
281
+ }
282
+ return ownership.value;
283
+ });
284
+ const decodeSubmissionSnapshot = Effect.fn("SqliteSubmissionLedger.decodeSubmissionSnapshot")(function* (operation, row) {
285
+ const agentDigests = yield* parseStoredJsonText(row.agent_digests_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", row.submission_id, error.message)));
286
+ const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", row.submission_id, error.message)));
287
+ if (row.parent_submission_id === null !== (row.parent_tool_call_id === null)) return yield* corruptionFailure(operation, "effect_agent_submissions", row.submission_id, "A parent linkage must record both the parent Submission and the parent Tool Call.");
288
+ return yield* decodeSubmissionSnapshotUnknown({
289
+ submissionId: row.submission_id,
290
+ threadId: row.thread_id,
291
+ queueSequence: row.queue_sequence,
292
+ principal: row.principal,
293
+ idempotencyKey: row.idempotency_key,
294
+ agentId: row.agent_id,
295
+ agentDigests,
296
+ deploymentId: row.deployment_id,
297
+ inputPayload,
298
+ inputDigest: row.input_digest,
299
+ receiptId: row.receipt_id,
300
+ state: row.state,
301
+ createdAt: row.created_at,
302
+ ...row.settled_outcome === null ? {} : { settledOutcome: row.settled_outcome },
303
+ ...row.ready_at === null ? {} : { readyAt: row.ready_at },
304
+ ...row.parent_submission_id === null || row.parent_tool_call_id === null ? {} : { parentLinkage: {
305
+ parentSubmissionId: row.parent_submission_id,
306
+ parentToolCallId: row.parent_tool_call_id
307
+ } }
308
+ }).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", row.submission_id, error.message)));
309
+ });
310
+ const readReservation = Effect.fn("SqliteSubmissionLedger.readReservation")(function* (operation, submissionId) {
311
+ const rows = yield* sql`
312
+ SELECT
313
+ submission_id,
314
+ settlement_id,
315
+ outcome,
316
+ record_id,
317
+ record_json,
318
+ record_digest,
319
+ reserved_at,
320
+ finalized_at
321
+ FROM effect_agent_settlement_reservations
322
+ WHERE submission_id = ${submissionId}
323
+ `.pipe(Effect.mapError(sqlFailure(operation)));
324
+ const decoded = yield* decodeRows(Schema.Array(ReservationRow), "effect_agent_settlement_reservations", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
325
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_settlement_reservations", submissionId, "A settlement reservation primary key returned more than one row.");
326
+ return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
327
+ });
328
+ const readAbortIntent = Effect.fn("SqliteSubmissionLedger.readAbortIntent")(function* (operation, submissionId) {
329
+ const rows = yield* sql`
330
+ SELECT
331
+ submission_id,
332
+ author,
333
+ reason,
334
+ requested_at,
335
+ canonical_record_id
336
+ FROM effect_agent_abort_intents
337
+ WHERE submission_id = ${submissionId}
338
+ `.pipe(Effect.mapError(sqlFailure(operation)));
339
+ const decoded = yield* decodeRows(Schema.Array(AbortIntentRow), "effect_agent_abort_intents", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
340
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_abort_intents", submissionId, "An abort intent primary key returned more than one row.");
341
+ return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
342
+ });
343
+ const decodeChildReservationRows = (operation, rowKey, rows) => decodeRows(Schema.Array(ChildReservationRow), "effect_agent_child_reservations", rowKey, rows).pipe(Effect.mapError(internalFailure(operation)));
344
+ const readChildReservation = Effect.fn("SqliteSubmissionLedger.readChildReservation")(function* (operation, reservationId) {
345
+ const rows = yield* sql`
346
+ SELECT ${sql.literal(CHILD_RESERVATION_COLUMNS)}
347
+ FROM effect_agent_child_reservations
348
+ WHERE reservation_id = ${reservationId}
349
+ `.pipe(Effect.mapError(sqlFailure(operation)));
350
+ const decoded = yield* decodeChildReservationRows(operation, reservationId, rows);
351
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_child_reservations", reservationId, "A child reservation primary key returned more than one row.");
352
+ return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
353
+ });
354
+ const readChildReservationForCall = Effect.fn("SqliteSubmissionLedger.readChildReservationForCall")(function* (operation, parentSubmissionId, parentToolCallId) {
355
+ const rows = yield* sql`
356
+ SELECT ${sql.literal(CHILD_RESERVATION_COLUMNS)}
357
+ FROM effect_agent_child_reservations
358
+ WHERE parent_submission_id = ${parentSubmissionId}
359
+ AND parent_tool_call_id = ${parentToolCallId}
360
+ `.pipe(Effect.mapError(sqlFailure(operation)));
361
+ const decoded = yield* decodeChildReservationRows(operation, `${parentSubmissionId}/${parentToolCallId}`, rows);
362
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_child_reservations", `${parentSubmissionId}/${parentToolCallId}`, "A parent Tool Call returned more than one child reservation.");
363
+ return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
364
+ });
365
+ const childReservationSnapshotFromRow = Effect.fn("SqliteSubmissionLedger.childReservationSnapshotFromRow")(function* (operation, row) {
366
+ const rowFailure = (error) => corruptionFailure(operation, "effect_agent_child_reservations", row.reservation_id, error.message);
367
+ const allocation = yield* parseStoredJsonText(row.allocation_json).pipe(Effect.mapError(rowFailure));
368
+ const accounting = row.accounting_json === null ? void 0 : yield* parseStoredJsonText(row.accounting_json).pipe(Effect.mapError(rowFailure));
369
+ return yield* decodeChildReservationSnapshotUnknown({
370
+ reservationId: row.reservation_id,
371
+ parentSubmissionId: row.parent_submission_id,
372
+ parentToolCallId: row.parent_tool_call_id,
373
+ status: row.status,
374
+ allocation,
375
+ allocationDigest: row.allocation_digest,
376
+ reservedAt: row.reserved_at,
377
+ ...row.child_submission_id === null ? {} : { childSubmissionId: row.child_submission_id },
378
+ ...accounting === void 0 ? {} : { accounting },
379
+ ...row.release_began_at === null ? {} : { releaseBeganAt: row.release_began_at },
380
+ ...row.released_at === null ? {} : { releasedAt: row.released_at }
381
+ }).pipe(Effect.mapError(rowFailure));
382
+ });
383
+ const readApprovalDecisions = Effect.fn("SqliteSubmissionLedger.readApprovalDecisions")(function* (operation, submissionId) {
384
+ const rows = yield* sql`
385
+ SELECT
386
+ submission_id,
387
+ tool_call_id,
388
+ decision,
389
+ resolver,
390
+ reason,
391
+ decided_at
392
+ FROM effect_agent_approval_decisions
393
+ WHERE submission_id = ${submissionId}
394
+ ORDER BY tool_call_id ASC
395
+ `.pipe(Effect.mapError(sqlFailure(operation)));
396
+ return yield* decodeRows(Schema.Array(ApprovalDecisionRow), "effect_agent_approval_decisions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
397
+ });
398
+ const approvalIntentFromRow = Effect.fn("SqliteSubmissionLedger.approvalIntentFromRow")(function* (operation, row) {
399
+ return yield* decodeApprovalDecisionIntent({
400
+ submissionId: row.submission_id,
401
+ toolCallId: row.tool_call_id,
402
+ decision: row.decision,
403
+ resolver: row.resolver,
404
+ reason: row.reason,
405
+ decidedAt: row.decided_at
406
+ }).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_approval_decisions", `${row.submission_id}/${row.tool_call_id}`, error.message)));
407
+ });
408
+ const readUnknownResolutions = Effect.fn("SqliteSubmissionLedger.readUnknownResolutions")(function* (operation, submissionId) {
409
+ const rows = yield* sql`
410
+ SELECT
411
+ submission_id,
412
+ tool_call_id,
413
+ author,
414
+ reason,
415
+ resolution_json,
416
+ resolved_at
417
+ FROM effect_agent_unknown_resolutions
418
+ WHERE submission_id = ${submissionId}
419
+ ORDER BY tool_call_id ASC
420
+ `.pipe(Effect.mapError(sqlFailure(operation)));
421
+ return yield* decodeRows(Schema.Array(UnknownResolutionRow), "effect_agent_unknown_resolutions", submissionId, rows).pipe(Effect.mapError(internalFailure(operation)));
422
+ });
423
+ const unknownResolutionIntentFromRow = Effect.fn("SqliteSubmissionLedger.unknownResolutionIntentFromRow")(function* (operation, row) {
424
+ const resolution = yield* parseStoredJsonText(row.resolution_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_unknown_resolutions", `${row.submission_id}/${row.tool_call_id}`, error.message)));
425
+ return yield* decodeUnknownResolutionIntent({
426
+ submissionId: row.submission_id,
427
+ toolCallId: row.tool_call_id,
428
+ author: row.author,
429
+ reason: row.reason,
430
+ resolution,
431
+ resolvedAt: row.resolved_at
432
+ }).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_unknown_resolutions", `${row.submission_id}/${row.tool_call_id}`, error.message)));
433
+ });
434
+ /** The Submission's marked-unknown open Tool Call identities, empty when never marked. */
435
+ const storedUnknownToolCallIds = Effect.fn("SqliteSubmissionLedger.storedUnknownToolCallIds")(function* (operation, submission) {
436
+ if (submission.unknown_tool_call_ids_json === null) return [];
437
+ return yield* decodeToolCallIdsText(submission.unknown_tool_call_ids_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", submission.submission_id, error.message)));
438
+ });
439
+ /**
440
+ * Canonical history is the abort authority (DUR-015): the intent's canonicalRecordId is
441
+ * derived from the shared canonical-records table using the deterministic abort record
442
+ * identity, never from a cached ledger marker.
443
+ */
444
+ const canonicalAbortRecordId = Effect.fn("SqliteSubmissionLedger.canonicalAbortRecordId")(function* (operation, threadId, submissionId) {
445
+ const recordId = submissionAbortRecordId(submissionId);
446
+ const rows = yield* sql`
447
+ SELECT record_id
448
+ FROM effect_agent_canonical_records
449
+ WHERE thread_id = ${threadId}
450
+ AND record_id = ${recordId}
451
+ `.pipe(Effect.mapError(sqlFailure(operation)));
452
+ return (yield* decodeRows(Schema.Array(CanonicalRecordIdRow), "effect_agent_canonical_records", `${threadId}/${recordId}`, rows).pipe(Effect.mapError(internalFailure(operation)))).length === 0 ? void 0 : recordId;
453
+ });
454
+ const abortIntentFromRow = Effect.fn("SqliteSubmissionLedger.abortIntentFromRow")(function* (operation, submission, submissionId, row) {
455
+ const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.thread_id, submissionId);
456
+ return yield* decodeAbortIntent({
457
+ submissionId: row.submission_id,
458
+ author: row.author,
459
+ reason: row.reason,
460
+ requestedAt: row.requested_at,
461
+ ...canonicalRecordId === void 0 ? {} : { canonicalRecordId }
462
+ }).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_abort_intents", row.submission_id, error.message)));
463
+ });
464
+ const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "durable-node" }));
465
+ const admit = Effect.fn("SqliteSubmissionLedger.admit")(function* (request) {
466
+ const operation = "ledger admit";
467
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AdmissionRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
468
+ const inputJson = yield* encodePersistedJsonText(validated.inputPayload).pipe(Effect.mapError(internalFailure(operation)));
469
+ const agentDigestsJson = yield* encodeDefinitionDigestsText(validated.agentDigests).pipe(Effect.mapError(internalFailure(operation)));
470
+ const mintedSubmissionId = yield* mintIdentifier("submission", operation);
471
+ const mintedReceiptId = yield* mintIdentifier("receipt", operation);
472
+ yield* hitFailpoint("ledger:admit:before", operation);
473
+ const result = yield* inWriteTransaction(operation, Effect.gen(function* () {
474
+ const keyRowKey = `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`;
475
+ const existingRows = yield* sql`
476
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
477
+ FROM effect_agent_submissions
478
+ WHERE thread_id = ${validated.threadId}
479
+ AND principal = ${validated.principal}
480
+ AND idempotency_key = ${validated.idempotencyKey}
481
+ `.pipe(Effect.mapError(sqlFailure(operation)));
482
+ const existing = yield* decodeSubmissionRows(operation, keyRowKey, existingRows);
483
+ if (existing.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", keyRowKey, "An admission idempotency key returned more than one row.");
484
+ if (existing.length === 1) {
485
+ const sameLinkage = validated.parentLinkage === void 0 ? existing[0].parent_submission_id === null && existing[0].parent_tool_call_id === null : existing[0].parent_submission_id === validated.parentLinkage.parentSubmissionId && existing[0].parent_tool_call_id === validated.parentLinkage.parentToolCallId;
486
+ if (existing[0].input_digest !== validated.inputDigest || !sameLinkage) return yield* AdmissionConflict.make({
487
+ threadId: validated.threadId,
488
+ principal: validated.principal,
489
+ idempotencyKey: validated.idempotencyKey,
490
+ existingInputDigest: existing[0].input_digest,
491
+ attemptedInputDigest: validated.inputDigest
492
+ });
493
+ return yield* decodeAdmissionResult({
494
+ submissionId: existing[0].submission_id,
495
+ receiptId: existing[0].receipt_id,
496
+ queueSequence: existing[0].queue_sequence,
497
+ state: existing[0].state,
498
+ replayed: true
499
+ }).pipe(Effect.mapError(internalFailure(operation)));
500
+ }
501
+ const maxRows = yield* sql`
502
+ SELECT COALESCE(MAX(queue_sequence), 0) AS max_queue_sequence
503
+ FROM effect_agent_submissions
504
+ WHERE thread_id = ${validated.threadId}
505
+ `.pipe(Effect.mapError(sqlFailure(operation)));
506
+ const decodedMax = yield* decodeRows(Schema.Array(MaxQueueSequenceRow), "effect_agent_submissions", validated.threadId, maxRows).pipe(Effect.mapError(internalFailure(operation)));
507
+ const queueSequence = yield* decodeQueueSequence((decodedMax[0]?.max_queue_sequence ?? 0) + 1).pipe(Effect.mapError(internalFailure(operation)));
508
+ const now = yield* currentInstant;
509
+ yield* sql`
510
+ INSERT INTO effect_agent_submissions (
511
+ submission_id,
512
+ thread_id,
513
+ queue_sequence,
514
+ principal,
515
+ idempotency_key,
516
+ agent_id,
517
+ agent_digests_json,
518
+ deployment_id,
519
+ input_json,
520
+ input_digest,
521
+ receipt_id,
522
+ state,
523
+ created_at,
524
+ parent_submission_id,
525
+ parent_tool_call_id
526
+ ) VALUES (
527
+ ${mintedSubmissionId},
528
+ ${validated.threadId},
529
+ ${queueSequence},
530
+ ${validated.principal},
531
+ ${validated.idempotencyKey},
532
+ ${validated.agentId},
533
+ ${agentDigestsJson},
534
+ ${validated.deploymentId},
535
+ ${inputJson},
536
+ ${validated.inputDigest},
537
+ ${mintedReceiptId},
538
+ 'admitted',
539
+ ${now.iso},
540
+ ${validated.parentLinkage?.parentSubmissionId ?? null},
541
+ ${validated.parentLinkage?.parentToolCallId ?? null}
542
+ )
543
+ `.pipe(Effect.mapError(sqlFailure(operation)));
544
+ return yield* decodeAdmissionResult({
545
+ submissionId: mintedSubmissionId,
546
+ receiptId: mintedReceiptId,
547
+ queueSequence,
548
+ state: "admitted",
549
+ replayed: false
550
+ }).pipe(Effect.mapError(internalFailure(operation)));
551
+ }));
552
+ yield* hitFailpoint("ledger:admit:after", operation);
553
+ return result;
554
+ });
555
+ const markReady = Effect.fn("SqliteSubmissionLedger.markReady")(function* (request) {
556
+ const operation = "ledger mark ready";
557
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkReadyRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
558
+ yield* hitFailpoint("ledger:mark-ready:before", operation);
559
+ yield* inWriteTransaction(operation, Effect.gen(function* () {
560
+ if ((yield* requireSubmission(operation, validated.submissionId)).state !== "admitted") return;
561
+ const now = yield* currentInstant;
562
+ yield* sql`
563
+ UPDATE effect_agent_submissions
564
+ SET state = 'ready', ready_at = ${now.iso}
565
+ WHERE submission_id = ${validated.submissionId}
566
+ `.pipe(Effect.mapError(sqlFailure(operation)));
567
+ }));
568
+ yield* hitFailpoint("ledger:mark-ready:after", operation);
569
+ });
570
+ const lookup = Effect.fn("SqliteSubmissionLedger.lookup")(function* (request) {
571
+ const operation = "ledger lookup";
572
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SubmissionLookup))(request).pipe(Effect.mapError(internalFailure(operation)));
573
+ if (validated._tag === "SubmissionLookupById") {
574
+ const row = yield* readSubmission(operation, validated.submissionId);
575
+ if (Option.isNone(row)) return Option.none();
576
+ return Option.some(yield* decodeSubmissionSnapshot(operation, row.value));
577
+ }
578
+ const rows = yield* sql`
579
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
580
+ FROM effect_agent_submissions
581
+ WHERE thread_id = ${validated.threadId}
582
+ AND principal = ${validated.principal}
583
+ AND idempotency_key = ${validated.idempotencyKey}
584
+ `.pipe(Effect.mapError(sqlFailure(operation)));
585
+ const decoded = yield* decodeSubmissionRows(operation, `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, rows);
586
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, "An admission idempotency key returned more than one row.");
587
+ if (decoded.length === 0) return Option.none();
588
+ return Option.some(yield* decodeSubmissionSnapshot(operation, decoded[0]));
589
+ });
590
+ const resolveAdmission = Effect.fn("SqliteSubmissionLedger.resolveAdmission")(function* (request) {
591
+ const operation = "ledger resolve admission";
592
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SubmissionLookupByKey))(request).pipe(Effect.mapError(internalFailure(operation)));
593
+ const rows = yield* sql`
594
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
595
+ FROM effect_agent_submissions
596
+ WHERE thread_id = ${validated.threadId}
597
+ AND principal = ${validated.principal}
598
+ AND idempotency_key = ${validated.idempotencyKey}
599
+ `.pipe(Effect.mapError(sqlFailure(operation)));
600
+ const decoded = yield* decodeSubmissionRows(operation, `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, rows);
601
+ if (decoded.length > 1) return yield* corruptionFailure(operation, "effect_agent_submissions", `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`, "An admission idempotency key returned more than one row.");
602
+ if (decoded.length === 0) return AdmissionNotAdmitted.make();
603
+ return AdmissionAdmitted.make({ submission: yield* decodeSubmissionSnapshot(operation, decoded[0]) });
604
+ });
605
+ const claim = Effect.fn("SqliteSubmissionLedger.claim")(function* (request) {
606
+ const operation = "ledger claim";
607
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
608
+ const attemptId = yield* mintIdentifier("attempt", operation);
609
+ const ownershipToken = yield* mintIdentifier("owner", operation);
610
+ yield* hitFailpoint("ledger:claim:before", operation);
611
+ const claimed = yield* inWriteTransaction(operation, Effect.gen(function* () {
612
+ const headRows = yield* sql`
613
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
614
+ FROM effect_agent_submissions
615
+ WHERE thread_id = ${validated.threadId}
616
+ AND state <> 'settled'
617
+ ORDER BY queue_sequence ASC
618
+ LIMIT 1
619
+ `.pipe(Effect.mapError(sqlFailure(operation)));
620
+ const heads = yield* decodeSubmissionRows(operation, validated.threadId, headRows);
621
+ if (heads.length === 0) return Option.none();
622
+ const head = heads[0];
623
+ if (head.state === "joining" || head.state === "joined" || head.state === "suspended" || head.state === "unknown" && Option.isNone(yield* readAbortIntent(operation, head.submission_id))) return Option.none();
624
+ const now = yield* currentInstant;
625
+ const ownership = yield* readOwnership(operation, head.submission_id);
626
+ if (Option.isSome(ownership)) {
627
+ if ((yield* timestampMillis(operation, head.submission_id)(ownership.value.lease_expires_at)) > now.millis) return Option.none();
628
+ }
629
+ const threads = yield* journal.getThread(head.thread_id).pipe(Effect.mapError(internalFailure(operation)));
630
+ let producerEpoch;
631
+ if (threads.length === 0) {
632
+ producerEpoch = 1;
633
+ yield* sql`
634
+ INSERT INTO effect_agent_threads (
635
+ thread_id,
636
+ created_at,
637
+ tail_sequence,
638
+ tail_digest,
639
+ producer_epoch
640
+ ) VALUES (
641
+ ${head.thread_id},
642
+ ${now.iso},
643
+ 0,
644
+ ${EMPTY_TAIL_DIGEST},
645
+ ${producerEpoch}
646
+ )
647
+ `.pipe(Effect.mapError(sqlFailure(operation)));
648
+ } else {
649
+ producerEpoch = threads[0].producer_epoch + 1;
650
+ yield* sql`
651
+ UPDATE effect_agent_threads
652
+ SET producer_epoch = ${producerEpoch}
653
+ WHERE thread_id = ${head.thread_id}
654
+ `.pipe(Effect.mapError(sqlFailure(operation)));
655
+ }
656
+ const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
657
+ yield* sql`
658
+ INSERT INTO effect_agent_submission_ownership (
659
+ submission_id,
660
+ attempt_id,
661
+ ownership_token,
662
+ producer_epoch,
663
+ owner_producer_id,
664
+ lease_expires_at
665
+ ) VALUES (
666
+ ${head.submission_id},
667
+ ${attemptId},
668
+ ${ownershipToken},
669
+ ${producerEpoch},
670
+ ${validated.producerId},
671
+ ${leaseExpiresAt}
672
+ )
673
+ ON CONFLICT (submission_id) DO UPDATE SET
674
+ attempt_id = excluded.attempt_id,
675
+ ownership_token = excluded.ownership_token,
676
+ producer_epoch = excluded.producer_epoch,
677
+ owner_producer_id = excluded.owner_producer_id,
678
+ lease_expires_at = excluded.lease_expires_at
679
+ `.pipe(Effect.mapError(sqlFailure(operation)));
680
+ yield* sql`
681
+ INSERT INTO effect_agent_attempts (
682
+ attempt_id,
683
+ submission_id,
684
+ thread_id,
685
+ owner_producer_id,
686
+ producer_epoch,
687
+ claimed_at
688
+ ) VALUES (
689
+ ${attemptId},
690
+ ${head.submission_id},
691
+ ${head.thread_id},
692
+ ${validated.producerId},
693
+ ${producerEpoch},
694
+ ${now.iso}
695
+ )
696
+ `.pipe(Effect.mapError(sqlFailure(operation)));
697
+ if (head.state === "ready") yield* sql`
698
+ UPDATE effect_agent_submissions
699
+ SET state = 'running'
700
+ WHERE submission_id = ${head.submission_id}
701
+ `.pipe(Effect.mapError(sqlFailure(operation)));
702
+ const inputPayload = yield* parseStoredJsonText(head.input_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", head.submission_id, error.message)));
703
+ return Option.some(yield* decodeClaim({
704
+ submissionId: head.submission_id,
705
+ attemptId,
706
+ ownershipToken,
707
+ producerEpoch,
708
+ leaseExpiresAt,
709
+ inputPayload
710
+ }).pipe(Effect.mapError(internalFailure(operation))));
711
+ }));
712
+ yield* hitFailpoint("ledger:claim:after", operation);
713
+ return claimed;
714
+ });
715
+ const renewOwnership = Effect.fn("SqliteSubmissionLedger.renewOwnership")(function* (request) {
716
+ const operation = "ledger renew ownership";
717
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RenewOwnershipRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
718
+ yield* hitFailpoint("ledger:renew:before", operation);
719
+ const renewal = yield* inWriteTransaction(operation, Effect.gen(function* () {
720
+ const submission = yield* requireSubmission(operation, validated.submissionId);
721
+ yield* requireOwnership(operation, submission, validated.ownershipToken);
722
+ const now = yield* currentInstant;
723
+ const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
724
+ yield* sql`
725
+ UPDATE effect_agent_submission_ownership
726
+ SET lease_expires_at = ${leaseExpiresAt}
727
+ WHERE submission_id = ${validated.submissionId}
728
+ `.pipe(Effect.mapError(sqlFailure(operation)));
729
+ return yield* decodeOwnershipRenewal({
730
+ ownershipToken: validated.ownershipToken,
731
+ leaseExpiresAt
732
+ }).pipe(Effect.mapError(internalFailure(operation)));
733
+ }));
734
+ yield* hitFailpoint("ledger:renew:after", operation);
735
+ return renewal;
736
+ });
737
+ const releaseOwnership = Effect.fn("SqliteSubmissionLedger.releaseOwnership")(function* (request) {
738
+ const operation = "ledger release ownership";
739
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseOwnershipRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
740
+ yield* hitFailpoint("ledger:release:before", operation);
741
+ yield* inWriteTransaction(operation, Effect.gen(function* () {
742
+ const submission = yield* requireSubmission(operation, validated.submissionId);
743
+ yield* requireOwnership(operation, submission, validated.ownershipToken);
744
+ yield* sql`
745
+ DELETE FROM effect_agent_submission_ownership
746
+ WHERE submission_id = ${validated.submissionId}
747
+ `.pipe(Effect.mapError(sqlFailure(operation)));
748
+ if (submission.state === "running") yield* sql`
749
+ UPDATE effect_agent_submissions
750
+ SET state = 'ready'
751
+ WHERE submission_id = ${validated.submissionId}
752
+ `.pipe(Effect.mapError(sqlFailure(operation)));
753
+ }));
754
+ yield* hitFailpoint("ledger:release:after", operation);
755
+ });
756
+ const markInputApplied = Effect.fn("SqliteSubmissionLedger.markInputApplied")(function* (request) {
757
+ const operation = "ledger mark input applied";
758
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkInputAppliedRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
759
+ yield* hitFailpoint("ledger:mark-input-applied:before", operation);
760
+ yield* inWriteTransaction(operation, Effect.gen(function* () {
761
+ const submission = yield* requireSubmission(operation, validated.submissionId);
762
+ yield* requireOwnership(operation, submission, validated.ownershipToken);
763
+ if (submission.input_applied_record_id !== null) {
764
+ if (submission.input_applied_record_id === validated.recordId && submission.input_applied_sequence === validated.sequence) return;
765
+ return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A different canonical input marker is already recorded for this Submission.");
766
+ }
767
+ yield* sql`
768
+ UPDATE effect_agent_submissions
769
+ SET
770
+ input_applied_record_id = ${validated.recordId},
771
+ input_applied_sequence = ${validated.sequence},
772
+ state = CASE
773
+ WHEN state IN ('admitted', 'ready', 'running') THEN 'input-applied'
774
+ ELSE state
775
+ END
776
+ WHERE submission_id = ${validated.submissionId}
777
+ `.pipe(Effect.mapError(sqlFailure(operation)));
778
+ }));
779
+ yield* hitFailpoint("ledger:mark-input-applied:after", operation);
780
+ });
781
+ const reserveSettlement = Effect.fn("SqliteSubmissionLedger.reserveSettlement")(function* (request) {
782
+ const operation = "ledger reserve settlement";
783
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementReservation))(request).pipe(Effect.mapError(internalFailure(operation)));
784
+ const recordJson = yield* encodeRecordEnvelopeText(validated.record).pipe(Effect.mapError(internalFailure(operation)));
785
+ yield* hitFailpoint("ledger:reserve-settlement:before", operation);
786
+ const reserved = yield* inWriteTransaction(operation, Effect.gen(function* () {
787
+ const existing = yield* readReservation(operation, validated.submissionId);
788
+ if (Option.isSome(existing)) {
789
+ if (!(existing.value.settlement_id === validated.settlementId && existing.value.outcome === validated.outcome && existing.value.record_digest === validated.recordDigest && existing.value.record_json === recordJson)) return yield* SettlementConflict.make({
790
+ submissionId: validated.submissionId,
791
+ existingOutcome: existing.value.outcome
792
+ });
793
+ const record = yield* decodeRecordEnvelopeText(existing.value.record_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_settlement_reservations", validated.submissionId, error.message)));
794
+ return ReservedSettlement.make({
795
+ submissionId: validated.submissionId,
796
+ settlementId: validated.settlementId,
797
+ outcome: validated.outcome,
798
+ record,
799
+ recordDigest: validated.recordDigest,
800
+ replayed: true
801
+ });
802
+ }
803
+ const submission = yield* requireSubmission(operation, validated.submissionId);
804
+ if (submission.state === "settled") {
805
+ if (submission.settled_outcome === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A settled Submission carries no terminal outcome.");
806
+ return yield* SettlementConflict.make({
807
+ submissionId: validated.submissionId,
808
+ existingOutcome: submission.settled_outcome
809
+ });
810
+ }
811
+ if (!(submission.state === "joined" && submission.joined_host_submission_id !== null)) {
812
+ let queuedAbortSettlement = false;
813
+ if (validated.outcome === "aborted" && (submission.state === "ready" || submission.state === "terminalizing")) {
814
+ const abortIntent = yield* readAbortIntent(operation, validated.submissionId);
815
+ if (Option.isSome(abortIntent)) {
816
+ const ownership = yield* readOwnership(operation, validated.submissionId);
817
+ queuedAbortSettlement = Option.isNone(ownership);
818
+ }
819
+ }
820
+ if (!queuedAbortSettlement) yield* requireOwnership(operation, submission, validated.ownershipToken);
821
+ }
822
+ const now = yield* currentInstant;
823
+ yield* sql`
824
+ INSERT INTO effect_agent_settlement_reservations (
825
+ submission_id,
826
+ settlement_id,
827
+ outcome,
828
+ record_id,
829
+ record_json,
830
+ record_digest,
831
+ reserved_at
832
+ ) VALUES (
833
+ ${validated.submissionId},
834
+ ${validated.settlementId},
835
+ ${validated.outcome},
836
+ ${validated.record.recordId},
837
+ ${recordJson},
838
+ ${validated.recordDigest},
839
+ ${now.iso}
840
+ )
841
+ `.pipe(Effect.mapError(sqlFailure(operation)));
842
+ yield* sql`
843
+ UPDATE effect_agent_submissions
844
+ SET state = 'terminalizing'
845
+ WHERE submission_id = ${validated.submissionId}
846
+ `.pipe(Effect.mapError(sqlFailure(operation)));
847
+ return ReservedSettlement.make({
848
+ submissionId: validated.submissionId,
849
+ settlementId: validated.settlementId,
850
+ outcome: validated.outcome,
851
+ record: validated.record,
852
+ recordDigest: validated.recordDigest,
853
+ replayed: false
854
+ });
855
+ }));
856
+ yield* hitFailpoint("ledger:reserve-settlement:after", operation);
857
+ return reserved;
858
+ });
859
+ const finalizeSettlement = Effect.fn("SqliteSubmissionLedger.finalizeSettlement")(function* (request) {
860
+ const operation = "ledger finalize settlement";
861
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementFinalization))(request).pipe(Effect.mapError(internalFailure(operation)));
862
+ yield* hitFailpoint("ledger:finalize-settlement:before", operation);
863
+ const settlement = yield* inWriteTransaction(operation, Effect.gen(function* () {
864
+ const reservation = yield* readReservation(operation, validated.submissionId);
865
+ if (Option.isNone(reservation)) return yield* LedgerError.make({
866
+ operation,
867
+ message: `No settlement reservation exists for submission ${validated.submissionId}.`
868
+ });
869
+ if (reservation.value.settlement_id !== validated.settlementId) return yield* SettlementConflict.make({
870
+ submissionId: validated.submissionId,
871
+ existingOutcome: reservation.value.outcome
872
+ });
873
+ const reservationRecord = yield* decodeRecordEnvelopeText(reservation.value.record_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_settlement_reservations", validated.submissionId, error.message)));
874
+ const settlementFailure = settlementFailureFromRecord(reservationRecord);
875
+ if (reservation.value.outcome === "failed" !== (settlementFailure !== void 0)) return yield* corruptionFailure(operation, "effect_agent_settlement_reservations", validated.submissionId, "The reserved outcome and canonical failure diagnostic disagree.");
876
+ const submission = yield* requireSubmission(operation, validated.submissionId);
877
+ if (submission.state === "settled") {
878
+ if (reservation.value.finalized_at === null) return yield* corruptionFailure(operation, "effect_agent_settlement_reservations", validated.submissionId, "A settled Submission's reservation carries no finalization timestamp.");
879
+ return yield* decodeSettlement({
880
+ submissionId: validated.submissionId,
881
+ settlementId: validated.settlementId,
882
+ receiptId: submission.receipt_id,
883
+ outcome: reservation.value.outcome,
884
+ ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
885
+ settledAt: reservation.value.finalized_at
886
+ }).pipe(Effect.mapError(internalFailure(operation)));
887
+ }
888
+ const now = yield* currentInstant;
889
+ yield* sql`
890
+ UPDATE effect_agent_submissions
891
+ SET state = 'settled', settled_outcome = ${reservation.value.outcome}
892
+ WHERE submission_id = ${validated.submissionId}
893
+ `.pipe(Effect.mapError(sqlFailure(operation)));
894
+ yield* sql`
895
+ UPDATE effect_agent_settlement_reservations
896
+ SET finalized_at = ${now.iso}
897
+ WHERE submission_id = ${validated.submissionId}
898
+ `.pipe(Effect.mapError(sqlFailure(operation)));
899
+ yield* sql`
900
+ DELETE FROM effect_agent_submission_ownership
901
+ WHERE submission_id = ${validated.submissionId}
902
+ `.pipe(Effect.mapError(sqlFailure(operation)));
903
+ return yield* decodeSettlement({
904
+ submissionId: validated.submissionId,
905
+ settlementId: validated.settlementId,
906
+ receiptId: submission.receipt_id,
907
+ outcome: reservation.value.outcome,
908
+ ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
909
+ settledAt: now.iso
910
+ }).pipe(Effect.mapError(internalFailure(operation)));
911
+ }));
912
+ yield* hitFailpoint("ledger:finalize-settlement:after", operation);
913
+ return settlement;
914
+ });
915
+ const requestAbort = Effect.fn("SqliteSubmissionLedger.requestAbort")(function* (request) {
916
+ const operation = "ledger request abort";
917
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AbortCommand))(request).pipe(Effect.mapError(internalFailure(operation)));
918
+ yield* hitFailpoint("ledger:request-abort:before", operation);
919
+ const intent = yield* inWriteTransaction(operation, Effect.gen(function* () {
920
+ const submission = yield* requireSubmission(operation, validated.submissionId);
921
+ if (submission.state === "settled") {
922
+ if (submission.settled_outcome === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A settled Submission carries no terminal outcome.");
923
+ return yield* SettlementConflict.make({
924
+ submissionId: validated.submissionId,
925
+ existingOutcome: submission.settled_outcome
926
+ });
927
+ }
928
+ if (submission.state === "joined") {
929
+ if (submission.joined_host_submission_id === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A joined Submission carries no host linkage.");
930
+ const hostSubmissionId = yield* decodeSubmissionId(submission.joined_host_submission_id).pipe(Effect.mapError(internalFailure(operation)));
931
+ return yield* JoinedToHost.make({
932
+ submissionId: validated.submissionId,
933
+ hostSubmissionId
934
+ });
935
+ }
936
+ const existing = yield* readAbortIntent(operation, validated.submissionId);
937
+ if (Option.isSome(existing)) return yield* abortIntentFromRow(operation, submission, validated.submissionId, existing.value);
938
+ const now = yield* currentInstant;
939
+ yield* sql`
940
+ INSERT INTO effect_agent_abort_intents (
941
+ submission_id,
942
+ author,
943
+ reason,
944
+ requested_at
945
+ ) VALUES (
946
+ ${validated.submissionId},
947
+ ${validated.author},
948
+ ${validated.reason},
949
+ ${now.iso}
950
+ )
951
+ `.pipe(Effect.mapError(sqlFailure(operation)));
952
+ const canonicalRecordId = yield* canonicalAbortRecordId(operation, submission.thread_id, validated.submissionId);
953
+ return yield* decodeAbortIntent({
954
+ submissionId: validated.submissionId,
955
+ author: validated.author,
956
+ reason: validated.reason,
957
+ requestedAt: now.iso,
958
+ ...canonicalRecordId === void 0 ? {} : { canonicalRecordId }
959
+ }).pipe(Effect.mapError(internalFailure(operation)));
960
+ }));
961
+ yield* hitFailpoint("ledger:request-abort:after", operation);
962
+ return intent;
963
+ });
964
+ const claimJoining = Effect.fn("SqliteSubmissionLedger.claimJoining")(function* (request) {
965
+ const operation = "ledger claim joining";
966
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimJoiningRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
967
+ yield* hitFailpoint("ledger:claim-joining:before", operation);
968
+ const claims = yield* inWriteTransaction(operation, Effect.gen(function* () {
969
+ const host = yield* requireSubmission(operation, validated.hostSubmissionId);
970
+ if (host.thread_id !== validated.threadId) return yield* LedgerError.make({
971
+ operation,
972
+ message: `Host submission ${validated.hostSubmissionId} does not belong to thread ${validated.threadId}.`
973
+ });
974
+ yield* requireOwnership(operation, host, validated.ownershipToken);
975
+ const laterRows = yield* sql`
976
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
977
+ FROM effect_agent_submissions
978
+ WHERE thread_id = ${validated.threadId}
979
+ AND queue_sequence > ${host.queue_sequence}
980
+ ORDER BY queue_sequence ASC
981
+ `.pipe(Effect.mapError(sqlFailure(operation)));
982
+ const later = yield* decodeSubmissionRows(operation, validated.threadId, laterRows);
983
+ const claimed = [];
984
+ for (const row of later) {
985
+ if (claimed.length >= validated.maxCount) break;
986
+ if ((row.state === "joining" || row.state === "joined") && row.joined_host_submission_id === validated.hostSubmissionId) continue;
987
+ if (row.state === "settled" && row.settled_outcome === "aborted") continue;
988
+ if (row.state !== "ready") break;
989
+ yield* sql`
990
+ UPDATE effect_agent_submissions
991
+ SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
992
+ WHERE submission_id = ${row.submission_id}
993
+ `.pipe(Effect.mapError(sqlFailure(operation)));
994
+ const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", row.submission_id, error.message)));
995
+ claimed.push(yield* decodeJoiningClaim({
996
+ submissionId: row.submission_id,
997
+ queueSequence: row.queue_sequence,
998
+ inputPayload
999
+ }).pipe(Effect.mapError(internalFailure(operation))));
1000
+ }
1001
+ return claimed;
1002
+ }));
1003
+ yield* hitFailpoint("ledger:claim-joining:after", operation);
1004
+ return claims;
1005
+ });
1006
+ const markJoined = Effect.fn("SqliteSubmissionLedger.markJoined")(function* (request) {
1007
+ const operation = "ledger mark joined";
1008
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkJoinedRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1009
+ yield* hitFailpoint("ledger:mark-joined:before", operation);
1010
+ yield* inWriteTransaction(operation, Effect.gen(function* () {
1011
+ const submission = yield* requireSubmission(operation, validated.submissionId);
1012
+ if (submission.joined_host_submission_id === null) return yield* LedgerError.make({
1013
+ operation,
1014
+ message: `Submission ${validated.submissionId} was never claimed for joining.`
1015
+ });
1016
+ const host = yield* requireSubmission(operation, submission.joined_host_submission_id);
1017
+ yield* requireOwnership(operation, host, validated.ownershipToken);
1018
+ if (submission.input_applied_record_id !== null) {
1019
+ if (submission.input_applied_record_id === validated.recordId && submission.input_applied_sequence === validated.sequence) return;
1020
+ return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A different join marker is already recorded for this Submission.");
1021
+ }
1022
+ if (submission.state !== "joining" && submission.state !== "joined") return yield* LedgerError.make({
1023
+ operation,
1024
+ message: `Cannot mark submission ${validated.submissionId} joined from state ${submission.state}.`
1025
+ });
1026
+ yield* sql`
1027
+ UPDATE effect_agent_submissions
1028
+ SET
1029
+ input_applied_record_id = ${validated.recordId},
1030
+ input_applied_sequence = ${validated.sequence},
1031
+ state = 'joined'
1032
+ WHERE submission_id = ${validated.submissionId}
1033
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1034
+ }));
1035
+ yield* hitFailpoint("ledger:mark-joined:after", operation);
1036
+ });
1037
+ const revertJoining = Effect.fn("SqliteSubmissionLedger.revertJoining")(function* (request) {
1038
+ const operation = "ledger revert joining";
1039
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RevertJoiningRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1040
+ yield* hitFailpoint("ledger:revert-joining:before", operation);
1041
+ yield* inWriteTransaction(operation, Effect.gen(function* () {
1042
+ if ((yield* requireSubmission(operation, validated.submissionId)).state !== "joining") return;
1043
+ yield* sql`
1044
+ UPDATE effect_agent_submissions
1045
+ SET state = 'ready', joined_host_submission_id = NULL
1046
+ WHERE submission_id = ${validated.submissionId}
1047
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1048
+ }));
1049
+ yield* hitFailpoint("ledger:revert-joining:after", operation);
1050
+ });
1051
+ const suspend = Effect.fn("SqliteSubmissionLedger.suspend")(function* (request) {
1052
+ const operation = "ledger suspend";
1053
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SuspendRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1054
+ const reasonJson = yield* encodeSuspensionReasonText(validated.reason).pipe(Effect.mapError(internalFailure(operation)));
1055
+ yield* hitFailpoint("ledger:suspend:before", operation);
1056
+ const outcome = yield* inWriteTransaction(operation, Effect.gen(function* () {
1057
+ const submission = yield* requireSubmission(operation, validated.submissionId);
1058
+ if (submission.state === "settled") {
1059
+ if (submission.settled_outcome === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A settled Submission carries no terminal outcome.");
1060
+ return yield* SettlementConflict.make({
1061
+ submissionId: validated.submissionId,
1062
+ existingOutcome: submission.settled_outcome
1063
+ });
1064
+ }
1065
+ const reservation = yield* readReservation(operation, validated.submissionId);
1066
+ if (Option.isSome(reservation)) return yield* SettlementConflict.make({
1067
+ submissionId: validated.submissionId,
1068
+ existingOutcome: reservation.value.outcome
1069
+ });
1070
+ yield* requireOwnership(operation, submission, validated.ownershipToken);
1071
+ if (validated.reason._tag === "ApprovalPending") {
1072
+ const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
1073
+ const decided = new Set(decisions.map((row) => row.tool_call_id));
1074
+ if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return RESUME_IMMEDIATELY;
1075
+ } else {
1076
+ let allSettled = true;
1077
+ for (const child of validated.reason.children) {
1078
+ const childRow = yield* readSubmission(operation, child.childSubmissionId);
1079
+ if (Option.isNone(childRow) || childRow.value.state !== "settled") {
1080
+ allSettled = false;
1081
+ break;
1082
+ }
1083
+ }
1084
+ if (allSettled) return RESUME_IMMEDIATELY;
1085
+ }
1086
+ const now = yield* currentInstant;
1087
+ yield* sql`
1088
+ UPDATE effect_agent_submissions
1089
+ SET
1090
+ state = 'suspended',
1091
+ suspended_reason_json = ${reasonJson},
1092
+ suspended_at = ${now.iso}
1093
+ WHERE submission_id = ${validated.submissionId}
1094
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1095
+ yield* sql`
1096
+ DELETE FROM effect_agent_submission_ownership
1097
+ WHERE submission_id = ${validated.submissionId}
1098
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1099
+ return SUSPENDED;
1100
+ }));
1101
+ yield* hitFailpoint("ledger:suspend:after", operation);
1102
+ return outcome;
1103
+ });
1104
+ /**
1105
+ * Once every pending call of a recorded ApprovalPending suspension has a decision intent,
1106
+ * the lane wakes: suspended → input-applied, suspension cleared (plan §2.6). A
1107
+ * WaitingForChild suspension wakes only through recordChildSettled. Runs inside the caller's
1108
+ * write transaction.
1109
+ */
1110
+ const wakeSuspendedIfCovered = Effect.fn("SqliteSubmissionLedger.wakeSuspendedIfCovered")(function* (operation, submission) {
1111
+ if (submission.state !== "suspended" || submission.suspended_reason_json === null) return;
1112
+ const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(submission.suspended_reason_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", submission.submission_id, error.message)));
1113
+ if (reason._tag !== "ApprovalPending") return;
1114
+ const decisions = yield* readApprovalDecisions(operation, submission.submission_id);
1115
+ const decided = new Set(decisions.map((row) => row.tool_call_id));
1116
+ if (!reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return;
1117
+ yield* sql`
1118
+ UPDATE effect_agent_submissions
1119
+ SET
1120
+ state = 'input-applied',
1121
+ suspended_reason_json = NULL,
1122
+ suspended_at = NULL
1123
+ WHERE submission_id = ${submission.submission_id}
1124
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1125
+ });
1126
+ const recordApprovalDecision = Effect.fn("SqliteSubmissionLedger.recordApprovalDecision")(function* (command) {
1127
+ const operation = "ledger record approval decision";
1128
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ApprovalDecisionCommand))(command).pipe(Effect.mapError(internalFailure(operation)));
1129
+ yield* hitFailpoint("ledger:approval-decision:before", operation);
1130
+ const intent = yield* inWriteTransaction(operation, Effect.gen(function* () {
1131
+ const submission = yield* requireSubmission(operation, validated.submissionId);
1132
+ if (submission.state === "settled") {
1133
+ if (submission.settled_outcome === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A settled Submission carries no terminal outcome.");
1134
+ return yield* SettlementConflict.make({
1135
+ submissionId: validated.submissionId,
1136
+ existingOutcome: submission.settled_outcome
1137
+ });
1138
+ }
1139
+ const existing = (yield* readApprovalDecisions(operation, validated.submissionId)).find((row) => row.tool_call_id === validated.toolCallId);
1140
+ if (existing !== void 0) {
1141
+ if (existing.decision !== validated.decision) return yield* ApprovalConflict.make({
1142
+ submissionId: validated.submissionId,
1143
+ toolCallId: validated.toolCallId,
1144
+ existingDecision: existing.decision
1145
+ });
1146
+ return yield* approvalIntentFromRow(operation, existing);
1147
+ }
1148
+ const now = yield* currentInstant;
1149
+ yield* sql`
1150
+ INSERT INTO effect_agent_approval_decisions (
1151
+ submission_id,
1152
+ tool_call_id,
1153
+ decision,
1154
+ resolver,
1155
+ reason,
1156
+ decided_at
1157
+ ) VALUES (
1158
+ ${validated.submissionId},
1159
+ ${validated.toolCallId},
1160
+ ${validated.decision},
1161
+ ${validated.resolver},
1162
+ ${validated.reason},
1163
+ ${now.iso}
1164
+ )
1165
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1166
+ yield* wakeSuspendedIfCovered(operation, submission);
1167
+ return yield* decodeApprovalDecisionIntent({
1168
+ submissionId: validated.submissionId,
1169
+ toolCallId: validated.toolCallId,
1170
+ decision: validated.decision,
1171
+ resolver: validated.resolver,
1172
+ reason: validated.reason,
1173
+ decidedAt: now.iso
1174
+ }).pipe(Effect.mapError(internalFailure(operation)));
1175
+ }));
1176
+ yield* hitFailpoint("ledger:approval-decision:after", operation);
1177
+ return intent;
1178
+ });
1179
+ const markUnknown = Effect.fn("SqliteSubmissionLedger.markUnknown")(function* (request) {
1180
+ const operation = "ledger mark unknown";
1181
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkUnknownRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1182
+ yield* hitFailpoint("ledger:mark-unknown:before", operation);
1183
+ yield* inWriteTransaction(operation, Effect.gen(function* () {
1184
+ const submission = yield* requireSubmission(operation, validated.submissionId);
1185
+ if (submission.state === "settled") {
1186
+ if (submission.settled_outcome === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A settled Submission carries no terminal outcome.");
1187
+ return yield* SettlementConflict.make({
1188
+ submissionId: validated.submissionId,
1189
+ existingOutcome: submission.settled_outcome
1190
+ });
1191
+ }
1192
+ const reservation = yield* readReservation(operation, validated.submissionId);
1193
+ if (Option.isSome(reservation)) return yield* SettlementConflict.make({
1194
+ submissionId: validated.submissionId,
1195
+ existingOutcome: reservation.value.outcome
1196
+ });
1197
+ const existingIds = yield* storedUnknownToolCallIds(operation, submission);
1198
+ const known = new Set(existingIds);
1199
+ const merged = [...existingIds, ...validated.toolCallIds.filter((toolCallId) => !known.has(toolCallId))];
1200
+ const idsJson = yield* encodeToolCallIdsText(merged).pipe(Effect.mapError(internalFailure(operation)));
1201
+ yield* sql`
1202
+ UPDATE effect_agent_submissions
1203
+ SET
1204
+ state = 'unknown',
1205
+ unknown_reason = ${submission.unknown_reason ?? validated.reason},
1206
+ unknown_tool_call_ids_json = ${idsJson}
1207
+ WHERE submission_id = ${validated.submissionId}
1208
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1209
+ }));
1210
+ yield* hitFailpoint("ledger:mark-unknown:after", operation);
1211
+ });
1212
+ const recordUnknownResolution = Effect.fn("SqliteSubmissionLedger.recordUnknownResolution")(function* (command) {
1213
+ const operation = "ledger record unknown resolution";
1214
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(UnknownResolutionCommand))(command).pipe(Effect.mapError(internalFailure(operation)));
1215
+ const resolutionJson = yield* encodeUnknownResolutionText(validated.resolution).pipe(Effect.mapError(internalFailure(operation)));
1216
+ yield* hitFailpoint("ledger:unknown-resolution:before", operation);
1217
+ const intent = yield* inWriteTransaction(operation, Effect.gen(function* () {
1218
+ const submission = yield* requireSubmission(operation, validated.submissionId);
1219
+ if (submission.state === "settled") {
1220
+ if (submission.settled_outcome === null) return yield* corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, "A settled Submission carries no terminal outcome.");
1221
+ return yield* SettlementConflict.make({
1222
+ submissionId: validated.submissionId,
1223
+ existingOutcome: submission.settled_outcome
1224
+ });
1225
+ }
1226
+ const existing = (yield* readUnknownResolutions(operation, validated.submissionId)).find((row) => row.tool_call_id === validated.toolCallId);
1227
+ const existingIntent = existing === void 0 ? void 0 : yield* unknownResolutionIntentFromRow(operation, existing);
1228
+ if (existingIntent !== void 0 && !equivalentUnknownResolution(existingIntent.resolution, validated.resolution)) return yield* UnknownResolutionConflict.make({
1229
+ submissionId: validated.submissionId,
1230
+ toolCallId: validated.toolCallId
1231
+ });
1232
+ let resolved;
1233
+ if (existingIntent !== void 0) resolved = existingIntent;
1234
+ else {
1235
+ const now = yield* currentInstant;
1236
+ yield* sql`
1237
+ INSERT INTO effect_agent_unknown_resolutions (
1238
+ submission_id,
1239
+ tool_call_id,
1240
+ author,
1241
+ reason,
1242
+ resolution_json,
1243
+ resolved_at
1244
+ ) VALUES (
1245
+ ${validated.submissionId},
1246
+ ${validated.toolCallId},
1247
+ ${validated.author},
1248
+ ${validated.reason},
1249
+ ${resolutionJson},
1250
+ ${now.iso}
1251
+ )
1252
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1253
+ const resolution = yield* parseStoredJsonText(resolutionJson).pipe(Effect.mapError(internalFailure(operation)));
1254
+ resolved = yield* decodeUnknownResolutionIntent({
1255
+ submissionId: validated.submissionId,
1256
+ toolCallId: validated.toolCallId,
1257
+ author: validated.author,
1258
+ reason: validated.reason,
1259
+ resolution,
1260
+ resolvedAt: now.iso
1261
+ }).pipe(Effect.mapError(internalFailure(operation)));
1262
+ }
1263
+ if (submission.state === "unknown" && submission.unknown_tool_call_ids_json !== null) {
1264
+ const markedIds = yield* storedUnknownToolCallIds(operation, submission);
1265
+ const covering = yield* readUnknownResolutions(operation, validated.submissionId);
1266
+ const coveredIds = new Set(covering.map((row) => row.tool_call_id));
1267
+ if (markedIds.every((toolCallId) => coveredIds.has(toolCallId))) yield* sql`
1268
+ UPDATE effect_agent_submissions
1269
+ SET
1270
+ state = 'input-applied',
1271
+ unknown_reason = NULL,
1272
+ unknown_tool_call_ids_json = NULL
1273
+ WHERE submission_id = ${validated.submissionId}
1274
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1275
+ }
1276
+ return resolved;
1277
+ }));
1278
+ yield* hitFailpoint("ledger:unknown-resolution:after", operation);
1279
+ return intent;
1280
+ });
1281
+ const recordChildSettled = Effect.fn("SqliteSubmissionLedger.recordChildSettled")(function* (request) {
1282
+ const operation = "ledger record child settled";
1283
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ChildSettledNotification))(request).pipe(Effect.mapError(internalFailure(operation)));
1284
+ yield* hitFailpoint("ledger:child-settled:before", operation);
1285
+ const outcome = yield* inWriteTransaction(operation, Effect.gen(function* () {
1286
+ const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
1287
+ const child = yield* readSubmission(operation, validated.childSubmissionId);
1288
+ const childReservation = yield* readReservation(operation, validated.childSubmissionId);
1289
+ if (!(Option.isSome(child) && (child.value.state === "settled" || child.value.state === "terminalizing" && Option.isSome(childReservation)))) return yield* LedgerError.make({
1290
+ operation,
1291
+ message: `Child submission ${validated.childSubmissionId} has no recorded settlement.`
1292
+ });
1293
+ if (parent.state !== "suspended" || parent.suspended_reason_json === null) return NOT_WAITING;
1294
+ const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(parent.suspended_reason_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", parent.submission_id, error.message)));
1295
+ if (reason._tag !== "WaitingForChild") return NOT_WAITING;
1296
+ if (!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)) return NOT_WAITING;
1297
+ for (const entry of reason.children) {
1298
+ const listed = yield* readSubmission(operation, entry.childSubmissionId);
1299
+ const reservation = yield* readReservation(operation, entry.childSubmissionId);
1300
+ if (!(Option.isSome(listed) && (listed.value.state === "settled" || listed.value.state === "terminalizing" && Option.isSome(reservation)))) return STILL_WAITING;
1301
+ }
1302
+ yield* sql`
1303
+ UPDATE effect_agent_submissions
1304
+ SET
1305
+ state = 'input-applied',
1306
+ suspended_reason_json = NULL,
1307
+ suspended_at = NULL
1308
+ WHERE submission_id = ${validated.parentSubmissionId}
1309
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1310
+ return WOKEN;
1311
+ }));
1312
+ yield* hitFailpoint("ledger:child-settled:after", operation);
1313
+ return outcome;
1314
+ });
1315
+ const reserveChildBudget = Effect.fn("SqliteSubmissionLedger.reserveChildBudget")(function* (request) {
1316
+ const operation = "ledger reserve child budget";
1317
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ChildBudgetReservationRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1318
+ const allocationJson = yield* encodePersistedJsonText(validated.allocation).pipe(Effect.mapError(internalFailure(operation)));
1319
+ yield* hitFailpoint("ledger:child-reservation:before", operation);
1320
+ const reserved = yield* inWriteTransaction(operation, Effect.gen(function* () {
1321
+ const existing = yield* readChildReservation(operation, validated.reservationId);
1322
+ if (Option.isSome(existing)) {
1323
+ const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
1324
+ if (!(existing.value.parent_submission_id === validated.parentSubmissionId && existing.value.parent_tool_call_id === validated.parentToolCallId && existing.value.allocation_digest === validated.allocationDigest && equivalentPersistedJson(existingSnapshot.allocation, validated.allocation))) return yield* ChildReservationConflict.make({
1325
+ reservationId: validated.reservationId,
1326
+ status: existing.value.status,
1327
+ message: "A reservation with this identity exists with a different parent Tool Call or allocation."
1328
+ });
1329
+ return ReservedChildBudget.make({
1330
+ reservation: existingSnapshot,
1331
+ replayed: true
1332
+ });
1333
+ }
1334
+ const collision = yield* readChildReservationForCall(operation, validated.parentSubmissionId, validated.parentToolCallId);
1335
+ if (Option.isSome(collision)) return yield* ChildReservationConflict.make({
1336
+ reservationId: validated.reservationId,
1337
+ status: collision.value.status,
1338
+ message: `Parent Tool Call ${validated.parentToolCallId} already owns reservation ${collision.value.reservation_id}.`
1339
+ });
1340
+ const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
1341
+ yield* requireOwnership(operation, parent, validated.ownershipToken);
1342
+ const now = yield* currentInstant;
1343
+ yield* sql`
1344
+ INSERT INTO effect_agent_child_reservations (
1345
+ reservation_id,
1346
+ parent_submission_id,
1347
+ parent_tool_call_id,
1348
+ status,
1349
+ allocation_json,
1350
+ allocation_digest,
1351
+ reserved_at
1352
+ ) VALUES (
1353
+ ${validated.reservationId},
1354
+ ${validated.parentSubmissionId},
1355
+ ${validated.parentToolCallId},
1356
+ 'reserved',
1357
+ ${allocationJson},
1358
+ ${validated.allocationDigest},
1359
+ ${now.iso}
1360
+ )
1361
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1362
+ const inserted = yield* readChildReservation(operation, validated.reservationId);
1363
+ if (Option.isNone(inserted)) return yield* corruptionFailure(operation, "effect_agent_child_reservations", validated.reservationId, "An inserted child reservation row is missing inside its own transaction.");
1364
+ return ReservedChildBudget.make({
1365
+ reservation: yield* childReservationSnapshotFromRow(operation, inserted.value),
1366
+ replayed: false
1367
+ });
1368
+ }));
1369
+ yield* hitFailpoint("ledger:child-reservation:after", operation);
1370
+ return reserved;
1371
+ });
1372
+ const attachChildToReservation = Effect.fn("SqliteSubmissionLedger.attachChildToReservation")(function* (request) {
1373
+ const operation = "ledger attach child to reservation";
1374
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AttachChildToReservationRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1375
+ yield* hitFailpoint("ledger:child-attach:before", operation);
1376
+ const attached = yield* inWriteTransaction(operation, Effect.gen(function* () {
1377
+ const existing = yield* readChildReservation(operation, validated.reservationId);
1378
+ if (Option.isNone(existing)) return yield* LedgerError.make({
1379
+ operation,
1380
+ message: `Unknown child reservation ${validated.reservationId}.`
1381
+ });
1382
+ if (existing.value.child_submission_id !== null) {
1383
+ if (existing.value.child_submission_id === validated.childSubmissionId) return yield* childReservationSnapshotFromRow(operation, existing.value);
1384
+ return yield* ChildReservationConflict.make({
1385
+ reservationId: validated.reservationId,
1386
+ status: existing.value.status,
1387
+ message: `Reservation ${validated.reservationId} already records child ${existing.value.child_submission_id}.`
1388
+ });
1389
+ }
1390
+ const parent = yield* requireSubmission(operation, existing.value.parent_submission_id);
1391
+ yield* requireOwnership(operation, parent, validated.ownershipToken);
1392
+ if (existing.value.status !== "reserved") return yield* ChildReservationConflict.make({
1393
+ reservationId: validated.reservationId,
1394
+ status: existing.value.status,
1395
+ message: `Cannot attach a child to a ${existing.value.status} reservation.`
1396
+ });
1397
+ const child = yield* readSubmission(operation, validated.childSubmissionId);
1398
+ if (Option.isNone(child)) return yield* LedgerError.make({
1399
+ operation,
1400
+ message: `Unknown child submission ${validated.childSubmissionId}.`
1401
+ });
1402
+ yield* sql`
1403
+ UPDATE effect_agent_child_reservations
1404
+ SET child_submission_id = ${validated.childSubmissionId}
1405
+ WHERE reservation_id = ${validated.reservationId}
1406
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1407
+ const updated = yield* readChildReservation(operation, validated.reservationId);
1408
+ if (Option.isNone(updated)) return yield* corruptionFailure(operation, "effect_agent_child_reservations", validated.reservationId, "An updated child reservation row is missing inside its own transaction.");
1409
+ return yield* childReservationSnapshotFromRow(operation, updated.value);
1410
+ }));
1411
+ yield* hitFailpoint("ledger:child-attach:after", operation);
1412
+ return attached;
1413
+ });
1414
+ const beginChildBudgetRelease = Effect.fn("SqliteSubmissionLedger.beginChildBudgetRelease")(function* (request) {
1415
+ const operation = "ledger begin child budget release";
1416
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(BeginChildBudgetReleaseRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1417
+ const accountingJson = yield* encodePersistedJsonText(validated.accounting).pipe(Effect.mapError(internalFailure(operation)));
1418
+ yield* hitFailpoint("ledger:child-release-pending:before", operation);
1419
+ const frozen = yield* inWriteTransaction(operation, Effect.gen(function* () {
1420
+ const existing = yield* readChildReservation(operation, validated.reservationId);
1421
+ if (Option.isNone(existing)) return yield* LedgerError.make({
1422
+ operation,
1423
+ message: `Unknown child reservation ${validated.reservationId}.`
1424
+ });
1425
+ if (existing.value.status !== "reserved") {
1426
+ const existingSnapshot = yield* childReservationSnapshotFromRow(operation, existing.value);
1427
+ if (existingSnapshot.accounting !== void 0 && equivalentPersistedJson(existingSnapshot.accounting, validated.accounting)) return existingSnapshot;
1428
+ return yield* ChildReservationConflict.make({
1429
+ reservationId: validated.reservationId,
1430
+ status: existing.value.status,
1431
+ message: "A different accounting decision is already frozen for this reservation."
1432
+ });
1433
+ }
1434
+ const now = yield* currentInstant;
1435
+ yield* sql`
1436
+ UPDATE effect_agent_child_reservations
1437
+ SET
1438
+ status = 'releasePending',
1439
+ accounting_json = ${accountingJson},
1440
+ release_began_at = ${now.iso}
1441
+ WHERE reservation_id = ${validated.reservationId}
1442
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1443
+ const updated = yield* readChildReservation(operation, validated.reservationId);
1444
+ if (Option.isNone(updated)) return yield* corruptionFailure(operation, "effect_agent_child_reservations", validated.reservationId, "An updated child reservation row is missing inside its own transaction.");
1445
+ return yield* childReservationSnapshotFromRow(operation, updated.value);
1446
+ }));
1447
+ yield* hitFailpoint("ledger:child-release-pending:after", operation);
1448
+ return frozen;
1449
+ });
1450
+ const releaseChildBudget = Effect.fn("SqliteSubmissionLedger.releaseChildBudget")(function* (request) {
1451
+ const operation = "ledger release child budget";
1452
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseChildBudgetRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1453
+ yield* hitFailpoint("ledger:child-release:before", operation);
1454
+ const released = yield* inWriteTransaction(operation, Effect.gen(function* () {
1455
+ const existing = yield* readChildReservation(operation, validated.reservationId);
1456
+ if (Option.isNone(existing)) return yield* LedgerError.make({
1457
+ operation,
1458
+ message: `Unknown child reservation ${validated.reservationId}.`
1459
+ });
1460
+ if (existing.value.status === "released") return yield* childReservationSnapshotFromRow(operation, existing.value);
1461
+ if (existing.value.status !== "releasePending") return yield* ChildReservationConflict.make({
1462
+ reservationId: validated.reservationId,
1463
+ status: existing.value.status,
1464
+ message: "Cannot release a reservation whose accounting decision is not frozen."
1465
+ });
1466
+ const now = yield* currentInstant;
1467
+ yield* sql`
1468
+ UPDATE effect_agent_child_reservations
1469
+ SET status = 'released', released_at = ${now.iso}
1470
+ WHERE reservation_id = ${validated.reservationId}
1471
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1472
+ const updated = yield* readChildReservation(operation, validated.reservationId);
1473
+ if (Option.isNone(updated)) return yield* corruptionFailure(operation, "effect_agent_child_reservations", validated.reservationId, "An updated child reservation row is missing inside its own transaction.");
1474
+ return yield* childReservationSnapshotFromRow(operation, updated.value);
1475
+ }));
1476
+ yield* hitFailpoint("ledger:child-release:after", operation);
1477
+ return released;
1478
+ });
1479
+ const scanPage = Effect.fn("SqliteSubmissionLedger.scanPage")(function* (cursor) {
1480
+ const operation = "ledger scan nonterminal";
1481
+ const rows = yield* (cursor === void 0 ? sql`
1482
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
1483
+ FROM effect_agent_submissions
1484
+ WHERE state <> 'settled'
1485
+ ORDER BY thread_id ASC, queue_sequence ASC
1486
+ LIMIT ${SCAN_PAGE_SIZE}
1487
+ ` : sql`
1488
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
1489
+ FROM effect_agent_submissions
1490
+ WHERE state <> 'settled'
1491
+ AND (
1492
+ thread_id > ${cursor.threadId}
1493
+ OR (
1494
+ thread_id = ${cursor.threadId}
1495
+ AND queue_sequence > ${cursor.queueSequence}
1496
+ )
1497
+ )
1498
+ ORDER BY thread_id ASC, queue_sequence ASC
1499
+ LIMIT ${SCAN_PAGE_SIZE}
1500
+ `).pipe(Effect.mapError(sqlFailure(operation)));
1501
+ const decoded = yield* decodeSubmissionRows(operation, "nonterminal_scan", rows);
1502
+ const snapshots = yield* Effect.forEach(decoded, (row) => decodeSubmissionSnapshot(operation, row));
1503
+ const last = decoded[decoded.length - 1];
1504
+ return [snapshots, last === void 0 || decoded.length < SCAN_PAGE_SIZE ? Option.none() : Option.some({
1505
+ threadId: last.thread_id,
1506
+ queueSequence: last.queue_sequence
1507
+ })];
1508
+ });
1509
+ const scanNonterminal = Stream.paginate(void 0, scanPage);
1510
+ const loadRecoverySnapshot = Effect.fn("SqliteSubmissionLedger.loadRecoverySnapshot")(function* (request) {
1511
+ const operation = "ledger load recovery snapshot";
1512
+ const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RecoverySnapshotRequest))(request).pipe(Effect.mapError(internalFailure(operation)));
1513
+ return yield* sql.withTransaction(Effect.gen(function* () {
1514
+ const submissionRow = yield* requireSubmission(operation, validated.submissionId);
1515
+ const submission = yield* decodeSubmissionSnapshot(operation, submissionRow);
1516
+ let ownership;
1517
+ const ownershipRow = yield* readOwnership(operation, validated.submissionId);
1518
+ if (Option.isSome(ownershipRow)) ownership = yield* decodeOwnershipSnapshot({
1519
+ attemptId: ownershipRow.value.attempt_id,
1520
+ ownerProducerId: ownershipRow.value.owner_producer_id,
1521
+ producerEpoch: ownershipRow.value.producer_epoch,
1522
+ leaseExpiresAt: ownershipRow.value.lease_expires_at
1523
+ }).pipe(Effect.mapError(internalFailure(operation)));
1524
+ let inputApplied;
1525
+ if (submissionRow.input_applied_record_id !== null && submissionRow.input_applied_sequence !== null) inputApplied = yield* decodeInputAppliedMarker({
1526
+ recordId: submissionRow.input_applied_record_id,
1527
+ sequence: submissionRow.input_applied_sequence
1528
+ }).pipe(Effect.mapError(internalFailure(operation)));
1529
+ let reservation;
1530
+ const reservationRow = yield* readReservation(operation, validated.submissionId);
1531
+ if (Option.isSome(reservationRow)) {
1532
+ const record = yield* decodeRecordEnvelopeText(reservationRow.value.record_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_settlement_reservations", validated.submissionId, error.message)));
1533
+ const settlementId = yield* Schema.decodeUnknownEffect(SettlementReservationSnapshot.fields.settlementId)(reservationRow.value.settlement_id).pipe(Effect.mapError(internalFailure(operation)));
1534
+ reservation = SettlementReservationSnapshot.make({
1535
+ settlementId,
1536
+ outcome: reservationRow.value.outcome,
1537
+ record,
1538
+ recordDigest: reservationRow.value.record_digest,
1539
+ finalized: reservationRow.value.finalized_at !== null
1540
+ });
1541
+ }
1542
+ let abortIntent;
1543
+ const abortRow = yield* readAbortIntent(operation, validated.submissionId);
1544
+ if (Option.isSome(abortRow)) abortIntent = yield* abortIntentFromRow(operation, submissionRow, validated.submissionId, abortRow.value);
1545
+ const joinRows = yield* sql`
1546
+ SELECT ${sql.literal(SUBMISSION_COLUMNS)}
1547
+ FROM effect_agent_submissions
1548
+ WHERE joined_host_submission_id = ${validated.submissionId}
1549
+ ORDER BY queue_sequence ASC
1550
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1551
+ const joinSubmissions = yield* decodeSubmissionRows(operation, validated.submissionId, joinRows);
1552
+ const joins = yield* Effect.forEach(joinSubmissions, (row) => decodeJoinSnapshot({
1553
+ submissionId: row.submission_id,
1554
+ state: row.state,
1555
+ hostSubmissionId: validated.submissionId
1556
+ }).pipe(Effect.mapError(internalFailure(operation))));
1557
+ let hostSubmissionId;
1558
+ if (submissionRow.joined_host_submission_id !== null) hostSubmissionId = yield* decodeSubmissionId(submissionRow.joined_host_submission_id).pipe(Effect.mapError(internalFailure(operation)));
1559
+ let suspension;
1560
+ if (submissionRow.suspended_reason_json !== null && submissionRow.suspended_at !== null) {
1561
+ const reason = yield* parseStoredJsonText(submissionRow.suspended_reason_json).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, error.message)));
1562
+ suspension = yield* decodeSuspensionSnapshot({
1563
+ reason,
1564
+ suspendedAt: submissionRow.suspended_at
1565
+ }).pipe(Effect.mapError((error) => corruptionFailure(operation, "effect_agent_submissions", validated.submissionId, error.message)));
1566
+ }
1567
+ const decisionRows = yield* readApprovalDecisions(operation, validated.submissionId);
1568
+ const approvalDecisions = yield* Effect.forEach(decisionRows, (row) => approvalIntentFromRow(operation, row));
1569
+ const resolutionRows = yield* readUnknownResolutions(operation, validated.submissionId);
1570
+ const unknownResolutions = yield* Effect.forEach(resolutionRows, (row) => unknownResolutionIntentFromRow(operation, row));
1571
+ const childReservationRows = yield* sql`
1572
+ SELECT ${sql.literal(CHILD_RESERVATION_COLUMNS)}
1573
+ FROM effect_agent_child_reservations
1574
+ WHERE parent_submission_id = ${validated.submissionId}
1575
+ ORDER BY parent_tool_call_id ASC
1576
+ `.pipe(Effect.mapError(sqlFailure(operation)));
1577
+ const decodedChildReservations = yield* decodeChildReservationRows(operation, validated.submissionId, childReservationRows);
1578
+ const childReservations = yield* Effect.forEach(decodedChildReservations, (row) => childReservationSnapshotFromRow(operation, row));
1579
+ const childAttachments = [];
1580
+ for (const row of decodedChildReservations) {
1581
+ if (row.child_submission_id === null) continue;
1582
+ const child = yield* readSubmission(operation, row.child_submission_id);
1583
+ if (Option.isNone(child)) continue;
1584
+ childAttachments.push(yield* decodeChildAttachmentSnapshot({
1585
+ toolCallId: row.parent_tool_call_id,
1586
+ childSubmissionId: row.child_submission_id,
1587
+ childState: child.value.state,
1588
+ ...child.value.settled_outcome === null ? {} : { childOutcome: child.value.settled_outcome }
1589
+ }).pipe(Effect.mapError(internalFailure(operation))));
1590
+ }
1591
+ let parentLinkage;
1592
+ if (submissionRow.parent_submission_id !== null && submissionRow.parent_tool_call_id !== null) parentLinkage = yield* decodeParentLinkage({
1593
+ parentSubmissionId: submissionRow.parent_submission_id,
1594
+ parentToolCallId: submissionRow.parent_tool_call_id
1595
+ }).pipe(Effect.mapError(internalFailure(operation)));
1596
+ return RecoverySnapshot.make({
1597
+ submission,
1598
+ joins,
1599
+ approvalDecisions,
1600
+ unknownResolutions,
1601
+ childReservations,
1602
+ childAttachments,
1603
+ ...parentLinkage === void 0 ? {} : { parentLinkage },
1604
+ ...hostSubmissionId === void 0 ? {} : { hostSubmissionId },
1605
+ ...suspension === void 0 ? {} : { suspension },
1606
+ ...ownership === void 0 ? {} : { ownership },
1607
+ ...inputApplied === void 0 ? {} : { inputApplied },
1608
+ ...reservation === void 0 ? {} : { reservation },
1609
+ ...abortIntent === void 0 ? {} : { abortIntent }
1610
+ });
1611
+ })).pipe(Effect.catchTag("SqlError", (error) => Effect.fail(sqlFailure(operation)(error))));
1612
+ });
1613
+ return Context.make(SubmissionLedger, SubmissionLedger.of({
1614
+ capabilities,
1615
+ admit,
1616
+ markReady,
1617
+ lookup,
1618
+ resolveAdmission,
1619
+ claim,
1620
+ renewOwnership,
1621
+ releaseOwnership,
1622
+ markInputApplied,
1623
+ reserveSettlement,
1624
+ finalizeSettlement,
1625
+ requestAbort,
1626
+ claimJoining,
1627
+ markJoined,
1628
+ revertJoining,
1629
+ suspend,
1630
+ recordApprovalDecision,
1631
+ markUnknown,
1632
+ recordUnknownResolution,
1633
+ recordChildSettled,
1634
+ reserveChildBudget,
1635
+ attachChildToReservation,
1636
+ beginChildBudgetRelease,
1637
+ releaseChildBudget,
1638
+ scanNonterminal,
1639
+ loadRecoverySnapshot
1640
+ }));
1641
+ });
1642
+ /**
1643
+ * SQLite SubmissionLedger implementation sharing the journal's database file, write
1644
+ * transaction discipline, and producer-epoch fencing substrate. Configuration, failpoint,
1645
+ * SQL, and Crypto authority stay visible in the input channel.
1646
+ */
1647
+ const submissionLedgerLayer = Layer.effectContext(makeServices());
1648
+ /**
1649
+ * A composition-root convenience Layer for the durable Submission Ledger. Point it at the
1650
+ * same database file as the ThreadStore so claims fence the same producer epochs.
1651
+ */
1652
+ const ledgerLayer = (options) => Layer.unwrap(Effect.map(SqliteStorageConfig, (config) => submissionLedgerLayer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(SqliteStorageConfig)(config), storageFailpointLayer(options), SqliteClient.layer({ filename: options.filename }), NodeCrypto.layer))))).pipe(Layer.provide(storageConfigLayer(options)));
1653
+ //#endregion
1654
+ export { ledgerLayer, submissionLedgerLayer, SqliteSubmissionLedger_exports as t };
1655
+
1656
+ //# sourceMappingURL=SqliteSubmissionLedger.mjs.map