@yielded/agent-storage-cloudflare 0.1.0-beta.174 → 0.1.0-beta.176
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/DoMemoryStore.mjs +1 -1
- package/dist/DoMessageDeliveryStore.mjs +1 -1
- package/dist/DoStorageFailpoint.d.mts +3 -1
- package/dist/DoStorageFailpoint.mjs +7 -2
- package/dist/DoStorageFailpoint.mjs.map +1 -1
- package/dist/{DoStorageVersion-NKnQU3eR.d.mts → DoStorageVersion-Mc7CXMdN.d.mts} +2 -2
- package/dist/DoStorageVersion.d.mts +1 -1
- package/dist/DoStorageVersion.mjs +2 -2
- package/dist/DoSubmissionLedger.d.mts +1 -1
- package/dist/DoSubmissionLedger.mjs +194 -98
- package/dist/DoSubmissionLedger.mjs.map +1 -1
- package/dist/{DoThreadStore-Bd4fTns3.d.mts → DoThreadStore-BzYJjm4O.d.mts} +18 -8
- package/dist/{DoThreadStore-DyzOZDXK.mjs → DoThreadStore-DG0h5J6u.mjs} +25 -19
- package/dist/DoThreadStore-DG0h5J6u.mjs.map +1 -0
- package/dist/DoThreadStore.d.mts +2 -2
- package/dist/DoThreadStore.mjs +3 -2
- package/dist/PortProtocol.d.mts +7201 -7201
- package/dist/PortRouting.mjs +21 -11
- package/dist/PortRouting.mjs.map +1 -1
- package/dist/{do-journal-ZMAzzENZ.mjs → do-journal-ySC3eXNv.mjs} +144 -45
- package/dist/do-journal-ySC3eXNv.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{migrations-C3N9GN6A.mjs → migrations-CAO_akhD.mjs} +45 -22
- package/dist/migrations-CAO_akhD.mjs.map +1 -0
- package/dist/{owned-state-DcFh0YNo.mjs → owned-state-BgGOA2Ak.mjs} +4 -3
- package/dist/owned-state-BgGOA2Ak.mjs.map +1 -0
- package/package.json +1 -1
- package/src/DoStorageFailpoint.ts +4 -0
- package/src/DoSubmissionLedger.ts +430 -228
- package/src/DoThreadStore.ts +25 -44
- package/src/PortRouting.ts +57 -31
- package/src/internal/do-journal.ts +318 -70
- package/src/internal/migrations.ts +52 -10
- package/src/internal/owned-state.ts +12 -5
- package/src/internal/prompt-checkpoint.ts +6 -3
- package/src/internal/storage-span.ts +14 -10
- package/dist/DoThreadStore-DyzOZDXK.mjs.map +0 -1
- package/dist/do-journal-ZMAzzENZ.mjs.map +0 -1
- package/dist/migrations-C3N9GN6A.mjs.map +0 -1
- package/dist/owned-state-DcFh0YNo.mjs.map +0 -1
|
@@ -110,6 +110,8 @@ import {
|
|
|
110
110
|
UnknownResolutionKind,
|
|
111
111
|
unknownResolutionKind,
|
|
112
112
|
unknownResolutionMatchesResult,
|
|
113
|
+
submissionInputRecordId,
|
|
114
|
+
submissionInputBatchId,
|
|
113
115
|
submissionAbortRecordId,
|
|
114
116
|
submissionSettlementRecordId,
|
|
115
117
|
submissionSettlementBatchId,
|
|
@@ -345,8 +347,8 @@ const decodeAbortIntent = Schema.decodeUnknownEffect(AbortIntent);
|
|
|
345
347
|
const decodeInputAppliedMarker = Schema.decodeUnknownEffect(InputAppliedMarker);
|
|
346
348
|
|
|
347
349
|
const decodeSubmissionSnapshotType = (input: typeof SubmissionSnapshot.Type) =>
|
|
348
|
-
|
|
349
|
-
Effect.
|
|
350
|
+
SubmissionSnapshot.makeEffect(input).pipe(
|
|
351
|
+
Effect.mapError((issue) => new Schema.SchemaError(issue)),
|
|
350
352
|
);
|
|
351
353
|
|
|
352
354
|
const decodeSubmissionId = Schema.decodeUnknownEffect(SubmissionSnapshot.fields.submissionId);
|
|
@@ -366,6 +368,46 @@ const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(
|
|
|
366
368
|
const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
|
|
367
369
|
const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
|
|
368
370
|
const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
|
|
371
|
+
const decodeAdmissionRequestType = Schema.decodeEffect(Schema.toType(AdmissionRequest));
|
|
372
|
+
const decodeSubmissionLookupType = Schema.decodeEffect(Schema.toType(SubmissionLookup));
|
|
373
|
+
const decodeClaimRequestType = Schema.decodeEffect(Schema.toType(ClaimRequest));
|
|
374
|
+
|
|
375
|
+
const decodeMarkInputAppliedRequestType = Schema.decodeEffect(
|
|
376
|
+
Schema.toType(MarkInputAppliedRequest),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const decodeSettlementFinalizationType = Schema.decodeEffect(Schema.toType(SettlementFinalization));
|
|
380
|
+
const decodeClaimJoiningRequestType = Schema.decodeEffect(Schema.toType(ClaimJoiningRequest));
|
|
381
|
+
|
|
382
|
+
const decodeAbortIntentRequestType = Schema.decodeEffect(Schema.toType(AbortIntentRequest));
|
|
383
|
+
|
|
384
|
+
const decodeRecoverySnapshotRequestType = Schema.decodeEffect(
|
|
385
|
+
Schema.toType(RecoverySnapshotRequest),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
const SubmissionRows = Schema.Array(SubmissionRow);
|
|
389
|
+
const ChildReservationRows = Schema.Array(ChildReservationRow);
|
|
390
|
+
const MaxQueueSequenceRows = Schema.Array(MaxQueueSequenceRow);
|
|
391
|
+
const SubmissionWorkItems = Schema.Array(SubmissionWorkItem);
|
|
392
|
+
const decodeSubmissionWorkItems = Schema.decodeEffect(SubmissionWorkItems);
|
|
393
|
+
const equivalentWorkerOrigin = Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin));
|
|
394
|
+
|
|
395
|
+
const decodeFirstWorkerAdmissions = Schema.decodeUnknownEffect(
|
|
396
|
+
Schema.Array(
|
|
397
|
+
Schema.Struct({
|
|
398
|
+
worker_admission_json: Schema.NullOr(BoundedStoredText),
|
|
399
|
+
}),
|
|
400
|
+
),
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const CanonicalSettlementRows = Schema.Array(
|
|
404
|
+
Schema.Struct({
|
|
405
|
+
batch_id: Schema.fromJsonString(BoundedIdentifier),
|
|
406
|
+
record_id: Schema.fromJsonString(BoundedIdentifier),
|
|
407
|
+
record_json: BoundedStoredText,
|
|
408
|
+
}),
|
|
409
|
+
);
|
|
410
|
+
|
|
369
411
|
const isDoStorageError = Schema.is(DoStorageError);
|
|
370
412
|
|
|
371
413
|
/** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
|
|
@@ -606,7 +648,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
606
648
|
}));
|
|
607
649
|
|
|
608
650
|
const decodeSubmissionRows = (operation: string, rowKey: string, rows: unknown) =>
|
|
609
|
-
decodeRows(
|
|
651
|
+
decodeRows(SubmissionRows, "effect_agent_submissions", rowKey, rows).pipe(
|
|
610
652
|
Effect.mapError(internalFailure(operation)),
|
|
611
653
|
);
|
|
612
654
|
|
|
@@ -721,13 +763,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
721
763
|
AND record_id = ${JSON.stringify(recordId)}`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
722
764
|
|
|
723
765
|
const decoded = yield* decodeRows(
|
|
724
|
-
|
|
725
|
-
Schema.Struct({
|
|
726
|
-
batch_id: Schema.fromJsonString(BoundedIdentifier),
|
|
727
|
-
record_id: Schema.fromJsonString(BoundedIdentifier),
|
|
728
|
-
record_json: BoundedStoredText,
|
|
729
|
-
}),
|
|
730
|
-
),
|
|
766
|
+
CanonicalSettlementRows,
|
|
731
767
|
"effect_agent_canonical_records",
|
|
732
768
|
recordId,
|
|
733
769
|
found,
|
|
@@ -810,12 +846,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
810
846
|
});
|
|
811
847
|
|
|
812
848
|
const decodeChildReservationRows = (operation: string, rowKey: string, rows: unknown) =>
|
|
813
|
-
decodeRows(
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
rowKey,
|
|
817
|
-
rows,
|
|
818
|
-
).pipe(Effect.mapError(internalFailure(operation)));
|
|
849
|
+
decodeRows(ChildReservationRows, "effect_agent_child_reservations", rowKey, rows).pipe(
|
|
850
|
+
Effect.mapError(internalFailure(operation)),
|
|
851
|
+
);
|
|
819
852
|
|
|
820
853
|
const readChildReservation = Effect.fnUntraced(function* (
|
|
821
854
|
operation: string,
|
|
@@ -1036,10 +1069,11 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1036
1069
|
|
|
1037
1070
|
const admit: SubmissionLedger["Service"]["admit"] = Effect.fnUntraced(function* (
|
|
1038
1071
|
request: AdmissionRequest,
|
|
1072
|
+
options?: { readonly readyIfInitialized?: boolean },
|
|
1039
1073
|
) {
|
|
1040
1074
|
const operation = "ledger admit";
|
|
1041
1075
|
|
|
1042
|
-
const validated = yield*
|
|
1076
|
+
const validated = yield* decodeAdmissionRequestType(request).pipe(
|
|
1043
1077
|
Effect.mapError(internalFailure(operation)),
|
|
1044
1078
|
);
|
|
1045
1079
|
|
|
@@ -1114,9 +1148,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1114
1148
|
Effect.gen(function* () {
|
|
1115
1149
|
const keyRowKey = `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`;
|
|
1116
1150
|
|
|
1117
|
-
const
|
|
1118
|
-
|
|
1119
|
-
const existing = yield* decodeSubmissionRows(operation, keyRowKey, existingRows);
|
|
1151
|
+
const existing = yield* byKey(validated, operation);
|
|
1120
1152
|
|
|
1121
1153
|
if (existing.length > 1) {
|
|
1122
1154
|
return yield* corruptionFailure(
|
|
@@ -1220,13 +1252,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1220
1252
|
WHERE thread_id=${validated.threadId} ORDER BY queue_sequence LIMIT 1
|
|
1221
1253
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1222
1254
|
|
|
1223
|
-
const first = yield*
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
worker_admission_json: Schema.NullOr(BoundedStoredText),
|
|
1227
|
-
}),
|
|
1228
|
-
),
|
|
1229
|
-
)(firstRows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1255
|
+
const first = yield* decodeFirstWorkerAdmissions(firstRows).pipe(
|
|
1256
|
+
Effect.mapError(internalFailure(operation)),
|
|
1257
|
+
);
|
|
1230
1258
|
|
|
1231
1259
|
if (first[0] !== undefined) {
|
|
1232
1260
|
const previous =
|
|
@@ -1236,12 +1264,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1236
1264
|
first[0].worker_admission_json,
|
|
1237
1265
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1238
1266
|
|
|
1239
|
-
if (
|
|
1240
|
-
!Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin))(
|
|
1241
|
-
previous?.origin,
|
|
1242
|
-
validated.workerAdmission?.origin,
|
|
1243
|
-
)
|
|
1244
|
-
)
|
|
1267
|
+
if (!equivalentWorkerOrigin(previous?.origin, validated.workerAdmission?.origin))
|
|
1245
1268
|
return yield* AdmissionPolicyError.make({
|
|
1246
1269
|
reason: "refused",
|
|
1247
1270
|
code: "worker-origin-conflict",
|
|
@@ -1269,7 +1292,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1269
1292
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1270
1293
|
|
|
1271
1294
|
const decodedMax = yield* decodeRows(
|
|
1272
|
-
|
|
1295
|
+
MaxQueueSequenceRows,
|
|
1273
1296
|
"effect_agent_submissions",
|
|
1274
1297
|
validated.threadId,
|
|
1275
1298
|
maxRows,
|
|
@@ -1281,6 +1304,17 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1281
1304
|
|
|
1282
1305
|
const now = yield* currentInstant;
|
|
1283
1306
|
|
|
1307
|
+
// The first canonical record is ThreadCreated. This is the same positive-tail proof
|
|
1308
|
+
// used by ensureThreadCreated, captured under this admission's owner transaction.
|
|
1309
|
+
const ready =
|
|
1310
|
+
options?.readyIfInitialized === true &&
|
|
1311
|
+
validated.workerAdmission === undefined &&
|
|
1312
|
+
(yield* journal
|
|
1313
|
+
.getThread(validated.threadId)
|
|
1314
|
+
.pipe(Effect.mapError(internalFailure(operation)))).some(
|
|
1315
|
+
(thread) => thread.tail_sequence > 0,
|
|
1316
|
+
);
|
|
1317
|
+
|
|
1284
1318
|
yield* sql`
|
|
1285
1319
|
INSERT INTO effect_agent_submissions (
|
|
1286
1320
|
submission_id,
|
|
@@ -1296,6 +1330,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1296
1330
|
receipt_id,
|
|
1297
1331
|
state,
|
|
1298
1332
|
created_at,
|
|
1333
|
+
ready_at,
|
|
1299
1334
|
parent_submission_id,
|
|
1300
1335
|
parent_tool_call_id,
|
|
1301
1336
|
admission_group,
|
|
@@ -1314,8 +1349,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1314
1349
|
${inputJson},
|
|
1315
1350
|
${validated.inputDigest},
|
|
1316
1351
|
${mintedReceiptId},
|
|
1317
|
-
|
|
1352
|
+
${ready ? "ready" : "admitted"},
|
|
1318
1353
|
${now.iso},
|
|
1354
|
+
${ready ? now.iso : null},
|
|
1319
1355
|
${validated.parentLinkage?.parentSubmissionId ?? null},
|
|
1320
1356
|
${validated.parentLinkage?.parentToolCallId ?? null},
|
|
1321
1357
|
${validated.admissionGroup ?? null},
|
|
@@ -1341,6 +1377,14 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1341
1377
|
|
|
1342
1378
|
// The INSERT (never replay or hydration) proves these new submission-owned sets
|
|
1343
1379
|
// empty. Gated writes update them; rollback/invalidation discards that proof.
|
|
1380
|
+
// MAX(queue_sequence) and this insert share the transaction: no later row exists.
|
|
1381
|
+
// Every matching admission invalidates this complete empty suffix before readiness.
|
|
1382
|
+
submissionViews.seedMatching(
|
|
1383
|
+
JSON.stringify(["joining-suffix", validated.threadId, queueSequence]),
|
|
1384
|
+
(row) => row.thread_id === validated.threadId && row.queue_sequence > queueSequence,
|
|
1385
|
+
[],
|
|
1386
|
+
0,
|
|
1387
|
+
);
|
|
1344
1388
|
rows.aborts.seed("submission_id", mintedSubmissionId, []);
|
|
1345
1389
|
rows.submissions.seed("joined_host_submission_id", mintedSubmissionId, []);
|
|
1346
1390
|
rows.approvals.seed("submission_id", mintedSubmissionId, []);
|
|
@@ -1350,13 +1394,21 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1350
1394
|
|
|
1351
1395
|
yield* recordProgress("submission", operation);
|
|
1352
1396
|
|
|
1353
|
-
|
|
1397
|
+
const admitted = yield* decodeAdmissionResult({
|
|
1354
1398
|
submissionId: mintedSubmissionId,
|
|
1355
1399
|
receiptId: mintedReceiptId,
|
|
1356
1400
|
queueSequence,
|
|
1357
|
-
state: "admitted",
|
|
1401
|
+
state: ready ? "ready" : "admitted",
|
|
1358
1402
|
replayed: false,
|
|
1359
1403
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1404
|
+
|
|
1405
|
+
if (ready && lifecycle !== undefined)
|
|
1406
|
+
yield* retainLifecycle(yield* requireSubmission(operation, mintedSubmissionId), {
|
|
1407
|
+
_tag: "SubmissionReady",
|
|
1408
|
+
submissionId: admitted.submissionId,
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
return admitted;
|
|
1360
1412
|
}),
|
|
1361
1413
|
);
|
|
1362
1414
|
|
|
@@ -1466,7 +1518,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1466
1518
|
) {
|
|
1467
1519
|
const operation = "ledger lookup";
|
|
1468
1520
|
|
|
1469
|
-
const validated = yield*
|
|
1521
|
+
const validated = yield* decodeSubmissionLookupType(request).pipe(
|
|
1470
1522
|
Effect.mapError(internalFailure(operation)),
|
|
1471
1523
|
);
|
|
1472
1524
|
|
|
@@ -1478,13 +1530,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1478
1530
|
return Option.some(yield* decodeSubmissionSnapshot(operation, row.value));
|
|
1479
1531
|
}
|
|
1480
1532
|
|
|
1481
|
-
const
|
|
1482
|
-
|
|
1483
|
-
const decoded = yield* decodeSubmissionRows(
|
|
1484
|
-
operation,
|
|
1485
|
-
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1486
|
-
foundRows,
|
|
1487
|
-
);
|
|
1533
|
+
const decoded = yield* byKey(validated, operation);
|
|
1488
1534
|
|
|
1489
1535
|
if (decoded.length > 1) {
|
|
1490
1536
|
return yield* corruptionFailure(
|
|
@@ -1511,13 +1557,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1511
1557
|
request,
|
|
1512
1558
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1513
1559
|
|
|
1514
|
-
const
|
|
1515
|
-
|
|
1516
|
-
const decoded = yield* decodeSubmissionRows(
|
|
1517
|
-
operation,
|
|
1518
|
-
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1519
|
-
foundRows,
|
|
1520
|
-
);
|
|
1560
|
+
const decoded = yield* byKey(validated, operation);
|
|
1521
1561
|
|
|
1522
1562
|
if (decoded.length > 1) {
|
|
1523
1563
|
return yield* corruptionFailure(
|
|
@@ -1540,7 +1580,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1540
1580
|
)(function* (request: ClaimRequest) {
|
|
1541
1581
|
const operation = "ledger select head";
|
|
1542
1582
|
|
|
1543
|
-
const validated = yield*
|
|
1583
|
+
const validated = yield* decodeClaimRequestType(request).pipe(
|
|
1544
1584
|
Effect.mapError(internalFailure(operation)),
|
|
1545
1585
|
);
|
|
1546
1586
|
|
|
@@ -1552,7 +1592,28 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1552
1592
|
Effect.gen(function* () {
|
|
1553
1593
|
if (owner.hasOwner(validated.threadId)) return Option.none<Claim>();
|
|
1554
1594
|
|
|
1555
|
-
const
|
|
1595
|
+
const active = yield* activeNonterminal;
|
|
1596
|
+
const heads: Array<SubmissionRow> = [];
|
|
1597
|
+
|
|
1598
|
+
const limit =
|
|
1599
|
+
validated.handoff === undefined ? 1 : validated.handoff.deferredSubmissionIds.length + 1;
|
|
1600
|
+
|
|
1601
|
+
if (active.length <= 128) {
|
|
1602
|
+
const candidates = active
|
|
1603
|
+
.filter((row) => row.thread_id === validated.threadId)
|
|
1604
|
+
.sort((left, right) => left.queue_sequence - right.queue_sequence);
|
|
1605
|
+
|
|
1606
|
+
for (const candidate of candidates) {
|
|
1607
|
+
if (
|
|
1608
|
+
candidate.state === "unknown" &&
|
|
1609
|
+
Option.isNone(yield* readAbortIntent(operation, candidate.submission_id))
|
|
1610
|
+
)
|
|
1611
|
+
continue;
|
|
1612
|
+
heads.push(yield* requireSubmission(operation, candidate.submission_id));
|
|
1613
|
+
if (heads.length === limit) break;
|
|
1614
|
+
}
|
|
1615
|
+
} else {
|
|
1616
|
+
const headRows = yield* sql<Record<string, unknown>>`
|
|
1556
1617
|
SELECT ${submissionColumns}
|
|
1557
1618
|
FROM effect_agent_submissions
|
|
1558
1619
|
WHERE thread_id = ${validated.threadId}
|
|
@@ -1567,7 +1628,8 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1567
1628
|
LIMIT ${validated.handoff === undefined ? 1 : validated.handoff.deferredSubmissionIds.length + 1}
|
|
1568
1629
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1569
1630
|
|
|
1570
|
-
|
|
1631
|
+
heads.push(...(yield* decodeSubmissionRows(operation, validated.threadId, headRows)));
|
|
1632
|
+
}
|
|
1571
1633
|
|
|
1572
1634
|
if (heads.length === 0) return Option.none<Claim>();
|
|
1573
1635
|
let head = heads[0];
|
|
@@ -1632,53 +1694,159 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1632
1694
|
return claimed;
|
|
1633
1695
|
});
|
|
1634
1696
|
|
|
1697
|
+
const markInputAppliedInTransaction = Effect.fnUntraced(function* (
|
|
1698
|
+
validated: MarkInputAppliedRequest,
|
|
1699
|
+
) {
|
|
1700
|
+
const operation = "ledger mark input applied";
|
|
1701
|
+
|
|
1702
|
+
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1703
|
+
|
|
1704
|
+
yield* requireOwner(operation, submission);
|
|
1705
|
+
if (submission.input_applied_record_id !== null) {
|
|
1706
|
+
if (
|
|
1707
|
+
submission.input_applied_record_id === validated.recordId &&
|
|
1708
|
+
submission.input_applied_sequence === validated.sequence
|
|
1709
|
+
) {
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
return yield* corruptionFailure(
|
|
1714
|
+
operation,
|
|
1715
|
+
"effect_agent_submissions",
|
|
1716
|
+
validated.submissionId,
|
|
1717
|
+
"A different canonical input marker is already recorded for this Submission.",
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
yield* sql`
|
|
1721
|
+
UPDATE effect_agent_submissions
|
|
1722
|
+
SET
|
|
1723
|
+
input_applied_record_id = ${validated.recordId},
|
|
1724
|
+
input_applied_sequence = ${validated.sequence},
|
|
1725
|
+
state = CASE
|
|
1726
|
+
WHEN state IN ('admitted', 'ready') THEN 'input-applied'
|
|
1727
|
+
ELSE state
|
|
1728
|
+
END
|
|
1729
|
+
WHERE submission_id = ${validated.submissionId}
|
|
1730
|
+
RETURNING *, ${definitionJson(sql)} AS agent_digests_json`.pipe(
|
|
1731
|
+
rows.submissions.write,
|
|
1732
|
+
Effect.mapError(internalFailure(operation)),
|
|
1733
|
+
);
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1635
1736
|
const markInputApplied: SubmissionLedger["Service"]["markInputApplied"] = Effect.fnUntraced(
|
|
1636
1737
|
function* (request: MarkInputAppliedRequest) {
|
|
1637
1738
|
const operation = "ledger mark input applied";
|
|
1638
1739
|
|
|
1639
|
-
const validated = yield*
|
|
1640
|
-
|
|
1641
|
-
)
|
|
1740
|
+
const validated = yield* decodeMarkInputAppliedRequestType(request).pipe(
|
|
1741
|
+
Effect.mapError(internalFailure(operation)),
|
|
1742
|
+
);
|
|
1642
1743
|
|
|
1643
1744
|
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1644
|
-
yield* inWriteTransaction(
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1745
|
+
yield* inWriteTransaction(operation, markInputAppliedInTransaction(validated));
|
|
1746
|
+
yield* hitFailpoint("ledger:mark-input-applied:after", operation);
|
|
1747
|
+
},
|
|
1748
|
+
);
|
|
1648
1749
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
submission.input_applied_record_id === validated.recordId &&
|
|
1653
|
-
submission.input_applied_sequence === validated.sequence
|
|
1654
|
-
) {
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1750
|
+
const appendInput: NonNullable<SubmissionLedger["Service"]["appendInput"]> = Effect.fnUntraced(
|
|
1751
|
+
function* (submissionId, request) {
|
|
1752
|
+
const operation = "append original input and context";
|
|
1657
1753
|
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1754
|
+
const prepared = yield* prepareCanonicalAppend(request).pipe(
|
|
1755
|
+
Effect.provideService(Crypto.Crypto, crypto),
|
|
1756
|
+
);
|
|
1757
|
+
|
|
1758
|
+
const record = prepared.request.batch.records[0];
|
|
1759
|
+
|
|
1760
|
+
const context = prepared.request.executions
|
|
1761
|
+
?.flatMap((update) => update.facts ?? [])
|
|
1762
|
+
.find(({ payload }) => payload._tag === "RunContextRecorded");
|
|
1763
|
+
|
|
1764
|
+
if (
|
|
1765
|
+
prepared.request.executions?.length !== 1 ||
|
|
1766
|
+
prepared.request.executions[0]?.previousRevision !== 0 ||
|
|
1767
|
+
prepared.request.batch.records.length !== 1 ||
|
|
1768
|
+
record?.payload._tag !== "UserInputRecorded" ||
|
|
1769
|
+
record.payload.kind !== "user" ||
|
|
1770
|
+
record.payload.submissionId !== submissionId ||
|
|
1771
|
+
record.payload.runId !== runIdForSubmission(submissionId) ||
|
|
1772
|
+
record.recordId !== submissionInputRecordId(submissionId) ||
|
|
1773
|
+
prepared.request.batch.batchId !== submissionInputBatchId(submissionId) ||
|
|
1774
|
+
(context?.payload._tag === "RunContextRecorded" &&
|
|
1775
|
+
context.payload.historyThrough !== prepared.request.expectedTailSequence)
|
|
1776
|
+
)
|
|
1777
|
+
return yield* ThreadStoreError.make({
|
|
1778
|
+
operation,
|
|
1779
|
+
message: "Original input publication has a different context boundary",
|
|
1780
|
+
});
|
|
1781
|
+
|
|
1782
|
+
const mapError = (cause: { readonly message: string }) =>
|
|
1783
|
+
ThreadStoreError.make({ operation, message: cause.message, cause });
|
|
1784
|
+
|
|
1785
|
+
yield* journal.prepareAppend(prepared.raw).pipe(Effect.mapError(mapError));
|
|
1786
|
+
|
|
1787
|
+
return yield* owner.withProgress(
|
|
1788
|
+
prepared.request.threadId,
|
|
1789
|
+
owner.withMutation(
|
|
1790
|
+
Effect.gen(function* () {
|
|
1791
|
+
yield* hitFailpoint("append:before", operation);
|
|
1792
|
+
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1793
|
+
|
|
1794
|
+
const result = yield* journal
|
|
1795
|
+
.withWriteTransaction(operation)(
|
|
1796
|
+
Effect.gen(function* () {
|
|
1797
|
+
const submission = yield* requireSubmission(operation, submissionId);
|
|
1798
|
+
|
|
1799
|
+
yield* requireOwner(operation, submission);
|
|
1800
|
+
if (submission.thread_id !== prepared.request.threadId)
|
|
1801
|
+
return yield* LedgerError.make({
|
|
1802
|
+
operation,
|
|
1803
|
+
message: "Original input belongs to another Thread",
|
|
1804
|
+
});
|
|
1805
|
+
const result = yield* journal.appendPrepared(prepared.raw);
|
|
1806
|
+
|
|
1807
|
+
yield* markInputAppliedInTransaction(
|
|
1808
|
+
MarkInputAppliedRequest.make({
|
|
1809
|
+
submissionId,
|
|
1810
|
+
recordId: record.recordId,
|
|
1811
|
+
sequence: result.firstSequence,
|
|
1812
|
+
}),
|
|
1813
|
+
);
|
|
1814
|
+
|
|
1815
|
+
return result;
|
|
1816
|
+
}),
|
|
1817
|
+
)
|
|
1818
|
+
.pipe(
|
|
1819
|
+
Effect.mapError((error) =>
|
|
1820
|
+
error._tag === "DoAppendConflict"
|
|
1821
|
+
? AppendConflict.make({
|
|
1822
|
+
threadId: prepared.request.threadId,
|
|
1823
|
+
batchId: prepared.request.batch.batchId,
|
|
1824
|
+
reason: error.reason,
|
|
1825
|
+
...(error.actualTailSequence === undefined
|
|
1826
|
+
? {}
|
|
1827
|
+
: { actualTailSequence: error.actualTailSequence }),
|
|
1828
|
+
})
|
|
1829
|
+
: error._tag === "OwnershipLost"
|
|
1830
|
+
? error
|
|
1831
|
+
: mapError(error),
|
|
1832
|
+
),
|
|
1833
|
+
);
|
|
1834
|
+
|
|
1835
|
+
yield* hitFailpoint("ledger:mark-input-applied:after", operation);
|
|
1836
|
+
yield* hitFailpoint("append:after", operation);
|
|
1837
|
+
|
|
1838
|
+
return result;
|
|
1839
|
+
}).pipe(
|
|
1840
|
+
Effect.mapError((error) =>
|
|
1841
|
+
error._tag === "OwnershipLost" ||
|
|
1842
|
+
error._tag === "ThreadStoreError" ||
|
|
1843
|
+
error._tag === "AppendConflict"
|
|
1844
|
+
? error
|
|
1845
|
+
: mapError(error),
|
|
1846
|
+
),
|
|
1847
|
+
),
|
|
1848
|
+
),
|
|
1680
1849
|
);
|
|
1681
|
-
yield* hitFailpoint("ledger:mark-input-applied:after", operation);
|
|
1682
1850
|
},
|
|
1683
1851
|
);
|
|
1684
1852
|
|
|
@@ -1897,9 +2065,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1897
2065
|
)(function* (request: SettlementFinalization) {
|
|
1898
2066
|
const operation = "ledger finalize settlement";
|
|
1899
2067
|
|
|
1900
|
-
const validated = yield*
|
|
1901
|
-
|
|
1902
|
-
)
|
|
2068
|
+
const validated = yield* decodeSettlementFinalizationType(request).pipe(
|
|
2069
|
+
Effect.mapError(internalFailure(operation)),
|
|
2070
|
+
);
|
|
1903
2071
|
|
|
1904
2072
|
yield* hitFailpoint("ledger:finalize-settlement:before", operation);
|
|
1905
2073
|
|
|
@@ -2211,14 +2379,13 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
2211
2379
|
) {
|
|
2212
2380
|
const operation = "ledger claim joining";
|
|
2213
2381
|
|
|
2214
|
-
const validated = yield*
|
|
2382
|
+
const validated = yield* decodeClaimJoiningRequestType(request).pipe(
|
|
2215
2383
|
Effect.mapError(internalFailure(operation)),
|
|
2216
2384
|
);
|
|
2217
2385
|
|
|
2218
2386
|
yield* hitFailpoint("ledger:claim-joining:before", operation);
|
|
2219
2387
|
|
|
2220
|
-
const claims = yield*
|
|
2221
|
-
operation,
|
|
2388
|
+
const claims = yield* owner.withMutation(
|
|
2222
2389
|
Effect.gen(function* () {
|
|
2223
2390
|
const host = yield* requireSubmission(operation, validated.hostSubmissionId);
|
|
2224
2391
|
|
|
@@ -2238,68 +2405,92 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
2238
2405
|
|
|
2239
2406
|
if (stopped.length > 0) return [];
|
|
2240
2407
|
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2408
|
+
// Retain only an empty complete suffix. Any later admission invalidates this view;
|
|
2409
|
+
// nonempty rows keep their SQL ordering, and rollback discards the empty proof.
|
|
2410
|
+
const later = yield* submissionViews
|
|
2411
|
+
.matching(
|
|
2412
|
+
JSON.stringify(["joining-suffix", validated.threadId, host.queue_sequence]),
|
|
2413
|
+
(row) =>
|
|
2414
|
+
row.thread_id === validated.threadId && row.queue_sequence > host.queue_sequence,
|
|
2415
|
+
sql`
|
|
2416
|
+
SELECT ${submissionColumns}
|
|
2417
|
+
FROM effect_agent_submissions
|
|
2418
|
+
WHERE thread_id = ${validated.threadId}
|
|
2419
|
+
AND queue_sequence > ${host.queue_sequence}
|
|
2420
|
+
ORDER BY queue_sequence ASC
|
|
2421
|
+
`,
|
|
2422
|
+
0,
|
|
2423
|
+
)
|
|
2424
|
+
.pipe(
|
|
2425
|
+
Effect.mapError((error) =>
|
|
2426
|
+
error._tag === "SqlError"
|
|
2427
|
+
? sqlFailure(operation)(error)
|
|
2428
|
+
: internalFailure(operation)(error),
|
|
2429
|
+
),
|
|
2430
|
+
);
|
|
2431
|
+
|
|
2432
|
+
if (later.length === 0) return [];
|
|
2433
|
+
|
|
2434
|
+
// The reader gate stays held through promotion. Nonempty candidates retain
|
|
2435
|
+
// their native transaction, live-owner checks and contiguous-prefix rules.
|
|
2436
|
+
return yield* inWriteTransaction(
|
|
2437
|
+
operation,
|
|
2438
|
+
Effect.gen(function* () {
|
|
2439
|
+
const claimed: Array<JoiningClaim> = [];
|
|
2440
|
+
|
|
2441
|
+
for (const row of later) {
|
|
2442
|
+
if (claimed.length >= validated.maxCount) break;
|
|
2443
|
+
// Rows already claimed by THIS host extend its contiguous prefix and are skipped;
|
|
2444
|
+
// the coordinator re-delivers already-joined input through the coverage rule.
|
|
2445
|
+
if (
|
|
2446
|
+
(row.state === "joining" || row.state === "joined") &&
|
|
2447
|
+
row.joined_host_submission_id === validated.hostSubmissionId
|
|
2448
|
+
) {
|
|
2449
|
+
continue;
|
|
2450
|
+
}
|
|
2451
|
+
// P7 §7(c): an aborted-settled row is a CLOSED obligation, not a gap — recovery
|
|
2452
|
+
// settles aborted never-claimed queued work immediately, and settlement order of
|
|
2453
|
+
// never-run work is not execution order (DUR-004 bounds execution).
|
|
2454
|
+
if (row.state === "settled" && row.settled_outcome === "aborted") continue;
|
|
2455
|
+
// Any other non-ready row — an admitted-not-ready gap in particular — breaks the
|
|
2456
|
+
// contiguous ready prefix (plan §2.5); later ready work stays queued (DUR-004).
|
|
2457
|
+
if (row.state !== "ready") break;
|
|
2458
|
+
const canonical = yield* readCanonicalSettlement(operation, row);
|
|
2459
|
+
|
|
2460
|
+
if (canonical?.settlement.outcome === "aborted") continue;
|
|
2461
|
+
if (canonical !== undefined) break;
|
|
2462
|
+
yield* sql`
|
|
2274
2463
|
UPDATE effect_agent_submissions
|
|
2275
2464
|
SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
|
|
2276
2465
|
WHERE submission_id = ${row.submission_id}
|
|
2277
2466
|
RETURNING *, ${definitionJson(sql)} AS agent_digests_json`.pipe(
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2467
|
+
rows.submissions.write,
|
|
2468
|
+
Effect.mapError(internalFailure(operation)),
|
|
2469
|
+
);
|
|
2281
2470
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2471
|
+
const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
|
|
2472
|
+
Effect.mapError((error) =>
|
|
2473
|
+
corruptionFailure(
|
|
2474
|
+
operation,
|
|
2475
|
+
"effect_agent_submissions",
|
|
2476
|
+
row.submission_id,
|
|
2477
|
+
error.message,
|
|
2478
|
+
),
|
|
2479
|
+
),
|
|
2480
|
+
);
|
|
2292
2481
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2482
|
+
claimed.push(
|
|
2483
|
+
yield* decodeJoiningClaim({
|
|
2484
|
+
submissionId: row.submission_id,
|
|
2485
|
+
queueSequence: row.queue_sequence,
|
|
2486
|
+
inputPayload,
|
|
2487
|
+
}).pipe(Effect.mapError(internalFailure(operation))),
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2301
2490
|
|
|
2302
|
-
|
|
2491
|
+
return claimed;
|
|
2492
|
+
}),
|
|
2493
|
+
);
|
|
2303
2494
|
}),
|
|
2304
2495
|
);
|
|
2305
2496
|
|
|
@@ -3451,7 +3642,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3451
3642
|
).pipe(Effect.mapError(sqlFailure(operation)));
|
|
3452
3643
|
|
|
3453
3644
|
const decoded = yield* decodeRows(
|
|
3454
|
-
|
|
3645
|
+
SubmissionWorkItems,
|
|
3455
3646
|
"effect_agent_submissions",
|
|
3456
3647
|
"nonterminal_scan",
|
|
3457
3648
|
rows,
|
|
@@ -3471,59 +3662,74 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3471
3662
|
return [decoded, next] as const;
|
|
3472
3663
|
});
|
|
3473
3664
|
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
),
|
|
3487
|
-
"ledger scan nonterminal",
|
|
3488
|
-
);
|
|
3665
|
+
// Both discovery and head selection use this same complete, owner-invalidated bounded view.
|
|
3666
|
+
const activeNonterminal = cached(
|
|
3667
|
+
workRows.matching(
|
|
3668
|
+
"nonterminal",
|
|
3669
|
+
(row) => row.state !== "settled",
|
|
3670
|
+
sql`SELECT submission_id, thread_id, queue_sequence, principal, idempotency_key,
|
|
3671
|
+
deployment_id, receipt_id, state
|
|
3672
|
+
FROM effect_agent_submissions WHERE state <> 'settled' LIMIT 129`,
|
|
3673
|
+
128,
|
|
3674
|
+
),
|
|
3675
|
+
"ledger scan nonterminal",
|
|
3676
|
+
);
|
|
3489
3677
|
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
);
|
|
3678
|
+
const boundedNonterminal = state.read(
|
|
3679
|
+
Effect.gen(function* () {
|
|
3680
|
+
// Cache only a complete bounded active set. Large hosts keep the paged scan.
|
|
3681
|
+
const active = yield* activeNonterminal;
|
|
3495
3682
|
|
|
3496
|
-
|
|
3497
|
-
left.thread_id < right.thread_id
|
|
3498
|
-
? -1
|
|
3499
|
-
: left.thread_id > right.thread_id
|
|
3500
|
-
? 1
|
|
3501
|
-
: left.queue_sequence - right.queue_sequence,
|
|
3502
|
-
);
|
|
3683
|
+
if (active.length > 128) return undefined;
|
|
3503
3684
|
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3685
|
+
const sorted = [...active].sort((left, right) =>
|
|
3686
|
+
left.thread_id < right.thread_id
|
|
3687
|
+
? -1
|
|
3688
|
+
: left.thread_id > right.thread_id
|
|
3689
|
+
? 1
|
|
3690
|
+
: left.queue_sequence - right.queue_sequence,
|
|
3691
|
+
);
|
|
3692
|
+
|
|
3693
|
+
const work = yield* decodeSubmissionWorkItems(
|
|
3694
|
+
sorted.map((row) => ({
|
|
3695
|
+
submissionId: row.submission_id,
|
|
3696
|
+
threadId: row.thread_id,
|
|
3697
|
+
queueSequence: row.queue_sequence,
|
|
3698
|
+
principal: row.principal,
|
|
3699
|
+
idempotencyKey: row.idempotency_key,
|
|
3700
|
+
deploymentId: row.deployment_id,
|
|
3701
|
+
receiptId: row.receipt_id,
|
|
3702
|
+
state: row.state,
|
|
3703
|
+
})),
|
|
3704
|
+
).pipe(Effect.mapError(internalFailure("ledger scan nonterminal")));
|
|
3705
|
+
|
|
3706
|
+
return work;
|
|
3707
|
+
}),
|
|
3708
|
+
);
|
|
3709
|
+
|
|
3710
|
+
const pagedNonterminal = Stream.paginate<ScanCursor | undefined, SubmissionWorkItem, LedgerError>(
|
|
3711
|
+
undefined,
|
|
3712
|
+
scanPage,
|
|
3713
|
+
);
|
|
3714
|
+
|
|
3715
|
+
// Large scans keep their bounded page lifetime and streaming backpressure.
|
|
3716
|
+
const scanNonterminal = Stream.unwrap(
|
|
3717
|
+
Effect.map(boundedNonterminal, (work) =>
|
|
3718
|
+
work === undefined ? pagedNonterminal : Stream.fromIterable(work),
|
|
3519
3719
|
),
|
|
3520
3720
|
);
|
|
3521
3721
|
|
|
3722
|
+
const listNonterminal = Effect.filterOrElse(
|
|
3723
|
+
boundedNonterminal,
|
|
3724
|
+
(work) => work !== undefined,
|
|
3725
|
+
() => Stream.runCollect(pagedNonterminal),
|
|
3726
|
+
);
|
|
3727
|
+
|
|
3522
3728
|
const readAbortIntentForSubmission: SubmissionLedger["Service"]["readAbortIntent"] =
|
|
3523
3729
|
Effect.fnUntraced(function* (request) {
|
|
3524
3730
|
const operation = "ledger read abort intent";
|
|
3525
3731
|
|
|
3526
|
-
const validated = yield*
|
|
3732
|
+
const validated = yield* decodeAbortIntentRequestType(request).pipe(
|
|
3527
3733
|
Effect.mapError(internalFailure(operation)),
|
|
3528
3734
|
);
|
|
3529
3735
|
|
|
@@ -3548,9 +3754,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3548
3754
|
Effect.fnUntraced(function* (request: RecoverySnapshotRequest) {
|
|
3549
3755
|
const operation = "ledger load recovery snapshot";
|
|
3550
3756
|
|
|
3551
|
-
const validated = yield*
|
|
3552
|
-
|
|
3553
|
-
)
|
|
3757
|
+
const validated = yield* decodeRecoverySnapshotRequestType(request).pipe(
|
|
3758
|
+
Effect.mapError(internalFailure(operation)),
|
|
3759
|
+
);
|
|
3554
3760
|
|
|
3555
3761
|
return yield* state.read(
|
|
3556
3762
|
Effect.gen(function* () {
|
|
@@ -3590,19 +3796,16 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3590
3796
|
.filter((row) => row.joined_host_submission_id === validated.submissionId)
|
|
3591
3797
|
.sort((a, b) => a.queue_sequence - b.queue_sequence);
|
|
3592
3798
|
|
|
3593
|
-
const
|
|
3594
|
-
operation,
|
|
3595
|
-
validated.submissionId,
|
|
3596
|
-
joinRows,
|
|
3597
|
-
);
|
|
3799
|
+
const joins: Array<JoinSnapshot> = [];
|
|
3598
3800
|
|
|
3599
|
-
const
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3801
|
+
for (const row of joinRows)
|
|
3802
|
+
joins.push(
|
|
3803
|
+
yield* decodeJoinSnapshot({
|
|
3804
|
+
submissionId: row.submission_id,
|
|
3805
|
+
state: row.state,
|
|
3806
|
+
hostSubmissionId: validated.submissionId,
|
|
3807
|
+
}).pipe(Effect.mapError(internalFailure(operation))),
|
|
3808
|
+
);
|
|
3606
3809
|
|
|
3607
3810
|
let hostSubmissionId: RecoverySnapshot["hostSubmissionId"];
|
|
3608
3811
|
|
|
@@ -3643,15 +3846,17 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3643
3846
|
|
|
3644
3847
|
const decisionRows = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
3645
3848
|
|
|
3646
|
-
const approvalDecisions =
|
|
3647
|
-
|
|
3648
|
-
)
|
|
3849
|
+
const approvalDecisions: Array<ApprovalDecisionIntent> = [];
|
|
3850
|
+
|
|
3851
|
+
for (const row of decisionRows)
|
|
3852
|
+
approvalDecisions.push(yield* approvalIntentFromRow(operation, row));
|
|
3649
3853
|
|
|
3650
3854
|
const resolutionRows = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
3651
3855
|
|
|
3652
|
-
const unknownResolutions =
|
|
3653
|
-
|
|
3654
|
-
)
|
|
3856
|
+
const unknownResolutions: Array<UnknownResolutionIntent> = [];
|
|
3857
|
+
|
|
3858
|
+
for (const row of resolutionRows)
|
|
3859
|
+
unknownResolutions.push(yield* unknownResolutionIntentFromRow(operation, row));
|
|
3655
3860
|
|
|
3656
3861
|
// Parent-side subagent view: this Submission's child budget reservations in parent
|
|
3657
3862
|
// Tool Call order, plus each attached child's current lane state (a disposable
|
|
@@ -3666,21 +3871,16 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3666
3871
|
.filter((row) => row.parent_submission_id === validated.submissionId)
|
|
3667
3872
|
.sort((a, b) => a.parent_tool_call_id.localeCompare(b.parent_tool_call_id));
|
|
3668
3873
|
|
|
3669
|
-
const
|
|
3670
|
-
operation,
|
|
3671
|
-
validated.submissionId,
|
|
3672
|
-
childReservationRows,
|
|
3673
|
-
);
|
|
3874
|
+
const childReservations: Array<ChildBudgetReservationSnapshot> = [];
|
|
3674
3875
|
|
|
3675
|
-
const
|
|
3676
|
-
childReservationSnapshotFromRow(operation, row)
|
|
3677
|
-
);
|
|
3876
|
+
for (const row of childReservationRows)
|
|
3877
|
+
childReservations.push(yield* childReservationSnapshotFromRow(operation, row));
|
|
3678
3878
|
|
|
3679
3879
|
const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
|
|
3680
3880
|
const markersByChild = new Map(markers.map((row) => [row.child_submission_id, row]));
|
|
3681
3881
|
const childAttachments: Array<ChildAttachmentSnapshot> = [];
|
|
3682
3882
|
|
|
3683
|
-
for (const row of
|
|
3883
|
+
for (const row of childReservationRows) {
|
|
3684
3884
|
if (row.child_submission_id === null) continue;
|
|
3685
3885
|
const child = yield* readSubmission(operation, row.child_submission_id);
|
|
3686
3886
|
|
|
@@ -3751,6 +3951,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3751
3951
|
resolveAdmission,
|
|
3752
3952
|
selectHead,
|
|
3753
3953
|
markInputApplied,
|
|
3954
|
+
appendInput,
|
|
3754
3955
|
finalizeSettlement: (request) => owner.withMutation(finalizeSettlement(request)),
|
|
3755
3956
|
requestAbort,
|
|
3756
3957
|
stopWorker,
|
|
@@ -3768,6 +3969,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3768
3969
|
beginChildBudgetRelease,
|
|
3769
3970
|
releaseChildBudget,
|
|
3770
3971
|
scanNonterminal,
|
|
3972
|
+
listNonterminal,
|
|
3771
3973
|
loadRecoverySnapshot,
|
|
3772
3974
|
readAbortIntent: readAbortIntentForSubmission,
|
|
3773
3975
|
}),
|