@effect-agent/storage-sqlite 0.1.0-beta.42 → 0.1.0-beta.45

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.
@@ -277,12 +277,15 @@ const decodeSuspensionSnapshot = Schema.decodeUnknownEffect(SuspensionSnapshot);
277
277
  const decodeApprovalDecisionIntent = Schema.decodeUnknownEffect(ApprovalDecisionIntent);
278
278
  const decodeUnknownResolutionIntent = Schema.decodeUnknownEffect(UnknownResolutionIntent);
279
279
  const decodeParentLinkage = Schema.decodeUnknownEffect(ParentLinkage);
280
+
280
281
  const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(
281
282
  ChildBudgetReservationSnapshot,
282
283
  );
284
+
283
285
  const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
284
286
  const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
285
287
  const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
288
+
286
289
  const isSqliteTransactionFailure = Schema.is(
287
290
  Schema.Union([SqliteStorageError, SqliteWriteContention]),
288
291
  );
@@ -309,6 +312,7 @@ const sqlFailure =
309
312
  operation,
310
313
  message: error.message,
311
314
  });
315
+
312
316
  return internalFailure(operation)(internal);
313
317
  };
314
318
 
@@ -390,7 +394,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
390
394
  FROM effect_agent_submissions
391
395
  WHERE submission_id = ${submissionId}
392
396
  `.pipe(Effect.mapError(sqlFailure(operation)));
397
+
393
398
  const decoded = yield* decodeSubmissionRows(operation, submissionId, rows);
399
+
394
400
  if (decoded.length > 1) {
395
401
  return yield* corruptionFailure(
396
402
  operation,
@@ -399,6 +405,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
399
405
  "A submission primary key returned more than one row.",
400
406
  );
401
407
  }
408
+
402
409
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
403
410
  });
404
411
 
@@ -407,12 +414,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
407
414
  submissionId: string,
408
415
  ): Effect.fn.Return<SubmissionRow, LedgerError> {
409
416
  const submission = yield* readSubmission(operation, submissionId);
417
+
410
418
  if (Option.isNone(submission)) {
411
419
  return yield* LedgerError.make({
412
420
  operation,
413
421
  message: `Unknown submission ${submissionId}.`,
414
422
  });
415
423
  }
424
+
416
425
  return submission.value;
417
426
  });
418
427
 
@@ -431,12 +440,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
431
440
  FROM effect_agent_submission_ownership
432
441
  WHERE submission_id = ${submissionId}
433
442
  `.pipe(Effect.mapError(sqlFailure(operation)));
443
+
434
444
  const decoded = yield* decodeRows(
435
445
  Schema.Array(OwnershipRow),
436
446
  "effect_agent_submission_ownership",
437
447
  submissionId,
438
448
  rows,
439
449
  ).pipe(Effect.mapError(internalFailure(operation)));
450
+
440
451
  if (decoded.length > 1) {
441
452
  return yield* corruptionFailure(
442
453
  operation,
@@ -445,6 +456,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
445
456
  "An ownership primary key returned more than one row.",
446
457
  );
447
458
  }
459
+
448
460
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
449
461
  });
450
462
 
@@ -455,6 +467,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
455
467
  const threads = yield* journal
456
468
  .getThread(threadId)
457
469
  .pipe(Effect.mapError(internalFailure(operation)));
470
+
458
471
  return threads.length === 0 ? EPOCH_ZERO : threads[0].producer_epoch;
459
472
  });
460
473
 
@@ -469,13 +482,17 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
469
482
  ownershipToken: string,
470
483
  ): Effect.fn.Return<OwnershipRow, OwnershipLost | LedgerError> {
471
484
  const ownership = yield* readOwnership(operation, submission.submission_id);
485
+
472
486
  if (Option.isNone(ownership) || ownership.value.ownership_token !== ownershipToken) {
473
487
  const actualEpoch = yield* threadEpoch(operation, submission.thread_id);
488
+
474
489
  const submissionId = yield* Schema.decodeUnknownEffect(
475
490
  SubmissionSnapshot.fields.submissionId,
476
491
  )(submission.submission_id).pipe(Effect.mapError(internalFailure(operation)));
492
+
477
493
  return yield* OwnershipLost.make({ submissionId, actualEpoch });
478
494
  }
495
+
479
496
  return ownership.value;
480
497
  });
481
498
 
@@ -494,6 +511,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
494
511
  ),
495
512
  ),
496
513
  );
514
+
497
515
  const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
498
516
  Effect.mapError((error) =>
499
517
  corruptionFailure(
@@ -504,6 +522,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
504
522
  ),
505
523
  ),
506
524
  );
525
+
507
526
  if ((row.parent_submission_id === null) !== (row.parent_tool_call_id === null)) {
508
527
  return yield* corruptionFailure(
509
528
  operation,
@@ -512,6 +531,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
512
531
  "A parent linkage must record both the parent Submission and the parent Tool Call.",
513
532
  );
514
533
  }
534
+
515
535
  return yield* decodeSubmissionSnapshotUnknown({
516
536
  submissionId: row.submission_id,
517
537
  threadId: row.thread_id,
@@ -566,12 +586,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
566
586
  FROM effect_agent_settlement_reservations
567
587
  WHERE submission_id = ${submissionId}
568
588
  `.pipe(Effect.mapError(sqlFailure(operation)));
589
+
569
590
  const decoded = yield* decodeRows(
570
591
  Schema.Array(ReservationRow),
571
592
  "effect_agent_settlement_reservations",
572
593
  submissionId,
573
594
  rows,
574
595
  ).pipe(Effect.mapError(internalFailure(operation)));
596
+
575
597
  if (decoded.length > 1) {
576
598
  return yield* corruptionFailure(
577
599
  operation,
@@ -580,6 +602,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
580
602
  "A settlement reservation primary key returned more than one row.",
581
603
  );
582
604
  }
605
+
583
606
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
584
607
  });
585
608
 
@@ -597,12 +620,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
597
620
  FROM effect_agent_abort_intents
598
621
  WHERE submission_id = ${submissionId}
599
622
  `.pipe(Effect.mapError(sqlFailure(operation)));
623
+
600
624
  const decoded = yield* decodeRows(
601
625
  Schema.Array(AbortIntentRow),
602
626
  "effect_agent_abort_intents",
603
627
  submissionId,
604
628
  rows,
605
629
  ).pipe(Effect.mapError(internalFailure(operation)));
630
+
606
631
  if (decoded.length > 1) {
607
632
  return yield* corruptionFailure(
608
633
  operation,
@@ -611,6 +636,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
611
636
  "An abort intent primary key returned more than one row.",
612
637
  );
613
638
  }
639
+
614
640
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
615
641
  });
616
642
 
@@ -631,7 +657,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
631
657
  FROM effect_agent_child_reservations
632
658
  WHERE reservation_id = ${reservationId}
633
659
  `.pipe(Effect.mapError(sqlFailure(operation)));
660
+
634
661
  const decoded = yield* decodeChildReservationRows(operation, reservationId, rows);
662
+
635
663
  if (decoded.length > 1) {
636
664
  return yield* corruptionFailure(
637
665
  operation,
@@ -640,6 +668,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
640
668
  "A child reservation primary key returned more than one row.",
641
669
  );
642
670
  }
671
+
643
672
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
644
673
  });
645
674
 
@@ -656,11 +685,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
656
685
  WHERE parent_submission_id = ${parentSubmissionId}
657
686
  AND parent_tool_call_id = ${parentToolCallId}
658
687
  `.pipe(Effect.mapError(sqlFailure(operation)));
688
+
659
689
  const decoded = yield* decodeChildReservationRows(
660
690
  operation,
661
691
  `${parentSubmissionId}/${parentToolCallId}`,
662
692
  rows,
663
693
  );
694
+
664
695
  if (decoded.length > 1) {
665
696
  return yield* corruptionFailure(
666
697
  operation,
@@ -669,6 +700,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
669
700
  "A parent Tool Call returned more than one child reservation.",
670
701
  );
671
702
  }
703
+
672
704
  return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
673
705
  });
674
706
 
@@ -685,13 +717,16 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
685
717
  row.reservation_id,
686
718
  error.message,
687
719
  );
720
+
688
721
  const allocation = yield* parseStoredJsonText(row.allocation_json).pipe(
689
722
  Effect.mapError(rowFailure),
690
723
  );
724
+
691
725
  const accounting =
692
726
  row.accounting_json === null
693
727
  ? undefined
694
728
  : yield* parseStoredJsonText(row.accounting_json).pipe(Effect.mapError(rowFailure));
729
+
695
730
  return yield* decodeChildReservationSnapshotUnknown({
696
731
  reservationId: row.reservation_id,
697
732
  parentSubmissionId: row.parent_submission_id,
@@ -724,6 +759,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
724
759
  WHERE submission_id = ${submissionId}
725
760
  ORDER BY tool_call_id ASC
726
761
  `.pipe(Effect.mapError(sqlFailure(operation)));
762
+
727
763
  return yield* decodeRows(
728
764
  Schema.Array(ApprovalDecisionRow),
729
765
  "effect_agent_approval_decisions",
@@ -775,6 +811,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
775
811
  WHERE submission_id = ${submissionId}
776
812
  ORDER BY tool_call_id ASC
777
813
  `.pipe(Effect.mapError(sqlFailure(operation)));
814
+
778
815
  return yield* decodeRows(
779
816
  Schema.Array(UnknownResolutionRow),
780
817
  "effect_agent_unknown_resolutions",
@@ -800,6 +837,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
800
837
  ),
801
838
  ),
802
839
  );
