@yielded/agent-storage-cloudflare 0.1.0-beta.174 → 0.1.0-beta.175
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.mjs +116 -65
- package/dist/DoSubmissionLedger.mjs.map +1 -1
- package/dist/{DoThreadStore-DyzOZDXK.mjs → DoThreadStore-CgIZQUNR.mjs} +24 -19
- package/dist/DoThreadStore-CgIZQUNR.mjs.map +1 -0
- package/dist/DoThreadStore.mjs +1 -1
- package/dist/PortProtocol.d.mts +7063 -7063
- package/dist/PortRouting.mjs +6 -1
- package/dist/PortRouting.mjs.map +1 -1
- package/dist/{do-journal-ZMAzzENZ.mjs → do-journal-7P3DUBz0.mjs} +131 -43
- package/dist/do-journal-7P3DUBz0.mjs.map +1 -0
- package/dist/index.d.mts +1 -1
- 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-B4QhezMp.mjs} +2 -1
- package/dist/{owned-state-DcFh0YNo.mjs.map → owned-state-B4QhezMp.mjs.map} +1 -1
- package/package.json +1 -1
- package/src/DoStorageFailpoint.ts +4 -0
- package/src/DoSubmissionLedger.ts +262 -149
- package/src/DoThreadStore.ts +18 -44
- package/src/PortRouting.ts +10 -2
- package/src/internal/do-journal.ts +286 -66
- package/src/internal/migrations.ts +52 -10
- package/src/internal/owned-state.ts +9 -1
- 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
|
@@ -345,8 +345,8 @@ const decodeAbortIntent = Schema.decodeUnknownEffect(AbortIntent);
|
|
|
345
345
|
const decodeInputAppliedMarker = Schema.decodeUnknownEffect(InputAppliedMarker);
|
|
346
346
|
|
|
347
347
|
const decodeSubmissionSnapshotType = (input: typeof SubmissionSnapshot.Type) =>
|
|
348
|
-
|
|
349
|
-
Effect.
|
|
348
|
+
SubmissionSnapshot.makeEffect(input).pipe(
|
|
349
|
+
Effect.mapError((issue) => new Schema.SchemaError(issue)),
|
|
350
350
|
);
|
|
351
351
|
|
|
352
352
|
const decodeSubmissionId = Schema.decodeUnknownEffect(SubmissionSnapshot.fields.submissionId);
|
|
@@ -366,6 +366,46 @@ const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(
|
|
|
366
366
|
const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
|
|
367
367
|
const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
|
|
368
368
|
const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
|
|
369
|
+
const decodeAdmissionRequestType = Schema.decodeEffect(Schema.toType(AdmissionRequest));
|
|
370
|
+
const decodeSubmissionLookupType = Schema.decodeEffect(Schema.toType(SubmissionLookup));
|
|
371
|
+
const decodeClaimRequestType = Schema.decodeEffect(Schema.toType(ClaimRequest));
|
|
372
|
+
|
|
373
|
+
const decodeMarkInputAppliedRequestType = Schema.decodeEffect(
|
|
374
|
+
Schema.toType(MarkInputAppliedRequest),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const decodeSettlementFinalizationType = Schema.decodeEffect(Schema.toType(SettlementFinalization));
|
|
378
|
+
const decodeClaimJoiningRequestType = Schema.decodeEffect(Schema.toType(ClaimJoiningRequest));
|
|
379
|
+
|
|
380
|
+
const decodeAbortIntentRequestType = Schema.decodeEffect(Schema.toType(AbortIntentRequest));
|
|
381
|
+
|
|
382
|
+
const decodeRecoverySnapshotRequestType = Schema.decodeEffect(
|
|
383
|
+
Schema.toType(RecoverySnapshotRequest),
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const SubmissionRows = Schema.Array(SubmissionRow);
|
|
387
|
+
const ChildReservationRows = Schema.Array(ChildReservationRow);
|
|
388
|
+
const MaxQueueSequenceRows = Schema.Array(MaxQueueSequenceRow);
|
|
389
|
+
const SubmissionWorkItems = Schema.Array(SubmissionWorkItem);
|
|
390
|
+
const decodeSubmissionWorkItems = Schema.decodeEffect(SubmissionWorkItems);
|
|
391
|
+
const equivalentWorkerOrigin = Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin));
|
|
392
|
+
|
|
393
|
+
const decodeFirstWorkerAdmissions = Schema.decodeUnknownEffect(
|
|
394
|
+
Schema.Array(
|
|
395
|
+
Schema.Struct({
|
|
396
|
+
worker_admission_json: Schema.NullOr(BoundedStoredText),
|
|
397
|
+
}),
|
|
398
|
+
),
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const CanonicalSettlementRows = Schema.Array(
|
|
402
|
+
Schema.Struct({
|
|
403
|
+
batch_id: Schema.fromJsonString(BoundedIdentifier),
|
|
404
|
+
record_id: Schema.fromJsonString(BoundedIdentifier),
|
|
405
|
+
record_json: BoundedStoredText,
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
408
|
+
|
|
369
409
|
const isDoStorageError = Schema.is(DoStorageError);
|
|
370
410
|
|
|
371
411
|
/** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
|
|
@@ -606,7 +646,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
606
646
|
}));
|
|
607
647
|
|
|
608
648
|
const decodeSubmissionRows = (operation: string, rowKey: string, rows: unknown) =>
|
|
609
|
-
decodeRows(
|
|
649
|
+
decodeRows(SubmissionRows, "effect_agent_submissions", rowKey, rows).pipe(
|
|
610
650
|
Effect.mapError(internalFailure(operation)),
|
|
611
651
|
);
|
|
612
652
|
|
|
@@ -721,13 +761,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
721
761
|
AND record_id = ${JSON.stringify(recordId)}`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
722
762
|
|
|
723
763
|
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
|
-
),
|
|
764
|
+
CanonicalSettlementRows,
|
|
731
765
|
"effect_agent_canonical_records",
|
|
732
766
|
recordId,
|
|
733
767
|
found,
|
|
@@ -810,12 +844,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
810
844
|
});
|
|
811
845
|
|
|
812
846
|
const decodeChildReservationRows = (operation: string, rowKey: string, rows: unknown) =>
|
|
813
|
-
decodeRows(
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
rowKey,
|
|
817
|
-
rows,
|
|
818
|
-
).pipe(Effect.mapError(internalFailure(operation)));
|
|
847
|
+
decodeRows(ChildReservationRows, "effect_agent_child_reservations", rowKey, rows).pipe(
|
|
848
|
+
Effect.mapError(internalFailure(operation)),
|
|
849
|
+
);
|
|
819
850
|
|
|
820
851
|
const readChildReservation = Effect.fnUntraced(function* (
|
|
821
852
|
operation: string,
|
|
@@ -1036,10 +1067,11 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1036
1067
|
|
|
1037
1068
|
const admit: SubmissionLedger["Service"]["admit"] = Effect.fnUntraced(function* (
|
|
1038
1069
|
request: AdmissionRequest,
|
|
1070
|
+
options?: { readonly readyIfInitialized?: boolean },
|
|
1039
1071
|
) {
|
|
1040
1072
|
const operation = "ledger admit";
|
|
1041
1073
|
|
|
1042
|
-
const validated = yield*
|
|
1074
|
+
const validated = yield* decodeAdmissionRequestType(request).pipe(
|
|
1043
1075
|
Effect.mapError(internalFailure(operation)),
|
|
1044
1076
|
);
|
|
1045
1077
|
|
|
@@ -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
|
|
|
@@ -1540,7 +1592,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1540
1592
|
)(function* (request: ClaimRequest) {
|
|
1541
1593
|
const operation = "ledger select head";
|
|
1542
1594
|
|
|
1543
|
-
const validated = yield*
|
|
1595
|
+
const validated = yield* decodeClaimRequestType(request).pipe(
|
|
1544
1596
|
Effect.mapError(internalFailure(operation)),
|
|
1545
1597
|
);
|
|
1546
1598
|
|
|
@@ -1552,7 +1604,28 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1552
1604
|
Effect.gen(function* () {
|
|
1553
1605
|
if (owner.hasOwner(validated.threadId)) return Option.none<Claim>();
|
|
1554
1606
|
|
|
1555
|
-
const
|
|
1607
|
+
const active = yield* activeNonterminal;
|
|
1608
|
+
const heads: Array<SubmissionRow> = [];
|
|
1609
|
+
|
|
1610
|
+
const limit =
|
|
1611
|
+
validated.handoff === undefined ? 1 : validated.handoff.deferredSubmissionIds.length + 1;
|
|
1612
|
+
|
|
1613
|
+
if (active.length <= 128) {
|
|
1614
|
+
const candidates = active
|
|
1615
|
+
.filter((row) => row.thread_id === validated.threadId)
|
|
1616
|
+
.sort((left, right) => left.queue_sequence - right.queue_sequence);
|
|
1617
|
+
|
|
1618
|
+
for (const candidate of candidates) {
|
|
1619
|
+
if (
|
|
1620
|
+
candidate.state === "unknown" &&
|
|
1621
|
+
Option.isNone(yield* readAbortIntent(operation, candidate.submission_id))
|
|
1622
|
+
)
|
|
1623
|
+
continue;
|
|
1624
|
+
heads.push(yield* requireSubmission(operation, candidate.submission_id));
|
|
1625
|
+
if (heads.length === limit) break;
|
|
1626
|
+
}
|
|
1627
|
+
} else {
|
|
1628
|
+
const headRows = yield* sql<Record<string, unknown>>`
|
|
1556
1629
|
SELECT ${submissionColumns}
|
|
1557
1630
|
FROM effect_agent_submissions
|
|
1558
1631
|
WHERE thread_id = ${validated.threadId}
|
|
@@ -1567,7 +1640,8 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1567
1640
|
LIMIT ${validated.handoff === undefined ? 1 : validated.handoff.deferredSubmissionIds.length + 1}
|
|
1568
1641
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1569
1642
|
|
|
1570
|
-
|
|
1643
|
+
heads.push(...(yield* decodeSubmissionRows(operation, validated.threadId, headRows)));
|
|
1644
|
+
}
|
|
1571
1645
|
|
|
1572
1646
|
if (heads.length === 0) return Option.none<Claim>();
|
|
1573
1647
|
let head = heads[0];
|
|
@@ -1636,9 +1710,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1636
1710
|
function* (request: MarkInputAppliedRequest) {
|
|
1637
1711
|
const operation = "ledger mark input applied";
|
|
1638
1712
|
|
|
1639
|
-
const validated = yield*
|
|
1640
|
-
|
|
1641
|
-
)
|
|
1713
|
+
const validated = yield* decodeMarkInputAppliedRequestType(request).pipe(
|
|
1714
|
+
Effect.mapError(internalFailure(operation)),
|
|
1715
|
+
);
|
|
1642
1716
|
|
|
1643
1717
|
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1644
1718
|
yield* inWriteTransaction(
|
|
@@ -1897,9 +1971,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1897
1971
|
)(function* (request: SettlementFinalization) {
|
|
1898
1972
|
const operation = "ledger finalize settlement";
|
|
1899
1973
|
|
|
1900
|
-
const validated = yield*
|
|
1901
|
-
|
|
1902
|
-
)
|
|
1974
|
+
const validated = yield* decodeSettlementFinalizationType(request).pipe(
|
|
1975
|
+
Effect.mapError(internalFailure(operation)),
|
|
1976
|
+
);
|
|
1903
1977
|
|
|
1904
1978
|
yield* hitFailpoint("ledger:finalize-settlement:before", operation);
|
|
1905
1979
|
|
|
@@ -2211,14 +2285,13 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
2211
2285
|
) {
|
|
2212
2286
|
const operation = "ledger claim joining";
|
|
2213
2287
|
|
|
2214
|
-
const validated = yield*
|
|
2288
|
+
const validated = yield* decodeClaimJoiningRequestType(request).pipe(
|
|
2215
2289
|
Effect.mapError(internalFailure(operation)),
|
|
2216
2290
|
);
|
|
2217
2291
|
|
|
2218
2292
|
yield* hitFailpoint("ledger:claim-joining:before", operation);
|
|
2219
2293
|
|
|
2220
|
-
const claims = yield*
|
|
2221
|
-
operation,
|
|
2294
|
+
const claims = yield* owner.withMutation(
|
|
2222
2295
|
Effect.gen(function* () {
|
|
2223
2296
|
const host = yield* requireSubmission(operation, validated.hostSubmissionId);
|
|
2224
2297
|
|
|
@@ -2238,68 +2311,92 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
2238
2311
|
|
|
2239
2312
|
if (stopped.length > 0) return [];
|
|
2240
2313
|
|
|
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
|
-
|
|
2314
|
+
// Retain only an empty complete suffix. Any later admission invalidates this view;
|
|
2315
|
+
// nonempty rows keep their SQL ordering, and rollback discards the empty proof.
|
|
2316
|
+
const later = yield* submissionViews
|
|
2317
|
+
.matching(
|
|
2318
|
+
JSON.stringify(["joining-suffix", validated.threadId, host.queue_sequence]),
|
|
2319
|
+
(row) =>
|
|
2320
|
+
row.thread_id === validated.threadId && row.queue_sequence > host.queue_sequence,
|
|
2321
|
+
sql`
|
|
2322
|
+
SELECT ${submissionColumns}
|
|
2323
|
+
FROM effect_agent_submissions
|
|
2324
|
+
WHERE thread_id = ${validated.threadId}
|
|
2325
|
+
AND queue_sequence > ${host.queue_sequence}
|
|
2326
|
+
ORDER BY queue_sequence ASC
|
|
2327
|
+
`,
|
|
2328
|
+
0,
|
|
2329
|
+
)
|
|
2330
|
+
.pipe(
|
|
2331
|
+
Effect.mapError((error) =>
|
|
2332
|
+
error._tag === "SqlError"
|
|
2333
|
+
? sqlFailure(operation)(error)
|
|
2334
|
+
: internalFailure(operation)(error),
|
|
2335
|
+
),
|
|
2336
|
+
);
|
|
2337
|
+
|
|
2338
|
+
if (later.length === 0) return [];
|
|
2339
|
+
|
|
2340
|
+
// The reader gate stays held through promotion. Nonempty candidates retain
|
|
2341
|
+
// their native transaction, live-owner checks and contiguous-prefix rules.
|
|
2342
|
+
return yield* inWriteTransaction(
|
|
2343
|
+
operation,
|
|
2344
|
+
Effect.gen(function* () {
|
|
2345
|
+
const claimed: Array<JoiningClaim> = [];
|
|
2346
|
+
|
|
2347
|
+
for (const row of later) {
|
|
2348
|
+
if (claimed.length >= validated.maxCount) break;
|
|
2349
|
+
// Rows already claimed by THIS host extend its contiguous prefix and are skipped;
|
|
2350
|
+
// the coordinator re-delivers already-joined input through the coverage rule.
|
|
2351
|
+
if (
|
|
2352
|
+
(row.state === "joining" || row.state === "joined") &&
|
|
2353
|
+
row.joined_host_submission_id === validated.hostSubmissionId
|
|
2354
|
+
) {
|
|
2355
|
+
continue;
|
|
2356
|
+
}
|
|
2357
|
+
// P7 §7(c): an aborted-settled row is a CLOSED obligation, not a gap — recovery
|
|
2358
|
+
// settles aborted never-claimed queued work immediately, and settlement order of
|
|
2359
|
+
// never-run work is not execution order (DUR-004 bounds execution).
|
|
2360
|
+
if (row.state === "settled" && row.settled_outcome === "aborted") continue;
|
|
2361
|
+
// Any other non-ready row — an admitted-not-ready gap in particular — breaks the
|
|
2362
|
+
// contiguous ready prefix (plan §2.5); later ready work stays queued (DUR-004).
|
|
2363
|
+
if (row.state !== "ready") break;
|
|
2364
|
+
const canonical = yield* readCanonicalSettlement(operation, row);
|
|
2365
|
+
|
|
2366
|
+
if (canonical?.settlement.outcome === "aborted") continue;
|
|
2367
|
+
if (canonical !== undefined) break;
|
|
2368
|
+
yield* sql`
|
|
2274
2369
|
UPDATE effect_agent_submissions
|
|
2275
2370
|
SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
|
|
2276
2371
|
WHERE submission_id = ${row.submission_id}
|
|
2277
2372
|
RETURNING *, ${definitionJson(sql)} AS agent_digests_json`.pipe(
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2373
|
+
rows.submissions.write,
|
|
2374
|
+
Effect.mapError(internalFailure(operation)),
|
|
2375
|
+
);
|
|
2281
2376
|
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2377
|
+
const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
|
|
2378
|
+
Effect.mapError((error) =>
|
|
2379
|
+
corruptionFailure(
|
|
2380
|
+
operation,
|
|
2381
|
+
"effect_agent_submissions",
|
|
2382
|
+
row.submission_id,
|
|
2383
|
+
error.message,
|
|
2384
|
+
),
|
|
2385
|
+
),
|
|
2386
|
+
);
|
|
2292
2387
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2388
|
+
claimed.push(
|
|
2389
|
+
yield* decodeJoiningClaim({
|
|
2390
|
+
submissionId: row.submission_id,
|
|
2391
|
+
queueSequence: row.queue_sequence,
|
|
2392
|
+
inputPayload,
|
|
2393
|
+
}).pipe(Effect.mapError(internalFailure(operation))),
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2301
2396
|
|
|
2302
|
-
|
|
2397
|
+
return claimed;
|
|
2398
|
+
}),
|
|
2399
|
+
);
|
|
2303
2400
|
}),
|
|
2304
2401
|
);
|
|
2305
2402
|
|
|
@@ -3451,7 +3548,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3451
3548
|
).pipe(Effect.mapError(sqlFailure(operation)));
|
|
3452
3549
|
|
|
3453
3550
|
const decoded = yield* decodeRows(
|
|
3454
|
-
|
|
3551
|
+
SubmissionWorkItems,
|
|
3455
3552
|
"effect_agent_submissions",
|
|
3456
3553
|
"nonterminal_scan",
|
|
3457
3554
|
rows,
|
|
@@ -3471,59 +3568,74 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3471
3568
|
return [decoded, next] as const;
|
|
3472
3569
|
});
|
|
3473
3570
|
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
),
|
|
3487
|
-
"ledger scan nonterminal",
|
|
3488
|
-
);
|
|
3571
|
+
// Both discovery and head selection use this same complete, owner-invalidated bounded view.
|
|
3572
|
+
const activeNonterminal = cached(
|
|
3573
|
+
workRows.matching(
|
|
3574
|
+
"nonterminal",
|
|
3575
|
+
(row) => row.state !== "settled",
|
|
3576
|
+
sql`SELECT submission_id, thread_id, queue_sequence, principal, idempotency_key,
|
|
3577
|
+
deployment_id, receipt_id, state
|
|
3578
|
+
FROM effect_agent_submissions WHERE state <> 'settled' LIMIT 129`,
|
|
3579
|
+
128,
|
|
3580
|
+
),
|
|
3581
|
+
"ledger scan nonterminal",
|
|
3582
|
+
);
|
|
3489
3583
|
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
);
|
|
3584
|
+
const boundedNonterminal = state.read(
|
|
3585
|
+
Effect.gen(function* () {
|
|
3586
|
+
// Cache only a complete bounded active set. Large hosts keep the paged scan.
|
|
3587
|
+
const active = yield* activeNonterminal;
|
|
3495
3588
|
|
|
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
|
-
);
|
|
3589
|
+
if (active.length > 128) return undefined;
|
|
3503
3590
|
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3591
|
+
const sorted = [...active].sort((left, right) =>
|
|
3592
|
+
left.thread_id < right.thread_id
|
|
3593
|
+
? -1
|
|
3594
|
+
: left.thread_id > right.thread_id
|
|
3595
|
+
? 1
|
|
3596
|
+
: left.queue_sequence - right.queue_sequence,
|
|
3597
|
+
);
|
|
3598
|
+
|
|
3599
|
+
const work = yield* decodeSubmissionWorkItems(
|
|
3600
|
+
sorted.map((row) => ({
|
|
3601
|
+
submissionId: row.submission_id,
|
|
3602
|
+
threadId: row.thread_id,
|
|
3603
|
+
queueSequence: row.queue_sequence,
|
|
3604
|
+
principal: row.principal,
|
|
3605
|
+
idempotencyKey: row.idempotency_key,
|
|
3606
|
+
deploymentId: row.deployment_id,
|
|
3607
|
+
receiptId: row.receipt_id,
|
|
3608
|
+
state: row.state,
|
|
3609
|
+
})),
|
|
3610
|
+
).pipe(Effect.mapError(internalFailure("ledger scan nonterminal")));
|
|
3611
|
+
|
|
3612
|
+
return work;
|
|
3613
|
+
}),
|
|
3614
|
+
);
|
|
3615
|
+
|
|
3616
|
+
const pagedNonterminal = Stream.paginate<ScanCursor | undefined, SubmissionWorkItem, LedgerError>(
|
|
3617
|
+
undefined,
|
|
3618
|
+
scanPage,
|
|
3619
|
+
);
|
|
3620
|
+
|
|
3621
|
+
// Large scans keep their bounded page lifetime and streaming backpressure.
|
|
3622
|
+
const scanNonterminal = Stream.unwrap(
|
|
3623
|
+
Effect.map(boundedNonterminal, (work) =>
|
|
3624
|
+
work === undefined ? pagedNonterminal : Stream.fromIterable(work),
|
|
3519
3625
|
),
|
|
3520
3626
|
);
|
|
3521
3627
|
|
|
3628
|
+
const listNonterminal = Effect.filterOrElse(
|
|
3629
|
+
boundedNonterminal,
|
|
3630
|
+
(work) => work !== undefined,
|
|
3631
|
+
() => Stream.runCollect(pagedNonterminal),
|
|
3632
|
+
);
|
|
3633
|
+
|
|
3522
3634
|
const readAbortIntentForSubmission: SubmissionLedger["Service"]["readAbortIntent"] =
|
|
3523
3635
|
Effect.fnUntraced(function* (request) {
|
|
3524
3636
|
const operation = "ledger read abort intent";
|
|
3525
3637
|
|
|
3526
|
-
const validated = yield*
|
|
3638
|
+
const validated = yield* decodeAbortIntentRequestType(request).pipe(
|
|
3527
3639
|
Effect.mapError(internalFailure(operation)),
|
|
3528
3640
|
);
|
|
3529
3641
|
|
|
@@ -3548,9 +3660,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3548
3660
|
Effect.fnUntraced(function* (request: RecoverySnapshotRequest) {
|
|
3549
3661
|
const operation = "ledger load recovery snapshot";
|
|
3550
3662
|
|
|
3551
|
-
const validated = yield*
|
|
3552
|
-
|
|
3553
|
-
)
|
|
3663
|
+
const validated = yield* decodeRecoverySnapshotRequestType(request).pipe(
|
|
3664
|
+
Effect.mapError(internalFailure(operation)),
|
|
3665
|
+
);
|
|
3554
3666
|
|
|
3555
3667
|
return yield* state.read(
|
|
3556
3668
|
Effect.gen(function* () {
|
|
@@ -3768,6 +3880,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3768
3880
|
beginChildBudgetRelease,
|
|
3769
3881
|
releaseChildBudget,
|
|
3770
3882
|
scanNonterminal,
|
|
3883
|
+
listNonterminal,
|
|
3771
3884
|
loadRecoverySnapshot,
|
|
3772
3885
|
readAbortIntent: readAbortIntentForSubmission,
|
|
3773
3886
|
}),
|