@yielded/agent-storage-cloudflare 0.1.0-beta.173 → 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/DoStorageFailpointTesting.mjs +1 -4
- package/dist/DoStorageFailpointTesting.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 +121 -67
- package/dist/DoSubmissionLedger.mjs.map +1 -1
- package/dist/DoSubscriptionStore.mjs.map +1 -1
- package/dist/{DoThreadStore-CtyahRTW.mjs → DoThreadStore-CgIZQUNR.mjs} +30 -25
- package/dist/DoThreadStore-CgIZQUNR.mjs.map +1 -0
- package/dist/DoThreadStore.mjs +1 -1
- package/dist/PortProtocol.d.mts +12 -0
- package/dist/PortRouting.mjs +6 -1
- package/dist/PortRouting.mjs.map +1 -1
- package/dist/{do-journal-_MdLGTd3.mjs → do-journal-7P3DUBz0.mjs} +140 -65
- 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-BHzUi0eb.mjs → owned-state-B4QhezMp.mjs} +8 -6
- package/dist/owned-state-B4QhezMp.mjs.map +1 -0
- package/package.json +1 -1
- package/src/DoStorageFailpoint.ts +4 -0
- package/src/DoStorageFailpointTesting.ts +9 -7
- package/src/DoSubmissionLedger.ts +271 -150
- package/src/DoSubscriptionStore.ts +1 -0
- package/src/DoThreadStore.ts +27 -50
- package/src/PortRouting.ts +10 -2
- package/src/internal/canonical-append.ts +1 -1
- package/src/internal/do-journal.ts +987 -800
- package/src/internal/migrations.ts +53 -10
- package/src/internal/owned-state.ts +18 -7
- package/src/internal/prompt-checkpoint.ts +19 -13
- package/src/internal/storage-span.ts +22 -13
- package/dist/DoThreadStore-CtyahRTW.mjs.map +0 -1
- package/dist/do-journal-_MdLGTd3.mjs.map +0 -1
- package/dist/migrations-C3N9GN6A.mjs.map +0 -1
- package/dist/owned-state-BHzUi0eb.mjs.map +0 -1
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
PersistedJson,
|
|
28
28
|
WorkerAdmission,
|
|
29
29
|
RecordEnvelope,
|
|
30
|
+
RecordId,
|
|
30
31
|
SettlementOutcome,
|
|
31
32
|
} from "@yielded/agent/records";
|
|
32
33
|
import { runIdForSubmission } from "@yielded/agent/run-journal";
|
|
@@ -189,7 +190,7 @@ class SubmissionRow extends Schema.Class<SubmissionRow>("SubmissionRow")({
|
|
|
189
190
|
receipt_id: BoundedIdentifier,
|
|
190
191
|
state: SubmissionState,
|
|
191
192
|
settled_outcome: Schema.NullOr(SettlementOutcome),
|
|
192
|
-
settled_record_id: Schema.NullOr(BoundedIdentifier),
|
|
193
|
+
settled_record_id: Schema.NullOr(BoundedIdentifier.pipe(Schema.decodeTo(RecordId))),
|
|
193
194
|
finalized_at: Schema.NullOr(BoundedTimestamp),
|
|
194
195
|
created_at: BoundedTimestamp,
|
|
195
196
|
ready_at: Schema.NullOr(BoundedTimestamp),
|
|
@@ -344,8 +345,8 @@ const decodeAbortIntent = Schema.decodeUnknownEffect(AbortIntent);
|
|
|
344
345
|
const decodeInputAppliedMarker = Schema.decodeUnknownEffect(InputAppliedMarker);
|
|
345
346
|
|
|
346
347
|
const decodeSubmissionSnapshotType = (input: typeof SubmissionSnapshot.Type) =>
|
|
347
|
-
|
|
348
|
-
Effect.
|
|
348
|
+
SubmissionSnapshot.makeEffect(input).pipe(
|
|
349
|
+
Effect.mapError((issue) => new Schema.SchemaError(issue)),
|
|
349
350
|
);
|
|
350
351
|
|
|
351
352
|
const decodeSubmissionId = Schema.decodeUnknownEffect(SubmissionSnapshot.fields.submissionId);
|
|
@@ -365,6 +366,46 @@ const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(
|
|
|
365
366
|
const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
|
|
366
367
|
const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
|
|
367
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
|
+
|
|
368
409
|
const isDoStorageError = Schema.is(DoStorageError);
|
|
369
410
|
|
|
370
411
|
/** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
|
|
@@ -605,7 +646,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
605
646
|
}));
|
|
606
647
|
|
|
607
648
|
const decodeSubmissionRows = (operation: string, rowKey: string, rows: unknown) =>
|
|
608
|
-
decodeRows(
|
|
649
|
+
decodeRows(SubmissionRows, "effect_agent_submissions", rowKey, rows).pipe(
|
|
609
650
|
Effect.mapError(internalFailure(operation)),
|
|
610
651
|
);
|
|
611
652
|
|
|
@@ -683,6 +724,11 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
683
724
|
|
|
684
725
|
const admission = yield* decodeAdmissionFact(row).pipe(Effect.mapError(decodeFailure));
|
|
685
726
|
|
|
727
|
+
const settledAt =
|
|
728
|
+
row.finalized_at === null
|
|
729
|
+
? undefined
|
|
730
|
+
: yield* decodeUtcInstant(row.finalized_at).pipe(Effect.mapError(decodeFailure));
|
|
731
|
+
|
|
686
732
|
const readyAt =
|
|
687
733
|
row.ready_at === null
|
|
688
734
|
? undefined
|
|
@@ -692,6 +738,8 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
692
738
|
...admission,
|
|
693
739
|
state: row.state,
|
|
694
740
|
...(row.settled_outcome === null ? {} : { settledOutcome: row.settled_outcome }),
|
|
741
|
+
...(settledAt === undefined ? {} : { settledAt }),
|
|
742
|
+
...(row.settled_record_id === null ? {} : { settledRecordId: row.settled_record_id }),
|
|
695
743
|
...(readyAt === undefined ? {} : { readyAt }),
|
|
696
744
|
}).pipe(Effect.mapError(decodeFailure));
|
|
697
745
|
|
|
@@ -713,13 +761,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
713
761
|
AND record_id = ${JSON.stringify(recordId)}`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
714
762
|
|
|
715
763
|
const decoded = yield* decodeRows(
|
|
716
|
-
|
|
717
|
-
Schema.Struct({
|
|
718
|
-
batch_id: Schema.fromJsonString(BoundedIdentifier),
|
|
719
|
-
record_id: Schema.fromJsonString(BoundedIdentifier),
|
|
720
|
-
record_json: BoundedStoredText,
|
|
721
|
-
}),
|
|
722
|
-
),
|
|
764
|
+
CanonicalSettlementRows,
|
|
723
765
|
"effect_agent_canonical_records",
|
|
724
766
|
recordId,
|
|
725
767
|
found,
|
|
@@ -802,12 +844,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
802
844
|
});
|
|
803
845
|
|
|
804
846
|
const decodeChildReservationRows = (operation: string, rowKey: string, rows: unknown) =>
|
|
805
|
-
decodeRows(
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
rowKey,
|
|
809
|
-
rows,
|
|
810
|
-
).pipe(Effect.mapError(internalFailure(operation)));
|
|
847
|
+
decodeRows(ChildReservationRows, "effect_agent_child_reservations", rowKey, rows).pipe(
|
|
848
|
+
Effect.mapError(internalFailure(operation)),
|
|
849
|
+
);
|
|
811
850
|
|
|
812
851
|
const readChildReservation = Effect.fnUntraced(function* (
|
|
813
852
|
operation: string,
|
|
@@ -1028,10 +1067,11 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1028
1067
|
|
|
1029
1068
|
const admit: SubmissionLedger["Service"]["admit"] = Effect.fnUntraced(function* (
|
|
1030
1069
|
request: AdmissionRequest,
|
|
1070
|
+
options?: { readonly readyIfInitialized?: boolean },
|
|
1031
1071
|
) {
|
|
1032
1072
|
const operation = "ledger admit";
|
|
1033
1073
|
|
|
1034
|
-
const validated = yield*
|
|
1074
|
+
const validated = yield* decodeAdmissionRequestType(request).pipe(
|
|
1035
1075
|
Effect.mapError(internalFailure(operation)),
|
|
1036
1076
|
);
|
|
1037
1077
|
|
|
@@ -1212,13 +1252,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1212
1252
|
WHERE thread_id=${validated.threadId} ORDER BY queue_sequence LIMIT 1
|
|
1213
1253
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1214
1254
|
|
|
1215
|
-
const first = yield*
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
worker_admission_json: Schema.NullOr(BoundedStoredText),
|
|
1219
|
-
}),
|
|
1220
|
-
),
|
|
1221
|
-
)(firstRows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1255
|
+
const first = yield* decodeFirstWorkerAdmissions(firstRows).pipe(
|
|
1256
|
+
Effect.mapError(internalFailure(operation)),
|
|
1257
|
+
);
|
|
1222
1258
|
|
|
1223
1259
|
if (first[0] !== undefined) {
|
|
1224
1260
|
const previous =
|
|
@@ -1228,12 +1264,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1228
1264
|
first[0].worker_admission_json,
|
|
1229
1265
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1230
1266
|
|
|
1231
|
-
if (
|
|
1232
|
-
!Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin))(
|
|
1233
|
-
previous?.origin,
|
|
1234
|
-
validated.workerAdmission?.origin,
|
|
1235
|
-
)
|
|
1236
|
-
)
|
|
1267
|
+
if (!equivalentWorkerOrigin(previous?.origin, validated.workerAdmission?.origin))
|
|
1237
1268
|
return yield* AdmissionPolicyError.make({
|
|
1238
1269
|
reason: "refused",
|
|
1239
1270
|
code: "worker-origin-conflict",
|
|
@@ -1261,7 +1292,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1261
1292
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1262
1293
|
|
|
1263
1294
|
const decodedMax = yield* decodeRows(
|
|
1264
|
-
|
|
1295
|
+
MaxQueueSequenceRows,
|
|
1265
1296
|
"effect_agent_submissions",
|
|
1266
1297
|
validated.threadId,
|
|
1267
1298
|
maxRows,
|
|
@@ -1273,6 +1304,17 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1273
1304
|
|
|
1274
1305
|
const now = yield* currentInstant;
|
|
1275
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
|
+
|
|
1276
1318
|
yield* sql`
|
|
1277
1319
|
INSERT INTO effect_agent_submissions (
|
|
1278
1320
|
submission_id,
|
|
@@ -1288,6 +1330,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1288
1330
|
receipt_id,
|
|
1289
1331
|
state,
|
|
1290
1332
|
created_at,
|
|
1333
|
+
ready_at,
|
|
1291
1334
|
parent_submission_id,
|
|
1292
1335
|
parent_tool_call_id,
|
|
1293
1336
|
admission_group,
|
|
@@ -1306,8 +1349,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1306
1349
|
${inputJson},
|
|
1307
1350
|
${validated.inputDigest},
|
|
1308
1351
|
${mintedReceiptId},
|
|
1309
|
-
|
|
1352
|
+
${ready ? "ready" : "admitted"},
|
|
1310
1353
|
${now.iso},
|
|
1354
|
+
${ready ? now.iso : null},
|
|
1311
1355
|
${validated.parentLinkage?.parentSubmissionId ?? null},
|
|
1312
1356
|
${validated.parentLinkage?.parentToolCallId ?? null},
|
|
1313
1357
|
${validated.admissionGroup ?? null},
|
|
@@ -1333,6 +1377,14 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1333
1377
|
|
|
1334
1378
|
// The INSERT (never replay or hydration) proves these new submission-owned sets
|
|
1335
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
|
+
);
|
|
1336
1388
|
rows.aborts.seed("submission_id", mintedSubmissionId, []);
|
|
1337
1389
|
rows.submissions.seed("joined_host_submission_id", mintedSubmissionId, []);
|
|
1338
1390
|
rows.approvals.seed("submission_id", mintedSubmissionId, []);
|
|
@@ -1342,13 +1394,21 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1342
1394
|
|
|
1343
1395
|
yield* recordProgress("submission", operation);
|
|
1344
1396
|
|
|
1345
|
-
|
|
1397
|
+
const admitted = yield* decodeAdmissionResult({
|
|
1346
1398
|
submissionId: mintedSubmissionId,
|
|
1347
1399
|
receiptId: mintedReceiptId,
|
|
1348
1400
|
queueSequence,
|
|
1349
|
-
state: "admitted",
|
|
1401
|
+
state: ready ? "ready" : "admitted",
|
|
1350
1402
|
replayed: false,
|
|
1351
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;
|
|
1352
1412
|
}),
|
|
1353
1413
|
);
|
|
1354
1414
|
|
|
@@ -1458,7 +1518,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1458
1518
|
) {
|
|
1459
1519
|
const operation = "ledger lookup";
|
|
1460
1520
|
|
|
1461
|
-
const validated = yield*
|
|
1521
|
+
const validated = yield* decodeSubmissionLookupType(request).pipe(
|
|
1462
1522
|
Effect.mapError(internalFailure(operation)),
|
|
1463
1523
|
);
|
|
1464
1524
|
|
|
@@ -1532,7 +1592,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1532
1592
|
)(function* (request: ClaimRequest) {
|
|
1533
1593
|
const operation = "ledger select head";
|
|
1534
1594
|
|
|
1535
|
-
const validated = yield*
|
|
1595
|
+
const validated = yield* decodeClaimRequestType(request).pipe(
|
|
1536
1596
|
Effect.mapError(internalFailure(operation)),
|
|
1537
1597
|
);
|
|
1538
1598
|
|
|
@@ -1544,7 +1604,28 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1544
1604
|
Effect.gen(function* () {
|
|
1545
1605
|
if (owner.hasOwner(validated.threadId)) return Option.none<Claim>();
|
|
1546
1606
|
|
|
1547
|
-
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>>`
|
|
1548
1629
|
SELECT ${submissionColumns}
|
|
1549
1630
|
FROM effect_agent_submissions
|
|
1550
1631
|
WHERE thread_id = ${validated.threadId}
|
|
@@ -1559,7 +1640,8 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1559
1640
|
LIMIT ${validated.handoff === undefined ? 1 : validated.handoff.deferredSubmissionIds.length + 1}
|
|
1560
1641
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1561
1642
|
|
|
1562
|
-
|
|
1643
|
+
heads.push(...(yield* decodeSubmissionRows(operation, validated.threadId, headRows)));
|
|
1644
|
+
}
|
|
1563
1645
|
|
|
1564
1646
|
if (heads.length === 0) return Option.none<Claim>();
|
|
1565
1647
|
let head = heads[0];
|
|
@@ -1628,9 +1710,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1628
1710
|
function* (request: MarkInputAppliedRequest) {
|
|
1629
1711
|
const operation = "ledger mark input applied";
|
|
1630
1712
|
|
|
1631
|
-
const validated = yield*
|
|
1632
|
-
|
|
1633
|
-
)
|
|
1713
|
+
const validated = yield* decodeMarkInputAppliedRequestType(request).pipe(
|
|
1714
|
+
Effect.mapError(internalFailure(operation)),
|
|
1715
|
+
);
|
|
1634
1716
|
|
|
1635
1717
|
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1636
1718
|
yield* inWriteTransaction(
|
|
@@ -1889,9 +1971,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
1889
1971
|
)(function* (request: SettlementFinalization) {
|
|
1890
1972
|
const operation = "ledger finalize settlement";
|
|
1891
1973
|
|
|
1892
|
-
const validated = yield*
|
|
1893
|
-
|
|
1894
|
-
)
|
|
1974
|
+
const validated = yield* decodeSettlementFinalizationType(request).pipe(
|
|
1975
|
+
Effect.mapError(internalFailure(operation)),
|
|
1976
|
+
);
|
|
1895
1977
|
|
|
1896
1978
|
yield* hitFailpoint("ledger:finalize-settlement:before", operation);
|
|
1897
1979
|
|
|
@@ -2203,14 +2285,13 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
2203
2285
|
) {
|
|
2204
2286
|
const operation = "ledger claim joining";
|
|
2205
2287
|
|
|
2206
|
-
const validated = yield*
|
|
2288
|
+
const validated = yield* decodeClaimJoiningRequestType(request).pipe(
|
|
2207
2289
|
Effect.mapError(internalFailure(operation)),
|
|
2208
2290
|
);
|
|
2209
2291
|
|
|
2210
2292
|
yield* hitFailpoint("ledger:claim-joining:before", operation);
|
|
2211
2293
|
|
|
2212
|
-
const claims = yield*
|
|
2213
|
-
operation,
|
|
2294
|
+
const claims = yield* owner.withMutation(
|
|
2214
2295
|
Effect.gen(function* () {
|
|
2215
2296
|
const host = yield* requireSubmission(operation, validated.hostSubmissionId);
|
|
2216
2297
|
|
|
@@ -2230,68 +2311,92 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
2230
2311
|
|
|
2231
2312
|
if (stopped.length > 0) return [];
|
|
2232
2313
|
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
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
|
-
|
|
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`
|
|
2266
2369
|
UPDATE effect_agent_submissions
|
|
2267
2370
|
SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
|
|
2268
2371
|
WHERE submission_id = ${row.submission_id}
|
|
2269
2372
|
RETURNING *, ${definitionJson(sql)} AS agent_digests_json`.pipe(
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2373
|
+
rows.submissions.write,
|
|
2374
|
+
Effect.mapError(internalFailure(operation)),
|
|
2375
|
+
);
|
|
2273
2376
|
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
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
|
+
);
|
|
2284
2387
|
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
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
|
+
}
|
|
2293
2396
|
|
|
2294
|
-
|
|
2397
|
+
return claimed;
|
|
2398
|
+
}),
|
|
2399
|
+
);
|
|
2295
2400
|
}),
|
|
2296
2401
|
);
|
|
2297
2402
|
|
|
@@ -3443,7 +3548,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3443
3548
|
).pipe(Effect.mapError(sqlFailure(operation)));
|
|
3444
3549
|
|
|
3445
3550
|
const decoded = yield* decodeRows(
|
|
3446
|
-
|
|
3551
|
+
SubmissionWorkItems,
|
|
3447
3552
|
"effect_agent_submissions",
|
|
3448
3553
|
"nonterminal_scan",
|
|
3449
3554
|
rows,
|
|
@@ -3463,59 +3568,74 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3463
3568
|
return [decoded, next] as const;
|
|
3464
3569
|
});
|
|
3465
3570
|
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
),
|
|
3479
|
-
"ledger scan nonterminal",
|
|
3480
|
-
);
|
|
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
|
+
);
|
|
3481
3583
|
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
);
|
|
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;
|
|
3487
3588
|
|
|
3488
|
-
|
|
3489
|
-
left.thread_id < right.thread_id
|
|
3490
|
-
? -1
|
|
3491
|
-
: left.thread_id > right.thread_id
|
|
3492
|
-
? 1
|
|
3493
|
-
: left.queue_sequence - right.queue_sequence,
|
|
3494
|
-
);
|
|
3589
|
+
if (active.length > 128) return undefined;
|
|
3495
3590
|
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
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),
|
|
3511
3625
|
),
|
|
3512
3626
|
);
|
|
3513
3627
|
|
|
3628
|
+
const listNonterminal = Effect.filterOrElse(
|
|
3629
|
+
boundedNonterminal,
|
|
3630
|
+
(work) => work !== undefined,
|
|
3631
|
+
() => Stream.runCollect(pagedNonterminal),
|
|
3632
|
+
);
|
|
3633
|
+
|
|
3514
3634
|
const readAbortIntentForSubmission: SubmissionLedger["Service"]["readAbortIntent"] =
|
|
3515
3635
|
Effect.fnUntraced(function* (request) {
|
|
3516
3636
|
const operation = "ledger read abort intent";
|
|
3517
3637
|
|
|
3518
|
-
const validated = yield*
|
|
3638
|
+
const validated = yield* decodeAbortIntentRequestType(request).pipe(
|
|
3519
3639
|
Effect.mapError(internalFailure(operation)),
|
|
3520
3640
|
);
|
|
3521
3641
|
|
|
@@ -3540,9 +3660,9 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3540
3660
|
Effect.fnUntraced(function* (request: RecoverySnapshotRequest) {
|
|
3541
3661
|
const operation = "ledger load recovery snapshot";
|
|
3542
3662
|
|
|
3543
|
-
const validated = yield*
|
|
3544
|
-
|
|
3545
|
-
)
|
|
3663
|
+
const validated = yield* decodeRecoverySnapshotRequestType(request).pipe(
|
|
3664
|
+
Effect.mapError(internalFailure(operation)),
|
|
3665
|
+
);
|
|
3546
3666
|
|
|
3547
3667
|
return yield* state.read(
|
|
3548
3668
|
Effect.gen(function* () {
|
|
@@ -3760,6 +3880,7 @@ const makeServices = Effect.fnUntraced(function* () {
|
|
|
3760
3880
|
beginChildBudgetRelease,
|
|
3761
3881
|
releaseChildBudget,
|
|
3762
3882
|
scanNonterminal,
|
|
3883
|
+
listNonterminal,
|
|
3763
3884
|
loadRecoverySnapshot,
|
|
3764
3885
|
readAbortIntent: readAbortIntentForSubmission,
|
|
3765
3886
|
}),
|
|
@@ -89,6 +89,7 @@ const layoutStatements = [
|
|
|
89
89
|
const layoutObjects = layoutStatements.map((statement, index) => {
|
|
90
90
|
const name = /^CREATE (TABLE|INDEX) ([a-z_][a-z_0-9]*)/.exec(statement);
|
|
91
91
|
|
|
92
|
+
// oxlint-disable-next-line effect/no-untyped-throw, effect/no-native-error -- Module initialization validates developer-authored SQL constants before any storage workflow starts.
|
|
92
93
|
if (name === null) throw new Error("Invalid subscription layout statement");
|
|
93
94
|
|
|
94
95
|
return [name[1].toLowerCase(), name[2], index] as const;
|