840
+
803
841
  return yield* decodeUnknownResolutionIntent({
804
842
  submissionId: row.submission_id,
805
843
  toolCallId: row.tool_call_id,
@@ -826,6 +864,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
826
864
  submission: SubmissionRow,
827
865
  ): Effect.fn.Return<ReadonlyArray<typeof ToolCallIdSchema.Type>, LedgerError> {
828
866
  if (submission.unknown_tool_call_ids_json === null) return [];
867
+
829
868
  return yield* decodeToolCallIdsText(submission.unknown_tool_call_ids_json).pipe(
830
869
  Effect.mapError((error) =>
831
870
  corruptionFailure(
@@ -851,18 +890,21 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
851
890
  submissionId: SubmissionId,
852
891
  ): Effect.fn.Return<string | undefined, LedgerError> {
853
892
  const recordId = submissionAbortRecordId(submissionId);
893
+
854
894
  const rows = yield* sql<Record<string, unknown>>`
855
895
  SELECT record_id
856
896
  FROM effect_agent_canonical_records
857
897
  WHERE thread_id = ${threadId}
858
898
  AND record_id = ${recordId}
859
899
  `.pipe(Effect.mapError(sqlFailure(operation)));
900
+
860
901
  const decoded = yield* decodeRows(
861
902
  Schema.Array(CanonicalRecordIdRow),
862
903
  "effect_agent_canonical_records",
863
904
  `${threadId}/${recordId}`,
864
905
  rows,
865
906
  ).pipe(Effect.mapError(internalFailure(operation)));
907
+
866
908
  return decoded.length === 0 ? undefined : recordId;
867
909
  },
868
910
  );
@@ -878,6 +920,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
878
920
  submission.thread_id,
879
921
  submissionId,
880
922
  );
923
+
881
924
  return yield* decodeAbortIntent({
882
925
  submissionId: row.submission_id,
883
926
  author: row.author,
@@ -901,22 +944,29 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
901
944
  const admit: SubmissionLedger["Service"]["admit"] = Effect.fn("SqliteSubmissionLedger.admit")(
902
945
  function* (request: AdmissionRequest) {
903
946
  const operation = "ledger admit";
947
+
904
948
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AdmissionRequest))(
905
949
  request,
906
950
  ).pipe(Effect.mapError(internalFailure(operation)));
951
+
907
952
  const inputJson = yield* encodePersistedJsonText(validated.inputPayload).pipe(
908
953
  Effect.mapError(internalFailure(operation)),
909
954
  );
955
+
910
956
  const agentDigestsJson = yield* encodeDefinitionDigestsText(validated.agentDigests).pipe(
911
957
  Effect.mapError(internalFailure(operation)),
912
958
  );
959
+
913
960
  const mintedSubmissionId = yield* mintIdentifier("submission", operation);
914
961
  const mintedReceiptId = yield* mintIdentifier("receipt", operation);
962
+
915
963
  yield* hitFailpoint("ledger:admit:before", operation);
964
+
916
965
  const result = yield* inWriteTransaction(
917
966
  operation,
918
967
  Effect.gen(function* () {
919
968
  const keyRowKey = `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`;
969
+
920
970
  const existingRows = yield* sql<Record<string, unknown>>`
921
971
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
922
972
  FROM effect_agent_submissions
@@ -924,7 +974,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
924
974
  AND principal = ${validated.principal}
925
975
  AND idempotency_key = ${validated.idempotencyKey}
926
976
  `.pipe(Effect.mapError(sqlFailure(operation)));
977
+
927
978
  const existing = yield* decodeSubmissionRows(operation, keyRowKey, existingRows);
979
+
928
980
  if (existing.length > 1) {
929
981
  return yield* corruptionFailure(
930
982
  operation,
@@ -942,6 +994,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
942
994
  existing[0].parent_tool_call_id === null
943
995
  : existing[0].parent_submission_id === validated.parentLinkage.parentSubmissionId &&
944
996
  existing[0].parent_tool_call_id === validated.parentLinkage.parentToolCallId;
997
+
945
998
  if (existing[0].input_digest !== validated.inputDigest || !sameLinkage) {
946
999
  return yield* AdmissionConflict.make({
947
1000
  threadId: validated.threadId,
@@ -951,6 +1004,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
951
1004
  attemptedInputDigest: validated.inputDigest,
952
1005
  });
953
1006
  }
1007
+
954
1008
  return yield* decodeAdmissionResult({
955
1009
  submissionId: existing[0].submission_id,
956
1010
  receiptId: existing[0].receipt_id,
@@ -965,15 +1019,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
965
1019
  FROM effect_agent_submissions
966
1020
  WHERE thread_id = ${validated.threadId}
967
1021
  `.pipe(Effect.mapError(sqlFailure(operation)));
1022
+
968
1023
  const decodedMax = yield* decodeRows(
969
1024
  Schema.Array(MaxQueueSequenceRow),
970
1025
  "effect_agent_submissions",
971
1026
  validated.threadId,
972
1027
  maxRows,
973
1028
  ).pipe(Effect.mapError(internalFailure(operation)));
1029
+
974
1030
  const queueSequence = yield* decodeQueueSequence(
975
1031
  (decodedMax[0]?.max_queue_sequence ?? 0) + 1,
976
1032
  ).pipe(Effect.mapError(internalFailure(operation)));
1033
+
977
1034
  const now = yield* currentInstant;
978
1035
 
979
1036
  yield* sql`
@@ -1021,7 +1078,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1021
1078
  }).pipe(Effect.mapError(internalFailure(operation)));
1022
1079
  }),
1023
1080
  );
1081
+
1024
1082
  yield* hitFailpoint("ledger:admit:after", operation);
1083
+
1025
1084
  return result;
1026
1085
  },
1027
1086
  );
@@ -1030,16 +1089,20 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1030
1089
  "SqliteSubmissionLedger.markReady",
1031
1090
  )(function* (request: MarkReadyRequest) {
1032
1091
  const operation = "ledger mark ready";
1092
+
1033
1093
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkReadyRequest))(
1034
1094
  request,
1035
1095
  ).pipe(Effect.mapError(internalFailure(operation)));
1096
+
1036
1097
  yield* hitFailpoint("ledger:mark-ready:before", operation);
1037
1098
  yield* inWriteTransaction(
1038
1099
  operation,
1039
1100
  Effect.gen(function* () {
1040
1101
  const submission = yield* requireSubmission(operation, validated.submissionId);
1102
+
1041
1103
  if (submission.state !== "admitted") return;
1042
1104
  const now = yield* currentInstant;
1105
+
1043
1106
  yield* sql`
1044
1107
  UPDATE effect_agent_submissions
1045
1108
  SET state = 'ready', ready_at = ${now.iso}
@@ -1053,14 +1116,19 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1053
1116
  const lookup: SubmissionLedger["Service"]["lookup"] = Effect.fn("SqliteSubmissionLedger.lookup")(
1054
1117
  function* (request: SubmissionLookup) {
1055
1118
  const operation = "ledger lookup";
1119
+
1056
1120
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SubmissionLookup))(
1057
1121
  request,
1058
1122
  ).pipe(Effect.mapError(internalFailure(operation)));
1123
+
1059
1124
  if (validated._tag === "SubmissionLookupById") {
1060
1125
  const row = yield* readSubmission(operation, validated.submissionId);
1126
+
1061
1127
  if (Option.isNone(row)) return Option.none();
1128
+
1062
1129
  return Option.some(yield* decodeSubmissionSnapshot(operation, row.value));
1063
1130
  }
1131
+
1064
1132
  const rows = yield* sql<Record<string, unknown>>`
1065
1133
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
1066
1134
  FROM effect_agent_submissions
@@ -1068,11 +1136,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1068
1136
  AND principal = ${validated.principal}
1069
1137
  AND idempotency_key = ${validated.idempotencyKey}
1070
1138
  `.pipe(Effect.mapError(sqlFailure(operation)));
1139
+
1071
1140
  const decoded = yield* decodeSubmissionRows(
1072
1141
  operation,
1073
1142
  `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
1074
1143
  rows,
1075
1144
  );
1145
+
1076
1146
  if (decoded.length > 1) {
1077
1147
  return yield* corruptionFailure(
1078
1148
  operation,
@@ -1082,6 +1152,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1082
1152
  );
1083
1153
  }
1084
1154
  if (decoded.length === 0) return Option.none();
1155
+
1085
1156
  return Option.some(yield* decodeSubmissionSnapshot(operation, decoded[0]));
1086
1157
  },
1087
1158
  );
@@ -1093,9 +1164,11 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1093
1164
  "SqliteSubmissionLedger.resolveAdmission",
1094
1165
  )(function* (request: SubmissionLookupByKey) {
1095
1166
  const operation = "ledger resolve admission";
1167
+
1096
1168
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SubmissionLookupByKey))(
1097
1169
  request,
1098
1170
  ).pipe(Effect.mapError(internalFailure(operation)));
1171
+
1099
1172
  const rows = yield* sql<Record<string, unknown>>`
1100
1173
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
1101
1174
  FROM effect_agent_submissions
@@ -1103,11 +1176,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1103
1176
  AND principal = ${validated.principal}
1104
1177
  AND idempotency_key = ${validated.idempotencyKey}
1105
1178
  `.pipe(Effect.mapError(sqlFailure(operation)));
1179
+
1106
1180
  const decoded = yield* decodeSubmissionRows(
1107
1181
  operation,
1108
1182
  `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
1109
1183
  rows,
1110
1184
  );
1185
+
1111
1186
  if (decoded.length > 1) {
1112
1187
  return yield* corruptionFailure(
1113
1188
  operation,
@@ -1117,6 +1192,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1117
1192
  );
1118
1193
  }
1119
1194
  if (decoded.length === 0) return AdmissionNotAdmitted.make();
1195
+
1120
1196
  return AdmissionAdmitted.make({
1121
1197
  submission: yield* decodeSubmissionSnapshot(operation, decoded[0]),
1122
1198
  });
@@ -1125,12 +1201,16 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1125
1201
  const claim: SubmissionLedger["Service"]["claim"] = Effect.fn("SqliteSubmissionLedger.claim")(
1126
1202
  function* (request: ClaimRequest) {
1127
1203
  const operation = "ledger claim";
1204
+
1128
1205
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimRequest))(
1129
1206
  request,
1130
1207
  ).pipe(Effect.mapError(internalFailure(operation)));
1208
+
1131
1209
  const attemptId = yield* mintIdentifier("attempt", operation);
1132
1210
  const ownershipToken = yield* mintIdentifier("owner", operation);
1211
+
1133
1212
  yield* hitFailpoint("ledger:claim:before", operation);
1213
+
1134
1214
  const claimed = yield* inWriteTransaction(
1135
1215
  operation,
1136
1216
  Effect.gen(function* () {
@@ -1142,7 +1222,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1142
1222
  ORDER BY queue_sequence ASC
1143
1223
  LIMIT 1
1144
1224
  `.pipe(Effect.mapError(sqlFailure(operation)));
1225
+
1145
1226
  const heads = yield* decodeSubmissionRows(operation, validated.threadId, headRows);
1227
+
1146
1228
  if (heads.length === 0) return Option.none<Claim>();
1147
1229
  const head = heads[0];
1148
1230
 
@@ -1161,11 +1243,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1161
1243
 
1162
1244
  const now = yield* currentInstant;
1163
1245
  const ownership = yield* readOwnership(operation, head.submission_id);
1246
+
1164
1247
  if (Option.isSome(ownership)) {
1165
1248
  const expiresAt = yield* timestampMillis(
1166
1249
  operation,
1167
1250
  head.submission_id,
1168
1251
  )(ownership.value.lease_expires_at);
1252
+
1169
1253
  // A live lease blocks every new claim; expiry alone only revokes the liveness
1170
1254
  // assumption — correctness stays with producer-epoch fencing (D5).
1171
1255
  if (expiresAt > now.millis) return Option.none<Claim>();
@@ -1178,7 +1262,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1178
1262
  const threads = yield* journal
1179
1263
  .getThread(head.thread_id)
1180
1264
  .pipe(Effect.mapError(internalFailure(operation)));
1265
+
1181
1266
  let producerEpoch: number;
1267
+
1182
1268
  if (threads.length === 0) {
1183
1269
  producerEpoch = 1;
1184
1270
  yield* sql`
@@ -1206,6 +1292,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1206
1292
  }
1207
1293
 
1208
1294
  const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
1295
+
1209
1296
  yield* sql`
1210
1297
  INSERT INTO effect_agent_submission_ownership (
1211
1298
  submission_id,
@@ -1266,6 +1353,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1266
1353
  ),
1267
1354
  ),
1268
1355
  );
1356
+
1269
1357
  return Option.some(
1270
1358
  yield* decodeClaim({
1271
1359
  submissionId: head.submission_id,
@@ -1278,7 +1366,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1278
1366
  );
1279
1367
  }),
1280
1368
  );
1369
+
1281
1370
  yield* hitFailpoint("ledger:claim:after", operation);
1371
+
1282
1372
  return claimed;
1283
1373
  },
1284
1374
  );
