@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.
- package/dist/index.mjs.map +1 -1
- package/dist/testing.mjs.map +1 -1
- package/package.json +1 -49
- package/src/errors.ts +1 -0
- package/src/sqlite-activity-store.ts +53 -0
- package/src/sqlite-journal.ts +52 -0
- package/src/sqlite-ledger.ts +307 -0
- package/src/sqlite-schedule-store.ts +39 -0
- package/src/sqlite-storage-failpoint-testing.ts +1 -0
- package/src/sqlite-subscription-store.ts +129 -0
- package/src/sqlite-thread-store.ts +74 -0
package/src/sqlite-ledger.ts
CHANGED
|
@@ -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
|