@effect-agent/storage-cloudflare 0.1.0-beta.8 → 0.1.0-beta.80
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.d.mts +29 -0
- package/dist/DoMemoryStore.mjs +55 -0
- package/dist/DoMemoryStore.mjs.map +1 -0
- package/dist/DoMessageDeliveryStore.d.mts +17 -0
- package/dist/DoMessageDeliveryStore.mjs +32 -0
- package/dist/DoMessageDeliveryStore.mjs.map +1 -0
- package/dist/DoScheduleStore.d.mts +36 -0
- package/dist/DoScheduleStore.mjs +388 -0
- package/dist/DoScheduleStore.mjs.map +1 -0
- package/dist/DoStorageConfig.d.mts +45 -0
- package/dist/DoStorageConfig.mjs +52 -0
- package/dist/DoStorageConfig.mjs.map +1 -0
- package/dist/DoStorageError-Cmhvl4TQ.d.mts +98 -0
- package/dist/DoStorageError.d.mts +2 -0
- package/dist/DoStorageError.mjs +164 -0
- package/dist/DoStorageError.mjs.map +1 -0
- package/dist/DoStorageFailpoint.d.mts +17 -0
- package/dist/DoStorageFailpoint.mjs +14 -0
- package/dist/DoStorageFailpoint.mjs.map +1 -0
- package/dist/DoStorageFailpointTesting.d.mts +34 -0
- package/dist/DoStorageFailpointTesting.mjs +35 -0
- package/dist/DoStorageFailpointTesting.mjs.map +1 -0
- package/dist/DoStorageVersion-x5OOurvZ.d.mts +12 -0
- package/dist/DoStorageVersion.d.mts +2 -0
- package/dist/DoStorageVersion.mjs +8 -0
- package/dist/DoStorageVersion.mjs.map +1 -0
- package/dist/DoSubmissionLedger.d.mts +23 -0
- package/dist/DoSubmissionLedger.mjs +1849 -0
- package/dist/DoSubmissionLedger.mjs.map +1 -0
- package/dist/DoSubscriptionStore.d.mts +25 -0
- package/dist/DoSubscriptionStore.mjs +183 -0
- package/dist/DoSubscriptionStore.mjs.map +1 -0
- package/dist/DoThreadStore.d.mts +59 -0
- package/dist/DoThreadStore.mjs +465 -0
- package/dist/DoThreadStore.mjs.map +1 -0
- package/dist/MemoryProtocol.d.mts +38160 -0
- package/dist/MemoryProtocol.mjs +219 -0
- package/dist/MemoryProtocol.mjs.map +1 -0
- package/dist/PortProtocol.d.mts +3207 -0
- package/dist/PortProtocol.mjs +176 -0
- package/dist/PortProtocol.mjs.map +1 -0
- package/dist/PortRouting.d.mts +83 -0
- package/dist/PortRouting.mjs +410 -0
- package/dist/PortRouting.mjs.map +1 -0
- package/dist/do-journal-DHqCQeBU.mjs +991 -0
- package/dist/do-journal-DHqCQeBU.mjs.map +1 -0
- package/dist/index.d.mts +14 -1521
- package/dist/index.mjs +14 -3695
- package/dist/migrations-Ba_kOrSx.mjs +287 -0
- package/dist/migrations-Ba_kOrSx.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -45
- package/src/DoMemoryStore.ts +59 -0
- package/src/DoMessageDeliveryStore.ts +53 -0
- package/src/DoScheduleStore.ts +622 -0
- package/src/{errors.ts → DoStorageError.ts} +10 -3
- package/src/DoStorageFailpoint.ts +20 -0
- package/src/{do-storage-failpoint.ts → DoStorageFailpointTesting.ts} +5 -18
- package/src/DoStorageVersion.ts +2 -0
- package/src/{do-ledger.ts → DoSubmissionLedger.ts} +862 -163
- package/src/DoSubscriptionStore.ts +329 -0
- package/src/DoThreadStore.ts +1014 -0
- package/src/MemoryProtocol.ts +355 -0
- package/src/{port-protocol.ts → PortProtocol.ts} +44 -37
- package/src/{routing.ts → PortRouting.ts} +292 -264
- package/src/index.ts +13 -32
- package/src/{do-journal.ts → internal/do-journal.ts} +716 -232
- package/src/internal/message-delivery-schema.ts +24 -0
- package/src/{migrations.ts → internal/migrations.ts} +51 -35
- package/src/internal/recovery-checkpoint-schema.ts +17 -0
- package/dist/index.mjs.map +0 -1
- package/src/do-conversation-store.ts +0 -850
- /package/src/{do-storage-config.ts → DoStorageConfig.ts} +0 -0
|
@@ -1,18 +1,34 @@
|
|
|
1
|
+
import { MessageAdmission } from "@effect-agent/core/Messaging";
|
|
2
|
+
import { EMPTY_TAIL_DIGEST } from "@effect-agent/thread/Digest";
|
|
3
|
+
import {
|
|
4
|
+
ApprovalDecision,
|
|
5
|
+
CanonicalSequence,
|
|
6
|
+
DefinitionDigests,
|
|
7
|
+
Digest,
|
|
8
|
+
PersistedJson,
|
|
9
|
+
WorkerAdmission,
|
|
10
|
+
ProducerEpoch,
|
|
11
|
+
RecordEnvelope,
|
|
12
|
+
SettlementOutcome,
|
|
13
|
+
} from "@effect-agent/thread/Records";
|
|
1
14
|
import {
|
|
2
15
|
AbortCommand,
|
|
3
16
|
AbortIntent,
|
|
17
|
+
AbortIntentRequest,
|
|
4
18
|
AdmissionAdmitted,
|
|
5
19
|
AdmissionConflict,
|
|
20
|
+
AdmissionFence,
|
|
21
|
+
AdmissionGroup,
|
|
22
|
+
AdmissionPolicyError,
|
|
6
23
|
AdmissionNotAdmitted,
|
|
7
24
|
AdmissionRequest,
|
|
25
|
+
SubmissionAdmissionFence,
|
|
8
26
|
AdmissionResult,
|
|
9
27
|
ApprovalConflict,
|
|
10
|
-
ApprovalDecision,
|
|
11
28
|
ApprovalDecisionCommand,
|
|
12
29
|
ApprovalDecisionIntent,
|
|
13
30
|
AttachChildToReservationRequest,
|
|
14
31
|
BeginChildBudgetReleaseRequest,
|
|
15
|
-
CanonicalSequence,
|
|
16
32
|
ChildAttachmentSnapshot,
|
|
17
33
|
ChildBudgetReservationRequest,
|
|
18
34
|
ChildBudgetReservationSnapshot,
|
|
@@ -22,9 +38,6 @@ import {
|
|
|
22
38
|
Claim,
|
|
23
39
|
ClaimJoiningRequest,
|
|
24
40
|
ClaimRequest,
|
|
25
|
-
DefinitionDigests,
|
|
26
|
-
Digest,
|
|
27
|
-
EMPTY_TAIL_DIGEST,
|
|
28
41
|
InputAppliedMarker,
|
|
29
42
|
JoinSnapshot,
|
|
30
43
|
JoinedToHost,
|
|
@@ -39,10 +52,7 @@ import {
|
|
|
39
52
|
OwnershipRenewal,
|
|
40
53
|
OwnershipSnapshot,
|
|
41
54
|
ParentLinkage,
|
|
42
|
-
PersistedJson,
|
|
43
|
-
ProducerEpoch,
|
|
44
55
|
QueueSequence,
|
|
45
|
-
RecordEnvelope,
|
|
46
56
|
RecoverySnapshot,
|
|
47
57
|
RecoverySnapshotRequest,
|
|
48
58
|
ReleaseChildBudgetRequest,
|
|
@@ -54,7 +64,6 @@ import {
|
|
|
54
64
|
Settlement,
|
|
55
65
|
SettlementConflict,
|
|
56
66
|
SettlementFinalization,
|
|
57
|
-
SettlementOutcome,
|
|
58
67
|
SettlementReservation,
|
|
59
68
|
SubmissionLedger,
|
|
60
69
|
SubmissionLookup,
|
|
@@ -62,6 +71,7 @@ import {
|
|
|
62
71
|
SubmissionSnapshot,
|
|
63
72
|
SubmissionState,
|
|
64
73
|
SettlementReservationSnapshot,
|
|
74
|
+
settlementFailureFromRecord,
|
|
65
75
|
SuspendRequest,
|
|
66
76
|
SuspensionReason,
|
|
67
77
|
SuspensionSnapshot,
|
|
@@ -72,28 +82,28 @@ import {
|
|
|
72
82
|
submissionAbortRecordId,
|
|
73
83
|
type ChildSettledOutcome,
|
|
74
84
|
type SuspensionOutcome,
|
|
75
|
-
} from "@effect-agent/
|
|
85
|
+
} from "@effect-agent/thread/SubmissionLedger";
|
|
76
86
|
import { BrowserCrypto } from "@effect/platform-browser";
|
|
77
87
|
import { SqliteClient } from "@effect/sql-sqlite-do";
|
|
78
88
|
import { Clock, Context, Crypto, DateTime, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
79
89
|
import * as SqlClientService from "effect/unstable/sql/SqlClient";
|
|
80
90
|
import type { SqlError } from "effect/unstable/sql/SqlError";
|
|
81
91
|
|
|
82
|
-
import {
|
|
83
|
-
storageConfigLayer,
|
|
84
|
-
storageFailpointLayer,
|
|
85
|
-
type DoStorageInitializationError,
|
|
86
|
-
type DoStorageOptions,
|
|
87
|
-
} from "./do-conversation-store.ts";
|
|
88
|
-
import { decodeRows, initializeDoJournal } from "./do-journal.ts";
|
|
89
|
-
import { DoStorageConfig } from "./do-storage-config.ts";
|
|
90
|
-
import { DoStorageFailpoint } from "./do-storage-failpoint.ts";
|
|
92
|
+
import { DoStorageConfig } from "./DoStorageConfig.ts";
|
|
91
93
|
import {
|
|
92
94
|
DoLedgerError,
|
|
93
95
|
DoStorageCorruptionError,
|
|
94
96
|
DoStorageError,
|
|
95
97
|
type DoStorageFailpointLocation,
|
|
96
|
-
} from "./
|
|
98
|
+
} from "./DoStorageError.ts";
|
|
99
|
+
import { DoStorageFailpoint } from "./DoStorageFailpoint.ts";
|
|
100
|
+
import {
|
|
101
|
+
storageConfigLayer,
|
|
102
|
+
storageFailpointLayer,
|
|
103
|
+
type DoStorageInitializationError,
|
|
104
|
+
type DoStorageOptions,
|
|
105
|
+
} from "./DoThreadStore.ts";
|
|
106
|
+
import { decodeRows, initializeDoJournal } from "./internal/do-journal.ts";
|
|
97
107
|
|
|
98
108
|
type SubmissionId = SubmissionSnapshot["submissionId"];
|
|
99
109
|
|
|
@@ -107,11 +117,16 @@ const BoundedTimestamp = Schema.NonEmptyString.check(Schema.isMaxLength(128));
|
|
|
107
117
|
|
|
108
118
|
const SCAN_PAGE_SIZE = 256;
|
|
109
119
|
const EPOCH_ZERO = Schema.decodeSync(ProducerEpoch)(0);
|
|
120
|
+
const RESUME_IMMEDIATELY: SuspensionOutcome = "resume-immediately";
|
|
121
|
+
const SUSPENDED: SuspensionOutcome = "suspended";
|
|
122
|
+
const NOT_WAITING: ChildSettledOutcome = "not-waiting";
|
|
123
|
+
const STILL_WAITING: ChildSettledOutcome = "still-waiting";
|
|
124
|
+
const WOKEN: ChildSettledOutcome = "woken";
|
|
110
125
|
const MAX_IDENTIFIER_LENGTH = 1_024;
|
|
111
126
|
|
|
112
127
|
class SubmissionRow extends Schema.Class<SubmissionRow>("SubmissionRow")({
|
|
113
128
|
submission_id: BoundedIdentifier,
|
|
114
|
-
|
|
129
|
+
thread_id: BoundedIdentifier,
|
|
115
130
|
queue_sequence: QueueSequence,
|
|
116
131
|
principal: BoundedIdentifier,
|
|
117
132
|
idempotency_key: BoundedIdentifier,
|
|
@@ -134,6 +149,10 @@ class SubmissionRow extends Schema.Class<SubmissionRow>("SubmissionRow")({
|
|
|
134
149
|
unknown_tool_call_ids_json: Schema.NullOr(BoundedStoredText),
|
|
135
150
|
parent_submission_id: Schema.NullOr(BoundedIdentifier),
|
|
136
151
|
parent_tool_call_id: Schema.NullOr(BoundedIdentifier),
|
|
152
|
+
admission_group: Schema.NullOr(AdmissionGroup),
|
|
153
|
+
admission_fence_json: Schema.NullOr(BoundedStoredText),
|
|
154
|
+
worker_admission_json: Schema.NullOr(BoundedStoredText),
|
|
155
|
+
message_admission_json: Schema.NullOr(BoundedStoredText),
|
|
137
156
|
}) {}
|
|
138
157
|
|
|
139
158
|
class ChildReservationRow extends Schema.Class<ChildReservationRow>("ChildReservationRow")({
|
|
@@ -209,13 +228,22 @@ class MaxQueueSequenceRow extends Schema.Class<MaxQueueSequenceRow>("MaxQueueSeq
|
|
|
209
228
|
max_queue_sequence: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
|
|
210
229
|
}) {}
|
|
211
230
|
|
|
231
|
+
class AbortIntentLookupRow extends Schema.Class<AbortIntentLookupRow>("AbortIntentLookupRow")({
|
|
232
|
+
submission_id: BoundedIdentifier,
|
|
233
|
+
abort_submission_id: Schema.NullOr(BoundedIdentifier),
|
|
234
|
+
author: Schema.NullOr(BoundedIdentifier),
|
|
235
|
+
reason: Schema.NullOr(BoundedStoredText),
|
|
236
|
+
requested_at: Schema.NullOr(BoundedTimestamp),
|
|
237
|
+
canonical_record_id: Schema.NullOr(BoundedIdentifier),
|
|
238
|
+
}) {}
|
|
239
|
+
|
|
212
240
|
class CanonicalRecordIdRow extends Schema.Class<CanonicalRecordIdRow>("CanonicalRecordIdRow")({
|
|
213
241
|
record_id: BoundedIdentifier,
|
|
214
242
|
}) {}
|
|
215
243
|
|
|
216
244
|
const SUBMISSION_COLUMNS = `
|
|
217
245
|
submission_id,
|
|
218
|
-
|
|
246
|
+
thread_id,
|
|
219
247
|
queue_sequence,
|
|
220
248
|
principal,
|
|
221
249
|
idempotency_key,
|
|
@@ -237,7 +265,11 @@ const SUBMISSION_COLUMNS = `
|
|
|
237
265
|
unknown_reason,
|
|
238
266
|
unknown_tool_call_ids_json,
|
|
239
267
|
parent_submission_id,
|
|
240
|
-
parent_tool_call_id
|
|
268
|
+
parent_tool_call_id,
|
|
269
|
+
admission_group,
|
|
270
|
+
admission_fence_json,
|
|
271
|
+
worker_admission_json,
|
|
272
|
+
message_admission_json
|
|
241
273
|
`;
|
|
242
274
|
|
|
243
275
|
const CHILD_RESERVATION_COLUMNS = `
|
|
@@ -254,7 +286,7 @@ const CHILD_RESERVATION_COLUMNS = `
|
|
|
254
286
|
released_at
|
|
255
287
|
`;
|
|
256
288
|
|
|
257
|
-
/** The branded ToolCallId schema, reached through the
|
|
289
|
+
/** The branded ToolCallId schema, reached through the thread port so no core import is needed. */
|
|
258
290
|
const ToolCallIdSchema = ApprovalDecisionCommand.fields.toolCallId;
|
|
259
291
|
const ToolCallIdList = Schema.Array(ToolCallIdSchema);
|
|
260
292
|
|
|
@@ -284,10 +316,15 @@ const decodeSuspensionSnapshot = Schema.decodeUnknownEffect(SuspensionSnapshot);
|
|
|
284
316
|
const decodeApprovalDecisionIntent = Schema.decodeUnknownEffect(ApprovalDecisionIntent);
|
|
285
317
|
const decodeUnknownResolutionIntent = Schema.decodeUnknownEffect(UnknownResolutionIntent);
|
|
286
318
|
const decodeParentLinkage = Schema.decodeUnknownEffect(ParentLinkage);
|
|
319
|
+
|
|
287
320
|
const decodeChildReservationSnapshotUnknown = Schema.decodeUnknownEffect(
|
|
288
321
|
ChildBudgetReservationSnapshot,
|
|
289
322
|
);
|
|
323
|
+
|
|
290
324
|
const decodeChildAttachmentSnapshot = Schema.decodeUnknownEffect(ChildAttachmentSnapshot);
|
|
325
|
+
const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
|
|
326
|
+
const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
|
|
327
|
+
const isDoStorageError = Schema.is(DoStorageError);
|
|
291
328
|
|
|
292
329
|
/** Wrap an adapter-internal failure into the port's LedgerError without erasing its tag. */
|
|
293
330
|
const internalFailure =
|
|
@@ -319,6 +356,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
319
356
|
const failpoint = yield* DoStorageFailpoint;
|
|
320
357
|
const sql = yield* SqlClientService.SqlClient;
|
|
321
358
|
const crypto = yield* Crypto.Crypto;
|
|
359
|
+
const admissionFence = yield* SubmissionAdmissionFence;
|
|
322
360
|
const journal = yield* initializeDoJournal(sql, failpoint.hit, config.maxStoredValueBytes);
|
|
323
361
|
|
|
324
362
|
const hitFailpoint = (
|
|
@@ -336,6 +374,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
336
374
|
A,
|
|
337
375
|
E extends
|
|
338
376
|
| AdmissionConflict
|
|
377
|
+
| AdmissionPolicyError
|
|
339
378
|
| ApprovalConflict
|
|
340
379
|
| ChildReservationConflict
|
|
341
380
|
| JoinedToHost
|
|
@@ -351,7 +390,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
351
390
|
.withWriteTransaction(operation)(effect)
|
|
352
391
|
.pipe(
|
|
353
392
|
Effect.mapError((error) =>
|
|
354
|
-
error
|
|
393
|
+
isDoStorageError(error) ? internalFailure(operation)(error) : error,
|
|
355
394
|
),
|
|
356
395
|
);
|
|
357
396
|
|
|
@@ -385,7 +424,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
385
424
|
FROM effect_agent_submissions
|
|
386
425
|
WHERE submission_id = ${submissionId}
|
|
387
426
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
427
|
+
|
|
388
428
|
const decoded = yield* decodeSubmissionRows(operation, submissionId, rows);
|
|
429
|
+
|
|
389
430
|
if (decoded.length > 1) {
|
|
390
431
|
return yield* corruptionFailure(
|
|
391
432
|
operation,
|
|
@@ -394,6 +435,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
394
435
|
"A submission primary key returned more than one row.",
|
|
395
436
|
);
|
|
396
437
|
}
|
|
438
|
+
|
|
397
439
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
398
440
|
});
|
|
399
441
|
|
|
@@ -402,12 +444,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
402
444
|
submissionId: string,
|
|
403
445
|
): Effect.fn.Return<SubmissionRow, LedgerError> {
|
|
404
446
|
const submission = yield* readSubmission(operation, submissionId);
|
|
447
|
+
|
|
405
448
|
if (Option.isNone(submission)) {
|
|
406
449
|
return yield* LedgerError.make({
|
|
407
450
|
operation,
|
|
408
451
|
message: `Unknown submission ${submissionId}.`,
|
|
409
452
|
});
|
|
410
453
|
}
|
|
454
|
+
|
|
411
455
|
return submission.value;
|
|
412
456
|
});
|
|
413
457
|
|
|
@@ -426,12 +470,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
426
470
|
FROM effect_agent_submission_ownership
|
|
427
471
|
WHERE submission_id = ${submissionId}
|
|
428
472
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
473
|
+
|
|
429
474
|
const decoded = yield* decodeRows(
|
|
430
475
|
Schema.Array(OwnershipRow),
|
|
431
476
|
"effect_agent_submission_ownership",
|
|
432
477
|
submissionId,
|
|
433
478
|
rows,
|
|
434
479
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
480
|
+
|
|
435
481
|
if (decoded.length > 1) {
|
|
436
482
|
return yield* corruptionFailure(
|
|
437
483
|
operation,
|
|
@@ -440,23 +486,25 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
440
486
|
"An ownership primary key returned more than one row.",
|
|
441
487
|
);
|
|
442
488
|
}
|
|
489
|
+
|
|
443
490
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
444
491
|
});
|
|
445
492
|
|
|
446
|
-
const
|
|
493
|
+
const threadEpoch = Effect.fn("DoSubmissionLedger.threadEpoch")(function* (
|
|
447
494
|
operation: string,
|
|
448
|
-
|
|
495
|
+
threadId: string,
|
|
449
496
|
): Effect.fn.Return<ProducerEpoch, LedgerError> {
|
|
450
|
-
const
|
|
451
|
-
.
|
|
497
|
+
const threads = yield* journal
|
|
498
|
+
.getThread(threadId)
|
|
452
499
|
.pipe(Effect.mapError(internalFailure(operation)));
|
|
453
|
-
|
|
500
|
+
|
|
501
|
+
return threads.length === 0 ? EPOCH_ZERO : threads[0].producer_epoch;
|
|
454
502
|
});
|
|
455
503
|
|
|
456
504
|
/**
|
|
457
505
|
* Verify inside the surrounding write transaction that the presented token still owns the
|
|
458
506
|
* Submission's lane; a superseded or missing token fails with OwnershipLost carrying the
|
|
459
|
-
*
|
|
507
|
+
* Thread's current producer epoch (DUR-006).
|
|
460
508
|
*/
|
|
461
509
|
const requireOwnership = Effect.fn("DoSubmissionLedger.requireOwnership")(function* (
|
|
462
510
|
operation: string,
|
|
@@ -464,13 +512,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
464
512
|
ownershipToken: string,
|
|
465
513
|
): Effect.fn.Return<OwnershipRow, OwnershipLost | LedgerError> {
|
|
466
514
|
const ownership = yield* readOwnership(operation, submission.submission_id);
|
|
515
|
+
|
|
467
516
|
if (Option.isNone(ownership) || ownership.value.ownership_token !== ownershipToken) {
|
|
468
|
-
const actualEpoch = yield*
|
|
517
|
+
const actualEpoch = yield* threadEpoch(operation, submission.thread_id);
|
|
518
|
+
|
|
469
519
|
const submissionId = yield* Schema.decodeUnknownEffect(
|
|
470
520
|
SubmissionSnapshot.fields.submissionId,
|
|
471
521
|
)(submission.submission_id).pipe(Effect.mapError(internalFailure(operation)));
|
|
522
|
+
|
|
472
523
|
return yield* OwnershipLost.make({ submissionId, actualEpoch });
|
|
473
524
|
}
|
|
525
|
+
|
|
474
526
|
return ownership.value;
|
|
475
527
|
});
|
|
476
528
|
|
|
@@ -489,6 +541,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
489
541
|
),
|
|
490
542
|
),
|
|
491
543
|
);
|
|
544
|
+
|
|
492
545
|
const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
|
|
493
546
|
Effect.mapError((error) =>
|
|
494
547
|
corruptionFailure(
|
|
@@ -499,6 +552,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
499
552
|
),
|
|
500
553
|
),
|
|
501
554
|
);
|
|
555
|
+
|
|
502
556
|
if ((row.parent_submission_id === null) !== (row.parent_tool_call_id === null)) {
|
|
503
557
|
return yield* corruptionFailure(
|
|
504
558
|
operation,
|
|
@@ -507,9 +561,10 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
507
561
|
"A parent linkage must record both the parent Submission and the parent Tool Call.",
|
|
508
562
|
);
|
|
509
563
|
}
|
|
564
|
+
|
|
510
565
|
return yield* decodeSubmissionSnapshotUnknown({
|
|
511
566
|
submissionId: row.submission_id,
|
|
512
|
-
|
|
567
|
+
threadId: row.thread_id,
|
|
513
568
|
queueSequence: row.queue_sequence,
|
|
514
569
|
principal: row.principal,
|
|
515
570
|
idempotencyKey: row.idempotency_key,
|
|
@@ -521,6 +576,28 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
521
576
|
receiptId: row.receipt_id,
|
|
522
577
|
state: row.state,
|
|
523
578
|
createdAt: row.created_at,
|
|
579
|
+
...(row.admission_group === null ? {} : { admissionGroup: row.admission_group }),
|
|
580
|
+
...(row.worker_admission_json === null
|
|
581
|
+
? {}
|
|
582
|
+
: {
|
|
583
|
+
workerAdmission: yield* parseStoredJsonText(row.worker_admission_json).pipe(
|
|
584
|
+
Effect.mapError(internalFailure(operation)),
|
|
585
|
+
),
|
|
586
|
+
}),
|
|
587
|
+
...(row.message_admission_json === null
|
|
588
|
+
? {}
|
|
589
|
+
: {
|
|
590
|
+
messageAdmission: yield* parseStoredJsonText(row.message_admission_json).pipe(
|
|
591
|
+
Effect.mapError(internalFailure(operation)),
|
|
592
|
+
),
|
|
593
|
+
}),
|
|
594
|
+
...(row.admission_fence_json === null
|
|
595
|
+
? {}
|
|
596
|
+
: {
|
|
597
|
+
admissionFence: yield* parseStoredJsonText(row.admission_fence_json).pipe(
|
|
598
|
+
Effect.mapError(internalFailure(operation)),
|
|
599
|
+
),
|
|
600
|
+
}),
|
|
524
601
|
...(row.settled_outcome === null ? {} : { settledOutcome: row.settled_outcome }),
|
|
525
602
|
...(row.ready_at === null ? {} : { readyAt: row.ready_at }),
|
|
526
603
|
...(row.parent_submission_id === null || row.parent_tool_call_id === null
|
|
@@ -561,12 +638,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
561
638
|
FROM effect_agent_settlement_reservations
|
|
562
639
|
WHERE submission_id = ${submissionId}
|
|
563
640
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
641
|
+
|
|
564
642
|
const decoded = yield* decodeRows(
|
|
565
643
|
Schema.Array(ReservationRow),
|
|
566
644
|
"effect_agent_settlement_reservations",
|
|
567
645
|
submissionId,
|
|
568
646
|
rows,
|
|
569
647
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
648
|
+
|
|
570
649
|
if (decoded.length > 1) {
|
|
571
650
|
return yield* corruptionFailure(
|
|
572
651
|
operation,
|
|
@@ -575,6 +654,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
575
654
|
"A settlement reservation primary key returned more than one row.",
|
|
576
655
|
);
|
|
577
656
|
}
|
|
657
|
+
|
|
578
658
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
579
659
|
});
|
|
580
660
|
|
|
@@ -592,12 +672,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
592
672
|
FROM effect_agent_abort_intents
|
|
593
673
|
WHERE submission_id = ${submissionId}
|
|
594
674
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
675
|
+
|
|
595
676
|
const decoded = yield* decodeRows(
|
|
596
677
|
Schema.Array(AbortIntentRow),
|
|
597
678
|
"effect_agent_abort_intents",
|
|
598
679
|
submissionId,
|
|
599
680
|
rows,
|
|
600
681
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
682
|
+
|
|
601
683
|
if (decoded.length > 1) {
|
|
602
684
|
return yield* corruptionFailure(
|
|
603
685
|
operation,
|
|
@@ -606,6 +688,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
606
688
|
"An abort intent primary key returned more than one row.",
|
|
607
689
|
);
|
|
608
690
|
}
|
|
691
|
+
|
|
609
692
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
610
693
|
});
|
|
611
694
|
|
|
@@ -629,6 +712,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
629
712
|
WHERE parent_submission_id = ${parentSubmissionId}
|
|
630
713
|
ORDER BY child_submission_id ASC
|
|
631
714
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
715
|
+
|
|
632
716
|
return yield* decodeRows(
|
|
633
717
|
Schema.Array(ChildSettlementMarkerRow),
|
|
634
718
|
"effect_agent_child_settlements",
|
|
@@ -651,6 +735,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
651
735
|
): Effect.fn.Return<boolean, LedgerError> {
|
|
652
736
|
if (markerChildren.has(childSubmissionId)) return true;
|
|
653
737
|
const childRow = yield* readSubmission(operation, childSubmissionId);
|
|
738
|
+
|
|
654
739
|
return Option.isSome(childRow) && childRow.value.state === "settled";
|
|
655
740
|
});
|
|
656
741
|
|
|
@@ -671,7 +756,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
671
756
|
FROM effect_agent_child_reservations
|
|
672
757
|
WHERE reservation_id = ${reservationId}
|
|
673
758
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
759
|
+
|
|
674
760
|
const decoded = yield* decodeChildReservationRows(operation, reservationId, rows);
|
|
761
|
+
|
|
675
762
|
if (decoded.length > 1) {
|
|
676
763
|
return yield* corruptionFailure(
|
|
677
764
|
operation,
|
|
@@ -680,6 +767,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
680
767
|
"A child reservation primary key returned more than one row.",
|
|
681
768
|
);
|
|
682
769
|
}
|
|
770
|
+
|
|
683
771
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
684
772
|
});
|
|
685
773
|
|
|
@@ -695,11 +783,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
695
783
|
WHERE parent_submission_id = ${parentSubmissionId}
|
|
696
784
|
AND parent_tool_call_id = ${parentToolCallId}
|
|
697
785
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
786
|
+
|
|
698
787
|
const decoded = yield* decodeChildReservationRows(
|
|
699
788
|
operation,
|
|
700
789
|
`${parentSubmissionId}/${parentToolCallId}`,
|
|
701
790
|
rows,
|
|
702
791
|
);
|
|
792
|
+
|
|
703
793
|
if (decoded.length > 1) {
|
|
704
794
|
return yield* corruptionFailure(
|
|
705
795
|
operation,
|
|
@@ -708,6 +798,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
708
798
|
"A parent Tool Call returned more than one child reservation.",
|
|
709
799
|
);
|
|
710
800
|
}
|
|
801
|
+
|
|
711
802
|
return decoded.length === 0 ? Option.none() : Option.some(decoded[0]);
|
|
712
803
|
},
|
|
713
804
|
);
|
|
@@ -725,13 +816,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
725
816
|
row.reservation_id,
|
|
726
817
|
error.message,
|
|
727
818
|
);
|
|
819
|
+
|
|
728
820
|
const allocation = yield* parseStoredJsonText(row.allocation_json).pipe(
|
|
729
821
|
Effect.mapError(rowFailure),
|
|
730
822
|
);
|
|
823
|
+
|
|
731
824
|
const accounting =
|
|
732
825
|
row.accounting_json === null
|
|
733
826
|
? undefined
|
|
734
827
|
: yield* parseStoredJsonText(row.accounting_json).pipe(Effect.mapError(rowFailure));
|
|
828
|
+
|
|
735
829
|
return yield* decodeChildReservationSnapshotUnknown({
|
|
736
830
|
reservationId: row.reservation_id,
|
|
737
831
|
parentSubmissionId: row.parent_submission_id,
|
|
@@ -763,6 +857,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
763
857
|
WHERE submission_id = ${submissionId}
|
|
764
858
|
ORDER BY tool_call_id ASC
|
|
765
859
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
860
|
+
|
|
766
861
|
return yield* decodeRows(
|
|
767
862
|
Schema.Array(ApprovalDecisionRow),
|
|
768
863
|
"effect_agent_approval_decisions",
|
|
@@ -810,6 +905,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
810
905
|
WHERE submission_id = ${submissionId}
|
|
811
906
|
ORDER BY tool_call_id ASC
|
|
812
907
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
908
|
+
|
|
813
909
|
return yield* decodeRows(
|
|
814
910
|
Schema.Array(UnknownResolutionRow),
|
|
815
911
|
"effect_agent_unknown_resolutions",
|
|
@@ -834,6 +930,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
834
930
|
),
|
|
835
931
|
),
|
|
836
932
|
);
|
|
933
|
+
|
|
837
934
|
return yield* decodeUnknownResolutionIntent({
|
|
838
935
|
submissionId: row.submission_id,
|
|
839
936
|
toolCallId: row.tool_call_id,
|
|
@@ -860,6 +957,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
860
957
|
submission: SubmissionRow,
|
|
861
958
|
): Effect.fn.Return<ReadonlyArray<typeof ToolCallIdSchema.Type>, LedgerError> {
|
|
862
959
|
if (submission.unknown_tool_call_ids_json === null) return [];
|
|
960
|
+
|
|
863
961
|
return yield* decodeToolCallIdsText(submission.unknown_tool_call_ids_json).pipe(
|
|
864
962
|
Effect.mapError((error) =>
|
|
865
963
|
corruptionFailure(
|
|
@@ -880,22 +978,25 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
880
978
|
*/
|
|
881
979
|
const canonicalAbortRecordId = Effect.fn("DoSubmissionLedger.canonicalAbortRecordId")(function* (
|
|
882
980
|
operation: string,
|
|
883
|
-
|
|
981
|
+
threadId: string,
|
|
884
982
|
submissionId: SubmissionId,
|
|
885
983
|
): Effect.fn.Return<string | undefined, LedgerError> {
|
|
886
984
|
const recordId = submissionAbortRecordId(submissionId);
|
|
985
|
+
|
|
887
986
|
const rows = yield* sql<Record<string, unknown>>`
|
|
888
987
|
SELECT record_id
|
|
889
988
|
FROM effect_agent_canonical_records
|
|
890
|
-
WHERE
|
|
989
|
+
WHERE thread_id = ${threadId}
|
|
891
990
|
AND record_id = ${recordId}
|
|
892
991
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
992
|
+
|
|
893
993
|
const decoded = yield* decodeRows(
|
|
894
994
|
Schema.Array(CanonicalRecordIdRow),
|
|
895
995
|
"effect_agent_canonical_records",
|
|
896
|
-
`${
|
|
996
|
+
`${threadId}/${recordId}`,
|
|
897
997
|
rows,
|
|
898
998
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
999
|
+
|
|
899
1000
|
return decoded.length === 0 ? undefined : recordId;
|
|
900
1001
|
});
|
|
901
1002
|
|
|
@@ -907,9 +1008,10 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
907
1008
|
): Effect.fn.Return<AbortIntent, LedgerError> {
|
|
908
1009
|
const canonicalRecordId = yield* canonicalAbortRecordId(
|
|
909
1010
|
operation,
|
|
910
|
-
submission.
|
|
1011
|
+
submission.thread_id,
|
|
911
1012
|
submissionId,
|
|
912
1013
|
);
|
|
1014
|
+
|
|
913
1015
|
return yield* decodeAbortIntent({
|
|
914
1016
|
submissionId: row.submission_id,
|
|
915
1017
|
author: row.author,
|
|
@@ -938,47 +1040,92 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
938
1040
|
const admit: SubmissionLedger["Service"]["admit"] = Effect.fn("DoSubmissionLedger.admit")(
|
|
939
1041
|
function* (request: AdmissionRequest) {
|
|
940
1042
|
const operation = "ledger admit";
|
|
1043
|
+
|
|
941
1044
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AdmissionRequest))(
|
|
942
1045
|
request,
|
|
943
1046
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1047
|
+
|
|
944
1048
|
const inputJson = yield* encodePersistedJsonText(validated.inputPayload).pipe(
|
|
945
1049
|
Effect.mapError(internalFailure(operation)),
|
|
946
1050
|
);
|
|
1051
|
+
|
|
947
1052
|
// The platform's ~2 MB per-value bound, refused typed BEFORE any durable mutation
|
|
948
1053
|
// (resource-limits gate; oversized payloads are the designed R2 overflow path).
|
|
949
1054
|
yield* journal
|
|
950
1055
|
.checkValueBound(operation, inputJson)
|
|
951
1056
|
.pipe(Effect.mapError(internalFailure(operation)));
|
|
1057
|
+
|
|
1058
|
+
const workerAdmissionJson =
|
|
1059
|
+
validated.workerAdmission === undefined
|
|
1060
|
+
? null
|
|
1061
|
+
: yield* Schema.encodeEffect(Schema.fromJsonString(WorkerAdmission))(
|
|
1062
|
+
validated.workerAdmission,
|
|
1063
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1064
|
+
|
|
1065
|
+
if (
|
|
1066
|
+
workerAdmissionJson !== null &&
|
|
1067
|
+
new TextEncoder().encode(workerAdmissionJson).byteLength > config.maxStoredValueBytes
|
|
1068
|
+
) {
|
|
1069
|
+
return yield* LedgerError.make({
|
|
1070
|
+
operation,
|
|
1071
|
+
message: "Worker admission metadata exceeds the stored value bound",
|
|
1072
|
+
});
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const messageAdmissionJson =
|
|
1076
|
+
validated.messageAdmission === undefined
|
|
1077
|
+
? null
|
|
1078
|
+
: yield* Schema.encodeEffect(Schema.fromJsonString(MessageAdmission))(
|
|
1079
|
+
validated.messageAdmission,
|
|
1080
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1081
|
+
|
|
1082
|
+
if (
|
|
1083
|
+
messageAdmissionJson !== null &&
|
|
1084
|
+
new TextEncoder().encode(messageAdmissionJson).byteLength > config.maxStoredValueBytes
|
|
1085
|
+
) {
|
|
1086
|
+
return yield* LedgerError.make({
|
|
1087
|
+
operation,
|
|
1088
|
+
message: "Message admission metadata exceeds the stored value bound",
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
|
|
952
1092
|
const agentDigestsJson = yield* encodeDefinitionDigestsText(validated.agentDigests).pipe(
|
|
953
1093
|
Effect.mapError(internalFailure(operation)),
|
|
954
1094
|
);
|
|
955
|
-
|
|
1095
|
+
|
|
1096
|
+
// Routable Submission identity (D-P6-5): `{uuidv7}:{threadId}`. The cross-DO
|
|
956
1097
|
// routing layer parses ITS OWN minted format (split at the first ":") to address
|
|
957
|
-
// submissionId-only operations to the owning
|
|
1098
|
+
// submissionId-only operations to the owning Thread Object; the id stays opaque
|
|
958
1099
|
// to every other component, exactly like DN's `submission-{uuid}` prefix.
|
|
959
|
-
const mintedSubmissionId = `${yield* mintUuid(operation)}:${validated.
|
|
1100
|
+
const mintedSubmissionId = `${yield* mintUuid(operation)}:${validated.threadId}`;
|
|
1101
|
+
|
|
960
1102
|
if (mintedSubmissionId.length > MAX_IDENTIFIER_LENGTH) {
|
|
961
1103
|
return yield* LedgerError.make({
|
|
962
1104
|
operation,
|
|
963
1105
|
message:
|
|
964
1106
|
`A routable Submission identity of ${mintedSubmissionId.length} characters exceeds ` +
|
|
965
|
-
`the ${MAX_IDENTIFIER_LENGTH}-character ledger row bound; shorten the
|
|
1107
|
+
`the ${MAX_IDENTIFIER_LENGTH}-character ledger row bound; shorten the Thread identity.`,
|
|
966
1108
|
});
|
|
967
1109
|
}
|
|
968
1110
|
const mintedReceiptId = `receipt-${yield* mintUuid(operation)}`;
|
|
1111
|
+
|
|
969
1112
|
yield* hitFailpoint("ledger:admit:before", operation);
|
|
1113
|
+
|
|
970
1114
|
const result = yield* inWriteTransaction(
|
|
971
1115
|
operation,
|
|
972
1116
|
Effect.gen(function* () {
|
|
973
|
-
const keyRowKey = `${validated.
|
|
1117
|
+
const keyRowKey = `${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`;
|
|
1118
|
+
|
|
974
1119
|
const existingRows = yield* sql<Record<string, unknown>>`
|
|
975
1120
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
976
1121
|
FROM effect_agent_submissions
|
|
977
|
-
WHERE
|
|
1122
|
+
WHERE thread_id = ${validated.threadId}
|
|
978
1123
|
AND principal = ${validated.principal}
|
|
979
1124
|
AND idempotency_key = ${validated.idempotencyKey}
|
|
980
1125
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1126
|
+
|
|
981
1127
|
const existing = yield* decodeSubmissionRows(operation, keyRowKey, existingRows);
|
|
1128
|
+
|
|
982
1129
|
if (existing.length > 1) {
|
|
983
1130
|
return yield* corruptionFailure(
|
|
984
1131
|
operation,
|
|
@@ -996,15 +1143,61 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
996
1143
|
existing[0].parent_tool_call_id === null
|
|
997
1144
|
: existing[0].parent_submission_id === validated.parentLinkage.parentSubmissionId &&
|
|
998
1145
|
existing[0].parent_tool_call_id === validated.parentLinkage.parentToolCallId;
|
|
1146
|
+
|
|
999
1147
|
if (existing[0].input_digest !== validated.inputDigest || !sameLinkage) {
|
|
1000
1148
|
return yield* AdmissionConflict.make({
|
|
1001
|
-
|
|
1149
|
+
threadId: validated.threadId,
|
|
1002
1150
|
principal: validated.principal,
|
|
1003
1151
|
idempotencyKey: validated.idempotencyKey,
|
|
1004
1152
|
existingInputDigest: existing[0].input_digest,
|
|
1005
1153
|
attemptedInputDigest: validated.inputDigest,
|
|
1006
1154
|
});
|
|
1007
1155
|
}
|
|
1156
|
+
|
|
1157
|
+
const retainedWorkerAdmission =
|
|
1158
|
+
existing[0].worker_admission_json === null
|
|
1159
|
+
? undefined
|
|
1160
|
+
: yield* Schema.decodeEffect(Schema.fromJsonString(WorkerAdmission))(
|
|
1161
|
+
existing[0].worker_admission_json,
|
|
1162
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1163
|
+
|
|
1164
|
+
const retainedMessageAdmission =
|
|
1165
|
+
existing[0].message_admission_json === null
|
|
1166
|
+
? undefined
|
|
1167
|
+
: yield* Schema.decodeEffect(Schema.fromJsonString(MessageAdmission))(
|
|
1168
|
+
existing[0].message_admission_json,
|
|
1169
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1170
|
+
|
|
1171
|
+
const retainedFence =
|
|
1172
|
+
existing[0].admission_fence_json === null
|
|
1173
|
+
? undefined
|
|
1174
|
+
: yield* Schema.decodeEffect(Schema.fromJsonString(AdmissionFence))(
|
|
1175
|
+
existing[0].admission_fence_json,
|
|
1176
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1177
|
+
|
|
1178
|
+
if (
|
|
1179
|
+
(existing[0].admission_group ?? undefined) !== validated.admissionGroup ||
|
|
1180
|
+
!Schema.toEquivalence(Schema.optional(WorkerAdmission))(
|
|
1181
|
+
retainedWorkerAdmission,
|
|
1182
|
+
validated.workerAdmission,
|
|
1183
|
+
) ||
|
|
1184
|
+
!Schema.toEquivalence(Schema.optional(MessageAdmission))(
|
|
1185
|
+
retainedMessageAdmission,
|
|
1186
|
+
validated.messageAdmission,
|
|
1187
|
+
) ||
|
|
1188
|
+
!Schema.toEquivalence(Schema.optional(AdmissionFence))(
|
|
1189
|
+
retainedFence,
|
|
1190
|
+
validated.admissionFence,
|
|
1191
|
+
)
|
|
1192
|
+
)
|
|
1193
|
+
return yield* AdmissionConflict.make({
|
|
1194
|
+
threadId: validated.threadId,
|
|
1195
|
+
principal: validated.principal,
|
|
1196
|
+
idempotencyKey: validated.idempotencyKey,
|
|
1197
|
+
existingInputDigest: existing[0].input_digest,
|
|
1198
|
+
attemptedInputDigest: validated.inputDigest,
|
|
1199
|
+
});
|
|
1200
|
+
|
|
1008
1201
|
return yield* decodeAdmissionResult({
|
|
1009
1202
|
submissionId: existing[0].submission_id,
|
|
1010
1203
|
receiptId: existing[0].receipt_id,
|
|
@@ -1014,26 +1207,78 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1014
1207
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1015
1208
|
}
|
|
1016
1209
|
|
|
1210
|
+
// The first accepted input fixes ordinary/worker lane identity atomically with admission.
|
|
1211
|
+
// Canonical origin materialization can lag admission; a log scan cannot fence that race.
|
|
1212
|
+
const firstRows = yield* sql<Record<string, unknown>>`
|
|
1213
|
+
SELECT worker_admission_json FROM effect_agent_submissions
|
|
1214
|
+
WHERE thread_id=${validated.threadId} ORDER BY queue_sequence LIMIT 1
|
|
1215
|
+
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1216
|
+
|
|
1217
|
+
const first = yield* Schema.decodeUnknownEffect(
|
|
1218
|
+
Schema.Array(
|
|
1219
|
+
Schema.Struct({
|
|
1220
|
+
worker_admission_json: Schema.NullOr(BoundedStoredText),
|
|
1221
|
+
}),
|
|
1222
|
+
),
|
|
1223
|
+
)(firstRows).pipe(Effect.mapError(internalFailure(operation)));
|
|
1224
|
+
|
|
1225
|
+
if (first[0] !== undefined) {
|
|
1226
|
+
const previous =
|
|
1227
|
+
first[0].worker_admission_json === null
|
|
1228
|
+
? undefined
|
|
1229
|
+
: yield* Schema.decodeEffect(Schema.fromJsonString(WorkerAdmission))(
|
|
1230
|
+
first[0].worker_admission_json,
|
|
1231
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1232
|
+
|
|
1233
|
+
if (
|
|
1234
|
+
!Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin))(
|
|
1235
|
+
previous?.origin,
|
|
1236
|
+
validated.workerAdmission?.origin,
|
|
1237
|
+
)
|
|
1238
|
+
)
|
|
1239
|
+
return yield* AdmissionPolicyError.make({
|
|
1240
|
+
reason: "refused",
|
|
1241
|
+
code: "worker-origin-conflict",
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
yield* admissionFence.check(validated);
|
|
1246
|
+
if (validated.admissionGroup !== undefined) {
|
|
1247
|
+
const occupied = yield* sql<Record<string, unknown>>`
|
|
1248
|
+
SELECT submission_id FROM effect_agent_submissions
|
|
1249
|
+
WHERE thread_id=${validated.threadId} AND admission_group=${validated.admissionGroup} AND state<>'settled' LIMIT 1
|
|
1250
|
+
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1251
|
+
|
|
1252
|
+
if (occupied.length > 0)
|
|
1253
|
+
return yield* AdmissionPolicyError.make({
|
|
1254
|
+
reason: "occupied",
|
|
1255
|
+
code: "admission-group",
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1017
1259
|
const maxRows = yield* sql<Record<string, unknown>>`
|
|
1018
1260
|
SELECT COALESCE(MAX(queue_sequence), 0) AS max_queue_sequence
|
|
1019
1261
|
FROM effect_agent_submissions
|
|
1020
|
-
WHERE
|
|
1262
|
+
WHERE thread_id = ${validated.threadId}
|
|
1021
1263
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1264
|
+
|
|
1022
1265
|
const decodedMax = yield* decodeRows(
|
|
1023
1266
|
Schema.Array(MaxQueueSequenceRow),
|
|
1024
1267
|
"effect_agent_submissions",
|
|
1025
|
-
validated.
|
|
1268
|
+
validated.threadId,
|
|
1026
1269
|
maxRows,
|
|
1027
1270
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1271
|
+
|
|
1028
1272
|
const queueSequence = yield* decodeQueueSequence(
|
|
1029
1273
|
(decodedMax[0]?.max_queue_sequence ?? 0) + 1,
|
|
1030
1274
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1275
|
+
|
|
1031
1276
|
const now = yield* currentInstant;
|
|
1032
1277
|
|
|
1033
1278
|
yield* sql`
|
|
1034
1279
|
INSERT INTO effect_agent_submissions (
|
|
1035
1280
|
submission_id,
|
|
1036
|
-
|
|
1281
|
+
thread_id,
|
|
1037
1282
|
queue_sequence,
|
|
1038
1283
|
principal,
|
|
1039
1284
|
idempotency_key,
|
|
@@ -1046,10 +1291,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1046
1291
|
state,
|
|
1047
1292
|
created_at,
|
|
1048
1293
|
parent_submission_id,
|
|
1049
|
-
parent_tool_call_id
|
|
1294
|
+
parent_tool_call_id,
|
|
1295
|
+
admission_group,
|
|
1296
|
+
admission_fence_json,
|
|
1297
|
+
worker_admission_json,
|
|
1298
|
+
message_admission_json
|
|
1050
1299
|
) VALUES (
|
|
1051
1300
|
${mintedSubmissionId},
|
|
1052
|
-
${validated.
|
|
1301
|
+
${validated.threadId},
|
|
1053
1302
|
${queueSequence},
|
|
1054
1303
|
${validated.principal},
|
|
1055
1304
|
${validated.idempotencyKey},
|
|
@@ -1062,7 +1311,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1062
1311
|
'admitted',
|
|
1063
1312
|
${now.iso},
|
|
1064
1313
|
${validated.parentLinkage?.parentSubmissionId ?? null},
|
|
1065
|
-
${validated.parentLinkage?.parentToolCallId ?? null}
|
|
1314
|
+
${validated.parentLinkage?.parentToolCallId ?? null},
|
|
1315
|
+
${validated.admissionGroup ?? null},
|
|
1316
|
+
${validated.admissionFence === undefined ? null : JSON.stringify(validated.admissionFence)},
|
|
1317
|
+
${workerAdmissionJson},
|
|
1318
|
+
${messageAdmissionJson}
|
|
1066
1319
|
)
|
|
1067
1320
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1068
1321
|
|
|
@@ -1075,7 +1328,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1075
1328
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1076
1329
|
}),
|
|
1077
1330
|
);
|
|
1331
|
+
|
|
1078
1332
|
yield* hitFailpoint("ledger:admit:after", operation);
|
|
1333
|
+
|
|
1079
1334
|
return result;
|
|
1080
1335
|
},
|
|
1081
1336
|
);
|
|
@@ -1084,16 +1339,20 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1084
1339
|
"DoSubmissionLedger.markReady",
|
|
1085
1340
|
)(function* (request: MarkReadyRequest) {
|
|
1086
1341
|
const operation = "ledger mark ready";
|
|
1342
|
+
|
|
1087
1343
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkReadyRequest))(
|
|
1088
1344
|
request,
|
|
1089
1345
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1346
|
+
|
|
1090
1347
|
yield* hitFailpoint("ledger:mark-ready:before", operation);
|
|
1091
1348
|
yield* inWriteTransaction(
|
|
1092
1349
|
operation,
|
|
1093
1350
|
Effect.gen(function* () {
|
|
1094
1351
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1352
|
+
|
|
1095
1353
|
if (submission.state !== "admitted") return;
|
|
1096
1354
|
const now = yield* currentInstant;
|
|
1355
|
+
|
|
1097
1356
|
yield* sql`
|
|
1098
1357
|
UPDATE effect_agent_submissions
|
|
1099
1358
|
SET state = 'ready', ready_at = ${now.iso}
|
|
@@ -1107,40 +1366,48 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1107
1366
|
const lookup: SubmissionLedger["Service"]["lookup"] = Effect.fn("DoSubmissionLedger.lookup")(
|
|
1108
1367
|
function* (request: SubmissionLookup) {
|
|
1109
1368
|
const operation = "ledger lookup";
|
|
1369
|
+
|
|
1110
1370
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SubmissionLookup))(
|
|
1111
1371
|
request,
|
|
1112
1372
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1373
|
+
|
|
1113
1374
|
if (validated._tag === "SubmissionLookupById") {
|
|
1114
1375
|
const row = yield* readSubmission(operation, validated.submissionId);
|
|
1376
|
+
|
|
1115
1377
|
if (Option.isNone(row)) return Option.none();
|
|
1378
|
+
|
|
1116
1379
|
return Option.some(yield* decodeSubmissionSnapshot(operation, row.value));
|
|
1117
1380
|
}
|
|
1381
|
+
|
|
1118
1382
|
const rows = yield* sql<Record<string, unknown>>`
|
|
1119
1383
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
1120
1384
|
FROM effect_agent_submissions
|
|
1121
|
-
WHERE
|
|
1385
|
+
WHERE thread_id = ${validated.threadId}
|
|
1122
1386
|
AND principal = ${validated.principal}
|
|
1123
1387
|
AND idempotency_key = ${validated.idempotencyKey}
|
|
1124
1388
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1389
|
+
|
|
1125
1390
|
const decoded = yield* decodeSubmissionRows(
|
|
1126
1391
|
operation,
|
|
1127
|
-
`${validated.
|
|
1392
|
+
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1128
1393
|
rows,
|
|
1129
1394
|
);
|
|
1395
|
+
|
|
1130
1396
|
if (decoded.length > 1) {
|
|
1131
1397
|
return yield* corruptionFailure(
|
|
1132
1398
|
operation,
|
|
1133
1399
|
"effect_agent_submissions",
|
|
1134
|
-
`${validated.
|
|
1400
|
+
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1135
1401
|
"An admission idempotency key returned more than one row.",
|
|
1136
1402
|
);
|
|
1137
1403
|
}
|
|
1138
1404
|
if (decoded.length === 0) return Option.none();
|
|
1405
|
+
|
|
1139
1406
|
return Option.some(yield* decodeSubmissionSnapshot(operation, decoded[0]));
|
|
1140
1407
|
},
|
|
1141
1408
|
);
|
|
1142
1409
|
|
|
1143
|
-
// This LOCAL facet is the authoritative owner of every
|
|
1410
|
+
// This LOCAL facet is the authoritative owner of every Thread stored in this Durable
|
|
1144
1411
|
// Object, so the key-scoped read IS the admission truth and the tri-state degenerates to
|
|
1145
1412
|
// NotAdmitted or Admitted (SUB-031). `AdmissionIndeterminate` becomes real one layer out:
|
|
1146
1413
|
// the WP2 routed decorator answers it when the OWNING Durable Object is unreachable.
|
|
@@ -1148,30 +1415,35 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1148
1415
|
"DoSubmissionLedger.resolveAdmission",
|
|
1149
1416
|
)(function* (request: SubmissionLookupByKey) {
|
|
1150
1417
|
const operation = "ledger resolve admission";
|
|
1418
|
+
|
|
1151
1419
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SubmissionLookupByKey))(
|
|
1152
1420
|
request,
|
|
1153
1421
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1422
|
+
|
|
1154
1423
|
const rows = yield* sql<Record<string, unknown>>`
|
|
1155
1424
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
1156
1425
|
FROM effect_agent_submissions
|
|
1157
|
-
WHERE
|
|
1426
|
+
WHERE thread_id = ${validated.threadId}
|
|
1158
1427
|
AND principal = ${validated.principal}
|
|
1159
1428
|
AND idempotency_key = ${validated.idempotencyKey}
|
|
1160
1429
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1430
|
+
|
|
1161
1431
|
const decoded = yield* decodeSubmissionRows(
|
|
1162
1432
|
operation,
|
|
1163
|
-
`${validated.
|
|
1433
|
+
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1164
1434
|
rows,
|
|
1165
1435
|
);
|
|
1436
|
+
|
|
1166
1437
|
if (decoded.length > 1) {
|
|
1167
1438
|
return yield* corruptionFailure(
|
|
1168
1439
|
operation,
|
|
1169
1440
|
"effect_agent_submissions",
|
|
1170
|
-
`${validated.
|
|
1441
|
+
`${validated.threadId}/${validated.principal}/${validated.idempotencyKey}`,
|
|
1171
1442
|
"An admission idempotency key returned more than one row.",
|
|
1172
1443
|
);
|
|
1173
1444
|
}
|
|
1174
1445
|
if (decoded.length === 0) return AdmissionNotAdmitted.make();
|
|
1446
|
+
|
|
1175
1447
|
return AdmissionAdmitted.make({
|
|
1176
1448
|
submission: yield* decodeSubmissionSnapshot(operation, decoded[0]),
|
|
1177
1449
|
});
|
|
@@ -1180,71 +1452,82 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1180
1452
|
const claim: SubmissionLedger["Service"]["claim"] = Effect.fn("DoSubmissionLedger.claim")(
|
|
1181
1453
|
function* (request: ClaimRequest) {
|
|
1182
1454
|
const operation = "ledger claim";
|
|
1455
|
+
|
|
1183
1456
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimRequest))(
|
|
1184
1457
|
request,
|
|
1185
1458
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1459
|
+
|
|
1186
1460
|
const attemptId = `attempt-${yield* mintUuid(operation)}`;
|
|
1187
1461
|
const ownershipToken = `owner-${yield* mintUuid(operation)}`;
|
|
1462
|
+
|
|
1188
1463
|
yield* hitFailpoint("ledger:claim:before", operation);
|
|
1464
|
+
|
|
1189
1465
|
const claimed = yield* inWriteTransaction(
|
|
1190
1466
|
operation,
|
|
1191
1467
|
Effect.gen(function* () {
|
|
1192
1468
|
const headRows = yield* sql<Record<string, unknown>>`
|
|
1193
1469
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
1194
1470
|
FROM effect_agent_submissions
|
|
1195
|
-
WHERE
|
|
1471
|
+
WHERE thread_id = ${validated.threadId}
|
|
1196
1472
|
AND state <> 'settled'
|
|
1197
1473
|
ORDER BY queue_sequence ASC
|
|
1198
1474
|
LIMIT 1
|
|
1199
1475
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1200
|
-
|
|
1476
|
+
|
|
1477
|
+
const heads = yield* decodeSubmissionRows(operation, validated.threadId, headRows);
|
|
1478
|
+
|
|
1201
1479
|
if (heads.length === 0) return Option.none<Claim>();
|
|
1202
1480
|
const head = heads[0];
|
|
1203
1481
|
|
|
1204
|
-
//
|
|
1205
|
-
//
|
|
1206
|
-
//
|
|
1482
|
+
// Unknown work is claimable only for a durably requested abort: the coordinator
|
|
1483
|
+
// cleans up children and settles without replaying ordinary Tools. Read the intent
|
|
1484
|
+
// in this claim transaction; retain uncertainty evidence and all ownership fencing.
|
|
1207
1485
|
if (
|
|
1208
1486
|
head.state === "joining" ||
|
|
1209
1487
|
head.state === "joined" ||
|
|
1210
1488
|
head.state === "suspended" ||
|
|
1211
|
-
head.state === "unknown"
|
|
1489
|
+
(head.state === "unknown" &&
|
|
1490
|
+
Option.isNone(yield* readAbortIntent(operation, head.submission_id)))
|
|
1212
1491
|
) {
|
|
1213
1492
|
return Option.none<Claim>();
|
|
1214
1493
|
}
|
|
1215
1494
|
|
|
1216
1495
|
const now = yield* currentInstant;
|
|
1217
1496
|
const ownership = yield* readOwnership(operation, head.submission_id);
|
|
1497
|
+
|
|
1218
1498
|
if (Option.isSome(ownership)) {
|
|
1219
1499
|
const expiresAt = yield* timestampMillis(
|
|
1220
1500
|
operation,
|
|
1221
1501
|
head.submission_id,
|
|
1222
1502
|
)(ownership.value.lease_expires_at);
|
|
1503
|
+
|
|
1223
1504
|
// A live lease blocks every new claim; expiry alone only revokes the liveness
|
|
1224
1505
|
// assumption — correctness stays with producer-epoch fencing (D5). In DC a live
|
|
1225
1506
|
// lease under another token can only come from an evicted incarnation.
|
|
1226
1507
|
if (expiresAt > now.millis) return Option.none<Claim>();
|
|
1227
1508
|
}
|
|
1228
1509
|
|
|
1229
|
-
// Bump the
|
|
1230
|
-
// Attempt is fenced out of canonical appends (DUR-006). A
|
|
1510
|
+
// Bump the Thread's producer epoch atomically with the claim so every stale
|
|
1511
|
+
// Attempt is fenced out of canonical appends (DUR-006). A Thread that was
|
|
1231
1512
|
// never materialized (eviction between admission and materialization) is created
|
|
1232
1513
|
// here so recovery can claim first and re-materialize idempotently at this epoch.
|
|
1233
|
-
const
|
|
1234
|
-
.
|
|
1514
|
+
const threads = yield* journal
|
|
1515
|
+
.getThread(head.thread_id)
|
|
1235
1516
|
.pipe(Effect.mapError(internalFailure(operation)));
|
|
1517
|
+
|
|
1236
1518
|
let producerEpoch: number;
|
|
1237
|
-
|
|
1519
|
+
|
|
1520
|
+
if (threads.length === 0) {
|
|
1238
1521
|
producerEpoch = 1;
|
|
1239
1522
|
yield* sql`
|
|
1240
|
-
INSERT INTO
|
|
1241
|
-
|
|
1523
|
+
INSERT INTO effect_agent_threads (
|
|
1524
|
+
thread_id,
|
|
1242
1525
|
created_at,
|
|
1243
1526
|
tail_sequence,
|
|
1244
1527
|
tail_digest,
|
|
1245
1528
|
producer_epoch
|
|
1246
1529
|
) VALUES (
|
|
1247
|
-
${head.
|
|
1530
|
+
${head.thread_id},
|
|
1248
1531
|
${now.iso},
|
|
1249
1532
|
0,
|
|
1250
1533
|
${EMPTY_TAIL_DIGEST},
|
|
@@ -1252,15 +1535,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1252
1535
|
)
|
|
1253
1536
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1254
1537
|
} else {
|
|
1255
|
-
producerEpoch =
|
|
1538
|
+
producerEpoch = threads[0].producer_epoch + 1;
|
|
1256
1539
|
yield* sql`
|
|
1257
|
-
UPDATE
|
|
1540
|
+
UPDATE effect_agent_threads
|
|
1258
1541
|
SET producer_epoch = ${producerEpoch}
|
|
1259
|
-
WHERE
|
|
1542
|
+
WHERE thread_id = ${head.thread_id}
|
|
1260
1543
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1261
1544
|
}
|
|
1262
1545
|
|
|
1263
1546
|
const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
|
|
1547
|
+
|
|
1264
1548
|
yield* sql`
|
|
1265
1549
|
INSERT INTO effect_agent_submission_ownership (
|
|
1266
1550
|
submission_id,
|
|
@@ -1289,14 +1573,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1289
1573
|
INSERT INTO effect_agent_attempts (
|
|
1290
1574
|
attempt_id,
|
|
1291
1575
|
submission_id,
|
|
1292
|
-
|
|
1576
|
+
thread_id,
|
|
1293
1577
|
owner_producer_id,
|
|
1294
1578
|
producer_epoch,
|
|
1295
1579
|
claimed_at
|
|
1296
1580
|
) VALUES (
|
|
1297
1581
|
${attemptId},
|
|
1298
1582
|
${head.submission_id},
|
|
1299
|
-
${head.
|
|
1583
|
+
${head.thread_id},
|
|
1300
1584
|
${validated.producerId},
|
|
1301
1585
|
${producerEpoch},
|
|
1302
1586
|
${now.iso}
|
|
@@ -1321,6 +1605,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1321
1605
|
),
|
|
1322
1606
|
),
|
|
1323
1607
|
);
|
|
1608
|
+
|
|
1324
1609
|
return Option.some(
|
|
1325
1610
|
yield* decodeClaim({
|
|
1326
1611
|
submissionId: head.submission_id,
|
|
@@ -1333,7 +1618,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1333
1618
|
);
|
|
1334
1619
|
}),
|
|
1335
1620
|
);
|
|
1621
|
+
|
|
1336
1622
|
yield* hitFailpoint("ledger:claim:after", operation);
|
|
1623
|
+
|
|
1337
1624
|
return claimed;
|
|
1338
1625
|
},
|
|
1339
1626
|
);
|
|
@@ -1342,29 +1629,37 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1342
1629
|
"DoSubmissionLedger.renewOwnership",
|
|
1343
1630
|
)(function* (request: RenewOwnershipRequest) {
|
|
1344
1631
|
const operation = "ledger renew ownership";
|
|
1632
|
+
|
|
1345
1633
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RenewOwnershipRequest))(
|
|
1346
1634
|
request,
|
|
1347
1635
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1636
|
+
|
|
1348
1637
|
yield* hitFailpoint("ledger:renew:before", operation);
|
|
1638
|
+
|
|
1349
1639
|
const renewal = yield* inWriteTransaction(
|
|
1350
1640
|
operation,
|
|
1351
1641
|
Effect.gen(function* () {
|
|
1352
1642
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1643
|
+
|
|
1353
1644
|
yield* requireOwnership(operation, submission, validated.ownershipToken);
|
|
1354
1645
|
const now = yield* currentInstant;
|
|
1355
1646
|
const leaseExpiresAt = new Date(now.millis + config.ownershipLeaseDuration).toISOString();
|
|
1647
|
+
|
|
1356
1648
|
yield* sql`
|
|
1357
1649
|
UPDATE effect_agent_submission_ownership
|
|
1358
1650
|
SET lease_expires_at = ${leaseExpiresAt}
|
|
1359
1651
|
WHERE submission_id = ${validated.submissionId}
|
|
1360
1652
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1653
|
+
|
|
1361
1654
|
return yield* decodeOwnershipRenewal({
|
|
1362
1655
|
ownershipToken: validated.ownershipToken,
|
|
1363
1656
|
leaseExpiresAt,
|
|
1364
1657
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1365
1658
|
}),
|
|
1366
1659
|
);
|
|
1660
|
+
|
|
1367
1661
|
yield* hitFailpoint("ledger:renew:after", operation);
|
|
1662
|
+
|
|
1368
1663
|
return renewal;
|
|
1369
1664
|
});
|
|
1370
1665
|
|
|
@@ -1372,14 +1667,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1372
1667
|
"DoSubmissionLedger.releaseOwnership",
|
|
1373
1668
|
)(function* (request: ReleaseOwnershipRequest) {
|
|
1374
1669
|
const operation = "ledger release ownership";
|
|
1670
|
+
|
|
1375
1671
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseOwnershipRequest))(
|
|
1376
1672
|
request,
|
|
1377
1673
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1674
|
+
|
|
1378
1675
|
yield* hitFailpoint("ledger:release:before", operation);
|
|
1379
1676
|
yield* inWriteTransaction(
|
|
1380
1677
|
operation,
|
|
1381
1678
|
Effect.gen(function* () {
|
|
1382
1679
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1680
|
+
|
|
1383
1681
|
yield* requireOwnership(operation, submission, validated.ownershipToken);
|
|
1384
1682
|
yield* sql`
|
|
1385
1683
|
DELETE FROM effect_agent_submission_ownership
|
|
@@ -1401,14 +1699,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1401
1699
|
"DoSubmissionLedger.markInputApplied",
|
|
1402
1700
|
)(function* (request: MarkInputAppliedRequest) {
|
|
1403
1701
|
const operation = "ledger mark input applied";
|
|
1702
|
+
|
|
1404
1703
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkInputAppliedRequest))(
|
|
1405
1704
|
request,
|
|
1406
1705
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1706
|
+
|
|
1407
1707
|
yield* hitFailpoint("ledger:mark-input-applied:before", operation);
|
|
1408
1708
|
yield* inWriteTransaction(
|
|
1409
1709
|
operation,
|
|
1410
1710
|
Effect.gen(function* () {
|
|
1411
1711
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1712
|
+
|
|
1412
1713
|
yield* requireOwnership(operation, submission, validated.ownershipToken);
|
|
1413
1714
|
if (submission.input_applied_record_id !== null) {
|
|
1414
1715
|
if (
|
|
@@ -1417,6 +1718,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1417
1718
|
) {
|
|
1418
1719
|
return;
|
|
1419
1720
|
}
|
|
1721
|
+
|
|
1420
1722
|
return yield* corruptionFailure(
|
|
1421
1723
|
operation,
|
|
1422
1724
|
"effect_agent_submissions",
|
|
@@ -1444,34 +1746,41 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1444
1746
|
"DoSubmissionLedger.reserveSettlement",
|
|
1445
1747
|
)(function* (request: SettlementReservation) {
|
|
1446
1748
|
const operation = "ledger reserve settlement";
|
|
1749
|
+
|
|
1447
1750
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementReservation))(
|
|
1448
1751
|
request,
|
|
1449
1752
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1753
|
+
|
|
1450
1754
|
const recordJson = yield* encodeRecordEnvelopeText(validated.record).pipe(
|
|
1451
1755
|
Effect.mapError(internalFailure(operation)),
|
|
1452
1756
|
);
|
|
1757
|
+
|
|
1453
1758
|
// The reserved record is appended canonically later; refuse over-bound payloads typed
|
|
1454
1759
|
// before the reservation row exists.
|
|
1455
1760
|
yield* journal
|
|
1456
1761
|
.checkValueBound(operation, recordJson)
|
|
1457
1762
|
.pipe(Effect.mapError(internalFailure(operation)));
|
|
1458
1763
|
yield* hitFailpoint("ledger:reserve-settlement:before", operation);
|
|
1764
|
+
|
|
1459
1765
|
const reserved = yield* inWriteTransaction(
|
|
1460
1766
|
operation,
|
|
1461
1767
|
Effect.gen(function* () {
|
|
1462
1768
|
const existing = yield* readReservation(operation, validated.submissionId);
|
|
1769
|
+
|
|
1463
1770
|
if (Option.isSome(existing)) {
|
|
1464
1771
|
const identical =
|
|
1465
1772
|
existing.value.settlement_id === validated.settlementId &&
|
|
1466
1773
|
existing.value.outcome === validated.outcome &&
|
|
1467
1774
|
existing.value.record_digest === validated.recordDigest &&
|
|
1468
1775
|
existing.value.record_json === recordJson;
|
|
1776
|
+
|
|
1469
1777
|
if (!identical) {
|
|
1470
1778
|
return yield* SettlementConflict.make({
|
|
1471
1779
|
submissionId: validated.submissionId,
|
|
1472
1780
|
existingOutcome: existing.value.outcome,
|
|
1473
1781
|
});
|
|
1474
1782
|
}
|
|
1783
|
+
|
|
1475
1784
|
const record = yield* decodeRecordEnvelopeText(existing.value.record_json).pipe(
|
|
1476
1785
|
Effect.mapError((error) =>
|
|
1477
1786
|
corruptionFailure(
|
|
@@ -1482,6 +1791,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1482
1791
|
),
|
|
1483
1792
|
),
|
|
1484
1793
|
);
|
|
1794
|
+
|
|
1485
1795
|
return ReservedSettlement.make({
|
|
1486
1796
|
submissionId: validated.submissionId,
|
|
1487
1797
|
settlementId: validated.settlementId,
|
|
@@ -1493,6 +1803,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1493
1803
|
}
|
|
1494
1804
|
|
|
1495
1805
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1806
|
+
|
|
1496
1807
|
if (submission.state === "settled") {
|
|
1497
1808
|
if (submission.settled_outcome === null) {
|
|
1498
1809
|
return yield* corruptionFailure(
|
|
@@ -1502,6 +1813,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1502
1813
|
"A settled Submission carries no terminal outcome.",
|
|
1503
1814
|
);
|
|
1504
1815
|
}
|
|
1816
|
+
|
|
1505
1817
|
return yield* SettlementConflict.make({
|
|
1506
1818
|
submissionId: validated.submissionId,
|
|
1507
1819
|
existingOutcome: submission.settled_outcome,
|
|
@@ -1516,13 +1828,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1516
1828
|
// ABORTED settlement (`terminalizing` is the same pass's crash replay). Every other
|
|
1517
1829
|
// reservation stays fenced by the target lane's live ownership.
|
|
1518
1830
|
let queuedAbortSettlement = false;
|
|
1831
|
+
|
|
1519
1832
|
if (
|
|
1520
1833
|
validated.outcome === "aborted" &&
|
|
1521
1834
|
(submission.state === "ready" || submission.state === "terminalizing")
|
|
1522
1835
|
) {
|
|
1523
1836
|
const abortIntent = yield* readAbortIntent(operation, validated.submissionId);
|
|
1837
|
+
|
|
1524
1838
|
if (Option.isSome(abortIntent)) {
|
|
1525
1839
|
const ownership = yield* readOwnership(operation, validated.submissionId);
|
|
1840
|
+
|
|
1526
1841
|
queuedAbortSettlement = Option.isNone(ownership);
|
|
1527
1842
|
}
|
|
1528
1843
|
}
|
|
@@ -1531,6 +1846,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1531
1846
|
}
|
|
1532
1847
|
}
|
|
1533
1848
|
const now = yield* currentInstant;
|
|
1849
|
+
|
|
1534
1850
|
yield* sql`
|
|
1535
1851
|
INSERT INTO effect_agent_settlement_reservations (
|
|
1536
1852
|
submission_id,
|
|
@@ -1555,6 +1871,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1555
1871
|
SET state = 'terminalizing'
|
|
1556
1872
|
WHERE submission_id = ${validated.submissionId}
|
|
1557
1873
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1874
|
+
|
|
1558
1875
|
return ReservedSettlement.make({
|
|
1559
1876
|
submissionId: validated.submissionId,
|
|
1560
1877
|
settlementId: validated.settlementId,
|
|
@@ -1565,56 +1882,161 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1565
1882
|
});
|
|
1566
1883
|
}),
|
|
1567
1884
|
);
|
|
1885
|
+
|
|
1568
1886
|
yield* hitFailpoint("ledger:reserve-settlement:after", operation);
|
|
1887
|
+
|
|
1569
1888
|
return reserved;
|
|
1570
1889
|
});
|
|
1571
1890
|
|
|
1891
|
+
const validateFinalization = Effect.fn("DoSubmissionLedger.validateFinalization")(function* (
|
|
1892
|
+
validated: SettlementFinalization,
|
|
1893
|
+
reservation: Option.Option<ReservationRow>,
|
|
1894
|
+
) {
|
|
1895
|
+
const operation = "ledger finalize settlement";
|
|
1896
|
+
|
|
1897
|
+
if (Option.isNone(reservation)) {
|
|
1898
|
+
return yield* LedgerError.make({
|
|
1899
|
+
operation,
|
|
1900
|
+
message: `No settlement reservation exists for submission ${validated.submissionId}.`,
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1903
|
+
if (reservation.value.settlement_id !== validated.settlementId) {
|
|
1904
|
+
return yield* SettlementConflict.make({
|
|
1905
|
+
submissionId: validated.submissionId,
|
|
1906
|
+
existingOutcome: reservation.value.outcome,
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
const reservationRecord = yield* decodeRecordEnvelopeText(reservation.value.record_json).pipe(
|
|
1911
|
+
Effect.mapError((error) =>
|
|
1912
|
+
corruptionFailure(
|
|
1913
|
+
operation,
|
|
1914
|
+
"effect_agent_settlement_reservations",
|
|
1915
|
+
validated.submissionId,
|
|
1916
|
+
error.message,
|
|
1917
|
+
),
|
|
1918
|
+
),
|
|
1919
|
+
);
|
|
1920
|
+
|
|
1921
|
+
const settlementFailure = settlementFailureFromRecord(reservationRecord);
|
|
1922
|
+
|
|
1923
|
+
if ((reservation.value.outcome === "failed") !== (settlementFailure !== undefined)) {
|
|
1924
|
+
return yield* corruptionFailure(
|
|
1925
|
+
operation,
|
|
1926
|
+
"effect_agent_settlement_reservations",
|
|
1927
|
+
validated.submissionId,
|
|
1928
|
+
"The reserved outcome and canonical failure diagnostic disagree.",
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
return { reservation: reservation.value, settlementFailure };
|
|
1933
|
+
});
|
|
1934
|
+
|
|
1935
|
+
const replayFinalization = Effect.fn("DoSubmissionLedger.replayFinalization")(function* (
|
|
1936
|
+
validated: SettlementFinalization,
|
|
1937
|
+
submission: SubmissionRow,
|
|
1938
|
+
{ reservation, settlementFailure }: Effect.Success<ReturnType<typeof validateFinalization>>,
|
|
1939
|
+
) {
|
|
1940
|
+
const operation = "ledger finalize settlement";
|
|
1941
|
+
|
|
1942
|
+
if (reservation.finalized_at === null) {
|
|
1943
|
+
return yield* corruptionFailure(
|
|
1944
|
+
operation,
|
|
1945
|
+
"effect_agent_settlement_reservations",
|
|
1946
|
+
validated.submissionId,
|
|
1947
|
+
"A settled Submission's reservation carries no finalization timestamp.",
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
return yield* decodeSettlement({
|
|
1952
|
+
submissionId: validated.submissionId,
|
|
1953
|
+
settlementId: validated.settlementId,
|
|
1954
|
+
receiptId: submission.receipt_id,
|
|
1955
|
+
outcome: reservation.outcome,
|
|
1956
|
+
...(settlementFailure === undefined ? {} : { failure: settlementFailure }),
|
|
1957
|
+
settledAt: reservation.finalized_at,
|
|
1958
|
+
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1959
|
+
});
|
|
1960
|
+
|
|
1572
1961
|
const finalizeSettlement: SubmissionLedger["Service"]["finalizeSettlement"] = Effect.fn(
|
|
1573
1962
|
"DoSubmissionLedger.finalizeSettlement",
|
|
1574
1963
|
)(function* (request: SettlementFinalization) {
|
|
1575
1964
|
const operation = "ledger finalize settlement";
|
|
1965
|
+
|
|
1576
1966
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementFinalization))(
|
|
1577
1967
|
request,
|
|
1578
1968
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1969
|
+
|
|
1579
1970
|
yield* hitFailpoint("ledger:finalize-settlement:before", operation);
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1971
|
+
|
|
1972
|
+
// A single statement captures the settled row and its immutable reservation together.
|
|
1973
|
+
// A miss does not authorize finalization: the write path re-reads under its transaction.
|
|
1974
|
+
const replayRows = yield* sql<Record<string, unknown>>`
|
|
1975
|
+
SELECT submission.*, reservation.settlement_id, reservation.outcome,
|
|
1976
|
+
reservation.record_id, reservation.record_json, reservation.record_digest,
|
|
1977
|
+
reservation.reserved_at, reservation.finalized_at
|
|
1978
|
+
FROM effect_agent_submissions AS submission
|
|
1979
|
+
INNER JOIN effect_agent_settlement_reservations AS reservation
|
|
1980
|
+
ON reservation.submission_id = submission.submission_id
|
|
1981
|
+
WHERE submission.submission_id = ${validated.submissionId} AND submission.state = 'settled'
|
|
1982
|
+
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1983
|
+
|
|
1984
|
+
if (replayRows.length > 1) {
|
|
1985
|
+
return yield* corruptionFailure(
|
|
1986
|
+
operation,
|
|
1987
|
+
"effect_agent_submissions",
|
|
1988
|
+
validated.submissionId,
|
|
1989
|
+
"A submission primary key returned more than one row.",
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
const replayRow = replayRows[0];
|
|
1993
|
+
|
|
1994
|
+
if (replayRow !== undefined) {
|
|
1995
|
+
const reservations = yield* decodeRows(
|
|
1996
|
+
Schema.Array(ReservationRow),
|
|
1997
|
+
"effect_agent_settlement_reservations",
|
|
1998
|
+
validated.submissionId,
|
|
1999
|
+
replayRows,
|
|
2000
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2001
|
+
|
|
2002
|
+
const state = yield* validateFinalization(validated, Option.fromUndefinedOr(reservations[0]));
|
|
2003
|
+
|
|
2004
|
+
const submission = yield* Schema.decodeUnknownEffect(SubmissionRow)(replayRow).pipe(
|
|
2005
|
+
Effect.mapError((error) =>
|
|
2006
|
+
corruptionFailure(
|
|
2007
|
+
operation,
|
|
2008
|
+
"effect_agent_submissions",
|
|
2009
|
+
validated.submissionId,
|
|
2010
|
+
error.message,
|
|
2011
|
+
),
|
|
2012
|
+
),
|
|
2013
|
+
);
|
|
2014
|
+
|
|
2015
|
+
const settlement = yield* replayFinalization(validated, submission, state);
|
|
2016
|
+
|
|
2017
|
+
yield* hitFailpoint("ledger:finalize-settlement:after", operation);
|
|
2018
|
+
|
|
2019
|
+
return settlement;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
const settlement = yield* inWriteTransaction(
|
|
2023
|
+
operation,
|
|
2024
|
+
Effect.gen(function* () {
|
|
2025
|
+
const state = yield* validateFinalization(
|
|
2026
|
+
validated,
|
|
2027
|
+
yield* readReservation(operation, validated.submissionId),
|
|
2028
|
+
);
|
|
2029
|
+
|
|
2030
|
+
const { reservation, settlementFailure } = state;
|
|
1596
2031
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
operation,
|
|
1601
|
-
"effect_agent_settlement_reservations",
|
|
1602
|
-
validated.submissionId,
|
|
1603
|
-
"A settled Submission's reservation carries no finalization timestamp.",
|
|
1604
|
-
);
|
|
1605
|
-
}
|
|
1606
|
-
return yield* decodeSettlement({
|
|
1607
|
-
submissionId: validated.submissionId,
|
|
1608
|
-
settlementId: validated.settlementId,
|
|
1609
|
-
receiptId: submission.receipt_id,
|
|
1610
|
-
outcome: reservation.value.outcome,
|
|
1611
|
-
settledAt: reservation.value.finalized_at,
|
|
1612
|
-
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1613
|
-
}
|
|
2032
|
+
|
|
2033
|
+
if (submission.state === "settled")
|
|
2034
|
+
return yield* replayFinalization(validated, submission, state);
|
|
1614
2035
|
const now = yield* currentInstant;
|
|
2036
|
+
|
|
1615
2037
|
yield* sql`
|
|
1616
2038
|
UPDATE effect_agent_submissions
|
|
1617
|
-
SET state = 'settled', settled_outcome = ${reservation.
|
|
2039
|
+
SET state = 'settled', settled_outcome = ${reservation.outcome}
|
|
1618
2040
|
WHERE submission_id = ${validated.submissionId}
|
|
1619
2041
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1620
2042
|
yield* sql`
|
|
@@ -1626,16 +2048,20 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1626
2048
|
DELETE FROM effect_agent_submission_ownership
|
|
1627
2049
|
WHERE submission_id = ${validated.submissionId}
|
|
1628
2050
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2051
|
+
|
|
1629
2052
|
return yield* decodeSettlement({
|
|
1630
2053
|
submissionId: validated.submissionId,
|
|
1631
2054
|
settlementId: validated.settlementId,
|
|
1632
2055
|
receiptId: submission.receipt_id,
|
|
1633
|
-
outcome: reservation.
|
|
2056
|
+
outcome: reservation.outcome,
|
|
2057
|
+
...(settlementFailure === undefined ? {} : { failure: settlementFailure }),
|
|
1634
2058
|
settledAt: now.iso,
|
|
1635
2059
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1636
2060
|
}),
|
|
1637
2061
|
);
|
|
2062
|
+
|
|
1638
2063
|
yield* hitFailpoint("ledger:finalize-settlement:after", operation);
|
|
2064
|
+
|
|
1639
2065
|
return settlement;
|
|
1640
2066
|
});
|
|
1641
2067
|
|
|
@@ -1643,14 +2069,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1643
2069
|
"DoSubmissionLedger.requestAbort",
|
|
1644
2070
|
)(function* (request: AbortCommand) {
|
|
1645
2071
|
const operation = "ledger request abort";
|
|
2072
|
+
|
|
1646
2073
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AbortCommand))(request).pipe(
|
|
1647
2074
|
Effect.mapError(internalFailure(operation)),
|
|
1648
2075
|
);
|
|
2076
|
+
|
|
1649
2077
|
yield* hitFailpoint("ledger:request-abort:before", operation);
|
|
2078
|
+
|
|
1650
2079
|
const intent = yield* inWriteTransaction(
|
|
1651
2080
|
operation,
|
|
1652
2081
|
Effect.gen(function* () {
|
|
1653
2082
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2083
|
+
|
|
1654
2084
|
if (submission.state === "settled") {
|
|
1655
2085
|
if (submission.settled_outcome === null) {
|
|
1656
2086
|
return yield* corruptionFailure(
|
|
@@ -1660,6 +2090,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1660
2090
|
"A settled Submission carries no terminal outcome.",
|
|
1661
2091
|
);
|
|
1662
2092
|
}
|
|
2093
|
+
|
|
1663
2094
|
return yield* SettlementConflict.make({
|
|
1664
2095
|
submissionId: validated.submissionId,
|
|
1665
2096
|
existingOutcome: submission.settled_outcome,
|
|
@@ -1677,15 +2108,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1677
2108
|
"A joined Submission carries no host linkage.",
|
|
1678
2109
|
);
|
|
1679
2110
|
}
|
|
2111
|
+
|
|
1680
2112
|
const hostSubmissionId = yield* decodeSubmissionId(
|
|
1681
2113
|
submission.joined_host_submission_id,
|
|
1682
2114
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2115
|
+
|
|
1683
2116
|
return yield* JoinedToHost.make({
|
|
1684
2117
|
submissionId: validated.submissionId,
|
|
1685
2118
|
hostSubmissionId,
|
|
1686
2119
|
});
|
|
1687
2120
|
}
|
|
1688
2121
|
const existing = yield* readAbortIntent(operation, validated.submissionId);
|
|
2122
|
+
|
|
1689
2123
|
if (Option.isSome(existing)) {
|
|
1690
2124
|
return yield* abortIntentFromRow(
|
|
1691
2125
|
operation,
|
|
@@ -1695,6 +2129,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1695
2129
|
);
|
|
1696
2130
|
}
|
|
1697
2131
|
const now = yield* currentInstant;
|
|
2132
|
+
|
|
1698
2133
|
yield* sql`
|
|
1699
2134
|
INSERT INTO effect_agent_abort_intents (
|
|
1700
2135
|
submission_id,
|
|
@@ -1708,11 +2143,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1708
2143
|
${now.iso}
|
|
1709
2144
|
)
|
|
1710
2145
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2146
|
+
|
|
1711
2147
|
const canonicalRecordId = yield* canonicalAbortRecordId(
|
|
1712
2148
|
operation,
|
|
1713
|
-
submission.
|
|
2149
|
+
submission.thread_id,
|
|
1714
2150
|
validated.submissionId,
|
|
1715
2151
|
);
|
|
2152
|
+
|
|
1716
2153
|
return yield* decodeAbortIntent({
|
|
1717
2154
|
submissionId: validated.submissionId,
|
|
1718
2155
|
author: validated.author,
|
|
@@ -1722,7 +2159,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1722
2159
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1723
2160
|
}),
|
|
1724
2161
|
);
|
|
2162
|
+
|
|
1725
2163
|
yield* hitFailpoint("ledger:request-abort:after", operation);
|
|
2164
|
+
|
|
1726
2165
|
return intent;
|
|
1727
2166
|
});
|
|
1728
2167
|
|
|
@@ -1730,31 +2169,38 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1730
2169
|
"DoSubmissionLedger.claimJoining",
|
|
1731
2170
|
)(function* (request: ClaimJoiningRequest) {
|
|
1732
2171
|
const operation = "ledger claim joining";
|
|
2172
|
+
|
|
1733
2173
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimJoiningRequest))(
|
|
1734
2174
|
request,
|
|
1735
2175
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2176
|
+
|
|
1736
2177
|
yield* hitFailpoint("ledger:claim-joining:before", operation);
|
|
2178
|
+
|
|
1737
2179
|
const claims = yield* inWriteTransaction(
|
|
1738
2180
|
operation,
|
|
1739
2181
|
Effect.gen(function* () {
|
|
1740
2182
|
const host = yield* requireSubmission(operation, validated.hostSubmissionId);
|
|
1741
|
-
|
|
2183
|
+
|
|
2184
|
+
if (host.thread_id !== validated.threadId) {
|
|
1742
2185
|
return yield* LedgerError.make({
|
|
1743
2186
|
operation,
|
|
1744
|
-
message: `Host submission ${validated.hostSubmissionId} does not belong to
|
|
2187
|
+
message: `Host submission ${validated.hostSubmissionId} does not belong to thread ${validated.threadId}.`,
|
|
1745
2188
|
});
|
|
1746
2189
|
}
|
|
1747
2190
|
// The host Attempt already owns the lane; no epoch bump happens here (plan §2.5).
|
|
1748
2191
|
yield* requireOwnership(operation, host, validated.ownershipToken);
|
|
2192
|
+
|
|
1749
2193
|
const laterRows = yield* sql<Record<string, unknown>>`
|
|
1750
2194
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
1751
2195
|
FROM effect_agent_submissions
|
|
1752
|
-
WHERE
|
|
2196
|
+
WHERE thread_id = ${validated.threadId}
|
|
1753
2197
|
AND queue_sequence > ${host.queue_sequence}
|
|
1754
2198
|
ORDER BY queue_sequence ASC
|
|
1755
2199
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1756
|
-
|
|
2200
|
+
|
|
2201
|
+
const later = yield* decodeSubmissionRows(operation, validated.threadId, laterRows);
|
|
1757
2202
|
const claimed: Array<JoiningClaim> = [];
|
|
2203
|
+
|
|
1758
2204
|
for (const row of later) {
|
|
1759
2205
|
if (claimed.length >= validated.maxCount) break;
|
|
1760
2206
|
// Rows already claimed by THIS host extend its contiguous prefix and are skipped;
|
|
@@ -1777,6 +2223,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1777
2223
|
SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
|
|
1778
2224
|
WHERE submission_id = ${row.submission_id}
|
|
1779
2225
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2226
|
+
|
|
1780
2227
|
const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
|
|
1781
2228
|
Effect.mapError((error) =>
|
|
1782
2229
|
corruptionFailure(
|
|
@@ -1787,6 +2234,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1787
2234
|
),
|
|
1788
2235
|
),
|
|
1789
2236
|
);
|
|
2237
|
+
|
|
1790
2238
|
claimed.push(
|
|
1791
2239
|
yield* decodeJoiningClaim({
|
|
1792
2240
|
submissionId: row.submission_id,
|
|
@@ -1795,10 +2243,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1795
2243
|
}).pipe(Effect.mapError(internalFailure(operation))),
|
|
1796
2244
|
);
|
|
1797
2245
|
}
|
|
1798
|
-
|
|
2246
|
+
|
|
2247
|
+
return claimed;
|
|
1799
2248
|
}),
|
|
1800
2249
|
);
|
|
2250
|
+
|
|
1801
2251
|
yield* hitFailpoint("ledger:claim-joining:after", operation);
|
|
2252
|
+
|
|
1802
2253
|
return claims;
|
|
1803
2254
|
});
|
|
1804
2255
|
|
|
@@ -1806,14 +2257,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1806
2257
|
"DoSubmissionLedger.markJoined",
|
|
1807
2258
|
)(function* (request: MarkJoinedRequest) {
|
|
1808
2259
|
const operation = "ledger mark joined";
|
|
2260
|
+
|
|
1809
2261
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkJoinedRequest))(
|
|
1810
2262
|
request,
|
|
1811
2263
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2264
|
+
|
|
1812
2265
|
yield* hitFailpoint("ledger:mark-joined:before", operation);
|
|
1813
2266
|
yield* inWriteTransaction(
|
|
1814
2267
|
operation,
|
|
1815
2268
|
Effect.gen(function* () {
|
|
1816
2269
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2270
|
+
|
|
1817
2271
|
if (submission.joined_host_submission_id === null) {
|
|
1818
2272
|
return yield* LedgerError.make({
|
|
1819
2273
|
operation,
|
|
@@ -1821,6 +2275,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1821
2275
|
});
|
|
1822
2276
|
}
|
|
1823
2277
|
const host = yield* requireSubmission(operation, submission.joined_host_submission_id);
|
|
2278
|
+
|
|
1824
2279
|
// The lane is host-owned: the presented token must own the HOST's ownership period,
|
|
1825
2280
|
// which also lets a later host Attempt repair a lost marker from history (DUR-016).
|
|
1826
2281
|
yield* requireOwnership(operation, host, validated.ownershipToken);
|
|
@@ -1831,6 +2286,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1831
2286
|
) {
|
|
1832
2287
|
return;
|
|
1833
2288
|
}
|
|
2289
|
+
|
|
1834
2290
|
return yield* corruptionFailure(
|
|
1835
2291
|
operation,
|
|
1836
2292
|
"effect_agent_submissions",
|
|
@@ -1861,14 +2317,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1861
2317
|
"DoSubmissionLedger.revertJoining",
|
|
1862
2318
|
)(function* (request: RevertJoiningRequest) {
|
|
1863
2319
|
const operation = "ledger revert joining";
|
|
2320
|
+
|
|
1864
2321
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RevertJoiningRequest))(
|
|
1865
2322
|
request,
|
|
1866
2323
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2324
|
+
|
|
1867
2325
|
yield* hitFailpoint("ledger:revert-joining:before", operation);
|
|
1868
2326
|
yield* inWriteTransaction(
|
|
1869
2327
|
operation,
|
|
1870
2328
|
Effect.gen(function* () {
|
|
1871
2329
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2330
|
+
|
|
1872
2331
|
// Idempotent and recovery-only: only a still-`joining` Submission reverts; an
|
|
1873
2332
|
// already-joined (or already-reverted) Submission is a no-op (DUR-016).
|
|
1874
2333
|
if (submission.state !== "joining") return;
|
|
@@ -1885,17 +2344,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1885
2344
|
const suspend: SubmissionLedger["Service"]["suspend"] = Effect.fn("DoSubmissionLedger.suspend")(
|
|
1886
2345
|
function* (request: SuspendRequest) {
|
|
1887
2346
|
const operation = "ledger suspend";
|
|
2347
|
+
|
|
1888
2348
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SuspendRequest))(
|
|
1889
2349
|
request,
|
|
1890
2350
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2351
|
+
|
|
1891
2352
|
const reasonJson = yield* encodeSuspensionReasonText(validated.reason).pipe(
|
|
1892
2353
|
Effect.mapError(internalFailure(operation)),
|
|
1893
2354
|
);
|
|
2355
|
+
|
|
1894
2356
|
yield* hitFailpoint("ledger:suspend:before", operation);
|
|
2357
|
+
|
|
1895
2358
|
const outcome = yield* inWriteTransaction(
|
|
1896
2359
|
operation,
|
|
1897
2360
|
Effect.gen(function* () {
|
|
1898
2361
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2362
|
+
|
|
1899
2363
|
if (submission.state === "settled") {
|
|
1900
2364
|
if (submission.settled_outcome === null) {
|
|
1901
2365
|
return yield* corruptionFailure(
|
|
@@ -1905,6 +2369,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1905
2369
|
"A settled Submission carries no terminal outcome.",
|
|
1906
2370
|
);
|
|
1907
2371
|
}
|
|
2372
|
+
|
|
1908
2373
|
return yield* SettlementConflict.make({
|
|
1909
2374
|
submissionId: validated.submissionId,
|
|
1910
2375
|
existingOutcome: submission.settled_outcome,
|
|
@@ -1913,6 +2378,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1913
2378
|
// An exact terminal outcome is already reserved (DUR-011); suspension would
|
|
1914
2379
|
// contradict it, so the reservation wins.
|
|
1915
2380
|
const reservation = yield* readReservation(operation, validated.submissionId);
|
|
2381
|
+
|
|
1916
2382
|
if (Option.isSome(reservation)) {
|
|
1917
2383
|
return yield* SettlementConflict.make({
|
|
1918
2384
|
submissionId: validated.submissionId,
|
|
@@ -1923,35 +2389,39 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1923
2389
|
// A covering event that raced ahead of the suspend transaction resumes the caller
|
|
1924
2390
|
// immediately WITHOUT releasing the lane (plan §2.6, §12). For WaitingForChild the
|
|
1925
2391
|
// covering evidence is EITHER a locally settled child row OR a durable cross-store
|
|
1926
|
-
// notification marker: parent and child
|
|
2392
|
+
// notification marker: parent and child Threads live in different Durable
|
|
1927
2393
|
// Objects, and the port contract requires that a child settlement reported (via
|
|
1928
2394
|
// `recordChildSettled` → marker) before this suspend commits is observed here.
|
|
1929
2395
|
if (validated.reason._tag === "ApprovalPending") {
|
|
1930
2396
|
const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
1931
2397
|
const decided = new Set(decisions.map((row) => row.tool_call_id));
|
|
2398
|
+
|
|
1932
2399
|
if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) {
|
|
1933
|
-
return
|
|
2400
|
+
return RESUME_IMMEDIATELY;
|
|
1934
2401
|
}
|
|
1935
2402
|
} else {
|
|
1936
2403
|
const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
|
|
1937
2404
|
const markerChildren = new Set(markers.map((row) => row.child_submission_id));
|
|
1938
2405
|
let allSettled = true;
|
|
2406
|
+
|
|
1939
2407
|
for (const child of validated.reason.children) {
|
|
1940
2408
|
const settled = yield* childProvablySettled(
|
|
1941
2409
|
operation,
|
|
1942
2410
|
markerChildren,
|
|
1943
2411
|
child.childSubmissionId,
|
|
1944
2412
|
);
|
|
2413
|
+
|
|
1945
2414
|
if (!settled) {
|
|
1946
2415
|
allSettled = false;
|
|
1947
2416
|
break;
|
|
1948
2417
|
}
|
|
1949
2418
|
}
|
|
1950
2419
|
if (allSettled) {
|
|
1951
|
-
return
|
|
2420
|
+
return RESUME_IMMEDIATELY;
|
|
1952
2421
|
}
|
|
1953
2422
|
}
|
|
1954
2423
|
const now = yield* currentInstant;
|
|
2424
|
+
|
|
1955
2425
|
yield* sql`
|
|
1956
2426
|
UPDATE effect_agent_submissions
|
|
1957
2427
|
SET
|
|
@@ -1966,10 +2436,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1966
2436
|
DELETE FROM effect_agent_submission_ownership
|
|
1967
2437
|
WHERE submission_id = ${validated.submissionId}
|
|
1968
2438
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1969
|
-
|
|
2439
|
+
|
|
2440
|
+
return SUSPENDED;
|
|
1970
2441
|
}),
|
|
1971
2442
|
);
|
|
2443
|
+
|
|
1972
2444
|
yield* hitFailpoint("ledger:suspend:after", operation);
|
|
2445
|
+
|
|
1973
2446
|
return outcome;
|
|
1974
2447
|
},
|
|
1975
2448
|
);
|
|
@@ -1985,6 +2458,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1985
2458
|
submission: SubmissionRow,
|
|
1986
2459
|
): Effect.fn.Return<void, LedgerError> {
|
|
1987
2460
|
if (submission.state !== "suspended" || submission.suspended_reason_json === null) return;
|
|
2461
|
+
|
|
1988
2462
|
const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(
|
|
1989
2463
|
submission.suspended_reason_json,
|
|
1990
2464
|
).pipe(
|
|
@@ -1997,9 +2471,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1997
2471
|
),
|
|
1998
2472
|
),
|
|
1999
2473
|
);
|
|
2474
|
+
|
|
2000
2475
|
if (reason._tag !== "ApprovalPending") return;
|
|
2001
2476
|
const decisions = yield* readApprovalDecisions(operation, submission.submission_id);
|
|
2002
2477
|
const decided = new Set(decisions.map((row) => row.tool_call_id));
|
|
2478
|
+
|
|
2003
2479
|
if (!reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return;
|
|
2004
2480
|
yield* sql`
|
|
2005
2481
|
UPDATE effect_agent_submissions
|
|
@@ -2015,14 +2491,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2015
2491
|
"DoSubmissionLedger.recordApprovalDecision",
|
|
2016
2492
|
)(function* (command: ApprovalDecisionCommand) {
|
|
2017
2493
|
const operation = "ledger record approval decision";
|
|
2494
|
+
|
|
2018
2495
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ApprovalDecisionCommand))(
|
|
2019
2496
|
command,
|
|
2020
2497
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2498
|
+
|
|
2021
2499
|
yield* hitFailpoint("ledger:approval-decision:before", operation);
|
|
2500
|
+
|
|
2022
2501
|
const intent = yield* inWriteTransaction(
|
|
2023
2502
|
operation,
|
|
2024
2503
|
Effect.gen(function* () {
|
|
2025
2504
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2505
|
+
|
|
2026
2506
|
if (submission.state === "settled") {
|
|
2027
2507
|
if (submission.settled_outcome === null) {
|
|
2028
2508
|
return yield* corruptionFailure(
|
|
@@ -2032,6 +2512,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2032
2512
|
"A settled Submission carries no terminal outcome.",
|
|
2033
2513
|
);
|
|
2034
2514
|
}
|
|
2515
|
+
|
|
2035
2516
|
return yield* SettlementConflict.make({
|
|
2036
2517
|
submissionId: validated.submissionId,
|
|
2037
2518
|
existingOutcome: submission.settled_outcome,
|
|
@@ -2039,6 +2520,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2039
2520
|
}
|
|
2040
2521
|
const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
2041
2522
|
const existing = decisions.find((row) => row.tool_call_id === validated.toolCallId);
|
|
2523
|
+
|
|
2042
2524
|
if (existing !== undefined) {
|
|
2043
2525
|
// Idempotent per (submissionId, toolCallId): repeating the SAME decision replays
|
|
2044
2526
|
// the recorded intent unchanged; a divergent re-decision conflicts.
|
|
@@ -2049,9 +2531,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2049
2531
|
existingDecision: existing.decision,
|
|
2050
2532
|
});
|
|
2051
2533
|
}
|
|
2534
|
+
|
|
2052
2535
|
return yield* approvalIntentFromRow(operation, existing);
|
|
2053
2536
|
}
|
|
2054
2537
|
const now = yield* currentInstant;
|
|
2538
|
+
|
|
2055
2539
|
yield* sql`
|
|
2056
2540
|
INSERT INTO effect_agent_approval_decisions (
|
|
2057
2541
|
submission_id,
|
|
@@ -2070,6 +2554,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2070
2554
|
)
|
|
2071
2555
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2072
2556
|
yield* wakeSuspendedIfCovered(operation, submission);
|
|
2557
|
+
|
|
2073
2558
|
return yield* decodeApprovalDecisionIntent({
|
|
2074
2559
|
submissionId: validated.submissionId,
|
|
2075
2560
|
toolCallId: validated.toolCallId,
|
|
@@ -2080,7 +2565,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2080
2565
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
2081
2566
|
}),
|
|
2082
2567
|
);
|
|
2568
|
+
|
|
2083
2569
|
yield* hitFailpoint("ledger:approval-decision:after", operation);
|
|
2570
|
+
|
|
2084
2571
|
return intent;
|
|
2085
2572
|
});
|
|
2086
2573
|
|
|
@@ -2088,14 +2575,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2088
2575
|
"DoSubmissionLedger.markUnknown",
|
|
2089
2576
|
)(function* (request: MarkUnknownRequest) {
|
|
2090
2577
|
const operation = "ledger mark unknown";
|
|
2578
|
+
|
|
2091
2579
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkUnknownRequest))(
|
|
2092
2580
|
request,
|
|
2093
2581
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2582
|
+
|
|
2094
2583
|
yield* hitFailpoint("ledger:mark-unknown:before", operation);
|
|
2095
2584
|
yield* inWriteTransaction(
|
|
2096
2585
|
operation,
|
|
2097
2586
|
Effect.gen(function* () {
|
|
2098
2587
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2588
|
+
|
|
2099
2589
|
if (submission.state === "settled") {
|
|
2100
2590
|
if (submission.settled_outcome === null) {
|
|
2101
2591
|
return yield* corruptionFailure(
|
|
@@ -2105,6 +2595,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2105
2595
|
"A settled Submission carries no terminal outcome.",
|
|
2106
2596
|
);
|
|
2107
2597
|
}
|
|
2598
|
+
|
|
2108
2599
|
return yield* SettlementConflict.make({
|
|
2109
2600
|
submissionId: validated.submissionId,
|
|
2110
2601
|
existingOutcome: submission.settled_outcome,
|
|
@@ -2113,6 +2604,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2113
2604
|
// A reserved exact outcome wins over a late Unknown marking (DUR-011); the recovery
|
|
2114
2605
|
// classifier orders reservation ahead of MarkUnknown for the same reason.
|
|
2115
2606
|
const reservation = yield* readReservation(operation, validated.submissionId);
|
|
2607
|
+
|
|
2116
2608
|
if (Option.isSome(reservation)) {
|
|
2117
2609
|
return yield* SettlementConflict.make({
|
|
2118
2610
|
submissionId: validated.submissionId,
|
|
@@ -2123,13 +2615,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2123
2615
|
// set while the first recorded reason is kept.
|
|
2124
2616
|
const existingIds = yield* storedUnknownToolCallIds(operation, submission);
|
|
2125
2617
|
const known = new Set(existingIds);
|
|
2618
|
+
|
|
2126
2619
|
const merged = [
|
|
2127
2620
|
...existingIds,
|
|
2128
2621
|
...validated.toolCallIds.filter((toolCallId) => !known.has(toolCallId)),
|
|
2129
2622
|
];
|
|
2623
|
+
|
|
2130
2624
|
const idsJson = yield* encodeToolCallIdsText(merged).pipe(
|
|
2131
2625
|
Effect.mapError(internalFailure(operation)),
|
|
2132
2626
|
);
|
|
2627
|
+
|
|
2133
2628
|
yield* sql`
|
|
2134
2629
|
UPDATE effect_agent_submissions
|
|
2135
2630
|
SET
|
|
@@ -2147,17 +2642,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2147
2642
|
"DoSubmissionLedger.recordUnknownResolution",
|
|
2148
2643
|
)(function* (command: UnknownResolutionCommand) {
|
|
2149
2644
|
const operation = "ledger record unknown resolution";
|
|
2645
|
+
|
|
2150
2646
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(UnknownResolutionCommand))(
|
|
2151
2647
|
command,
|
|
2152
2648
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2649
|
+
|
|
2153
2650
|
const resolutionJson = yield* encodeUnknownResolutionText(validated.resolution).pipe(
|
|
2154
2651
|
Effect.mapError(internalFailure(operation)),
|
|
2155
2652
|
);
|
|
2653
|
+
|
|
2156
2654
|
yield* hitFailpoint("ledger:unknown-resolution:before", operation);
|
|
2655
|
+
|
|
2157
2656
|
const intent = yield* inWriteTransaction(
|
|
2158
2657
|
operation,
|
|
2159
2658
|
Effect.gen(function* () {
|
|
2160
2659
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2660
|
+
|
|
2161
2661
|
if (submission.state === "settled") {
|
|
2162
2662
|
if (submission.settled_outcome === null) {
|
|
2163
2663
|
return yield* corruptionFailure(
|
|
@@ -2167,6 +2667,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2167
2667
|
"A settled Submission carries no terminal outcome.",
|
|
2168
2668
|
);
|
|
2169
2669
|
}
|
|
2670
|
+
|
|
2170
2671
|
return yield* SettlementConflict.make({
|
|
2171
2672
|
submissionId: validated.submissionId,
|
|
2172
2673
|
existingOutcome: submission.settled_outcome,
|
|
@@ -2174,19 +2675,30 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2174
2675
|
}
|
|
2175
2676
|
const resolutions = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
2176
2677
|
const existing = resolutions.find((row) => row.tool_call_id === validated.toolCallId);
|
|
2177
|
-
|
|
2678
|
+
|
|
2679
|
+
const existingIntent =
|
|
2680
|
+
existing === undefined
|
|
2681
|
+
? undefined
|
|
2682
|
+
: yield* unknownResolutionIntentFromRow(operation, existing);
|
|
2683
|
+
|
|
2684
|
+
if (
|
|
2685
|
+
existingIntent !== undefined &&
|
|
2686
|
+
!equivalentUnknownResolution(existingIntent.resolution, validated.resolution)
|
|
2687
|
+
) {
|
|
2178
2688
|
return yield* UnknownResolutionConflict.make({
|
|
2179
2689
|
submissionId: validated.submissionId,
|
|
2180
2690
|
toolCallId: validated.toolCallId,
|
|
2181
2691
|
});
|
|
2182
2692
|
}
|
|
2183
2693
|
let resolved: UnknownResolutionIntent;
|
|
2184
|
-
|
|
2694
|
+
|
|
2695
|
+
if (existingIntent !== undefined) {
|
|
2185
2696
|
// Idempotent replay of the recorded intent (author/reason may differ; the stored
|
|
2186
2697
|
// audit fields win, exactly like requestAbort).
|
|
2187
|
-
resolved =
|
|
2698
|
+
resolved = existingIntent;
|
|
2188
2699
|
} else {
|
|
2189
2700
|
const now = yield* currentInstant;
|
|
2701
|
+
|
|
2190
2702
|
yield* sql`
|
|
2191
2703
|
INSERT INTO effect_agent_unknown_resolutions (
|
|
2192
2704
|
submission_id,
|
|
@@ -2204,9 +2716,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2204
2716
|
${now.iso}
|
|
2205
2717
|
)
|
|
2206
2718
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2719
|
+
|
|
2207
2720
|
const resolution = yield* parseStoredJsonText(resolutionJson).pipe(
|
|
2208
2721
|
Effect.mapError(internalFailure(operation)),
|
|
2209
2722
|
);
|
|
2723
|
+
|
|
2210
2724
|
resolved = yield* decodeUnknownResolutionIntent({
|
|
2211
2725
|
submissionId: validated.submissionId,
|
|
2212
2726
|
toolCallId: validated.toolCallId,
|
|
@@ -2223,6 +2737,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2223
2737
|
const markedIds = yield* storedUnknownToolCallIds(operation, submission);
|
|
2224
2738
|
const covering = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
2225
2739
|
const coveredIds = new Set(covering.map((row) => row.tool_call_id));
|
|
2740
|
+
|
|
2226
2741
|
if (markedIds.every((toolCallId) => coveredIds.has(toolCallId))) {
|
|
2227
2742
|
yield* sql`
|
|
2228
2743
|
UPDATE effect_agent_submissions
|
|
@@ -2234,10 +2749,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2234
2749
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2235
2750
|
}
|
|
2236
2751
|
}
|
|
2752
|
+
|
|
2237
2753
|
return resolved;
|
|
2238
2754
|
}),
|
|
2239
2755
|
);
|
|
2756
|
+
|
|
2240
2757
|
yield* hitFailpoint("ledger:unknown-resolution:after", operation);
|
|
2758
|
+
|
|
2241
2759
|
return intent;
|
|
2242
2760
|
});
|
|
2243
2761
|
|
|
@@ -2245,21 +2763,31 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2245
2763
|
"DoSubmissionLedger.recordChildSettled",
|
|
2246
2764
|
)(function* (request: ChildSettledNotification) {
|
|
2247
2765
|
const operation = "ledger record child settled";
|
|
2766
|
+
|
|
2248
2767
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ChildSettledNotification))(
|
|
2249
2768
|
request,
|
|
2250
2769
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2770
|
+
|
|
2251
2771
|
yield* hitFailpoint("ledger:child-settled:before", operation);
|
|
2772
|
+
|
|
2252
2773
|
const outcome = yield* inWriteTransaction(
|
|
2253
2774
|
operation,
|
|
2254
2775
|
Effect.gen(function* () {
|
|
2255
2776
|
const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
|
|
2256
2777
|
// The child's canonical Settlement is the authority for this wake. When the child's
|
|
2257
|
-
// row lives in THIS store (single-store latitude, and every conformance lane),
|
|
2258
|
-
//
|
|
2259
|
-
//
|
|
2260
|
-
//
|
|
2778
|
+
// row lives in THIS store (single-store latitude, and every conformance lane), either a
|
|
2779
|
+
// finalized row or an exact terminalizing reservation admits the notification: the
|
|
2780
|
+
// runtime calls only after the canonical append and before ledger finalization. When the
|
|
2781
|
+
// row does not live here — the normal cross-DO case — the routed notification from the
|
|
2782
|
+
// child's owning Durable Object is the settlement evidence this store records durably.
|
|
2261
2783
|
const child = yield* readSubmission(operation, validated.childSubmissionId);
|
|
2262
|
-
|
|
2784
|
+
const childReservation = yield* readReservation(operation, validated.childSubmissionId);
|
|
2785
|
+
|
|
2786
|
+
if (
|
|
2787
|
+
Option.isSome(child) &&
|
|
2788
|
+
child.value.state !== "settled" &&
|
|
2789
|
+
!(child.value.state === "terminalizing" && Option.isSome(childReservation))
|
|
2790
|
+
) {
|
|
2263
2791
|
return yield* LedgerError.make({
|
|
2264
2792
|
operation,
|
|
2265
2793
|
message: `Child submission ${validated.childSubmissionId} has no recorded settlement.`,
|
|
@@ -2270,6 +2798,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2270
2798
|
// before the parent's suspend transaction is observed by that suspend's covering
|
|
2271
2799
|
// check, even when the parent is not (or not yet) suspended.
|
|
2272
2800
|
const now = yield* currentInstant;
|
|
2801
|
+
|
|
2273
2802
|
yield* sql`
|
|
2274
2803
|
INSERT INTO effect_agent_child_settlements (
|
|
2275
2804
|
parent_submission_id,
|
|
@@ -2279,15 +2808,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2279
2808
|
) VALUES (
|
|
2280
2809
|
${validated.parentSubmissionId},
|
|
2281
2810
|
${validated.childSubmissionId},
|
|
2282
|
-
${
|
|
2811
|
+
${
|
|
2812
|
+
Option.isSome(child) && child.value.state === "settled"
|
|
2813
|
+
? child.value.settled_outcome
|
|
2814
|
+
: Option.isSome(childReservation)
|
|
2815
|
+
? childReservation.value.outcome
|
|
2816
|
+
: null
|
|
2817
|
+
},
|
|
2283
2818
|
${now.iso}
|
|
2284
2819
|
)
|
|
2285
2820
|
ON CONFLICT (parent_submission_id, child_submission_id) DO NOTHING
|
|
2286
2821
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2287
2822
|
|
|
2288
2823
|
if (parent.state !== "suspended" || parent.suspended_reason_json === null) {
|
|
2289
|
-
return
|
|
2824
|
+
return NOT_WAITING;
|
|
2290
2825
|
}
|
|
2826
|
+
|
|
2291
2827
|
const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(
|
|
2292
2828
|
parent.suspended_reason_json,
|
|
2293
2829
|
).pipe(
|
|
@@ -2300,27 +2836,30 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2300
2836
|
),
|
|
2301
2837
|
),
|
|
2302
2838
|
);
|
|
2839
|
+
|
|
2303
2840
|
if (reason._tag !== "WaitingForChild") {
|
|
2304
|
-
return
|
|
2841
|
+
return NOT_WAITING;
|
|
2305
2842
|
}
|
|
2306
2843
|
if (
|
|
2307
2844
|
!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)
|
|
2308
2845
|
) {
|
|
2309
|
-
return
|
|
2846
|
+
return NOT_WAITING;
|
|
2310
2847
|
}
|
|
2311
2848
|
// The parent wakes exactly when EVERY listed child is provably settled — from its
|
|
2312
2849
|
// local row or a recorded marker (spec §12 step 10); replays re-run the coverage
|
|
2313
2850
|
// check so a recovering caller wakes the lane idempotently.
|
|
2314
2851
|
const markers = yield* readChildSettlementMarkers(operation, validated.parentSubmissionId);
|
|
2315
2852
|
const markerChildren = new Set(markers.map((row) => row.child_submission_id));
|
|
2853
|
+
|
|
2316
2854
|
for (const entry of reason.children) {
|
|
2317
2855
|
const settled = yield* childProvablySettled(
|
|
2318
2856
|
operation,
|
|
2319
2857
|
markerChildren,
|
|
2320
2858
|
entry.childSubmissionId,
|
|
2321
2859
|
);
|
|
2860
|
+
|
|
2322
2861
|
if (!settled) {
|
|
2323
|
-
return
|
|
2862
|
+
return STILL_WAITING;
|
|
2324
2863
|
}
|
|
2325
2864
|
}
|
|
2326
2865
|
yield* sql`
|
|
@@ -2331,10 +2870,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2331
2870
|
suspended_at = NULL
|
|
2332
2871
|
WHERE submission_id = ${validated.parentSubmissionId}
|
|
2333
2872
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2334
|
-
|
|
2873
|
+
|
|
2874
|
+
return WOKEN;
|
|
2335
2875
|
}),
|
|
2336
2876
|
);
|
|
2877
|
+
|
|
2337
2878
|
yield* hitFailpoint("ledger:child-settled:after", operation);
|
|
2879
|
+
|
|
2338
2880
|
return outcome;
|
|
2339
2881
|
});
|
|
2340
2882
|
|
|
@@ -2342,25 +2884,36 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2342
2884
|
"DoSubmissionLedger.reserveChildBudget",
|
|
2343
2885
|
)(function* (request: ChildBudgetReservationRequest) {
|
|
2344
2886
|
const operation = "ledger reserve child budget";
|
|
2887
|
+
|
|
2345
2888
|
const validated = yield* Schema.decodeUnknownEffect(
|
|
2346
2889
|
Schema.toType(ChildBudgetReservationRequest),
|
|
2347
2890
|
)(request).pipe(Effect.mapError(internalFailure(operation)));
|
|
2891
|
+
|
|
2348
2892
|
const allocationJson = yield* encodePersistedJsonText(validated.allocation).pipe(
|
|
2349
2893
|
Effect.mapError(internalFailure(operation)),
|
|
2350
2894
|
);
|
|
2895
|
+
|
|
2351
2896
|
yield* hitFailpoint("ledger:child-reservation:before", operation);
|
|
2897
|
+
|
|
2352
2898
|
const reserved = yield* inWriteTransaction(
|
|
2353
2899
|
operation,
|
|
2354
2900
|
Effect.gen(function* () {
|
|
2355
2901
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
2902
|
+
|
|
2356
2903
|
if (Option.isSome(existing)) {
|
|
2904
|
+
const existingSnapshot = yield* childReservationSnapshotFromRow(
|
|
2905
|
+
operation,
|
|
2906
|
+
existing.value,
|
|
2907
|
+
);
|
|
2908
|
+
|
|
2357
2909
|
// Identical replays short-circuit before the fence, mirroring reserveSettlement: a
|
|
2358
2910
|
// replay creates nothing, so a recovering caller resumes rather than duplicates.
|
|
2359
2911
|
const identical =
|
|
2360
2912
|
existing.value.parent_submission_id === validated.parentSubmissionId &&
|
|
2361
2913
|
existing.value.parent_tool_call_id === validated.parentToolCallId &&
|
|
2362
2914
|
existing.value.allocation_digest === validated.allocationDigest &&
|
|
2363
|
-
|
|
2915
|
+
equivalentPersistedJson(existingSnapshot.allocation, validated.allocation);
|
|
2916
|
+
|
|
2364
2917
|
if (!identical) {
|
|
2365
2918
|
return yield* ChildReservationConflict.make({
|
|
2366
2919
|
reservationId: validated.reservationId,
|
|
@@ -2369,16 +2922,19 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2369
2922
|
"A reservation with this identity exists with a different parent Tool Call or allocation.",
|
|
2370
2923
|
});
|
|
2371
2924
|
}
|
|
2925
|
+
|
|
2372
2926
|
return ReservedChildBudget.make({
|
|
2373
|
-
reservation:
|
|
2927
|
+
reservation: existingSnapshot,
|
|
2374
2928
|
replayed: true,
|
|
2375
2929
|
});
|
|
2376
2930
|
}
|
|
2931
|
+
|
|
2377
2932
|
const collision = yield* readChildReservationForCall(
|
|
2378
2933
|
operation,
|
|
2379
2934
|
validated.parentSubmissionId,
|
|
2380
2935
|
validated.parentToolCallId,
|
|
2381
2936
|
);
|
|
2937
|
+
|
|
2382
2938
|
if (Option.isSome(collision)) {
|
|
2383
2939
|
return yield* ChildReservationConflict.make({
|
|
2384
2940
|
reservationId: validated.reservationId,
|
|
@@ -2387,10 +2943,12 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2387
2943
|
});
|
|
2388
2944
|
}
|
|
2389
2945
|
const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
|
|
2946
|
+
|
|
2390
2947
|
// Creation is fenced by the parent lane's live ownership (spec §12 step 2): a stale
|
|
2391
2948
|
// parent Attempt can never create new reservation state.
|
|
2392
2949
|
yield* requireOwnership(operation, parent, validated.ownershipToken);
|
|
2393
2950
|
const now = yield* currentInstant;
|
|
2951
|
+
|
|
2394
2952
|
yield* sql`
|
|
2395
2953
|
INSERT INTO effect_agent_child_reservations (
|
|
2396
2954
|
reservation_id,
|
|
@@ -2411,6 +2969,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2411
2969
|
)
|
|
2412
2970
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2413
2971
|
const inserted = yield* readChildReservation(operation, validated.reservationId);
|
|
2972
|
+
|
|
2414
2973
|
if (Option.isNone(inserted)) {
|
|
2415
2974
|
return yield* corruptionFailure(
|
|
2416
2975
|
operation,
|
|
@@ -2419,13 +2978,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2419
2978
|
"An inserted child reservation row is missing inside its own transaction.",
|
|
2420
2979
|
);
|
|
2421
2980
|
}
|
|
2981
|
+
|
|
2422
2982
|
return ReservedChildBudget.make({
|
|
2423
2983
|
reservation: yield* childReservationSnapshotFromRow(operation, inserted.value),
|
|
2424
2984
|
replayed: false,
|
|
2425
2985
|
});
|
|
2426
2986
|
}),
|
|
2427
2987
|
);
|
|
2988
|
+
|
|
2428
2989
|
yield* hitFailpoint("ledger:child-reservation:after", operation);
|
|
2990
|
+
|
|
2429
2991
|
return reserved;
|
|
2430
2992
|
});
|
|
2431
2993
|
|
|
@@ -2434,14 +2996,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2434
2996
|
request: AttachChildToReservationRequest,
|
|
2435
2997
|
) {
|
|
2436
2998
|
const operation = "ledger attach child to reservation";
|
|
2999
|
+
|
|
2437
3000
|
const validated = yield* Schema.decodeUnknownEffect(
|
|
2438
3001
|
Schema.toType(AttachChildToReservationRequest),
|
|
2439
3002
|
)(request).pipe(Effect.mapError(internalFailure(operation)));
|
|
3003
|
+
|
|
2440
3004
|
yield* hitFailpoint("ledger:child-attach:before", operation);
|
|
3005
|
+
|
|
2441
3006
|
const attached = yield* inWriteTransaction(
|
|
2442
3007
|
operation,
|
|
2443
3008
|
Effect.gen(function* () {
|
|
2444
3009
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
3010
|
+
|
|
2445
3011
|
if (Option.isNone(existing)) {
|
|
2446
3012
|
return yield* LedgerError.make({
|
|
2447
3013
|
operation,
|
|
@@ -2453,6 +3019,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2453
3019
|
if (existing.value.child_submission_id === validated.childSubmissionId) {
|
|
2454
3020
|
return yield* childReservationSnapshotFromRow(operation, existing.value);
|
|
2455
3021
|
}
|
|
3022
|
+
|
|
2456
3023
|
return yield* ChildReservationConflict.make({
|
|
2457
3024
|
reservationId: validated.reservationId,
|
|
2458
3025
|
status: existing.value.status,
|
|
@@ -2460,6 +3027,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2460
3027
|
});
|
|
2461
3028
|
}
|
|
2462
3029
|
const parent = yield* requireSubmission(operation, existing.value.parent_submission_id);
|
|
3030
|
+
|
|
2463
3031
|
yield* requireOwnership(operation, parent, validated.ownershipToken);
|
|
2464
3032
|
if (existing.value.status !== "reserved") {
|
|
2465
3033
|
return yield* ChildReservationConflict.make({
|
|
@@ -2478,6 +3046,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2478
3046
|
WHERE reservation_id = ${validated.reservationId}
|
|
2479
3047
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2480
3048
|
const updated = yield* readChildReservation(operation, validated.reservationId);
|
|
3049
|
+
|
|
2481
3050
|
if (Option.isNone(updated)) {
|
|
2482
3051
|
return yield* corruptionFailure(
|
|
2483
3052
|
operation,
|
|
@@ -2486,10 +3055,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2486
3055
|
"An updated child reservation row is missing inside its own transaction.",
|
|
2487
3056
|
);
|
|
2488
3057
|
}
|
|
3058
|
+
|
|
2489
3059
|
return yield* childReservationSnapshotFromRow(operation, updated.value);
|
|
2490
3060
|
}),
|
|
2491
3061
|
);
|
|
3062
|
+
|
|
2492
3063
|
yield* hitFailpoint("ledger:child-attach:after", operation);
|
|
3064
|
+
|
|
2493
3065
|
return attached;
|
|
2494
3066
|
});
|
|
2495
3067
|
|
|
@@ -2497,17 +3069,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2497
3069
|
"DoSubmissionLedger.beginChildBudgetRelease",
|
|
2498
3070
|
)(function* (request: BeginChildBudgetReleaseRequest) {
|
|
2499
3071
|
const operation = "ledger begin child budget release";
|
|
3072
|
+
|
|
2500
3073
|
const validated = yield* Schema.decodeUnknownEffect(
|
|
2501
3074
|
Schema.toType(BeginChildBudgetReleaseRequest),
|
|
2502
3075
|
)(request).pipe(Effect.mapError(internalFailure(operation)));
|
|
3076
|
+
|
|
2503
3077
|
const accountingJson = yield* encodePersistedJsonText(validated.accounting).pipe(
|
|
2504
3078
|
Effect.mapError(internalFailure(operation)),
|
|
2505
3079
|
);
|
|
3080
|
+
|
|
2506
3081
|
yield* hitFailpoint("ledger:child-release-pending:before", operation);
|
|
3082
|
+
|
|
2507
3083
|
const frozen = yield* inWriteTransaction(
|
|
2508
3084
|
operation,
|
|
2509
3085
|
Effect.gen(function* () {
|
|
2510
3086
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
3087
|
+
|
|
2511
3088
|
if (Option.isNone(existing)) {
|
|
2512
3089
|
return yield* LedgerError.make({
|
|
2513
3090
|
operation,
|
|
@@ -2515,11 +3092,20 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2515
3092
|
});
|
|
2516
3093
|
}
|
|
2517
3094
|
if (existing.value.status !== "reserved") {
|
|
3095
|
+
const existingSnapshot = yield* childReservationSnapshotFromRow(
|
|
3096
|
+
operation,
|
|
3097
|
+
existing.value,
|
|
3098
|
+
);
|
|
3099
|
+
|
|
2518
3100
|
// The accounting decision was already frozen exactly once; an identical replay is a
|
|
2519
3101
|
// no-op and a divergent decision conflicts (spec §12 join step 6).
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
3102
|
+
if (
|
|
3103
|
+
existingSnapshot.accounting !== undefined &&
|
|
3104
|
+
equivalentPersistedJson(existingSnapshot.accounting, validated.accounting)
|
|
3105
|
+
) {
|
|
3106
|
+
return existingSnapshot;
|
|
2522
3107
|
}
|
|
3108
|
+
|
|
2523
3109
|
return yield* ChildReservationConflict.make({
|
|
2524
3110
|
reservationId: validated.reservationId,
|
|
2525
3111
|
status: existing.value.status,
|
|
@@ -2527,6 +3113,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2527
3113
|
});
|
|
2528
3114
|
}
|
|
2529
3115
|
const now = yield* currentInstant;
|
|
3116
|
+
|
|
2530
3117
|
yield* sql`
|
|
2531
3118
|
UPDATE effect_agent_child_reservations
|
|
2532
3119
|
SET
|
|
@@ -2536,6 +3123,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2536
3123
|
WHERE reservation_id = ${validated.reservationId}
|
|
2537
3124
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2538
3125
|
const updated = yield* readChildReservation(operation, validated.reservationId);
|
|
3126
|
+
|
|
2539
3127
|
if (Option.isNone(updated)) {
|
|
2540
3128
|
return yield* corruptionFailure(
|
|
2541
3129
|
operation,
|
|
@@ -2544,10 +3132,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2544
3132
|
"An updated child reservation row is missing inside its own transaction.",
|
|
2545
3133
|
);
|
|
2546
3134
|
}
|
|
3135
|
+
|
|
2547
3136
|
return yield* childReservationSnapshotFromRow(operation, updated.value);
|
|
2548
3137
|
}),
|
|
2549
3138
|
);
|
|
3139
|
+
|
|
2550
3140
|
yield* hitFailpoint("ledger:child-release-pending:after", operation);
|
|
3141
|
+
|
|
2551
3142
|
return frozen;
|
|
2552
3143
|
});
|
|
2553
3144
|
|
|
@@ -2555,14 +3146,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2555
3146
|
"DoSubmissionLedger.releaseChildBudget",
|
|
2556
3147
|
)(function* (request: ReleaseChildBudgetRequest) {
|
|
2557
3148
|
const operation = "ledger release child budget";
|
|
3149
|
+
|
|
2558
3150
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseChildBudgetRequest))(
|
|
2559
3151
|
request,
|
|
2560
3152
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3153
|
+
|
|
2561
3154
|
yield* hitFailpoint("ledger:child-release:before", operation);
|
|
3155
|
+
|
|
2562
3156
|
const released = yield* inWriteTransaction(
|
|
2563
3157
|
operation,
|
|
2564
3158
|
Effect.gen(function* () {
|
|
2565
3159
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
3160
|
+
|
|
2566
3161
|
if (Option.isNone(existing)) {
|
|
2567
3162
|
return yield* LedgerError.make({
|
|
2568
3163
|
operation,
|
|
@@ -2582,12 +3177,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2582
3177
|
});
|
|
2583
3178
|
}
|
|
2584
3179
|
const now = yield* currentInstant;
|
|
3180
|
+
|
|
2585
3181
|
yield* sql`
|
|
2586
3182
|
UPDATE effect_agent_child_reservations
|
|
2587
3183
|
SET status = 'released', released_at = ${now.iso}
|
|
2588
3184
|
WHERE reservation_id = ${validated.reservationId}
|
|
2589
3185
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2590
3186
|
const updated = yield* readChildReservation(operation, validated.reservationId);
|
|
3187
|
+
|
|
2591
3188
|
if (Option.isNone(updated)) {
|
|
2592
3189
|
return yield* corruptionFailure(
|
|
2593
3190
|
operation,
|
|
@@ -2596,15 +3193,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2596
3193
|
"An updated child reservation row is missing inside its own transaction.",
|
|
2597
3194
|
);
|
|
2598
3195
|
}
|
|
3196
|
+
|
|
2599
3197
|
return yield* childReservationSnapshotFromRow(operation, updated.value);
|
|
2600
3198
|
}),
|
|
2601
3199
|
);
|
|
3200
|
+
|
|
2602
3201
|
yield* hitFailpoint("ledger:child-release:after", operation);
|
|
3202
|
+
|
|
2603
3203
|
return released;
|
|
2604
3204
|
});
|
|
2605
3205
|
|
|
2606
3206
|
interface ScanCursor {
|
|
2607
|
-
readonly
|
|
3207
|
+
readonly threadId: string;
|
|
2608
3208
|
readonly queueSequence: number;
|
|
2609
3209
|
}
|
|
2610
3210
|
|
|
@@ -2615,57 +3215,131 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2615
3215
|
LedgerError
|
|
2616
3216
|
> {
|
|
2617
3217
|
const operation = "ledger scan nonterminal";
|
|
3218
|
+
|
|
2618
3219
|
const rows = yield* (
|
|
2619
3220
|
cursor === undefined
|
|
2620
3221
|
? sql<Record<string, unknown>>`
|
|
2621
3222
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
2622
3223
|
FROM effect_agent_submissions
|
|
2623
3224
|
WHERE state <> 'settled'
|
|
2624
|
-
ORDER BY
|
|
3225
|
+
ORDER BY thread_id ASC, queue_sequence ASC
|
|
2625
3226
|
LIMIT ${SCAN_PAGE_SIZE}
|
|
2626
3227
|
`
|
|
2627
3228
|
: sql<Record<string, unknown>>`
|
|
2628
3229
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
2629
3230
|
FROM effect_agent_submissions
|
|
2630
3231
|
WHERE state <> 'settled'
|
|
2631
|
-
AND (
|
|
2632
|
-
|
|
2633
|
-
OR (
|
|
2634
|
-
conversation_id = ${cursor.conversationId}
|
|
2635
|
-
AND queue_sequence > ${cursor.queueSequence}
|
|
2636
|
-
)
|
|
2637
|
-
)
|
|
2638
|
-
ORDER BY conversation_id ASC, queue_sequence ASC
|
|
3232
|
+
AND (thread_id, queue_sequence) > (${cursor.threadId}, ${cursor.queueSequence})
|
|
3233
|
+
ORDER BY thread_id ASC, queue_sequence ASC
|
|
2639
3234
|
LIMIT ${SCAN_PAGE_SIZE}
|
|
2640
3235
|
`
|
|
2641
3236
|
).pipe(Effect.mapError(sqlFailure(operation)));
|
|
3237
|
+
|
|
2642
3238
|
const decoded = yield* decodeSubmissionRows(operation, "nonterminal_scan", rows);
|
|
3239
|
+
|
|
2643
3240
|
const snapshots = yield* Effect.forEach(decoded, (row) =>
|
|
2644
3241
|
decodeSubmissionSnapshot(operation, row),
|
|
2645
3242
|
);
|
|
3243
|
+
|
|
2646
3244
|
const last = decoded[decoded.length - 1];
|
|
3245
|
+
|
|
2647
3246
|
const next: Option.Option<ScanCursor | undefined> =
|
|
2648
3247
|
last === undefined || decoded.length < SCAN_PAGE_SIZE
|
|
2649
3248
|
? Option.none()
|
|
2650
3249
|
: Option.some({
|
|
2651
|
-
|
|
3250
|
+
threadId: last.thread_id,
|
|
2652
3251
|
queueSequence: last.queue_sequence,
|
|
2653
3252
|
});
|
|
3253
|
+
|
|
2654
3254
|
return [snapshots, next] as const;
|
|
2655
3255
|
});
|
|
2656
3256
|
|
|
2657
|
-
const scanNonterminal: Stream.Stream<SubmissionSnapshot, LedgerError> = Stream.paginate
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
3257
|
+
const scanNonterminal: Stream.Stream<SubmissionSnapshot, LedgerError> = Stream.paginate<
|
|
3258
|
+
ScanCursor | undefined,
|
|
3259
|
+
SubmissionSnapshot,
|
|
3260
|
+
LedgerError
|
|
3261
|
+
>(undefined, scanPage);
|
|
3262
|
+
|
|
3263
|
+
const readAbortIntentForSubmission: SubmissionLedger["Service"]["readAbortIntent"] = Effect.fn(
|
|
3264
|
+
"DoSubmissionLedger.readAbortIntentForSubmission",
|
|
3265
|
+
)(function* (request) {
|
|
3266
|
+
const operation = "ledger read abort intent";
|
|
3267
|
+
|
|
3268
|
+
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AbortIntentRequest))(
|
|
3269
|
+
request,
|
|
3270
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3271
|
+
|
|
3272
|
+
const recordId = submissionAbortRecordId(validated.submissionId);
|
|
3273
|
+
|
|
3274
|
+
const rows = yield* sql<Record<string, unknown>>`
|
|
3275
|
+
SELECT
|
|
3276
|
+
submission.submission_id,
|
|
3277
|
+
abort.submission_id AS abort_submission_id,
|
|
3278
|
+
abort.author,
|
|
3279
|
+
abort.reason,
|
|
3280
|
+
abort.requested_at,
|
|
3281
|
+
canonical.record_id AS canonical_record_id
|
|
3282
|
+
FROM effect_agent_submissions AS submission
|
|
3283
|
+
LEFT JOIN effect_agent_abort_intents AS abort
|
|
3284
|
+
ON abort.submission_id = submission.submission_id
|
|
3285
|
+
LEFT JOIN effect_agent_canonical_records AS canonical
|
|
3286
|
+
ON canonical.thread_id = submission.thread_id
|
|
3287
|
+
AND canonical.record_id = ${recordId}
|
|
3288
|
+
WHERE submission.submission_id = ${validated.submissionId}
|
|
3289
|
+
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
3290
|
+
|
|
3291
|
+
const decoded = yield* decodeRows(
|
|
3292
|
+
Schema.Array(AbortIntentLookupRow),
|
|
3293
|
+
"effect_agent_abort_intents",
|
|
3294
|
+
validated.submissionId,
|
|
3295
|
+
rows,
|
|
3296
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3297
|
+
|
|
3298
|
+
if (decoded.length === 0) {
|
|
3299
|
+
return yield* LedgerError.make({
|
|
3300
|
+
operation,
|
|
3301
|
+
message: `Unknown submission ${validated.submissionId}.`,
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
if (decoded.length !== 1) {
|
|
3305
|
+
return yield* corruptionFailure(
|
|
3306
|
+
operation,
|
|
3307
|
+
"effect_agent_abort_intents",
|
|
3308
|
+
validated.submissionId,
|
|
3309
|
+
"An abort intent lookup returned more than one row.",
|
|
3310
|
+
);
|
|
3311
|
+
}
|
|
3312
|
+
const row = decoded[0];
|
|
3313
|
+
|
|
3314
|
+
if (row.abort_submission_id === null) return undefined;
|
|
3315
|
+
|
|
3316
|
+
return yield* decodeAbortIntent({
|
|
3317
|
+
submissionId: row.abort_submission_id,
|
|
3318
|
+
author: row.author,
|
|
3319
|
+
reason: row.reason,
|
|
3320
|
+
requestedAt: row.requested_at,
|
|
3321
|
+
...(row.canonical_record_id === null ? {} : { canonicalRecordId: row.canonical_record_id }),
|
|
3322
|
+
}).pipe(
|
|
3323
|
+
Effect.mapError((error) =>
|
|
3324
|
+
corruptionFailure(
|
|
3325
|
+
operation,
|
|
3326
|
+
"effect_agent_abort_intents",
|
|
3327
|
+
validated.submissionId,
|
|
3328
|
+
error.message,
|
|
3329
|
+
),
|
|
3330
|
+
),
|
|
3331
|
+
);
|
|
3332
|
+
});
|
|
2661
3333
|
|
|
2662
3334
|
const loadRecoverySnapshot: SubmissionLedger["Service"]["loadRecoverySnapshot"] = Effect.fn(
|
|
2663
3335
|
"DoSubmissionLedger.loadRecoverySnapshot",
|
|
2664
3336
|
)(function* (request: RecoverySnapshotRequest) {
|
|
2665
3337
|
const operation = "ledger load recovery snapshot";
|
|
3338
|
+
|
|
2666
3339
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RecoverySnapshotRequest))(
|
|
2667
3340
|
request,
|
|
2668
3341
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3342
|
+
|
|
2669
3343
|
return yield* sql
|
|
2670
3344
|
.withTransaction(
|
|
2671
3345
|
Effect.gen(function* () {
|
|
@@ -2674,6 +3348,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2674
3348
|
|
|
2675
3349
|
let ownership: OwnershipSnapshot | undefined;
|
|
2676
3350
|
const ownershipRow = yield* readOwnership(operation, validated.submissionId);
|
|
3351
|
+
|
|
2677
3352
|
if (Option.isSome(ownershipRow)) {
|
|
2678
3353
|
ownership = yield* decodeOwnershipSnapshot({
|
|
2679
3354
|
attemptId: ownershipRow.value.attempt_id,
|
|
@@ -2684,6 +3359,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2684
3359
|
}
|
|
2685
3360
|
|
|
2686
3361
|
let inputApplied: InputAppliedMarker | undefined;
|
|
3362
|
+
|
|
2687
3363
|
if (
|
|
2688
3364
|
submissionRow.input_applied_record_id !== null &&
|
|
2689
3365
|
submissionRow.input_applied_sequence !== null
|
|
@@ -2696,6 +3372,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2696
3372
|
|
|
2697
3373
|
let reservation: SettlementReservationSnapshot | undefined;
|
|
2698
3374
|
const reservationRow = yield* readReservation(operation, validated.submissionId);
|
|
3375
|
+
|
|
2699
3376
|
if (Option.isSome(reservationRow)) {
|
|
2700
3377
|
const record = yield* decodeRecordEnvelopeText(reservationRow.value.record_json).pipe(
|
|
2701
3378
|
Effect.mapError((error) =>
|
|
@@ -2707,9 +3384,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2707
3384
|
),
|
|
2708
3385
|
),
|
|
2709
3386
|
);
|
|
3387
|
+
|
|
2710
3388
|
const settlementId = yield* Schema.decodeUnknownEffect(
|
|
2711
3389
|
SettlementReservationSnapshot.fields.settlementId,
|
|
2712
3390
|
)(reservationRow.value.settlement_id).pipe(Effect.mapError(internalFailure(operation)));
|
|
3391
|
+
|
|
2713
3392
|
reservation = SettlementReservationSnapshot.make({
|
|
2714
3393
|
settlementId,
|
|
2715
3394
|
outcome: reservationRow.value.outcome,
|
|
@@ -2721,6 +3400,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2721
3400
|
|
|
2722
3401
|
let abortIntent: AbortIntent | undefined;
|
|
2723
3402
|
const abortRow = yield* readAbortIntent(operation, validated.submissionId);
|
|
3403
|
+
|
|
2724
3404
|
if (Option.isSome(abortRow)) {
|
|
2725
3405
|
abortIntent = yield* abortIntentFromRow(
|
|
2726
3406
|
operation,
|
|
@@ -2738,11 +3418,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2738
3418
|
WHERE joined_host_submission_id = ${validated.submissionId}
|
|
2739
3419
|
ORDER BY queue_sequence ASC
|
|
2740
3420
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
3421
|
+
|
|
2741
3422
|
const joinSubmissions = yield* decodeSubmissionRows(
|
|
2742
3423
|
operation,
|
|
2743
3424
|
validated.submissionId,
|
|
2744
3425
|
joinRows,
|
|
2745
3426
|
);
|
|
3427
|
+
|
|
2746
3428
|
const joins = yield* Effect.forEach(joinSubmissions, (row) =>
|
|
2747
3429
|
decodeJoinSnapshot({
|
|
2748
3430
|
submissionId: row.submission_id,
|
|
@@ -2752,6 +3434,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2752
3434
|
);
|
|
2753
3435
|
|
|
2754
3436
|
let hostSubmissionId: RecoverySnapshot["hostSubmissionId"];
|
|
3437
|
+
|
|
2755
3438
|
if (submissionRow.joined_host_submission_id !== null) {
|
|
2756
3439
|
hostSubmissionId = yield* decodeSubmissionId(
|
|
2757
3440
|
submissionRow.joined_host_submission_id,
|
|
@@ -2759,6 +3442,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2759
3442
|
}
|
|
2760
3443
|
|
|
2761
3444
|
let suspension: SuspensionSnapshot | undefined;
|
|
3445
|
+
|
|
2762
3446
|
if (submissionRow.suspended_reason_json !== null && submissionRow.suspended_at !== null) {
|
|
2763
3447
|
const reason = yield* parseStoredJsonText(submissionRow.suspended_reason_json).pipe(
|
|
2764
3448
|
Effect.mapError((error) =>
|
|
@@ -2770,6 +3454,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2770
3454
|
),
|
|
2771
3455
|
),
|
|
2772
3456
|
);
|
|
3457
|
+
|
|
2773
3458
|
suspension = yield* decodeSuspensionSnapshot({
|
|
2774
3459
|
reason,
|
|
2775
3460
|
suspendedAt: submissionRow.suspended_at,
|
|
@@ -2786,11 +3471,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2786
3471
|
}
|
|
2787
3472
|
|
|
2788
3473
|
const decisionRows = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
3474
|
+
|
|
2789
3475
|
const approvalDecisions = yield* Effect.forEach(decisionRows, (row) =>
|
|
2790
3476
|
approvalIntentFromRow(operation, row),
|
|
2791
3477
|
);
|
|
2792
3478
|
|
|
2793
3479
|
const resolutionRows = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
3480
|
+
|
|
2794
3481
|
const unknownResolutions = yield* Effect.forEach(resolutionRows, (row) =>
|
|
2795
3482
|
unknownResolutionIntentFromRow(operation, row),
|
|
2796
3483
|
);
|
|
@@ -2807,20 +3494,25 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2807
3494
|
WHERE parent_submission_id = ${validated.submissionId}
|
|
2808
3495
|
ORDER BY parent_tool_call_id ASC
|
|
2809
3496
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
3497
|
+
|
|
2810
3498
|
const decodedChildReservations = yield* decodeChildReservationRows(
|
|
2811
3499
|
operation,
|
|
2812
3500
|
validated.submissionId,
|
|
2813
3501
|
childReservationRows,
|
|
2814
3502
|
);
|
|
3503
|
+
|
|
2815
3504
|
const childReservations = yield* Effect.forEach(decodedChildReservations, (row) =>
|
|
2816
3505
|
childReservationSnapshotFromRow(operation, row),
|
|
2817
3506
|
);
|
|
3507
|
+
|
|
2818
3508
|
const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
|
|
2819
3509
|
const markersByChild = new Map(markers.map((row) => [row.child_submission_id, row]));
|
|
2820
3510
|
const childAttachments: Array<ChildAttachmentSnapshot> = [];
|
|
3511
|
+
|
|
2821
3512
|
for (const row of decodedChildReservations) {
|
|
2822
3513
|
if (row.child_submission_id === null) continue;
|
|
2823
3514
|
const child = yield* readSubmission(operation, row.child_submission_id);
|
|
3515
|
+
|
|
2824
3516
|
if (Option.isSome(child)) {
|
|
2825
3517
|
childAttachments.push(
|
|
2826
3518
|
yield* decodeChildAttachmentSnapshot({
|
|
@@ -2835,6 +3527,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2835
3527
|
continue;
|
|
2836
3528
|
}
|
|
2837
3529
|
const marker = markersByChild.get(row.child_submission_id);
|
|
3530
|
+
|
|
2838
3531
|
if (marker === undefined) continue;
|
|
2839
3532
|
childAttachments.push(
|
|
2840
3533
|
yield* decodeChildAttachmentSnapshot({
|
|
@@ -2847,6 +3540,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2847
3540
|
}
|
|
2848
3541
|
|
|
2849
3542
|
let parentLinkage: ParentLinkage | undefined;
|
|
3543
|
+
|
|
2850
3544
|
if (
|
|
2851
3545
|
submissionRow.parent_submission_id !== null &&
|
|
2852
3546
|
submissionRow.parent_tool_call_id !== null
|
|
@@ -2906,6 +3600,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2906
3600
|
releaseChildBudget,
|
|
2907
3601
|
scanNonterminal,
|
|
2908
3602
|
loadRecoverySnapshot,
|
|
3603
|
+
readAbortIntent: readAbortIntentForSubmission,
|
|
2909
3604
|
}),
|
|
2910
3605
|
);
|
|
2911
3606
|
});
|
|
@@ -2923,18 +3618,22 @@ export const submissionLedgerLayer: Layer.Layer<
|
|
|
2923
3618
|
|
|
2924
3619
|
/**
|
|
2925
3620
|
* A composition-root convenience Layer for the durable Submission Ledger. Point it at the
|
|
2926
|
-
* same `ctx.storage` as the
|
|
3621
|
+
* same `ctx.storage` as the ThreadStore so claims fence the same producer epochs.
|
|
2927
3622
|
*/
|
|
2928
3623
|
export const ledgerLayer = (
|
|
2929
3624
|
options: DoStorageOptions,
|
|
2930
3625
|
): Layer.Layer<SubmissionLedger, DoStorageInitializationError> =>
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3626
|
+
Layer.unwrap(
|
|
3627
|
+
Effect.map(DoStorageConfig, (config) =>
|
|
3628
|
+
submissionLedgerLayer.pipe(
|
|
3629
|
+
Layer.provide(
|
|
3630
|
+
Layer.mergeAll(
|
|
3631
|
+
Layer.succeed(DoStorageConfig)(config),
|
|
3632
|
+
storageFailpointLayer(options),
|
|
3633
|
+
SqliteClient.layer({ storage: options.storage }),
|
|
3634
|
+
BrowserCrypto.layer,
|
|
3635
|
+
),
|
|
3636
|
+
),
|
|
2938
3637
|
),
|
|
2939
3638
|
),
|
|
2940
|
-
);
|
|
3639
|
+
).pipe(Layer.provide(storageConfigLayer(options)));
|