@@ -1287,29 +1377,37 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1287
1377
  "SqliteSubmissionLedger.renewOwnership",
1288
1378
  )(function* (request: RenewOwnershipRequest) {
1289
1379
  const operation = "ledger renew ownership";
1380
+
1290
1381
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RenewOwnershipRequest))(
1291
1382
  request,
1292
1383
  ).pipe(Effect.mapError(internalFailure(operation)));
1384
+
1293
1385
  yield* hitFailpoint("ledger:renew:before", operation);
1386
+
1294
1387
  const renewal = yield* inWriteTransaction(
1295
1388
  operation,
1296
1389
  Effect.gen(function* () {
1297
1390
  const submission = yield* requireSubmission(operation, validated.submissionId);
1391
+
1298
1392
  yield* requireOwnership(operation, submission, validated.ownershipToken);
1299
1393
  const now = yield* currentInstant;
1300
1394
  const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
1395
+
1301
1396
  yield* sql`
1302
1397
  UPDATE effect_agent_submission_ownership
1303
1398
  SET lease_expires_at = ${leaseExpiresAt}
1304
1399
  WHERE submission_id = ${validated.submissionId}
1305
1400
  `.pipe(Effect.mapError(sqlFailure(operation)));
1401
+
1306
1402
  return yield* decodeOwnershipRenewal({
1307
1403
  ownershipToken: validated.ownershipToken,
1308
1404
  leaseExpiresAt,
1309
1405
  }).pipe(Effect.mapError(internalFailure(operation)));
1310
1406
  }),
1311
1407
  );
1408
+
1312
1409
  yield* hitFailpoint("ledger:renew:after", operation);
1410
+
1313
1411
  return renewal;
1314
1412
  });
1315
1413
 
@@ -1317,14 +1415,17 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1317
1415
  "SqliteSubmissionLedger.releaseOwnership",
1318
1416
  )(function* (request: ReleaseOwnershipRequest) {
1319
1417
  const operation = "ledger release ownership";
1418
+
1320
1419
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseOwnershipRequest))(
1321
1420
  request,
1322
1421
  ).pipe(Effect.mapError(internalFailure(operation)));
1422
+
1323
1423
  yield* hitFailpoint("ledger:release:before", operation);
1324
1424
  yield* inWriteTransaction(
1325
1425
  operation,
1326
1426
  Effect.gen(function* () {
1327
1427
  const submission = yield* requireSubmission(operation, validated.submissionId);
1428
+
1328
1429
  yield* requireOwnership(operation, submission, validated.ownershipToken);
1329
1430
  yield* sql`
1330
1431
  DELETE FROM effect_agent_submission_ownership
@@ -1346,14 +1447,17 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1346
1447
  "SqliteSubmissionLedger.markInputApplied",
1347
1448
  )(function* (request: MarkInputAppliedRequest) {
1348
1449
  const operation = "ledger mark input applied";
1450
+
1349
1451
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkInputAppliedRequest))(
1350
1452
  request,
1351
1453
  ).pipe(Effect.mapError(internalFailure(operation)));
1454
+
1352
1455
  yield* hitFailpoint("ledger:mark-input-applied:before", operation);
1353
1456
  yield* inWriteTransaction(
1354
1457
  operation,
1355
1458
  Effect.gen(function* () {
1356
1459
  const submission = yield* requireSubmission(operation, validated.submissionId);
1460
+
1357
1461
  yield* requireOwnership(operation, submission, validated.ownershipToken);
1358
1462
  if (submission.input_applied_record_id !== null) {
1359
1463
  if (
@@ -1362,6 +1466,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1362
1466
  ) {
1363
1467
  return;
1364
1468
  }
1469
+
1365
1470
  return yield* corruptionFailure(
1366
1471
  operation,
1367
1472
  "effect_agent_submissions",
@@ -1389,29 +1494,36 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1389
1494
  "SqliteSubmissionLedger.reserveSettlement",
1390
1495
  )(function* (request: SettlementReservation) {
1391
1496
  const operation = "ledger reserve settlement";
1497
+
1392
1498
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementReservation))(
1393
1499
  request,
1394
1500
  ).pipe(Effect.mapError(internalFailure(operation)));
1501
+
1395
1502
  const recordJson = yield* encodeRecordEnvelopeText(validated.record).pipe(
1396
1503
  Effect.mapError(internalFailure(operation)),
1397
1504
  );
1505
+
1398
1506
  yield* hitFailpoint("ledger:reserve-settlement:before", operation);
1507
+
1399
1508
  const reserved = yield* inWriteTransaction(
1400
1509
  operation,
1401
1510
  Effect.gen(function* () {
1402
1511
  const existing = yield* readReservation(operation, validated.submissionId);
1512
+
1403
1513
  if (Option.isSome(existing)) {
1404
1514
  const identical =
1405
1515
  existing.value.settlement_id === validated.settlementId &&
1406
1516
  existing.value.outcome === validated.outcome &&
1407
1517
  existing.value.record_digest === validated.recordDigest &&
1408
1518
  existing.value.record_json === recordJson;
1519
+
1409
1520
  if (!identical) {
1410
1521
  return yield* SettlementConflict.make({
1411
1522
  submissionId: validated.submissionId,
1412
1523
  existingOutcome: existing.value.outcome,
1413
1524
  });
1414
1525
  }
1526
+
1415
1527
  const record = yield* decodeRecordEnvelopeText(existing.value.record_json).pipe(
1416
1528
  Effect.mapError((error) =>
1417
1529
  corruptionFailure(
@@ -1422,6 +1534,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1422
1534
  ),
1423
1535
  ),
1424
1536
  );
1537
+
1425
1538
  return ReservedSettlement.make({
1426
1539
  submissionId: validated.submissionId,
1427
1540
  settlementId: validated.settlementId,
@@ -1433,6 +1546,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1433
1546
  }
1434
1547
 
1435
1548
  const submission = yield* requireSubmission(operation, validated.submissionId);
1549
+
1436
1550
  if (submission.state === "settled") {
1437
1551
  if (submission.settled_outcome === null) {
1438
1552
  return yield* corruptionFailure(
@@ -1442,6 +1556,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1442
1556
  "A settled Submission carries no terminal outcome.",
1443
1557
  );
1444
1558
  }
1559
+
1445
1560
  return yield* SettlementConflict.make({
1446
1561
  submissionId: validated.submissionId,
1447
1562
  existingOutcome: submission.settled_outcome,
@@ -1456,13 +1571,16 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1456
1571
  // ABORTED settlement (`terminalizing` is the same pass's crash replay). Every other
1457
1572
  // reservation stays fenced by the target lane's live ownership.
1458
1573
  let queuedAbortSettlement = false;
1574
+
1459
1575
  if (
1460
1576
  validated.outcome === "aborted" &&
1461
1577
  (submission.state === "ready" || submission.state === "terminalizing")
1462
1578
  ) {
1463
1579
  const abortIntent = yield* readAbortIntent(operation, validated.submissionId);
1580
+
1464
1581
  if (Option.isSome(abortIntent)) {
1465
1582
  const ownership = yield* readOwnership(operation, validated.submissionId);
1583
+
1466
1584
  queuedAbortSettlement = Option.isNone(ownership);
1467
1585
  }
1468
1586
  }
@@ -1471,6 +1589,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1471
1589
  }
1472
1590
  }
1473
1591
  const now = yield* currentInstant;
1592
+
1474
1593
  yield* sql`
1475
1594
  INSERT INTO effect_agent_settlement_reservations (
1476
1595
  submission_id,
@@ -1495,6 +1614,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1495
1614
  SET state = 'terminalizing'
1496
1615
  WHERE submission_id = ${validated.submissionId}
1497
1616
  `.pipe(Effect.mapError(sqlFailure(operation)));
1617
+
1498
1618
  return ReservedSettlement.make({
1499
1619
  submissionId: validated.submissionId,
1500
1620
  settlementId: validated.settlementId,
@@ -1505,7 +1625,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1505
1625
  });
1506
1626
  }),
1507
1627
  );
1628
+
1508
1629
  yield* hitFailpoint("ledger:reserve-settlement:after", operation);
1630
+
1509
1631
  return reserved;
1510
1632
  });
1511
1633
 
@@ -1513,14 +1635,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1513
1635
  "SqliteSubmissionLedger.finalizeSettlement",
1514
1636
  )(function* (request: SettlementFinalization) {
1515
1637
  const operation = "ledger finalize settlement";
1638
+
1516
1639
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementFinalization))(
1517
1640
  request,
1518
1641
  ).pipe(Effect.mapError(internalFailure(operation)));
1642
+
1519
1643
  yield* hitFailpoint("ledger:finalize-settlement:before", operation);
1644
+
1520
1645
  const settlement = yield* inWriteTransaction(
1521
1646
  operation,
1522
1647
  Effect.gen(function* () {
1523
1648
  const reservation = yield* readReservation(operation, validated.submissionId);
1649
+
1524
1650
  if (Option.isNone(reservation)) {
1525
1651
  return yield* LedgerError.make({
1526
1652
  operation,
@@ -1533,6 +1659,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1533
1659
  existingOutcome: reservation.value.outcome,
1534
1660
  });
1535
1661
  }
1662
+
1536
1663
  const reservationRecord = yield* decodeRecordEnvelopeText(
1537
1664
  reservation.value.record_json,
1538
1665
  ).pipe(
@@ -1545,7 +1672,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1545
1672
  ),
1546
1673
  ),
1547
1674
  );
1675
+
1548
1676
  const settlementFailure = settlementFailureFromRecord(reservationRecord);
1677
+
1549
1678
  if ((reservation.value.outcome === "failed") !== (settlementFailure !== undefined)) {
1550
1679
  return yield* corruptionFailure(
1551
1680
  operation,
@@ -1555,6 +1684,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1555
1684
  );
1556
1685
  }
1557
1686
  const submission = yield* requireSubmission(operation, validated.submissionId);
1687
+
1558
1688
  if (submission.state === "settled") {
1559
1689
  if (reservation.value.finalized_at === null) {
1560
1690
  return yield* corruptionFailure(
@@ -1564,6 +1694,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1564
1694
  "A settled Submission's reservation carries no finalization timestamp.",
1565
1695
  );
1566
1696
  }
1697
+
1567
1698
  return yield* decodeSettlement({
1568
1699
  submissionId: validated.submissionId,
1569
1700
  settlementId: validated.settlementId,
@@ -1574,6 +1705,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1574
1705
  }).pipe(Effect.mapError(internalFailure(operation)));
1575
1706
  }
1576
1707
  const now = yield* currentInstant;
1708
+
1577
1709
  yield* sql`
1578
1710
  UPDATE effect_agent_submissions
1579
1711
  SET state = 'settled', settled_outcome = ${reservation.value.outcome}
@@ -1588,6 +1720,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1588
1720
  DELETE FROM effect_agent_submission_ownership
1589
1721
  WHERE submission_id = ${validated.submissionId}
1590
1722
  `.pipe(Effect.mapError(sqlFailure(operation)));
1723
+
1591
1724
  return yield* decodeSettlement({
1592
1725
  submissionId: validated.submissionId,
1593
1726
  settlementId: validated.settlementId,
@@ -1598,7 +1731,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1598
1731
  }).pipe(Effect.mapError(internalFailure(operation)));
1599
1732
  }),
1600
1733
  );
1734
+
1601
1735
  yield* hitFailpoint("ledger:finalize-settlement:after", operation);
1736
+
1602
1737
  return settlement;
1603
1738
  });
1604
1739
 
@@ -1606,14 +1741,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1606
1741
  "SqliteSubmissionLedger.requestAbort",
1607
1742
  )(function* (request: AbortCommand) {
1608
1743
  const operation = "ledger request abort";
1744
+
1609
1745
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AbortCommand))(request).pipe(
1610
1746
  Effect.mapError(internalFailure(operation)),
1611
1747
  );
1748
+
1612
1749
  yield* hitFailpoint("ledger:request-abort:before", operation);
1750
+
1613
1751
  const intent = yield* inWriteTransaction(
1614
1752
  operation,
1615
1753
  Effect.gen(function* () {
1616
1754
  const submission = yield* requireSubmission(operation, validated.submissionId);
1755
+
1617
1756
  if (submission.state === "settled") {
1618
1757
  if (submission.settled_outcome === null) {
1619
1758
  return yield* corruptionFailure(
@@ -1623,6 +1762,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1623
1762
  "A settled Submission carries no terminal outcome.",
1624
1763
  );
1625
1764
  }
1765
+
1626
1766
  return yield* SettlementConflict.make({
1627
1767
  submissionId: validated.submissionId,
1628
1768
  existingOutcome: submission.settled_outcome,
@@ -1640,15 +1780,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1640
1780
  "A joined Submission carries no host linkage.",
1641
1781
  );
1642
1782
  }
1783
+
1643
1784
  const hostSubmissionId = yield* decodeSubmissionId(
1644
1785
  submission.joined_host_submission_id,
1645
1786
  ).pipe(Effect.mapError(internalFailure(operation)));
1787
+
1646
1788
  return yield* JoinedToHost.make({
1647
1789
  submissionId: validated.submissionId,
1648
1790
  hostSubmissionId,
1649
1791
  });
1650
1792
  }
1651
1793
  const existing = yield* readAbortIntent(operation, validated.submissionId);
1794
+
1652
1795
  if (Option.isSome(existing)) {
1653
1796
  return yield* abortIntentFromRow(
1654
1797
  operation,
@@ -1658,6 +1801,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1658
1801
  );
1659
1802
  }
1660
1803
  const now = yield* currentInstant;
1804
+
1661
1805
  yield* sql`
1662
1806
  INSERT INTO effect_agent_abort_intents (
1663
1807
  submission_id,
@@ -1671,11 +1815,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1671
1815
  ${now.iso}
1672
1816
  )
1673
1817
  `.pipe(Effect.mapError(sqlFailure(operation)));
1818
+
1674
1819
  const canonicalRecordId = yield* canonicalAbortRecordId(
1675
1820
  operation,
1676
1821
  submission.thread_id,
1677
1822
  validated.submissionId,
1678
1823
  );
1824
+
1679
1825
  return yield* decodeAbortIntent({
1680
1826
  submissionId: validated.submissionId,
1681
1827
  author: validated.author,
@@ -1685,7 +1831,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1685
1831
  }).pipe(Effect.mapError(internalFailure(operation)));
1686
1832
  }),
1687
1833
  );
1834
+
1688
1835
  yield* hitFailpoint("ledger:request-abort:after", operation);
1836
+
1689
1837
  return intent;
1690
1838
  });
1691
1839
 
@@ -1693,14 +1841,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1693
1841
  "SqliteSubmissionLedger.claimJoining",
1694
1842
  )(function* (request: ClaimJoiningRequest) {
1695
1843
  const operation = "ledger claim joining";
1844
+
1696
1845
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimJoiningRequest))(
1697
1846
  request,
1698
1847
  ).pipe(Effect.mapError(internalFailure(operation)));
1848
+
1699
1849
  yield* hitFailpoint("ledger:claim-joining:before", operation);
1850
+
1700
1851
  const claims = yield* inWriteTransaction(
1701
1852
  operation,
1702
1853
  Effect.gen(function* () {
1703
1854
  const host = yield* requireSubmission(operation, validated.hostSubmissionId);
1855
+
1704
1856
  if (host.thread_id !== validated.threadId) {
1705
1857
  return yield* LedgerError.make({
1706
1858
  operation,
@@ -1709,6 +1861,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1709
1861
  }
1710
1862
  // The host Attempt already owns the lane; no epoch bump happens here (plan §2.5).
1711
1863
  yield* requireOwnership(operation, host, validated.ownershipToken);
1864
+
1712
1865
  const laterRows = yield* sql<Record<string, unknown>>`
1713
1866
  SELECT ${sql.literal(SUBMISSION_COLUMNS)}
1714
1867
  FROM effect_agent_submissions
@@ -1716,8 +1869,10 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1716
1869
  AND queue_sequence > ${host.queue_sequence}
1717
1870
  ORDER BY queue_sequence ASC
1718
1871
  `.pipe(Effect.mapError(sqlFailure(operation)));
1872
+
1719
1873
  const later = yield* decodeSubmissionRows(operation, validated.threadId, laterRows);
1720
1874
  const claimed: Array<JoiningClaim> = [];
1875
+
1721
1876
  for (const row of later) {
1722
1877
  if (claimed.length >= validated.maxCount) break;
1723
1878
  // Rows already claimed by THIS host extend its contiguous prefix and are skipped;
@@ -1740,6 +1895,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1740
1895
  SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
1741
1896
  WHERE submission_id = ${row.submission_id}
1742
1897
  `.pipe(Effect.mapError(sqlFailure(operation)));
1898
+
1743
1899
  const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
1744
1900
  Effect.mapError((error) =>
1745
1901
  corruptionFailure(
@@ -1750,6 +1906,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1750
1906
  ),
1751
1907
  ),
1752
1908
  );
1909
+
1753
1910
  claimed.push(
1754
1911
  yield* decodeJoiningClaim({
1755
1912
  submissionId: row.submission_id,
@@ -1758,10 +1915,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1758
1915
  }).pipe(Effect.mapError(internalFailure(operation))),
1759
1916
  );
1760
1917
  }
1918
+
1761
1919
  return claimed;
1762
1920
  }),
1763
1921
  );
1922
+
1764
1923
  yield* hitFailpoint("ledger:claim-joining:after", operation);
1924
+
1765
1925
  return claims;
1766
1926
  });
1767
1927
 
@@ -1769,14 +1929,17 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1769
1929
  "SqliteSubmissionLedger.markJoined",
1770
1930
  )(function* (request: MarkJoinedRequest) {
1771
1931
  const operation = "ledger mark joined";
1932
+
1772
1933
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkJoinedRequest))(
1773
1934
  request,
1774
1935
  ).pipe(Effect.mapError(internalFailure(operation)));
1936
+
1775
1937
  yield* hitFailpoint("ledger:mark-joined:before", operation);
1776
1938
  yield* inWriteTransaction(
1777
1939
  operation,
1778
1940
  Effect.gen(function* () {
1779
1941
  const submission = yield* requireSubmission(operation, validated.submissionId);
1942
+
1780
1943
  if (submission.joined_host_submission_id === null) {
1781
1944
  return yield* LedgerError.make({
1782
1945
  operation,
@@ -1784,6 +1947,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1784
1947
  });
1785
1948
  }
1786
1949
  const host = yield* requireSubmission(operation, submission.joined_host_submission_id);
1950
+
1787
1951
  // The lane is host-owned: the presented token must own the HOST's ownership period,
1788
1952
  // which also lets a later host Attempt repair a lost marker from history (DUR-016).
1789
1953
  yield* requireOwnership(operation, host, validated.ownershipToken);
@@ -1794,6 +1958,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1794
1958
  ) {
1795
1959
  return;
1796
1960
  }
1961
+
1797
1962
  return yield* corruptionFailure(
1798
1963
  operation,
1799
1964
  "effect_agent_submissions",
@@ -1824,14 +1989,17 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1824
1989
  "SqliteSubmissionLedger.revertJoining",
1825
1990
  )(function* (request: RevertJoiningRequest) {
1826
1991
  const operation = "ledger revert joining";
1992
+
1827
1993
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RevertJoiningRequest))(
1828
1994
  request,
1829
1995
  ).pipe(Effect.mapError(internalFailure(operation)));
1996
+
1830
1997
  yield* hitFailpoint("ledger:revert-joining:before", operation);
1831
1998
  yield* inWriteTransaction(
1832
1999
  operation,
1833
2000
  Effect.gen(function* () {
1834
2001
  const submission = yield* requireSubmission(operation, validated.submissionId);
2002
+
1835
2003
  // Idempotent and recovery-only: only a still-`joining` Submission reverts; an
1836
2004
  // already-joined (or already-reverted) Submission is a no-op (DUR-016).
1837
2005
  if (submission.state !== "joining") return;
@@ -1849,17 +2017,22 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1849
2017
  "SqliteSubmissionLedger.suspend",
1850
2018
  )(function* (request: SuspendRequest) {
1851
2019
  const operation = "ledger suspend";
2020
+
1852
2021
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SuspendRequest))(
1853
2022
  request,
1854
2023
  ).pipe(Effect.mapError(internalFailure(operation)));
2024
+
1855
2025
  const reasonJson = yield* encodeSuspensionReasonText(validated.reason).pipe(
1856
2026
  Effect.mapError(internalFailure(operation)),
1857
2027
  );
2028
+
1858
2029
  yield* hitFailpoint("ledger:suspend:before", operation);
2030
+
1859
2031
  const outcome = yield* inWriteTransaction(
1860
2032
  operation,
1861
2033
  Effect.gen(function* () {
1862
2034
  const submission = yield* requireSubmission(operation, validated.submissionId);
2035
+
1863
2036
  if (submission.state === "settled") {
1864
2037
  if (submission.settled_outcome === null) {
1865
2038
  return yield* corruptionFailure(
@@ -1869,6 +2042,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1869
2042
  "A settled Submission carries no terminal outcome.",
1870
2043
  );
1871
2044
  }
2045
+
1872
2046
  return yield* SettlementConflict.make({
1873
2047
  submissionId: validated.submissionId,
1874
2048
  existingOutcome: submission.settled_outcome,
@@ -1877,6 +2051,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1877
2051
  // An exact terminal outcome is already reserved (DUR-011); suspension would
1878
2052
  // contradict it, so the reservation wins.
1879
2053
  const reservation = yield* readReservation(operation, validated.submissionId);
2054
+
1880
2055
  if (Option.isSome(reservation)) {
1881
2056
  return yield* SettlementConflict.make({
1882
2057
  submissionId: validated.submissionId,
@@ -1890,13 +2065,16 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1890
2065
  if (validated.reason._tag === "ApprovalPending") {
1891
2066
  const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
1892
2067
  const decided = new Set(decisions.map((row) => row.tool_call_id));
2068
+
1893
2069
  if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) {
1894
2070
  return RESUME_IMMEDIATELY;
1895
2071
  }
1896
2072
  } else {
1897
2073
  let allSettled = true;
2074
+
1898
2075
  for (const child of validated.reason.children) {
1899
2076
  const childRow = yield* readSubmission(operation, child.childSubmissionId);
2077
+
1900
2078
  if (Option.isNone(childRow) || childRow.value.state !== "settled") {
1901
2079
  allSettled = false;
1902
2080
  break;
@@ -1907,6 +2085,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1907
2085
  }
1908
2086
  }
1909
2087
  const now = yield* currentInstant;
2088
+
1910
2089
  yield* sql`
1911
2090
  UPDATE effect_agent_submissions
1912
2091
  SET
@@ -1921,10 +2100,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1921
2100
  DELETE FROM effect_agent_submission_ownership
1922
2101
  WHERE submission_id = ${validated.submissionId}
1923
2102
  `.pipe(Effect.mapError(sqlFailure(operation)));
2103
+
1924
2104
  return SUSPENDED;
1925
2105
  }),
1926
2106
  );
2107
+
1927
2108
  yield* hitFailpoint("ledger:suspend:after", operation);
2109
+
1928
2110
  return outcome;
1929
2111
  });
1930
2112
 
@@ -1937,6 +2119,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1937
2119
  const wakeSuspendedIfCovered = Effect.fn("SqliteSubmissionLedger.wakeSuspendedIfCovered")(
1938
2120
  function* (operation: string, submission: SubmissionRow): Effect.fn.Return<void, LedgerError> {
1939
2121
  if (submission.state !== "suspended" || submission.suspended_reason_json === null) return;
2122
+
1940
2123
  const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(
1941
2124
  submission.suspended_reason_json,
1942
2125
  ).pipe(
@@ -1949,9 +2132,11 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1949
2132
  ),
1950
2133
  ),
1951
2134
  );
2135
+
1952
2136
  if (reason._tag !== "ApprovalPending") return;
1953
2137
  const decisions = yield* readApprovalDecisions(operation, submission.submission_id);
1954
2138
  const decided = new Set(decisions.map((row) => row.tool_call_id));
2139
+
1955
2140
  if (!reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return;
1956
2141
  yield* sql`
1957
2142
  UPDATE effect_agent_submissions
@@ -1968,14 +2153,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1968
2153
  "SqliteSubmissionLedger.recordApprovalDecision",
1969
2154
  )(function* (command: ApprovalDecisionCommand) {
1970
2155
  const operation = "ledger record approval decision";
2156
+
1971
2157
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ApprovalDecisionCommand))(
1972
2158
  command,
1973
2159
  ).pipe(Effect.mapError(internalFailure(operation)));
2160
+
1974
2161
  yield* hitFailpoint("ledger:approval-decision:before", operation);
2162
+
1975
2163
  const intent = yield* inWriteTransaction(
1976
2164
  operation,
1977
2165
  Effect.gen(function* () {
1978
2166
  const submission = yield* requireSubmission(operation, validated.submissionId);
2167
+
1979
2168
  if (submission.state === "settled") {
1980
2169
  if (submission.settled_outcome === null) {
1981
2170
  return yield* corruptionFailure(
@@ -1985,6 +2174,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1985
2174
  "A settled Submission carries no terminal outcome.",
1986
2175
  );
1987
2176
  }
2177
+
1988
2178
  return yield* SettlementConflict.make({
1989
2179
  submissionId: validated.submissionId,
1990
2180
  existingOutcome: submission.settled_outcome,
@@ -1992,6 +2182,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
1992
2182
  }
1993
2183
  const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
1994
2184
  const existing = decisions.find((row) => row.tool_call_id === validated.toolCallId);
2185
+
1995
2186
  if (existing !== undefined) {
1996
2187
  // Idempotent per (submissionId, toolCallId): repeating the SAME decision replays
1997
2188
  // the recorded intent unchanged; a divergent re-decision conflicts.
@@ -2002,9 +2193,11 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2002
2193
  existingDecision: existing.decision,
2003
2194
  });
2004
2195
  }
2196
+
2005
2197
  return yield* approvalIntentFromRow(operation, existing);
2006
2198
  }
2007
2199
  const now = yield* currentInstant;
2200
+
2008
2201
  yield* sql`
2009
2202
  INSERT INTO effect_agent_approval_decisions (
2010
2203
  submission_id,
@@ -2023,6 +2216,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2023
2216
  )
2024
2217
  `.pipe(Effect.mapError(sqlFailure(operation)));
2025
2218
  yield* wakeSuspendedIfCovered(operation, submission);
2219
+
2026
2220
  return yield* decodeApprovalDecisionIntent({
2027
2221
  submissionId: validated.submissionId,
2028
2222
  toolCallId: validated.toolCallId,
@@ -2033,7 +2227,9 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2033
2227
  }).pipe(Effect.mapError(internalFailure(operation)));
2034
2228
  }),
2035
2229
  );
2230
+
2036
2231
  yield* hitFailpoint("ledger:approval-decision:after", operation);
2232
+
2037
2233
  return intent;
2038
2234
  });
2039
2235
 
@@ -2041,14 +2237,17 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2041
2237
  "SqliteSubmissionLedger.markUnknown",
2042
2238
  )(function* (request: MarkUnknownRequest) {
2043
2239
  const operation = "ledger mark unknown";
2240
+
2044
2241
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkUnknownRequest))(
2045
2242
  request,
2046
2243
  ).pipe(Effect.mapError(internalFailure(operation)));
2244
+
2047
2245
  yield* hitFailpoint("ledger:mark-unknown:before", operation);
2048
2246
  yield* inWriteTransaction(
2049
2247
  operation,
2050
2248
  Effect.gen(function* () {
2051
2249
  const submission = yield* requireSubmission(operation, validated.submissionId);
2250
+
2052
2251
  if (submission.state === "settled") {
2053
2252
  if (submission.settled_outcome === null) {
2054
2253
  return yield* corruptionFailure(
@@ -2058,6 +2257,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2058
2257
  "A settled Submission carries no terminal outcome.",
2059
2258
  );
2060
2259
  }
2260
+
2061
2261
  return yield* SettlementConflict.make({
2062
2262
  submissionId: validated.submissionId,
2063
2263
  existingOutcome: submission.settled_outcome,
@@ -2066,6 +2266,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2066
2266
  // A reserved exact outcome wins over a late Unknown marking (DUR-011); the recovery
2067
2267
  // classifier orders reservation ahead of MarkUnknown for the same reason.
2068
2268
  const reservation = yield* readReservation(operation, validated.submissionId);
2269
+
2069
2270
  if (Option.isSome(reservation)) {
2070
2271
  return yield* SettlementConflict.make({
2071
2272
  submissionId: validated.submissionId,
@@ -2076,13 +2277,16 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2076
2277
  // set while the first recorded reason is kept.
2077
2278
  const existingIds = yield* storedUnknownToolCallIds(operation, submission);
2078
2279
  const known = new Set(existingIds);
2280
+
2079
2281
  const merged = [
2080
2282
  ...existingIds,
2081
2283
  ...validated.toolCallIds.filter((toolCallId) => !known.has(toolCallId)),
2082
2284
  ];
2285
+
2083
2286
  const idsJson = yield* encodeToolCallIdsText(merged).pipe(
2084
2287
  Effect.mapError(internalFailure(operation)),
2085
2288
  );
2289
+
2086
2290
  yield* sql`
2087
2291
  UPDATE effect_agent_submissions
2088
2292
  SET
@@ -2100,17 +2304,22 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2100
2304
  "SqliteSubmissionLedger.recordUnknownResolution",
2101
2305
  )(function* (command: UnknownResolutionCommand) {
2102
2306
  const operation = "ledger record unknown resolution";
2307
+
2103
2308
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(UnknownResolutionCommand))(
2104
2309
  command,
2105
2310
  ).pipe(Effect.mapError(internalFailure(operation)));
2311
+
2106
2312
  const resolutionJson = yield* encodeUnknownResolutionText(validated.resolution).pipe(
2107
2313
  Effect.mapError(internalFailure(operation)),
2108
2314
  );
2315
+
2109
2316
  yield* hitFailpoint("ledger:unknown-resolution:before", operation);
2317
+
2110
2318
  const intent = yield* inWriteTransaction(
2111
2319
  operation,
2112
2320
  Effect.gen(function* () {
2113
2321
  const submission = yield* requireSubmission(operation, validated.submissionId);
2322
+
2114
2323
  if (submission.state === "settled") {
2115
2324
  if (submission.settled_outcome === null) {
2116
2325
  return yield* corruptionFailure(
@@ -2120,6 +2329,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2120
2329
  "A settled Submission carries no terminal outcome.",
2121
2330
  );
2122
2331
  }
2332
+
2123
2333
  return yield* SettlementConflict.make({
2124
2334
  submissionId: validated.submissionId,
2125
2335
  existingOutcome: submission.settled_outcome,
@@ -2127,10 +2337,12 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2127
2337
  }
2128
2338
  const resolutions = yield* readUnknownResolutions(operation, validated.submissionId);
2129
2339
  const existing = resolutions.find((row) => row.tool_call_id === validated.toolCallId);
2340
+
2130
2341
  const existingIntent =
2131
2342
  existing === undefined
2132
2343
  ? undefined
2133
2344
  : yield* unknownResolutionIntentFromRow(operation, existing);
2345
+
2134
2346
  if (
2135
2347
  existingIntent !== undefined &&
2136
2348
  !equivalentUnknownResolution(existingIntent.resolution, validated.resolution)
@@ -2141,12 +2353,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2141
2353
  });
2142
2354
  }
2143
2355
  let resolved: UnknownResolutionIntent;
2356
+
2144
2357
  if (existingIntent !== undefined) {
2145
2358
  // Idempotent replay of the recorded intent (author/reason may differ; the stored
2146
2359
  // audit fields win, exactly like requestAbort).
2147
2360
  resolved = existingIntent;
2148
2361
  } else {
2149
2362
  const now = yield* currentInstant;
2363
+
2150
2364
  yield* sql`
2151
2365
  INSERT INTO effect_agent_unknown_resolutions (
2152
2366
  submission_id,
@@ -2164,9 +2378,11 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2164
2378
  ${now.iso}
2165
2379
  )
2166
2380
  `.pipe(Effect.mapError(sqlFailure(operation)));
2381
+
2167
2382
  const resolution = yield* parseStoredJsonText(resolutionJson).pipe(
2168
2383
  Effect.mapError(internalFailure(operation)),
2169
2384
  );
2385
+
2170
2386
  resolved = yield* decodeUnknownResolutionIntent({
2171
2387
  submissionId: validated.submissionId,
2172
2388
  toolCallId: validated.toolCallId,
@@ -2183,6 +2399,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2183
2399
  const markedIds = yield* storedUnknownToolCallIds(operation, submission);
2184
2400
  const covering = yield* readUnknownResolutions(operation, validated.submissionId);
2185
2401
  const coveredIds = new Set(covering.map((row) => row.tool_call_id));
2402
+
2186
2403
  if (markedIds.every((toolCallId) => coveredIds.has(toolCallId))) {
2187
2404
  yield* sql`
2188
2405
  UPDATE effect_agent_submissions
@@ -2194,10 +2411,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2194
2411
  `.pipe(Effect.mapError(sqlFailure(operation)));
2195
2412
  }
2196
2413
  }
2414
+
2197
2415
  return resolved;
2198
2416
  }),
2199
2417
  );
2418
+
2200
2419
  yield* hitFailpoint("ledger:unknown-resolution:after", operation);
2420
+
2201
2421
  return intent;
2202
2422
  });
2203
2423
 
@@ -2205,10 +2425,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2205
2425
  "SqliteSubmissionLedger.recordChildSettled",
2206
2426
  )(function* (request: ChildSettledNotification) {
2207
2427
  const operation = "ledger record child settled";
2428
+
2208
2429
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ChildSettledNotification))(
2209
2430
  request,
2210
2431
  ).pipe(Effect.mapError(internalFailure(operation)));
2432
+
2211
2433
  yield* hitFailpoint("ledger:child-settled:before", operation);
2434
+
2212
2435
  const outcome = yield* inWriteTransaction(
2213
2436
  operation,
2214
2437
  Effect.gen(function* () {
@@ -2218,10 +2441,12 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2218
2441
  // that makes that ordering admissible; earlier states remain a caller error.
2219
2442
  const child = yield* readSubmission(operation, validated.childSubmissionId);
2220
2443
  const childReservation = yield* readReservation(operation, validated.childSubmissionId);
2444
+
2221
2445
  const announced =
2222
2446
  Option.isSome(child) &&
2223
2447
  (child.value.state === "settled" ||
2224
2448
  (child.value.state === "terminalizing" && Option.isSome(childReservation)));
2449
+
2225
2450
  if (!announced) {
2226
2451
  return yield* LedgerError.make({
2227
2452
  operation,
@@ -2231,6 +2456,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2231
2456
  if (parent.state !== "suspended" || parent.suspended_reason_json === null) {
2232
2457
  return NOT_WAITING;
2233
2458
  }
2459
+
2234
2460
  const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(
2235
2461
  parent.suspended_reason_json,
2236
2462
  ).pipe(
@@ -2243,6 +2469,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2243
2469
  ),
2244
2470
  ),
2245
2471
  );
2472
+
2246
2473
  if (reason._tag !== "WaitingForChild") {
2247
2474
  return NOT_WAITING;
2248
2475
  }
@@ -2256,10 +2483,12 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2256
2483
  for (const entry of reason.children) {
2257
2484
  const listed = yield* readSubmission(operation, entry.childSubmissionId);
2258
2485
  const reservation = yield* readReservation(operation, entry.childSubmissionId);
2486
+
2259
2487
  const covered =
2260
2488
  Option.isSome(listed) &&
2261
2489
  (listed.value.state === "settled" ||
2262
2490
  (listed.value.state === "terminalizing" && Option.isSome(reservation)));
2491
+
2263
2492
  if (!covered) {
2264
2493
  return STILL_WAITING;
2265
2494
  }
@@ -2272,10 +2501,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2272
2501
  suspended_at = NULL
2273
2502
  WHERE submission_id = ${validated.parentSubmissionId}
2274
2503
  `.pipe(Effect.mapError(sqlFailure(operation)));
2504
+
2275
2505
  return WOKEN;
2276
2506
  }),
2277
2507
  );
2508
+
2278
2509
  yield* hitFailpoint("ledger:child-settled:after", operation);
2510
+
2279
2511
  return outcome;
2280
2512
  });
2281
2513
 
@@ -2283,22 +2515,28 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2283
2515
  "SqliteSubmissionLedger.reserveChildBudget",
2284
2516
  )(function* (request: ChildBudgetReservationRequest) {
2285
2517
  const operation = "ledger reserve child budget";
2518
+
2286
2519
  const validated = yield* Schema.decodeUnknownEffect(
2287
2520
  Schema.toType(ChildBudgetReservationRequest),
2288
2521
  )(request).pipe(Effect.mapError(internalFailure(operation)));
2522
+
2289
2523
  const allocationJson = yield* encodePersistedJsonText(validated.allocation).pipe(
2290
2524
  Effect.mapError(internalFailure(operation)),
2291
2525
  );
2526
+
2292
2527
  yield* hitFailpoint("ledger:child-reservation:before", operation);
2528
+
2293
2529
  const reserved = yield* inWriteTransaction(
2294
2530
  operation,
2295
2531
  Effect.gen(function* () {
2296
2532
  const existing = yield* readChildReservation(operation, validated.reservationId);
2533
+
2297
2534
  if (Option.isSome(existing)) {
2298
2535
  const existingSnapshot = yield* childReservationSnapshotFromRow(
2299
2536
  operation,
2300
2537
  existing.value,
2301
2538
  );
2539
+
2302
2540
  // Identical replays short-circuit before the fence, mirroring reserveSettlement: a
2303
2541
  // replay creates nothing, so a recovering caller resumes rather than duplicates.
2304
2542
  const identical =
@@ -2306,6 +2544,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2306
2544
  existing.value.parent_tool_call_id === validated.parentToolCallId &&
2307
2545
  existing.value.allocation_digest === validated.allocationDigest &&
2308
2546
  equivalentPersistedJson(existingSnapshot.allocation, validated.allocation);
2547
+
2309
2548
  if (!identical) {
2310
2549
  return yield* ChildReservationConflict.make({
2311
2550
  reservationId: validated.reservationId,
@@ -2314,16 +2553,19 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2314
2553
  "A reservation with this identity exists with a different parent Tool Call or allocation.",
2315
2554
  });
2316
2555
  }
2556
+
2317
2557
  return ReservedChildBudget.make({
2318
2558
  reservation: existingSnapshot,
2319
2559
  replayed: true,
2320
2560
  });
2321
2561
  }
2562
+
2322
2563
  const collision = yield* readChildReservationForCall(
2323
2564
  operation,
2324
2565
  validated.parentSubmissionId,
2325
2566
  validated.parentToolCallId,
2326
2567
  );
2568
+
2327
2569
  if (Option.isSome(collision)) {
2328
2570
  return yield* ChildReservationConflict.make({
2329
2571
  reservationId: validated.reservationId,
@@ -2332,10 +2574,12 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2332
2574
  });
2333
2575
  }
2334
2576
  const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
2577
+
2335
2578
  // Creation is fenced by the parent lane's live ownership (spec §12 step 2): a stale
2336
2579
  // parent Attempt can never create new reservation state.
2337
2580
  yield* requireOwnership(operation, parent, validated.ownershipToken);
2338
2581
  const now = yield* currentInstant;
2582
+
2339
2583
  yield* sql`
2340
2584
  INSERT INTO effect_agent_child_reservations (
2341
2585
  reservation_id,
@@ -2356,6 +2600,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2356
2600
  )
2357
2601
  `.pipe(Effect.mapError(sqlFailure(operation)));
2358
2602
  const inserted = yield* readChildReservation(operation, validated.reservationId);
2603
+
2359
2604
  if (Option.isNone(inserted)) {
2360
2605
  return yield* corruptionFailure(
2361
2606
  operation,
@@ -2364,13 +2609,16 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2364
2609
  "An inserted child reservation row is missing inside its own transaction.",
2365
2610
  );
2366
2611
  }
2612
+
2367
2613
  return ReservedChildBudget.make({
2368
2614
  reservation: yield* childReservationSnapshotFromRow(operation, inserted.value),
2369
2615
  replayed: false,
2370
2616
  });
2371
2617
  }),
2372
2618
  );
2619
+
2373
2620
  yield* hitFailpoint("ledger:child-reservation:after", operation);
2621
+
2374
2622
  return reserved;
2375
2623
  });
2376
2624
 
@@ -2379,14 +2627,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2379
2627
  request: AttachChildToReservationRequest,
2380
2628
  ) {
2381
2629
  const operation = "ledger attach child to reservation";
2630
+
2382
2631
  const validated = yield* Schema.decodeUnknownEffect(
2383
2632
  Schema.toType(AttachChildToReservationRequest),
2384
2633
  )(request).pipe(Effect.mapError(internalFailure(operation)));
2634
+
2385
2635
  yield* hitFailpoint("ledger:child-attach:before", operation);
2636
+
2386
2637
  const attached = yield* inWriteTransaction(
2387
2638
  operation,
2388
2639
  Effect.gen(function* () {
2389
2640
  const existing = yield* readChildReservation(operation, validated.reservationId);
2641
+
2390
2642
  if (Option.isNone(existing)) {
2391
2643
  return yield* LedgerError.make({
2392
2644
  operation,
@@ -2398,6 +2650,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2398
2650
  if (existing.value.child_submission_id === validated.childSubmissionId) {
2399
2651
  return yield* childReservationSnapshotFromRow(operation, existing.value);
2400
2652
  }
2653
+
2401
2654
  return yield* ChildReservationConflict.make({
2402
2655
  reservationId: validated.reservationId,
2403
2656
  status: existing.value.status,
@@ -2405,6 +2658,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2405
2658
  });
2406
2659
  }
2407
2660
  const parent = yield* requireSubmission(operation, existing.value.parent_submission_id);
2661
+
2408
2662
  yield* requireOwnership(operation, parent, validated.ownershipToken);
2409
2663
  if (existing.value.status !== "reserved") {
2410
2664
  return yield* ChildReservationConflict.make({
@@ -2416,6 +2670,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2416
2670
  // Single-store latitude: the admitted child must exist here, so a dangling
2417
2671
  // attachment can never enter the recovery view.
2418
2672
  const child = yield* readSubmission(operation, validated.childSubmissionId);
2673
+
2419
2674
  if (Option.isNone(child)) {
2420
2675
  return yield* LedgerError.make({
2421
2676
  operation,
@@ -2428,6 +2683,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2428
2683
  WHERE reservation_id = ${validated.reservationId}
2429
2684
  `.pipe(Effect.mapError(sqlFailure(operation)));
2430
2685
  const updated = yield* readChildReservation(operation, validated.reservationId);
2686
+
2431
2687
  if (Option.isNone(updated)) {
2432
2688
  return yield* corruptionFailure(
2433
2689
  operation,
@@ -2436,10 +2692,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2436
2692
  "An updated child reservation row is missing inside its own transaction.",
2437
2693
  );
2438
2694
  }
2695
+
2439
2696
  return yield* childReservationSnapshotFromRow(operation, updated.value);
2440
2697
  }),
2441
2698
  );
2699
+
2442
2700
  yield* hitFailpoint("ledger:child-attach:after", operation);
2701
+
2443
2702
  return attached;
2444
2703
  });
2445
2704
 
@@ -2447,17 +2706,22 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2447
2706
  "SqliteSubmissionLedger.beginChildBudgetRelease",
2448
2707
  )(function* (request: BeginChildBudgetReleaseRequest) {
2449
2708
  const operation = "ledger begin child budget release";
2709
+
2450
2710
  const validated = yield* Schema.decodeUnknownEffect(
2451
2711
  Schema.toType(BeginChildBudgetReleaseRequest),
2452
2712
  )(request).pipe(Effect.mapError(internalFailure(operation)));
2713
+
2453
2714
  const accountingJson = yield* encodePersistedJsonText(validated.accounting).pipe(
2454
2715
  Effect.mapError(internalFailure(operation)),
2455
2716
  );
2717
+
2456
2718
  yield* hitFailpoint("ledger:child-release-pending:before", operation);
2719
+
2457
2720
  const frozen = yield* inWriteTransaction(
2458
2721
  operation,
2459
2722
  Effect.gen(function* () {
2460
2723
  const existing = yield* readChildReservation(operation, validated.reservationId);
2724
+
2461
2725
  if (Option.isNone(existing)) {
2462
2726
  return yield* LedgerError.make({
2463
2727
  operation,
@@ -2469,6 +2733,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2469
2733
  operation,
2470
2734
  existing.value,
2471
2735
  );
2736
+
2472
2737
  // The accounting decision was already frozen exactly once; an identical replay is a
2473
2738
  // no-op and a divergent decision conflicts (spec §12 join step 6).
2474
2739
  if (
@@ -2477,6 +2742,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2477
2742
  ) {
2478
2743
  return existingSnapshot;
2479
2744
  }
2745
+
2480
2746
  return yield* ChildReservationConflict.make({
2481
2747
  reservationId: validated.reservationId,
2482
2748
  status: existing.value.status,
@@ -2484,6 +2750,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2484
2750
  });
2485
2751
  }
2486
2752
  const now = yield* currentInstant;
2753
+
2487
2754
  yield* sql`
2488
2755
  UPDATE effect_agent_child_reservations
2489
2756
  SET
@@ -2493,6 +2760,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2493
2760
  WHERE reservation_id = ${validated.reservationId}
2494
2761
  `.pipe(Effect.mapError(sqlFailure(operation)));
2495
2762
  const updated = yield* readChildReservation(operation, validated.reservationId);
2763
+
2496
2764
  if (Option.isNone(updated)) {
2497
2765
  return yield* corruptionFailure(
2498
2766
  operation,
@@ -2501,10 +2769,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2501
2769
  "An updated child reservation row is missing inside its own transaction.",
2502
2770
  );
2503
2771
  }
2772
+
2504
2773
  return yield* childReservationSnapshotFromRow(operation, updated.value);
2505
2774
  }),
2506
2775
  );
2776
+
2507
2777
  yield* hitFailpoint("ledger:child-release-pending:after", operation);
2778
+
2508
2779
  return frozen;
2509
2780
  });
2510
2781
 
@@ -2512,14 +2783,18 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2512
2783
  "SqliteSubmissionLedger.releaseChildBudget",
2513
2784
  )(function* (request: ReleaseChildBudgetRequest) {
2514
2785
  const operation = "ledger release child budget";
2786
+
2515
2787
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseChildBudgetRequest))(
2516
2788
  request,
2517
2789
  ).pipe(Effect.mapError(internalFailure(operation)));
2790
+
2518
2791
  yield* hitFailpoint("ledger:child-release:before", operation);
2792
+
2519
2793
  const released = yield* inWriteTransaction(
2520
2794
  operation,
2521
2795
  Effect.gen(function* () {
2522
2796
  const existing = yield* readChildReservation(operation, validated.reservationId);
2797
+
2523
2798
  if (Option.isNone(existing)) {
2524
2799
  return yield* LedgerError.make({
2525
2800
  operation,
@@ -2539,12 +2814,14 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2539
2814
  });
2540
2815
  }
2541
2816
  const now = yield* currentInstant;
2817
+
2542
2818
  yield* sql`
2543
2819
  UPDATE effect_agent_child_reservations
2544
2820
  SET status = 'released', released_at = ${now.iso}
2545
2821
  WHERE reservation_id = ${validated.reservationId}
2546
2822
  `.pipe(Effect.mapError(sqlFailure(operation)));
2547
2823
  const updated = yield* readChildReservation(operation, validated.reservationId);
2824
+
2548
2825
  if (Option.isNone(updated)) {
2549
2826
  return yield* corruptionFailure(
2550
2827
  operation,
@@ -2553,10 +2830,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2553
2830
  "An updated child reservation row is missing inside its own transaction.",
2554
2831
  );
2555
2832
  }
2833
+
2556
2834
  return yield* childReservationSnapshotFromRow(operation, updated.value);
2557
2835
  }),
2558
2836
  );
2837
+
2559
2838
  yield* hitFailpoint("ledger:child-release:after", operation);
2839
+
2560
2840
  return released;
2561
2841
  });
2562
2842
 
@@ -2572,6 +2852,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2572
2852
  LedgerError
2573
2853
  > {
2574
2854
  const operation = "ledger scan nonterminal";
2855
+
2575
2856
  const rows = yield* (
2576
2857
  cursor === undefined
2577
2858
  ? sql<Record<string, unknown>>`
@@ -2596,11 +2877,15 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2596
2877
  LIMIT ${SCAN_PAGE_SIZE}
2597
2878
  `
2598
2879
  ).pipe(Effect.mapError(sqlFailure(operation)));
2880
+
2599
2881
  const decoded = yield* decodeSubmissionRows(operation, "nonterminal_scan", rows);
2882
+
2600
2883
  const snapshots = yield* Effect.forEach(decoded, (row) =>
2601
2884
  decodeSubmissionSnapshot(operation, row),
2602
2885
  );
2886
+
2603
2887
  const last = decoded[decoded.length - 1];
2888
+
2604
2889
  const next: Option.Option<ScanCursor | undefined> =
2605
2890
  last === undefined || decoded.length < SCAN_PAGE_SIZE
2606
2891
  ? Option.none()
@@ -2608,6 +2893,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2608
2893
  threadId: last.thread_id,
2609
2894
  queueSequence: last.queue_sequence,
2610
2895
  });
2896
+
2611
2897
  return [snapshots, next] as const;
2612
2898
  });
2613
2899
 
@@ -2621,9 +2907,11 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2621
2907
  "SqliteSubmissionLedger.loadRecoverySnapshot",
2622
2908
  )(function* (request: RecoverySnapshotRequest) {
2623
2909
  const operation = "ledger load recovery snapshot";
2910
+
2624
2911
  const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RecoverySnapshotRequest))(
2625
2912
  request,
2626
2913
  ).pipe(Effect.mapError(internalFailure(operation)));
2914
+
2627
2915
  return yield* sql
2628
2916
  .withTransaction(
2629
2917
  Effect.gen(function* () {
@@ -2632,6 +2920,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2632
2920
 
2633
2921
  let ownership: OwnershipSnapshot | undefined;
2634
2922
  const ownershipRow = yield* readOwnership(operation, validated.submissionId);
2923
+
2635
2924
  if (Option.isSome(ownershipRow)) {
2636
2925
  ownership = yield* decodeOwnershipSnapshot({
2637
2926
  attemptId: ownershipRow.value.attempt_id,
@@ -2642,6 +2931,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2642
2931
  }
2643
2932
 
2644
2933
  let inputApplied: InputAppliedMarker | undefined;
2934
+
2645
2935
  if (
2646
2936
  submissionRow.input_applied_record_id !== null &&
2647
2937
  submissionRow.input_applied_sequence !== null
@@ -2654,6 +2944,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2654
2944
 
2655
2945
  let reservation: SettlementReservationSnapshot | undefined;
2656
2946
  const reservationRow = yield* readReservation(operation, validated.submissionId);
2947
+
2657
2948
  if (Option.isSome(reservationRow)) {
2658
2949
  const record = yield* decodeRecordEnvelopeText(reservationRow.value.record_json).pipe(
2659
2950
  Effect.mapError((error) =>
@@ -2665,9 +2956,11 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2665
2956
  ),
2666
2957
  ),
2667
2958
  );
2959
+
2668
2960
  const settlementId = yield* Schema.decodeUnknownEffect(
2669
2961
  SettlementReservationSnapshot.fields.settlementId,
2670
2962
  )(reservationRow.value.settlement_id).pipe(Effect.mapError(internalFailure(operation)));
2963
+
2671
2964
  reservation = SettlementReservationSnapshot.make({
2672
2965
  settlementId,
2673
2966
  outcome: reservationRow.value.outcome,
@@ -2679,6 +2972,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2679
2972
 
2680
2973
  let abortIntent: AbortIntent | undefined;
2681
2974
  const abortRow = yield* readAbortIntent(operation, validated.submissionId);
2975
+
2682
2976
  if (Option.isSome(abortRow)) {
2683
2977
  abortIntent = yield* abortIntentFromRow(
2684
2978
  operation,
@@ -2696,11 +2990,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2696
2990
  WHERE joined_host_submission_id = ${validated.submissionId}
2697
2991
  ORDER BY queue_sequence ASC
2698
2992
  `.pipe(Effect.mapError(sqlFailure(operation)));
2993
+
2699
2994
  const joinSubmissions = yield* decodeSubmissionRows(
2700
2995
  operation,
2701
2996
  validated.submissionId,
2702
2997
  joinRows,
2703
2998
  );
2999
+
2704
3000
  const joins = yield* Effect.forEach(joinSubmissions, (row) =>
2705
3001
  decodeJoinSnapshot({
2706
3002
  submissionId: row.submission_id,
@@ -2710,6 +3006,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2710
3006
  );
2711
3007
 
2712
3008
  let hostSubmissionId: RecoverySnapshot["hostSubmissionId"];
3009
+
2713
3010
  if (submissionRow.joined_host_submission_id !== null) {
2714
3011
  hostSubmissionId = yield* decodeSubmissionId(
2715
3012
  submissionRow.joined_host_submission_id,
@@ -2717,6 +3014,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2717
3014
  }
2718
3015
 
2719
3016
  let suspension: SuspensionSnapshot | undefined;
3017
+
2720
3018
  if (submissionRow.suspended_reason_json !== null && submissionRow.suspended_at !== null) {
2721
3019
  const reason = yield* parseStoredJsonText(submissionRow.suspended_reason_json).pipe(
2722
3020
  Effect.mapError((error) =>
@@ -2728,6 +3026,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2728
3026
  ),
2729
3027
  ),
2730
3028
  );
3029
+
2731
3030
  suspension = yield* decodeSuspensionSnapshot({
2732
3031
  reason,
2733
3032
  suspendedAt: submissionRow.suspended_at,
@@ -2744,11 +3043,13 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2744
3043
  }
2745
3044
 
2746
3045
  const decisionRows = yield* readApprovalDecisions(operation, validated.submissionId);
3046
+
2747
3047
  const approvalDecisions = yield* Effect.forEach(decisionRows, (row) =>
2748
3048
  approvalIntentFromRow(operation, row),
2749
3049
  );
2750
3050
 
2751
3051
  const resolutionRows = yield* readUnknownResolutions(operation, validated.submissionId);
3052
+
2752
3053
  const unknownResolutions = yield* Effect.forEach(resolutionRows, (row) =>
2753
3054
  unknownResolutionIntentFromRow(operation, row),
2754
3055
  );
@@ -2762,18 +3063,23 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2762
3063
  WHERE parent_submission_id = ${validated.submissionId}
2763
3064
  ORDER BY parent_tool_call_id ASC
2764
3065
  `.pipe(Effect.mapError(sqlFailure(operation)));
3066
+
2765
3067
  const decodedChildReservations = yield* decodeChildReservationRows(
2766
3068
  operation,
2767
3069
  validated.submissionId,
2768
3070
  childReservationRows,
2769
3071
  );
3072
+
2770
3073
  const childReservations = yield* Effect.forEach(decodedChildReservations, (row) =>
2771
3074
  childReservationSnapshotFromRow(operation, row),
2772
3075
  );
3076
+
2773
3077
  const childAttachments: Array<ChildAttachmentSnapshot> = [];
3078
+
2774
3079
  for (const row of decodedChildReservations) {
2775
3080
  if (row.child_submission_id === null) continue;
2776
3081
  const child = yield* readSubmission(operation, row.child_submission_id);
3082
+
2777
3083
  if (Option.isNone(child)) continue;
2778
3084
  childAttachments.push(
2779
3085
  yield* decodeChildAttachmentSnapshot({
@@ -2788,6 +3094,7 @@ const makeServices = Effect.fn("SqliteSubmissionLedger.makeServices")(function*
2788
3094
  }
2789
3095
 
2790
3096
  let parentLinkage: ParentLinkage | undefined;
3097
+
2791
3098
  if (
2792
3099
  submissionRow.parent_submission_id !== null &&
2793
3100
  submissionRow.parent_tool_call_id !== null