@effect-agent/storage-cloudflare 0.1.0-beta.6 → 0.1.0-beta.60
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-UrxrvbnF.d.mts +98 -0
- package/dist/DoStorageError.d.mts +2 -0
- package/dist/DoStorageError.mjs +162 -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-Pt3jEHdW.d.mts +10 -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 +1826 -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 +432 -0
- package/dist/DoThreadStore.mjs.map +1 -0
- package/dist/MemoryProtocol.d.mts +19796 -0
- package/dist/MemoryProtocol.mjs +182 -0
- package/dist/MemoryProtocol.mjs.map +1 -0
- package/dist/PortProtocol.d.mts +2951 -0
- package/dist/PortProtocol.mjs +176 -0
- package/dist/PortProtocol.mjs.map +1 -0
- package/dist/PortRouting.d.mts +82 -0
- package/dist/PortRouting.mjs +405 -0
- package/dist/PortRouting.mjs.map +1 -0
- package/dist/do-journal-Brv7xSH5.mjs +869 -0
- package/dist/do-journal-Brv7xSH5.mjs.map +1 -0
- package/dist/index.d.mts +14 -1505
- package/dist/index.mjs +14 -3695
- package/dist/migrations-soC86CQA.mjs +267 -0
- package/dist/migrations-soC86CQA.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} +8 -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} +757 -123
- package/src/DoSubscriptionStore.ts +329 -0
- package/src/DoThreadStore.ts +924 -0
- package/src/MemoryProtocol.ts +291 -0
- package/src/{port-protocol.ts → PortProtocol.ts} +41 -34
- package/src/{routing.ts → PortRouting.ts} +266 -259
- package/src/index.ts +13 -32
- package/src/{do-journal.ts → internal/do-journal.ts} +495 -214
- package/src/internal/message-delivery-schema.ts +24 -0
- package/src/{migrations.ts → internal/migrations.ts} +40 -35
- 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,7 +1882,9 @@ 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
|
|
|
@@ -1573,14 +1892,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1573
1892
|
"DoSubmissionLedger.finalizeSettlement",
|
|
1574
1893
|
)(function* (request: SettlementFinalization) {
|
|
1575
1894
|
const operation = "ledger finalize settlement";
|
|
1895
|
+
|
|
1576
1896
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SettlementFinalization))(
|
|
1577
1897
|
request,
|
|
1578
1898
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
1899
|
+
|
|
1579
1900
|
yield* hitFailpoint("ledger:finalize-settlement:before", operation);
|
|
1901
|
+
|
|
1580
1902
|
const settlement = yield* inWriteTransaction(
|
|
1581
1903
|
operation,
|
|
1582
1904
|
Effect.gen(function* () {
|
|
1583
1905
|
const reservation = yield* readReservation(operation, validated.submissionId);
|
|
1906
|
+
|
|
1584
1907
|
if (Option.isNone(reservation)) {
|
|
1585
1908
|
return yield* LedgerError.make({
|
|
1586
1909
|
operation,
|
|
@@ -1593,7 +1916,32 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1593
1916
|
existingOutcome: reservation.value.outcome,
|
|
1594
1917
|
});
|
|
1595
1918
|
}
|
|
1919
|
+
|
|
1920
|
+
const reservationRecord = yield* decodeRecordEnvelopeText(
|
|
1921
|
+
reservation.value.record_json,
|
|
1922
|
+
).pipe(
|
|
1923
|
+
Effect.mapError((error) =>
|
|
1924
|
+
corruptionFailure(
|
|
1925
|
+
operation,
|
|
1926
|
+
"effect_agent_settlement_reservations",
|
|
1927
|
+
validated.submissionId,
|
|
1928
|
+
error.message,
|
|
1929
|
+
),
|
|
1930
|
+
),
|
|
1931
|
+
);
|
|
1932
|
+
|
|
1933
|
+
const settlementFailure = settlementFailureFromRecord(reservationRecord);
|
|
1934
|
+
|
|
1935
|
+
if ((reservation.value.outcome === "failed") !== (settlementFailure !== undefined)) {
|
|
1936
|
+
return yield* corruptionFailure(
|
|
1937
|
+
operation,
|
|
1938
|
+
"effect_agent_settlement_reservations",
|
|
1939
|
+
validated.submissionId,
|
|
1940
|
+
"The reserved outcome and canonical failure diagnostic disagree.",
|
|
1941
|
+
);
|
|
1942
|
+
}
|
|
1596
1943
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
1944
|
+
|
|
1597
1945
|
if (submission.state === "settled") {
|
|
1598
1946
|
if (reservation.value.finalized_at === null) {
|
|
1599
1947
|
return yield* corruptionFailure(
|
|
@@ -1603,15 +1951,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1603
1951
|
"A settled Submission's reservation carries no finalization timestamp.",
|
|
1604
1952
|
);
|
|
1605
1953
|
}
|
|
1954
|
+
|
|
1606
1955
|
return yield* decodeSettlement({
|
|
1607
1956
|
submissionId: validated.submissionId,
|
|
1608
1957
|
settlementId: validated.settlementId,
|
|
1609
1958
|
receiptId: submission.receipt_id,
|
|
1610
1959
|
outcome: reservation.value.outcome,
|
|
1960
|
+
...(settlementFailure === undefined ? {} : { failure: settlementFailure }),
|
|
1611
1961
|
settledAt: reservation.value.finalized_at,
|
|
1612
1962
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1613
1963
|
}
|
|
1614
1964
|
const now = yield* currentInstant;
|
|
1965
|
+
|
|
1615
1966
|
yield* sql`
|
|
1616
1967
|
UPDATE effect_agent_submissions
|
|
1617
1968
|
SET state = 'settled', settled_outcome = ${reservation.value.outcome}
|
|
@@ -1626,16 +1977,20 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1626
1977
|
DELETE FROM effect_agent_submission_ownership
|
|
1627
1978
|
WHERE submission_id = ${validated.submissionId}
|
|
1628
1979
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1980
|
+
|
|
1629
1981
|
return yield* decodeSettlement({
|
|
1630
1982
|
submissionId: validated.submissionId,
|
|
1631
1983
|
settlementId: validated.settlementId,
|
|
1632
1984
|
receiptId: submission.receipt_id,
|
|
1633
1985
|
outcome: reservation.value.outcome,
|
|
1986
|
+
...(settlementFailure === undefined ? {} : { failure: settlementFailure }),
|
|
1634
1987
|
settledAt: now.iso,
|
|
1635
1988
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1636
1989
|
}),
|
|
1637
1990
|
);
|
|
1991
|
+
|
|
1638
1992
|
yield* hitFailpoint("ledger:finalize-settlement:after", operation);
|
|
1993
|
+
|
|
1639
1994
|
return settlement;
|
|
1640
1995
|
});
|
|
1641
1996
|
|
|
@@ -1643,14 +1998,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1643
1998
|
"DoSubmissionLedger.requestAbort",
|
|
1644
1999
|
)(function* (request: AbortCommand) {
|
|
1645
2000
|
const operation = "ledger request abort";
|
|
2001
|
+
|
|
1646
2002
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AbortCommand))(request).pipe(
|
|
1647
2003
|
Effect.mapError(internalFailure(operation)),
|
|
1648
2004
|
);
|
|
2005
|
+
|
|
1649
2006
|
yield* hitFailpoint("ledger:request-abort:before", operation);
|
|
2007
|
+
|
|
1650
2008
|
const intent = yield* inWriteTransaction(
|
|
1651
2009
|
operation,
|
|
1652
2010
|
Effect.gen(function* () {
|
|
1653
2011
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2012
|
+
|
|
1654
2013
|
if (submission.state === "settled") {
|
|
1655
2014
|
if (submission.settled_outcome === null) {
|
|
1656
2015
|
return yield* corruptionFailure(
|
|
@@ -1660,6 +2019,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1660
2019
|
"A settled Submission carries no terminal outcome.",
|
|
1661
2020
|
);
|
|
1662
2021
|
}
|
|
2022
|
+
|
|
1663
2023
|
return yield* SettlementConflict.make({
|
|
1664
2024
|
submissionId: validated.submissionId,
|
|
1665
2025
|
existingOutcome: submission.settled_outcome,
|
|
@@ -1677,15 +2037,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1677
2037
|
"A joined Submission carries no host linkage.",
|
|
1678
2038
|
);
|
|
1679
2039
|
}
|
|
2040
|
+
|
|
1680
2041
|
const hostSubmissionId = yield* decodeSubmissionId(
|
|
1681
2042
|
submission.joined_host_submission_id,
|
|
1682
2043
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2044
|
+
|
|
1683
2045
|
return yield* JoinedToHost.make({
|
|
1684
2046
|
submissionId: validated.submissionId,
|
|
1685
2047
|
hostSubmissionId,
|
|
1686
2048
|
});
|
|
1687
2049
|
}
|
|
1688
2050
|
const existing = yield* readAbortIntent(operation, validated.submissionId);
|
|
2051
|
+
|
|
1689
2052
|
if (Option.isSome(existing)) {
|
|
1690
2053
|
return yield* abortIntentFromRow(
|
|
1691
2054
|
operation,
|
|
@@ -1695,6 +2058,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1695
2058
|
);
|
|
1696
2059
|
}
|
|
1697
2060
|
const now = yield* currentInstant;
|
|
2061
|
+
|
|
1698
2062
|
yield* sql`
|
|
1699
2063
|
INSERT INTO effect_agent_abort_intents (
|
|
1700
2064
|
submission_id,
|
|
@@ -1708,11 +2072,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1708
2072
|
${now.iso}
|
|
1709
2073
|
)
|
|
1710
2074
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2075
|
+
|
|
1711
2076
|
const canonicalRecordId = yield* canonicalAbortRecordId(
|
|
1712
2077
|
operation,
|
|
1713
|
-
submission.
|
|
2078
|
+
submission.thread_id,
|
|
1714
2079
|
validated.submissionId,
|
|
1715
2080
|
);
|
|
2081
|
+
|
|
1716
2082
|
return yield* decodeAbortIntent({
|
|
1717
2083
|
submissionId: validated.submissionId,
|
|
1718
2084
|
author: validated.author,
|
|
@@ -1722,7 +2088,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1722
2088
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
1723
2089
|
}),
|
|
1724
2090
|
);
|
|
2091
|
+
|
|
1725
2092
|
yield* hitFailpoint("ledger:request-abort:after", operation);
|
|
2093
|
+
|
|
1726
2094
|
return intent;
|
|
1727
2095
|
});
|
|
1728
2096
|
|
|
@@ -1730,31 +2098,38 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1730
2098
|
"DoSubmissionLedger.claimJoining",
|
|
1731
2099
|
)(function* (request: ClaimJoiningRequest) {
|
|
1732
2100
|
const operation = "ledger claim joining";
|
|
2101
|
+
|
|
1733
2102
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ClaimJoiningRequest))(
|
|
1734
2103
|
request,
|
|
1735
2104
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2105
|
+
|
|
1736
2106
|
yield* hitFailpoint("ledger:claim-joining:before", operation);
|
|
2107
|
+
|
|
1737
2108
|
const claims = yield* inWriteTransaction(
|
|
1738
2109
|
operation,
|
|
1739
2110
|
Effect.gen(function* () {
|
|
1740
2111
|
const host = yield* requireSubmission(operation, validated.hostSubmissionId);
|
|
1741
|
-
|
|
2112
|
+
|
|
2113
|
+
if (host.thread_id !== validated.threadId) {
|
|
1742
2114
|
return yield* LedgerError.make({
|
|
1743
2115
|
operation,
|
|
1744
|
-
message: `Host submission ${validated.hostSubmissionId} does not belong to
|
|
2116
|
+
message: `Host submission ${validated.hostSubmissionId} does not belong to thread ${validated.threadId}.`,
|
|
1745
2117
|
});
|
|
1746
2118
|
}
|
|
1747
2119
|
// The host Attempt already owns the lane; no epoch bump happens here (plan §2.5).
|
|
1748
2120
|
yield* requireOwnership(operation, host, validated.ownershipToken);
|
|
2121
|
+
|
|
1749
2122
|
const laterRows = yield* sql<Record<string, unknown>>`
|
|
1750
2123
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
1751
2124
|
FROM effect_agent_submissions
|
|
1752
|
-
WHERE
|
|
2125
|
+
WHERE thread_id = ${validated.threadId}
|
|
1753
2126
|
AND queue_sequence > ${host.queue_sequence}
|
|
1754
2127
|
ORDER BY queue_sequence ASC
|
|
1755
2128
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1756
|
-
|
|
2129
|
+
|
|
2130
|
+
const later = yield* decodeSubmissionRows(operation, validated.threadId, laterRows);
|
|
1757
2131
|
const claimed: Array<JoiningClaim> = [];
|
|
2132
|
+
|
|
1758
2133
|
for (const row of later) {
|
|
1759
2134
|
if (claimed.length >= validated.maxCount) break;
|
|
1760
2135
|
// Rows already claimed by THIS host extend its contiguous prefix and are skipped;
|
|
@@ -1777,6 +2152,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1777
2152
|
SET state = 'joining', joined_host_submission_id = ${validated.hostSubmissionId}
|
|
1778
2153
|
WHERE submission_id = ${row.submission_id}
|
|
1779
2154
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2155
|
+
|
|
1780
2156
|
const inputPayload = yield* parseStoredJsonText(row.input_json).pipe(
|
|
1781
2157
|
Effect.mapError((error) =>
|
|
1782
2158
|
corruptionFailure(
|
|
@@ -1787,6 +2163,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1787
2163
|
),
|
|
1788
2164
|
),
|
|
1789
2165
|
);
|
|
2166
|
+
|
|
1790
2167
|
claimed.push(
|
|
1791
2168
|
yield* decodeJoiningClaim({
|
|
1792
2169
|
submissionId: row.submission_id,
|
|
@@ -1795,10 +2172,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1795
2172
|
}).pipe(Effect.mapError(internalFailure(operation))),
|
|
1796
2173
|
);
|
|
1797
2174
|
}
|
|
1798
|
-
|
|
2175
|
+
|
|
2176
|
+
return claimed;
|
|
1799
2177
|
}),
|
|
1800
2178
|
);
|
|
2179
|
+
|
|
1801
2180
|
yield* hitFailpoint("ledger:claim-joining:after", operation);
|
|
2181
|
+
|
|
1802
2182
|
return claims;
|
|
1803
2183
|
});
|
|
1804
2184
|
|
|
@@ -1806,14 +2186,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1806
2186
|
"DoSubmissionLedger.markJoined",
|
|
1807
2187
|
)(function* (request: MarkJoinedRequest) {
|
|
1808
2188
|
const operation = "ledger mark joined";
|
|
2189
|
+
|
|
1809
2190
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkJoinedRequest))(
|
|
1810
2191
|
request,
|
|
1811
2192
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2193
|
+
|
|
1812
2194
|
yield* hitFailpoint("ledger:mark-joined:before", operation);
|
|
1813
2195
|
yield* inWriteTransaction(
|
|
1814
2196
|
operation,
|
|
1815
2197
|
Effect.gen(function* () {
|
|
1816
2198
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2199
|
+
|
|
1817
2200
|
if (submission.joined_host_submission_id === null) {
|
|
1818
2201
|
return yield* LedgerError.make({
|
|
1819
2202
|
operation,
|
|
@@ -1821,6 +2204,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1821
2204
|
});
|
|
1822
2205
|
}
|
|
1823
2206
|
const host = yield* requireSubmission(operation, submission.joined_host_submission_id);
|
|
2207
|
+
|
|
1824
2208
|
// The lane is host-owned: the presented token must own the HOST's ownership period,
|
|
1825
2209
|
// which also lets a later host Attempt repair a lost marker from history (DUR-016).
|
|
1826
2210
|
yield* requireOwnership(operation, host, validated.ownershipToken);
|
|
@@ -1831,6 +2215,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1831
2215
|
) {
|
|
1832
2216
|
return;
|
|
1833
2217
|
}
|
|
2218
|
+
|
|
1834
2219
|
return yield* corruptionFailure(
|
|
1835
2220
|
operation,
|
|
1836
2221
|
"effect_agent_submissions",
|
|
@@ -1861,14 +2246,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1861
2246
|
"DoSubmissionLedger.revertJoining",
|
|
1862
2247
|
)(function* (request: RevertJoiningRequest) {
|
|
1863
2248
|
const operation = "ledger revert joining";
|
|
2249
|
+
|
|
1864
2250
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RevertJoiningRequest))(
|
|
1865
2251
|
request,
|
|
1866
2252
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2253
|
+
|
|
1867
2254
|
yield* hitFailpoint("ledger:revert-joining:before", operation);
|
|
1868
2255
|
yield* inWriteTransaction(
|
|
1869
2256
|
operation,
|
|
1870
2257
|
Effect.gen(function* () {
|
|
1871
2258
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2259
|
+
|
|
1872
2260
|
// Idempotent and recovery-only: only a still-`joining` Submission reverts; an
|
|
1873
2261
|
// already-joined (or already-reverted) Submission is a no-op (DUR-016).
|
|
1874
2262
|
if (submission.state !== "joining") return;
|
|
@@ -1885,17 +2273,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1885
2273
|
const suspend: SubmissionLedger["Service"]["suspend"] = Effect.fn("DoSubmissionLedger.suspend")(
|
|
1886
2274
|
function* (request: SuspendRequest) {
|
|
1887
2275
|
const operation = "ledger suspend";
|
|
2276
|
+
|
|
1888
2277
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(SuspendRequest))(
|
|
1889
2278
|
request,
|
|
1890
2279
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2280
|
+
|
|
1891
2281
|
const reasonJson = yield* encodeSuspensionReasonText(validated.reason).pipe(
|
|
1892
2282
|
Effect.mapError(internalFailure(operation)),
|
|
1893
2283
|
);
|
|
2284
|
+
|
|
1894
2285
|
yield* hitFailpoint("ledger:suspend:before", operation);
|
|
2286
|
+
|
|
1895
2287
|
const outcome = yield* inWriteTransaction(
|
|
1896
2288
|
operation,
|
|
1897
2289
|
Effect.gen(function* () {
|
|
1898
2290
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2291
|
+
|
|
1899
2292
|
if (submission.state === "settled") {
|
|
1900
2293
|
if (submission.settled_outcome === null) {
|
|
1901
2294
|
return yield* corruptionFailure(
|
|
@@ -1905,6 +2298,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1905
2298
|
"A settled Submission carries no terminal outcome.",
|
|
1906
2299
|
);
|
|
1907
2300
|
}
|
|
2301
|
+
|
|
1908
2302
|
return yield* SettlementConflict.make({
|
|
1909
2303
|
submissionId: validated.submissionId,
|
|
1910
2304
|
existingOutcome: submission.settled_outcome,
|
|
@@ -1913,6 +2307,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1913
2307
|
// An exact terminal outcome is already reserved (DUR-011); suspension would
|
|
1914
2308
|
// contradict it, so the reservation wins.
|
|
1915
2309
|
const reservation = yield* readReservation(operation, validated.submissionId);
|
|
2310
|
+
|
|
1916
2311
|
if (Option.isSome(reservation)) {
|
|
1917
2312
|
return yield* SettlementConflict.make({
|
|
1918
2313
|
submissionId: validated.submissionId,
|
|
@@ -1923,35 +2318,39 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1923
2318
|
// A covering event that raced ahead of the suspend transaction resumes the caller
|
|
1924
2319
|
// immediately WITHOUT releasing the lane (plan §2.6, §12). For WaitingForChild the
|
|
1925
2320
|
// covering evidence is EITHER a locally settled child row OR a durable cross-store
|
|
1926
|
-
// notification marker: parent and child
|
|
2321
|
+
// notification marker: parent and child Threads live in different Durable
|
|
1927
2322
|
// Objects, and the port contract requires that a child settlement reported (via
|
|
1928
2323
|
// `recordChildSettled` → marker) before this suspend commits is observed here.
|
|
1929
2324
|
if (validated.reason._tag === "ApprovalPending") {
|
|
1930
2325
|
const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
1931
2326
|
const decided = new Set(decisions.map((row) => row.tool_call_id));
|
|
2327
|
+
|
|
1932
2328
|
if (validated.reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) {
|
|
1933
|
-
return
|
|
2329
|
+
return RESUME_IMMEDIATELY;
|
|
1934
2330
|
}
|
|
1935
2331
|
} else {
|
|
1936
2332
|
const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
|
|
1937
2333
|
const markerChildren = new Set(markers.map((row) => row.child_submission_id));
|
|
1938
2334
|
let allSettled = true;
|
|
2335
|
+
|
|
1939
2336
|
for (const child of validated.reason.children) {
|
|
1940
2337
|
const settled = yield* childProvablySettled(
|
|
1941
2338
|
operation,
|
|
1942
2339
|
markerChildren,
|
|
1943
2340
|
child.childSubmissionId,
|
|
1944
2341
|
);
|
|
2342
|
+
|
|
1945
2343
|
if (!settled) {
|
|
1946
2344
|
allSettled = false;
|
|
1947
2345
|
break;
|
|
1948
2346
|
}
|
|
1949
2347
|
}
|
|
1950
2348
|
if (allSettled) {
|
|
1951
|
-
return
|
|
2349
|
+
return RESUME_IMMEDIATELY;
|
|
1952
2350
|
}
|
|
1953
2351
|
}
|
|
1954
2352
|
const now = yield* currentInstant;
|
|
2353
|
+
|
|
1955
2354
|
yield* sql`
|
|
1956
2355
|
UPDATE effect_agent_submissions
|
|
1957
2356
|
SET
|
|
@@ -1966,10 +2365,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1966
2365
|
DELETE FROM effect_agent_submission_ownership
|
|
1967
2366
|
WHERE submission_id = ${validated.submissionId}
|
|
1968
2367
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
1969
|
-
|
|
2368
|
+
|
|
2369
|
+
return SUSPENDED;
|
|
1970
2370
|
}),
|
|
1971
2371
|
);
|
|
2372
|
+
|
|
1972
2373
|
yield* hitFailpoint("ledger:suspend:after", operation);
|
|
2374
|
+
|
|
1973
2375
|
return outcome;
|
|
1974
2376
|
},
|
|
1975
2377
|
);
|
|
@@ -1985,6 +2387,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1985
2387
|
submission: SubmissionRow,
|
|
1986
2388
|
): Effect.fn.Return<void, LedgerError> {
|
|
1987
2389
|
if (submission.state !== "suspended" || submission.suspended_reason_json === null) return;
|
|
2390
|
+
|
|
1988
2391
|
const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(
|
|
1989
2392
|
submission.suspended_reason_json,
|
|
1990
2393
|
).pipe(
|
|
@@ -1997,9 +2400,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
1997
2400
|
),
|
|
1998
2401
|
),
|
|
1999
2402
|
);
|
|
2403
|
+
|
|
2000
2404
|
if (reason._tag !== "ApprovalPending") return;
|
|
2001
2405
|
const decisions = yield* readApprovalDecisions(operation, submission.submission_id);
|
|
2002
2406
|
const decided = new Set(decisions.map((row) => row.tool_call_id));
|
|
2407
|
+
|
|
2003
2408
|
if (!reason.toolCallIds.every((toolCallId) => decided.has(toolCallId))) return;
|
|
2004
2409
|
yield* sql`
|
|
2005
2410
|
UPDATE effect_agent_submissions
|
|
@@ -2015,14 +2420,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2015
2420
|
"DoSubmissionLedger.recordApprovalDecision",
|
|
2016
2421
|
)(function* (command: ApprovalDecisionCommand) {
|
|
2017
2422
|
const operation = "ledger record approval decision";
|
|
2423
|
+
|
|
2018
2424
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ApprovalDecisionCommand))(
|
|
2019
2425
|
command,
|
|
2020
2426
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2427
|
+
|
|
2021
2428
|
yield* hitFailpoint("ledger:approval-decision:before", operation);
|
|
2429
|
+
|
|
2022
2430
|
const intent = yield* inWriteTransaction(
|
|
2023
2431
|
operation,
|
|
2024
2432
|
Effect.gen(function* () {
|
|
2025
2433
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2434
|
+
|
|
2026
2435
|
if (submission.state === "settled") {
|
|
2027
2436
|
if (submission.settled_outcome === null) {
|
|
2028
2437
|
return yield* corruptionFailure(
|
|
@@ -2032,6 +2441,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2032
2441
|
"A settled Submission carries no terminal outcome.",
|
|
2033
2442
|
);
|
|
2034
2443
|
}
|
|
2444
|
+
|
|
2035
2445
|
return yield* SettlementConflict.make({
|
|
2036
2446
|
submissionId: validated.submissionId,
|
|
2037
2447
|
existingOutcome: submission.settled_outcome,
|
|
@@ -2039,6 +2449,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2039
2449
|
}
|
|
2040
2450
|
const decisions = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
2041
2451
|
const existing = decisions.find((row) => row.tool_call_id === validated.toolCallId);
|
|
2452
|
+
|
|
2042
2453
|
if (existing !== undefined) {
|
|
2043
2454
|
// Idempotent per (submissionId, toolCallId): repeating the SAME decision replays
|
|
2044
2455
|
// the recorded intent unchanged; a divergent re-decision conflicts.
|
|
@@ -2049,9 +2460,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2049
2460
|
existingDecision: existing.decision,
|
|
2050
2461
|
});
|
|
2051
2462
|
}
|
|
2463
|
+
|
|
2052
2464
|
return yield* approvalIntentFromRow(operation, existing);
|
|
2053
2465
|
}
|
|
2054
2466
|
const now = yield* currentInstant;
|
|
2467
|
+
|
|
2055
2468
|
yield* sql`
|
|
2056
2469
|
INSERT INTO effect_agent_approval_decisions (
|
|
2057
2470
|
submission_id,
|
|
@@ -2070,6 +2483,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2070
2483
|
)
|
|
2071
2484
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2072
2485
|
yield* wakeSuspendedIfCovered(operation, submission);
|
|
2486
|
+
|
|
2073
2487
|
return yield* decodeApprovalDecisionIntent({
|
|
2074
2488
|
submissionId: validated.submissionId,
|
|
2075
2489
|
toolCallId: validated.toolCallId,
|
|
@@ -2080,7 +2494,9 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2080
2494
|
}).pipe(Effect.mapError(internalFailure(operation)));
|
|
2081
2495
|
}),
|
|
2082
2496
|
);
|
|
2497
|
+
|
|
2083
2498
|
yield* hitFailpoint("ledger:approval-decision:after", operation);
|
|
2499
|
+
|
|
2084
2500
|
return intent;
|
|
2085
2501
|
});
|
|
2086
2502
|
|
|
@@ -2088,14 +2504,17 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2088
2504
|
"DoSubmissionLedger.markUnknown",
|
|
2089
2505
|
)(function* (request: MarkUnknownRequest) {
|
|
2090
2506
|
const operation = "ledger mark unknown";
|
|
2507
|
+
|
|
2091
2508
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(MarkUnknownRequest))(
|
|
2092
2509
|
request,
|
|
2093
2510
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2511
|
+
|
|
2094
2512
|
yield* hitFailpoint("ledger:mark-unknown:before", operation);
|
|
2095
2513
|
yield* inWriteTransaction(
|
|
2096
2514
|
operation,
|
|
2097
2515
|
Effect.gen(function* () {
|
|
2098
2516
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2517
|
+
|
|
2099
2518
|
if (submission.state === "settled") {
|
|
2100
2519
|
if (submission.settled_outcome === null) {
|
|
2101
2520
|
return yield* corruptionFailure(
|
|
@@ -2105,6 +2524,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2105
2524
|
"A settled Submission carries no terminal outcome.",
|
|
2106
2525
|
);
|
|
2107
2526
|
}
|
|
2527
|
+
|
|
2108
2528
|
return yield* SettlementConflict.make({
|
|
2109
2529
|
submissionId: validated.submissionId,
|
|
2110
2530
|
existingOutcome: submission.settled_outcome,
|
|
@@ -2113,6 +2533,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2113
2533
|
// A reserved exact outcome wins over a late Unknown marking (DUR-011); the recovery
|
|
2114
2534
|
// classifier orders reservation ahead of MarkUnknown for the same reason.
|
|
2115
2535
|
const reservation = yield* readReservation(operation, validated.submissionId);
|
|
2536
|
+
|
|
2116
2537
|
if (Option.isSome(reservation)) {
|
|
2117
2538
|
return yield* SettlementConflict.make({
|
|
2118
2539
|
submissionId: validated.submissionId,
|
|
@@ -2123,13 +2544,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2123
2544
|
// set while the first recorded reason is kept.
|
|
2124
2545
|
const existingIds = yield* storedUnknownToolCallIds(operation, submission);
|
|
2125
2546
|
const known = new Set(existingIds);
|
|
2547
|
+
|
|
2126
2548
|
const merged = [
|
|
2127
2549
|
...existingIds,
|
|
2128
2550
|
...validated.toolCallIds.filter((toolCallId) => !known.has(toolCallId)),
|
|
2129
2551
|
];
|
|
2552
|
+
|
|
2130
2553
|
const idsJson = yield* encodeToolCallIdsText(merged).pipe(
|
|
2131
2554
|
Effect.mapError(internalFailure(operation)),
|
|
2132
2555
|
);
|
|
2556
|
+
|
|
2133
2557
|
yield* sql`
|
|
2134
2558
|
UPDATE effect_agent_submissions
|
|
2135
2559
|
SET
|
|
@@ -2147,17 +2571,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2147
2571
|
"DoSubmissionLedger.recordUnknownResolution",
|
|
2148
2572
|
)(function* (command: UnknownResolutionCommand) {
|
|
2149
2573
|
const operation = "ledger record unknown resolution";
|
|
2574
|
+
|
|
2150
2575
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(UnknownResolutionCommand))(
|
|
2151
2576
|
command,
|
|
2152
2577
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2578
|
+
|
|
2153
2579
|
const resolutionJson = yield* encodeUnknownResolutionText(validated.resolution).pipe(
|
|
2154
2580
|
Effect.mapError(internalFailure(operation)),
|
|
2155
2581
|
);
|
|
2582
|
+
|
|
2156
2583
|
yield* hitFailpoint("ledger:unknown-resolution:before", operation);
|
|
2584
|
+
|
|
2157
2585
|
const intent = yield* inWriteTransaction(
|
|
2158
2586
|
operation,
|
|
2159
2587
|
Effect.gen(function* () {
|
|
2160
2588
|
const submission = yield* requireSubmission(operation, validated.submissionId);
|
|
2589
|
+
|
|
2161
2590
|
if (submission.state === "settled") {
|
|
2162
2591
|
if (submission.settled_outcome === null) {
|
|
2163
2592
|
return yield* corruptionFailure(
|
|
@@ -2167,6 +2596,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2167
2596
|
"A settled Submission carries no terminal outcome.",
|
|
2168
2597
|
);
|
|
2169
2598
|
}
|
|
2599
|
+
|
|
2170
2600
|
return yield* SettlementConflict.make({
|
|
2171
2601
|
submissionId: validated.submissionId,
|
|
2172
2602
|
existingOutcome: submission.settled_outcome,
|
|
@@ -2174,19 +2604,30 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2174
2604
|
}
|
|
2175
2605
|
const resolutions = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
2176
2606
|
const existing = resolutions.find((row) => row.tool_call_id === validated.toolCallId);
|
|
2177
|
-
|
|
2607
|
+
|
|
2608
|
+
const existingIntent =
|
|
2609
|
+
existing === undefined
|
|
2610
|
+
? undefined
|
|
2611
|
+
: yield* unknownResolutionIntentFromRow(operation, existing);
|
|
2612
|
+
|
|
2613
|
+
if (
|
|
2614
|
+
existingIntent !== undefined &&
|
|
2615
|
+
!equivalentUnknownResolution(existingIntent.resolution, validated.resolution)
|
|
2616
|
+
) {
|
|
2178
2617
|
return yield* UnknownResolutionConflict.make({
|
|
2179
2618
|
submissionId: validated.submissionId,
|
|
2180
2619
|
toolCallId: validated.toolCallId,
|
|
2181
2620
|
});
|
|
2182
2621
|
}
|
|
2183
2622
|
let resolved: UnknownResolutionIntent;
|
|
2184
|
-
|
|
2623
|
+
|
|
2624
|
+
if (existingIntent !== undefined) {
|
|
2185
2625
|
// Idempotent replay of the recorded intent (author/reason may differ; the stored
|
|
2186
2626
|
// audit fields win, exactly like requestAbort).
|
|
2187
|
-
resolved =
|
|
2627
|
+
resolved = existingIntent;
|
|
2188
2628
|
} else {
|
|
2189
2629
|
const now = yield* currentInstant;
|
|
2630
|
+
|
|
2190
2631
|
yield* sql`
|
|
2191
2632
|
INSERT INTO effect_agent_unknown_resolutions (
|
|
2192
2633
|
submission_id,
|
|
@@ -2204,9 +2645,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2204
2645
|
${now.iso}
|
|
2205
2646
|
)
|
|
2206
2647
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2648
|
+
|
|
2207
2649
|
const resolution = yield* parseStoredJsonText(resolutionJson).pipe(
|
|
2208
2650
|
Effect.mapError(internalFailure(operation)),
|
|
2209
2651
|
);
|
|
2652
|
+
|
|
2210
2653
|
resolved = yield* decodeUnknownResolutionIntent({
|
|
2211
2654
|
submissionId: validated.submissionId,
|
|
2212
2655
|
toolCallId: validated.toolCallId,
|
|
@@ -2223,6 +2666,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2223
2666
|
const markedIds = yield* storedUnknownToolCallIds(operation, submission);
|
|
2224
2667
|
const covering = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
2225
2668
|
const coveredIds = new Set(covering.map((row) => row.tool_call_id));
|
|
2669
|
+
|
|
2226
2670
|
if (markedIds.every((toolCallId) => coveredIds.has(toolCallId))) {
|
|
2227
2671
|
yield* sql`
|
|
2228
2672
|
UPDATE effect_agent_submissions
|
|
@@ -2234,10 +2678,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2234
2678
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2235
2679
|
}
|
|
2236
2680
|
}
|
|
2681
|
+
|
|
2237
2682
|
return resolved;
|
|
2238
2683
|
}),
|
|
2239
2684
|
);
|
|
2685
|
+
|
|
2240
2686
|
yield* hitFailpoint("ledger:unknown-resolution:after", operation);
|
|
2687
|
+
|
|
2241
2688
|
return intent;
|
|
2242
2689
|
});
|
|
2243
2690
|
|
|
@@ -2245,21 +2692,31 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2245
2692
|
"DoSubmissionLedger.recordChildSettled",
|
|
2246
2693
|
)(function* (request: ChildSettledNotification) {
|
|
2247
2694
|
const operation = "ledger record child settled";
|
|
2695
|
+
|
|
2248
2696
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ChildSettledNotification))(
|
|
2249
2697
|
request,
|
|
2250
2698
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
2699
|
+
|
|
2251
2700
|
yield* hitFailpoint("ledger:child-settled:before", operation);
|
|
2701
|
+
|
|
2252
2702
|
const outcome = yield* inWriteTransaction(
|
|
2253
2703
|
operation,
|
|
2254
2704
|
Effect.gen(function* () {
|
|
2255
2705
|
const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
|
|
2256
2706
|
// 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
|
-
//
|
|
2707
|
+
// row lives in THIS store (single-store latitude, and every conformance lane), either a
|
|
2708
|
+
// finalized row or an exact terminalizing reservation admits the notification: the
|
|
2709
|
+
// runtime calls only after the canonical append and before ledger finalization. When the
|
|
2710
|
+
// row does not live here — the normal cross-DO case — the routed notification from the
|
|
2711
|
+
// child's owning Durable Object is the settlement evidence this store records durably.
|
|
2261
2712
|
const child = yield* readSubmission(operation, validated.childSubmissionId);
|
|
2262
|
-
|
|
2713
|
+
const childReservation = yield* readReservation(operation, validated.childSubmissionId);
|
|
2714
|
+
|
|
2715
|
+
if (
|
|
2716
|
+
Option.isSome(child) &&
|
|
2717
|
+
child.value.state !== "settled" &&
|
|
2718
|
+
!(child.value.state === "terminalizing" && Option.isSome(childReservation))
|
|
2719
|
+
) {
|
|
2263
2720
|
return yield* LedgerError.make({
|
|
2264
2721
|
operation,
|
|
2265
2722
|
message: `Child submission ${validated.childSubmissionId} has no recorded settlement.`,
|
|
@@ -2270,6 +2727,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2270
2727
|
// before the parent's suspend transaction is observed by that suspend's covering
|
|
2271
2728
|
// check, even when the parent is not (or not yet) suspended.
|
|
2272
2729
|
const now = yield* currentInstant;
|
|
2730
|
+
|
|
2273
2731
|
yield* sql`
|
|
2274
2732
|
INSERT INTO effect_agent_child_settlements (
|
|
2275
2733
|
parent_submission_id,
|
|
@@ -2279,15 +2737,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2279
2737
|
) VALUES (
|
|
2280
2738
|
${validated.parentSubmissionId},
|
|
2281
2739
|
${validated.childSubmissionId},
|
|
2282
|
-
${
|
|
2740
|
+
${
|
|
2741
|
+
Option.isSome(child) && child.value.state === "settled"
|
|
2742
|
+
? child.value.settled_outcome
|
|
2743
|
+
: Option.isSome(childReservation)
|
|
2744
|
+
? childReservation.value.outcome
|
|
2745
|
+
: null
|
|
2746
|
+
},
|
|
2283
2747
|
${now.iso}
|
|
2284
2748
|
)
|
|
2285
2749
|
ON CONFLICT (parent_submission_id, child_submission_id) DO NOTHING
|
|
2286
2750
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2287
2751
|
|
|
2288
2752
|
if (parent.state !== "suspended" || parent.suspended_reason_json === null) {
|
|
2289
|
-
return
|
|
2753
|
+
return NOT_WAITING;
|
|
2290
2754
|
}
|
|
2755
|
+
|
|
2291
2756
|
const reason = yield* Schema.decodeEffect(Schema.fromJsonString(SuspensionReason))(
|
|
2292
2757
|
parent.suspended_reason_json,
|
|
2293
2758
|
).pipe(
|
|
@@ -2300,27 +2765,30 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2300
2765
|
),
|
|
2301
2766
|
),
|
|
2302
2767
|
);
|
|
2768
|
+
|
|
2303
2769
|
if (reason._tag !== "WaitingForChild") {
|
|
2304
|
-
return
|
|
2770
|
+
return NOT_WAITING;
|
|
2305
2771
|
}
|
|
2306
2772
|
if (
|
|
2307
2773
|
!reason.children.some((entry) => entry.childSubmissionId === validated.childSubmissionId)
|
|
2308
2774
|
) {
|
|
2309
|
-
return
|
|
2775
|
+
return NOT_WAITING;
|
|
2310
2776
|
}
|
|
2311
2777
|
// The parent wakes exactly when EVERY listed child is provably settled — from its
|
|
2312
2778
|
// local row or a recorded marker (spec §12 step 10); replays re-run the coverage
|
|
2313
2779
|
// check so a recovering caller wakes the lane idempotently.
|
|
2314
2780
|
const markers = yield* readChildSettlementMarkers(operation, validated.parentSubmissionId);
|
|
2315
2781
|
const markerChildren = new Set(markers.map((row) => row.child_submission_id));
|
|
2782
|
+
|
|
2316
2783
|
for (const entry of reason.children) {
|
|
2317
2784
|
const settled = yield* childProvablySettled(
|
|
2318
2785
|
operation,
|
|
2319
2786
|
markerChildren,
|
|
2320
2787
|
entry.childSubmissionId,
|
|
2321
2788
|
);
|
|
2789
|
+
|
|
2322
2790
|
if (!settled) {
|
|
2323
|
-
return
|
|
2791
|
+
return STILL_WAITING;
|
|
2324
2792
|
}
|
|
2325
2793
|
}
|
|
2326
2794
|
yield* sql`
|
|
@@ -2331,10 +2799,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2331
2799
|
suspended_at = NULL
|
|
2332
2800
|
WHERE submission_id = ${validated.parentSubmissionId}
|
|
2333
2801
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2334
|
-
|
|
2802
|
+
|
|
2803
|
+
return WOKEN;
|
|
2335
2804
|
}),
|
|
2336
2805
|
);
|
|
2806
|
+
|
|
2337
2807
|
yield* hitFailpoint("ledger:child-settled:after", operation);
|
|
2808
|
+
|
|
2338
2809
|
return outcome;
|
|
2339
2810
|
});
|
|
2340
2811
|
|
|
@@ -2342,25 +2813,36 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2342
2813
|
"DoSubmissionLedger.reserveChildBudget",
|
|
2343
2814
|
)(function* (request: ChildBudgetReservationRequest) {
|
|
2344
2815
|
const operation = "ledger reserve child budget";
|
|
2816
|
+
|
|
2345
2817
|
const validated = yield* Schema.decodeUnknownEffect(
|
|
2346
2818
|
Schema.toType(ChildBudgetReservationRequest),
|
|
2347
2819
|
)(request).pipe(Effect.mapError(internalFailure(operation)));
|
|
2820
|
+
|
|
2348
2821
|
const allocationJson = yield* encodePersistedJsonText(validated.allocation).pipe(
|
|
2349
2822
|
Effect.mapError(internalFailure(operation)),
|
|
2350
2823
|
);
|
|
2824
|
+
|
|
2351
2825
|
yield* hitFailpoint("ledger:child-reservation:before", operation);
|
|
2826
|
+
|
|
2352
2827
|
const reserved = yield* inWriteTransaction(
|
|
2353
2828
|
operation,
|
|
2354
2829
|
Effect.gen(function* () {
|
|
2355
2830
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
2831
|
+
|
|
2356
2832
|
if (Option.isSome(existing)) {
|
|
2833
|
+
const existingSnapshot = yield* childReservationSnapshotFromRow(
|
|
2834
|
+
operation,
|
|
2835
|
+
existing.value,
|
|
2836
|
+
);
|
|
2837
|
+
|
|
2357
2838
|
// Identical replays short-circuit before the fence, mirroring reserveSettlement: a
|
|
2358
2839
|
// replay creates nothing, so a recovering caller resumes rather than duplicates.
|
|
2359
2840
|
const identical =
|
|
2360
2841
|
existing.value.parent_submission_id === validated.parentSubmissionId &&
|
|
2361
2842
|
existing.value.parent_tool_call_id === validated.parentToolCallId &&
|
|
2362
2843
|
existing.value.allocation_digest === validated.allocationDigest &&
|
|
2363
|
-
|
|
2844
|
+
equivalentPersistedJson(existingSnapshot.allocation, validated.allocation);
|
|
2845
|
+
|
|
2364
2846
|
if (!identical) {
|
|
2365
2847
|
return yield* ChildReservationConflict.make({
|
|
2366
2848
|
reservationId: validated.reservationId,
|
|
@@ -2369,16 +2851,19 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2369
2851
|
"A reservation with this identity exists with a different parent Tool Call or allocation.",
|
|
2370
2852
|
});
|
|
2371
2853
|
}
|
|
2854
|
+
|
|
2372
2855
|
return ReservedChildBudget.make({
|
|
2373
|
-
reservation:
|
|
2856
|
+
reservation: existingSnapshot,
|
|
2374
2857
|
replayed: true,
|
|
2375
2858
|
});
|
|
2376
2859
|
}
|
|
2860
|
+
|
|
2377
2861
|
const collision = yield* readChildReservationForCall(
|
|
2378
2862
|
operation,
|
|
2379
2863
|
validated.parentSubmissionId,
|
|
2380
2864
|
validated.parentToolCallId,
|
|
2381
2865
|
);
|
|
2866
|
+
|
|
2382
2867
|
if (Option.isSome(collision)) {
|
|
2383
2868
|
return yield* ChildReservationConflict.make({
|
|
2384
2869
|
reservationId: validated.reservationId,
|
|
@@ -2387,10 +2872,12 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2387
2872
|
});
|
|
2388
2873
|
}
|
|
2389
2874
|
const parent = yield* requireSubmission(operation, validated.parentSubmissionId);
|
|
2875
|
+
|
|
2390
2876
|
// Creation is fenced by the parent lane's live ownership (spec §12 step 2): a stale
|
|
2391
2877
|
// parent Attempt can never create new reservation state.
|
|
2392
2878
|
yield* requireOwnership(operation, parent, validated.ownershipToken);
|
|
2393
2879
|
const now = yield* currentInstant;
|
|
2880
|
+
|
|
2394
2881
|
yield* sql`
|
|
2395
2882
|
INSERT INTO effect_agent_child_reservations (
|
|
2396
2883
|
reservation_id,
|
|
@@ -2411,6 +2898,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2411
2898
|
)
|
|
2412
2899
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2413
2900
|
const inserted = yield* readChildReservation(operation, validated.reservationId);
|
|
2901
|
+
|
|
2414
2902
|
if (Option.isNone(inserted)) {
|
|
2415
2903
|
return yield* corruptionFailure(
|
|
2416
2904
|
operation,
|
|
@@ -2419,13 +2907,16 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2419
2907
|
"An inserted child reservation row is missing inside its own transaction.",
|
|
2420
2908
|
);
|
|
2421
2909
|
}
|
|
2910
|
+
|
|
2422
2911
|
return ReservedChildBudget.make({
|
|
2423
2912
|
reservation: yield* childReservationSnapshotFromRow(operation, inserted.value),
|
|
2424
2913
|
replayed: false,
|
|
2425
2914
|
});
|
|
2426
2915
|
}),
|
|
2427
2916
|
);
|
|
2917
|
+
|
|
2428
2918
|
yield* hitFailpoint("ledger:child-reservation:after", operation);
|
|
2919
|
+
|
|
2429
2920
|
return reserved;
|
|
2430
2921
|
});
|
|
2431
2922
|
|
|
@@ -2434,14 +2925,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2434
2925
|
request: AttachChildToReservationRequest,
|
|
2435
2926
|
) {
|
|
2436
2927
|
const operation = "ledger attach child to reservation";
|
|
2928
|
+
|
|
2437
2929
|
const validated = yield* Schema.decodeUnknownEffect(
|
|
2438
2930
|
Schema.toType(AttachChildToReservationRequest),
|
|
2439
2931
|
)(request).pipe(Effect.mapError(internalFailure(operation)));
|
|
2932
|
+
|
|
2440
2933
|
yield* hitFailpoint("ledger:child-attach:before", operation);
|
|
2934
|
+
|
|
2441
2935
|
const attached = yield* inWriteTransaction(
|
|
2442
2936
|
operation,
|
|
2443
2937
|
Effect.gen(function* () {
|
|
2444
2938
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
2939
|
+
|
|
2445
2940
|
if (Option.isNone(existing)) {
|
|
2446
2941
|
return yield* LedgerError.make({
|
|
2447
2942
|
operation,
|
|
@@ -2453,6 +2948,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2453
2948
|
if (existing.value.child_submission_id === validated.childSubmissionId) {
|
|
2454
2949
|
return yield* childReservationSnapshotFromRow(operation, existing.value);
|
|
2455
2950
|
}
|
|
2951
|
+
|
|
2456
2952
|
return yield* ChildReservationConflict.make({
|
|
2457
2953
|
reservationId: validated.reservationId,
|
|
2458
2954
|
status: existing.value.status,
|
|
@@ -2460,6 +2956,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2460
2956
|
});
|
|
2461
2957
|
}
|
|
2462
2958
|
const parent = yield* requireSubmission(operation, existing.value.parent_submission_id);
|
|
2959
|
+
|
|
2463
2960
|
yield* requireOwnership(operation, parent, validated.ownershipToken);
|
|
2464
2961
|
if (existing.value.status !== "reserved") {
|
|
2465
2962
|
return yield* ChildReservationConflict.make({
|
|
@@ -2478,6 +2975,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2478
2975
|
WHERE reservation_id = ${validated.reservationId}
|
|
2479
2976
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2480
2977
|
const updated = yield* readChildReservation(operation, validated.reservationId);
|
|
2978
|
+
|
|
2481
2979
|
if (Option.isNone(updated)) {
|
|
2482
2980
|
return yield* corruptionFailure(
|
|
2483
2981
|
operation,
|
|
@@ -2486,10 +2984,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2486
2984
|
"An updated child reservation row is missing inside its own transaction.",
|
|
2487
2985
|
);
|
|
2488
2986
|
}
|
|
2987
|
+
|
|
2489
2988
|
return yield* childReservationSnapshotFromRow(operation, updated.value);
|
|
2490
2989
|
}),
|
|
2491
2990
|
);
|
|
2991
|
+
|
|
2492
2992
|
yield* hitFailpoint("ledger:child-attach:after", operation);
|
|
2993
|
+
|
|
2493
2994
|
return attached;
|
|
2494
2995
|
});
|
|
2495
2996
|
|
|
@@ -2497,17 +2998,22 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2497
2998
|
"DoSubmissionLedger.beginChildBudgetRelease",
|
|
2498
2999
|
)(function* (request: BeginChildBudgetReleaseRequest) {
|
|
2499
3000
|
const operation = "ledger begin child budget release";
|
|
3001
|
+
|
|
2500
3002
|
const validated = yield* Schema.decodeUnknownEffect(
|
|
2501
3003
|
Schema.toType(BeginChildBudgetReleaseRequest),
|
|
2502
3004
|
)(request).pipe(Effect.mapError(internalFailure(operation)));
|
|
3005
|
+
|
|
2503
3006
|
const accountingJson = yield* encodePersistedJsonText(validated.accounting).pipe(
|
|
2504
3007
|
Effect.mapError(internalFailure(operation)),
|
|
2505
3008
|
);
|
|
3009
|
+
|
|
2506
3010
|
yield* hitFailpoint("ledger:child-release-pending:before", operation);
|
|
3011
|
+
|
|
2507
3012
|
const frozen = yield* inWriteTransaction(
|
|
2508
3013
|
operation,
|
|
2509
3014
|
Effect.gen(function* () {
|
|
2510
3015
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
3016
|
+
|
|
2511
3017
|
if (Option.isNone(existing)) {
|
|
2512
3018
|
return yield* LedgerError.make({
|
|
2513
3019
|
operation,
|
|
@@ -2515,11 +3021,20 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2515
3021
|
});
|
|
2516
3022
|
}
|
|
2517
3023
|
if (existing.value.status !== "reserved") {
|
|
3024
|
+
const existingSnapshot = yield* childReservationSnapshotFromRow(
|
|
3025
|
+
operation,
|
|
3026
|
+
existing.value,
|
|
3027
|
+
);
|
|
3028
|
+
|
|
2518
3029
|
// The accounting decision was already frozen exactly once; an identical replay is a
|
|
2519
3030
|
// no-op and a divergent decision conflicts (spec §12 join step 6).
|
|
2520
|
-
if (
|
|
2521
|
-
|
|
3031
|
+
if (
|
|
3032
|
+
existingSnapshot.accounting !== undefined &&
|
|
3033
|
+
equivalentPersistedJson(existingSnapshot.accounting, validated.accounting)
|
|
3034
|
+
) {
|
|
3035
|
+
return existingSnapshot;
|
|
2522
3036
|
}
|
|
3037
|
+
|
|
2523
3038
|
return yield* ChildReservationConflict.make({
|
|
2524
3039
|
reservationId: validated.reservationId,
|
|
2525
3040
|
status: existing.value.status,
|
|
@@ -2527,6 +3042,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2527
3042
|
});
|
|
2528
3043
|
}
|
|
2529
3044
|
const now = yield* currentInstant;
|
|
3045
|
+
|
|
2530
3046
|
yield* sql`
|
|
2531
3047
|
UPDATE effect_agent_child_reservations
|
|
2532
3048
|
SET
|
|
@@ -2536,6 +3052,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2536
3052
|
WHERE reservation_id = ${validated.reservationId}
|
|
2537
3053
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2538
3054
|
const updated = yield* readChildReservation(operation, validated.reservationId);
|
|
3055
|
+
|
|
2539
3056
|
if (Option.isNone(updated)) {
|
|
2540
3057
|
return yield* corruptionFailure(
|
|
2541
3058
|
operation,
|
|
@@ -2544,10 +3061,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2544
3061
|
"An updated child reservation row is missing inside its own transaction.",
|
|
2545
3062
|
);
|
|
2546
3063
|
}
|
|
3064
|
+
|
|
2547
3065
|
return yield* childReservationSnapshotFromRow(operation, updated.value);
|
|
2548
3066
|
}),
|
|
2549
3067
|
);
|
|
3068
|
+
|
|
2550
3069
|
yield* hitFailpoint("ledger:child-release-pending:after", operation);
|
|
3070
|
+
|
|
2551
3071
|
return frozen;
|
|
2552
3072
|
});
|
|
2553
3073
|
|
|
@@ -2555,14 +3075,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2555
3075
|
"DoSubmissionLedger.releaseChildBudget",
|
|
2556
3076
|
)(function* (request: ReleaseChildBudgetRequest) {
|
|
2557
3077
|
const operation = "ledger release child budget";
|
|
3078
|
+
|
|
2558
3079
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(ReleaseChildBudgetRequest))(
|
|
2559
3080
|
request,
|
|
2560
3081
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3082
|
+
|
|
2561
3083
|
yield* hitFailpoint("ledger:child-release:before", operation);
|
|
3084
|
+
|
|
2562
3085
|
const released = yield* inWriteTransaction(
|
|
2563
3086
|
operation,
|
|
2564
3087
|
Effect.gen(function* () {
|
|
2565
3088
|
const existing = yield* readChildReservation(operation, validated.reservationId);
|
|
3089
|
+
|
|
2566
3090
|
if (Option.isNone(existing)) {
|
|
2567
3091
|
return yield* LedgerError.make({
|
|
2568
3092
|
operation,
|
|
@@ -2582,12 +3106,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2582
3106
|
});
|
|
2583
3107
|
}
|
|
2584
3108
|
const now = yield* currentInstant;
|
|
3109
|
+
|
|
2585
3110
|
yield* sql`
|
|
2586
3111
|
UPDATE effect_agent_child_reservations
|
|
2587
3112
|
SET status = 'released', released_at = ${now.iso}
|
|
2588
3113
|
WHERE reservation_id = ${validated.reservationId}
|
|
2589
3114
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
2590
3115
|
const updated = yield* readChildReservation(operation, validated.reservationId);
|
|
3116
|
+
|
|
2591
3117
|
if (Option.isNone(updated)) {
|
|
2592
3118
|
return yield* corruptionFailure(
|
|
2593
3119
|
operation,
|
|
@@ -2596,15 +3122,18 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2596
3122
|
"An updated child reservation row is missing inside its own transaction.",
|
|
2597
3123
|
);
|
|
2598
3124
|
}
|
|
3125
|
+
|
|
2599
3126
|
return yield* childReservationSnapshotFromRow(operation, updated.value);
|
|
2600
3127
|
}),
|
|
2601
3128
|
);
|
|
3129
|
+
|
|
2602
3130
|
yield* hitFailpoint("ledger:child-release:after", operation);
|
|
3131
|
+
|
|
2603
3132
|
return released;
|
|
2604
3133
|
});
|
|
2605
3134
|
|
|
2606
3135
|
interface ScanCursor {
|
|
2607
|
-
readonly
|
|
3136
|
+
readonly threadId: string;
|
|
2608
3137
|
readonly queueSequence: number;
|
|
2609
3138
|
}
|
|
2610
3139
|
|
|
@@ -2615,13 +3144,14 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2615
3144
|
LedgerError
|
|
2616
3145
|
> {
|
|
2617
3146
|
const operation = "ledger scan nonterminal";
|
|
3147
|
+
|
|
2618
3148
|
const rows = yield* (
|
|
2619
3149
|
cursor === undefined
|
|
2620
3150
|
? sql<Record<string, unknown>>`
|
|
2621
3151
|
SELECT ${sql.literal(SUBMISSION_COLUMNS)}
|
|
2622
3152
|
FROM effect_agent_submissions
|
|
2623
3153
|
WHERE state <> 'settled'
|
|
2624
|
-
ORDER BY
|
|
3154
|
+
ORDER BY thread_id ASC, queue_sequence ASC
|
|
2625
3155
|
LIMIT ${SCAN_PAGE_SIZE}
|
|
2626
3156
|
`
|
|
2627
3157
|
: sql<Record<string, unknown>>`
|
|
@@ -2629,43 +3159,122 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2629
3159
|
FROM effect_agent_submissions
|
|
2630
3160
|
WHERE state <> 'settled'
|
|
2631
3161
|
AND (
|
|
2632
|
-
|
|
3162
|
+
thread_id > ${cursor.threadId}
|
|
2633
3163
|
OR (
|
|
2634
|
-
|
|
3164
|
+
thread_id = ${cursor.threadId}
|
|
2635
3165
|
AND queue_sequence > ${cursor.queueSequence}
|
|
2636
3166
|
)
|
|
2637
3167
|
)
|
|
2638
|
-
ORDER BY
|
|
3168
|
+
ORDER BY thread_id ASC, queue_sequence ASC
|
|
2639
3169
|
LIMIT ${SCAN_PAGE_SIZE}
|
|
2640
3170
|
`
|
|
2641
3171
|
).pipe(Effect.mapError(sqlFailure(operation)));
|
|
3172
|
+
|
|
2642
3173
|
const decoded = yield* decodeSubmissionRows(operation, "nonterminal_scan", rows);
|
|
3174
|
+
|
|
2643
3175
|
const snapshots = yield* Effect.forEach(decoded, (row) =>
|
|
2644
3176
|
decodeSubmissionSnapshot(operation, row),
|
|
2645
3177
|
);
|
|
3178
|
+
|
|
2646
3179
|
const last = decoded[decoded.length - 1];
|
|
3180
|
+
|
|
2647
3181
|
const next: Option.Option<ScanCursor | undefined> =
|
|
2648
3182
|
last === undefined || decoded.length < SCAN_PAGE_SIZE
|
|
2649
3183
|
? Option.none()
|
|
2650
3184
|
: Option.some({
|
|
2651
|
-
|
|
3185
|
+
threadId: last.thread_id,
|
|
2652
3186
|
queueSequence: last.queue_sequence,
|
|
2653
3187
|
});
|
|
3188
|
+
|
|
2654
3189
|
return [snapshots, next] as const;
|
|
2655
3190
|
});
|
|
2656
3191
|
|
|
2657
|
-
const scanNonterminal: Stream.Stream<SubmissionSnapshot, LedgerError> = Stream.paginate
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
3192
|
+
const scanNonterminal: Stream.Stream<SubmissionSnapshot, LedgerError> = Stream.paginate<
|
|
3193
|
+
ScanCursor | undefined,
|
|
3194
|
+
SubmissionSnapshot,
|
|
3195
|
+
LedgerError
|
|
3196
|
+
>(undefined, scanPage);
|
|
3197
|
+
|
|
3198
|
+
const readAbortIntentForSubmission: SubmissionLedger["Service"]["readAbortIntent"] = Effect.fn(
|
|
3199
|
+
"DoSubmissionLedger.readAbortIntentForSubmission",
|
|
3200
|
+
)(function* (request) {
|
|
3201
|
+
const operation = "ledger read abort intent";
|
|
3202
|
+
|
|
3203
|
+
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(AbortIntentRequest))(
|
|
3204
|
+
request,
|
|
3205
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3206
|
+
|
|
3207
|
+
const recordId = submissionAbortRecordId(validated.submissionId);
|
|
3208
|
+
|
|
3209
|
+
const rows = yield* sql<Record<string, unknown>>`
|
|
3210
|
+
SELECT
|
|
3211
|
+
submission.submission_id,
|
|
3212
|
+
abort.submission_id AS abort_submission_id,
|
|
3213
|
+
abort.author,
|
|
3214
|
+
abort.reason,
|
|
3215
|
+
abort.requested_at,
|
|
3216
|
+
canonical.record_id AS canonical_record_id
|
|
3217
|
+
FROM effect_agent_submissions AS submission
|
|
3218
|
+
LEFT JOIN effect_agent_abort_intents AS abort
|
|
3219
|
+
ON abort.submission_id = submission.submission_id
|
|
3220
|
+
LEFT JOIN effect_agent_canonical_records AS canonical
|
|
3221
|
+
ON canonical.thread_id = submission.thread_id
|
|
3222
|
+
AND canonical.record_id = ${recordId}
|
|
3223
|
+
WHERE submission.submission_id = ${validated.submissionId}
|
|
3224
|
+
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
3225
|
+
|
|
3226
|
+
const decoded = yield* decodeRows(
|
|
3227
|
+
Schema.Array(AbortIntentLookupRow),
|
|
3228
|
+
"effect_agent_abort_intents",
|
|
3229
|
+
validated.submissionId,
|
|
3230
|
+
rows,
|
|
3231
|
+
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3232
|
+
|
|
3233
|
+
if (decoded.length === 0) {
|
|
3234
|
+
return yield* LedgerError.make({
|
|
3235
|
+
operation,
|
|
3236
|
+
message: `Unknown submission ${validated.submissionId}.`,
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
if (decoded.length !== 1) {
|
|
3240
|
+
return yield* corruptionFailure(
|
|
3241
|
+
operation,
|
|
3242
|
+
"effect_agent_abort_intents",
|
|
3243
|
+
validated.submissionId,
|
|
3244
|
+
"An abort intent lookup returned more than one row.",
|
|
3245
|
+
);
|
|
3246
|
+
}
|
|
3247
|
+
const row = decoded[0];
|
|
3248
|
+
|
|
3249
|
+
if (row.abort_submission_id === null) return undefined;
|
|
3250
|
+
|
|
3251
|
+
return yield* decodeAbortIntent({
|
|
3252
|
+
submissionId: row.abort_submission_id,
|
|
3253
|
+
author: row.author,
|
|
3254
|
+
reason: row.reason,
|
|
3255
|
+
requestedAt: row.requested_at,
|
|
3256
|
+
...(row.canonical_record_id === null ? {} : { canonicalRecordId: row.canonical_record_id }),
|
|
3257
|
+
}).pipe(
|
|
3258
|
+
Effect.mapError((error) =>
|
|
3259
|
+
corruptionFailure(
|
|
3260
|
+
operation,
|
|
3261
|
+
"effect_agent_abort_intents",
|
|
3262
|
+
validated.submissionId,
|
|
3263
|
+
error.message,
|
|
3264
|
+
),
|
|
3265
|
+
),
|
|
3266
|
+
);
|
|
3267
|
+
});
|
|
2661
3268
|
|
|
2662
3269
|
const loadRecoverySnapshot: SubmissionLedger["Service"]["loadRecoverySnapshot"] = Effect.fn(
|
|
2663
3270
|
"DoSubmissionLedger.loadRecoverySnapshot",
|
|
2664
3271
|
)(function* (request: RecoverySnapshotRequest) {
|
|
2665
3272
|
const operation = "ledger load recovery snapshot";
|
|
3273
|
+
|
|
2666
3274
|
const validated = yield* Schema.decodeUnknownEffect(Schema.toType(RecoverySnapshotRequest))(
|
|
2667
3275
|
request,
|
|
2668
3276
|
).pipe(Effect.mapError(internalFailure(operation)));
|
|
3277
|
+
|
|
2669
3278
|
return yield* sql
|
|
2670
3279
|
.withTransaction(
|
|
2671
3280
|
Effect.gen(function* () {
|
|
@@ -2674,6 +3283,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2674
3283
|
|
|
2675
3284
|
let ownership: OwnershipSnapshot | undefined;
|
|
2676
3285
|
const ownershipRow = yield* readOwnership(operation, validated.submissionId);
|
|
3286
|
+
|
|
2677
3287
|
if (Option.isSome(ownershipRow)) {
|
|
2678
3288
|
ownership = yield* decodeOwnershipSnapshot({
|
|
2679
3289
|
attemptId: ownershipRow.value.attempt_id,
|
|
@@ -2684,6 +3294,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2684
3294
|
}
|
|
2685
3295
|
|
|
2686
3296
|
let inputApplied: InputAppliedMarker | undefined;
|
|
3297
|
+
|
|
2687
3298
|
if (
|
|
2688
3299
|
submissionRow.input_applied_record_id !== null &&
|
|
2689
3300
|
submissionRow.input_applied_sequence !== null
|
|
@@ -2696,6 +3307,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2696
3307
|
|
|
2697
3308
|
let reservation: SettlementReservationSnapshot | undefined;
|
|
2698
3309
|
const reservationRow = yield* readReservation(operation, validated.submissionId);
|
|
3310
|
+
|
|
2699
3311
|
if (Option.isSome(reservationRow)) {
|
|
2700
3312
|
const record = yield* decodeRecordEnvelopeText(reservationRow.value.record_json).pipe(
|
|
2701
3313
|
Effect.mapError((error) =>
|
|
@@ -2707,9 +3319,11 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2707
3319
|
),
|
|
2708
3320
|
),
|
|
2709
3321
|
);
|
|
3322
|
+
|
|
2710
3323
|
const settlementId = yield* Schema.decodeUnknownEffect(
|
|
2711
3324
|
SettlementReservationSnapshot.fields.settlementId,
|
|
2712
3325
|
)(reservationRow.value.settlement_id).pipe(Effect.mapError(internalFailure(operation)));
|
|
3326
|
+
|
|
2713
3327
|
reservation = SettlementReservationSnapshot.make({
|
|
2714
3328
|
settlementId,
|
|
2715
3329
|
outcome: reservationRow.value.outcome,
|
|
@@ -2721,6 +3335,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2721
3335
|
|
|
2722
3336
|
let abortIntent: AbortIntent | undefined;
|
|
2723
3337
|
const abortRow = yield* readAbortIntent(operation, validated.submissionId);
|
|
3338
|
+
|
|
2724
3339
|
if (Option.isSome(abortRow)) {
|
|
2725
3340
|
abortIntent = yield* abortIntentFromRow(
|
|
2726
3341
|
operation,
|
|
@@ -2738,11 +3353,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2738
3353
|
WHERE joined_host_submission_id = ${validated.submissionId}
|
|
2739
3354
|
ORDER BY queue_sequence ASC
|
|
2740
3355
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
3356
|
+
|
|
2741
3357
|
const joinSubmissions = yield* decodeSubmissionRows(
|
|
2742
3358
|
operation,
|
|
2743
3359
|
validated.submissionId,
|
|
2744
3360
|
joinRows,
|
|
2745
3361
|
);
|
|
3362
|
+
|
|
2746
3363
|
const joins = yield* Effect.forEach(joinSubmissions, (row) =>
|
|
2747
3364
|
decodeJoinSnapshot({
|
|
2748
3365
|
submissionId: row.submission_id,
|
|
@@ -2752,6 +3369,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2752
3369
|
);
|
|
2753
3370
|
|
|
2754
3371
|
let hostSubmissionId: RecoverySnapshot["hostSubmissionId"];
|
|
3372
|
+
|
|
2755
3373
|
if (submissionRow.joined_host_submission_id !== null) {
|
|
2756
3374
|
hostSubmissionId = yield* decodeSubmissionId(
|
|
2757
3375
|
submissionRow.joined_host_submission_id,
|
|
@@ -2759,6 +3377,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2759
3377
|
}
|
|
2760
3378
|
|
|
2761
3379
|
let suspension: SuspensionSnapshot | undefined;
|
|
3380
|
+
|
|
2762
3381
|
if (submissionRow.suspended_reason_json !== null && submissionRow.suspended_at !== null) {
|
|
2763
3382
|
const reason = yield* parseStoredJsonText(submissionRow.suspended_reason_json).pipe(
|
|
2764
3383
|
Effect.mapError((error) =>
|
|
@@ -2770,6 +3389,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2770
3389
|
),
|
|
2771
3390
|
),
|
|
2772
3391
|
);
|
|
3392
|
+
|
|
2773
3393
|
suspension = yield* decodeSuspensionSnapshot({
|
|
2774
3394
|
reason,
|
|
2775
3395
|
suspendedAt: submissionRow.suspended_at,
|
|
@@ -2786,11 +3406,13 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2786
3406
|
}
|
|
2787
3407
|
|
|
2788
3408
|
const decisionRows = yield* readApprovalDecisions(operation, validated.submissionId);
|
|
3409
|
+
|
|
2789
3410
|
const approvalDecisions = yield* Effect.forEach(decisionRows, (row) =>
|
|
2790
3411
|
approvalIntentFromRow(operation, row),
|
|
2791
3412
|
);
|
|
2792
3413
|
|
|
2793
3414
|
const resolutionRows = yield* readUnknownResolutions(operation, validated.submissionId);
|
|
3415
|
+
|
|
2794
3416
|
const unknownResolutions = yield* Effect.forEach(resolutionRows, (row) =>
|
|
2795
3417
|
unknownResolutionIntentFromRow(operation, row),
|
|
2796
3418
|
);
|
|
@@ -2807,20 +3429,25 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2807
3429
|
WHERE parent_submission_id = ${validated.submissionId}
|
|
2808
3430
|
ORDER BY parent_tool_call_id ASC
|
|
2809
3431
|
`.pipe(Effect.mapError(sqlFailure(operation)));
|
|
3432
|
+
|
|
2810
3433
|
const decodedChildReservations = yield* decodeChildReservationRows(
|
|
2811
3434
|
operation,
|
|
2812
3435
|
validated.submissionId,
|
|
2813
3436
|
childReservationRows,
|
|
2814
3437
|
);
|
|
3438
|
+
|
|
2815
3439
|
const childReservations = yield* Effect.forEach(decodedChildReservations, (row) =>
|
|
2816
3440
|
childReservationSnapshotFromRow(operation, row),
|
|
2817
3441
|
);
|
|
3442
|
+
|
|
2818
3443
|
const markers = yield* readChildSettlementMarkers(operation, validated.submissionId);
|
|
2819
3444
|
const markersByChild = new Map(markers.map((row) => [row.child_submission_id, row]));
|
|
2820
3445
|
const childAttachments: Array<ChildAttachmentSnapshot> = [];
|
|
3446
|
+
|
|
2821
3447
|
for (const row of decodedChildReservations) {
|
|
2822
3448
|
if (row.child_submission_id === null) continue;
|
|
2823
3449
|
const child = yield* readSubmission(operation, row.child_submission_id);
|
|
3450
|
+
|
|
2824
3451
|
if (Option.isSome(child)) {
|
|
2825
3452
|
childAttachments.push(
|
|
2826
3453
|
yield* decodeChildAttachmentSnapshot({
|
|
@@ -2835,6 +3462,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2835
3462
|
continue;
|
|
2836
3463
|
}
|
|
2837
3464
|
const marker = markersByChild.get(row.child_submission_id);
|
|
3465
|
+
|
|
2838
3466
|
if (marker === undefined) continue;
|
|
2839
3467
|
childAttachments.push(
|
|
2840
3468
|
yield* decodeChildAttachmentSnapshot({
|
|
@@ -2847,6 +3475,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2847
3475
|
}
|
|
2848
3476
|
|
|
2849
3477
|
let parentLinkage: ParentLinkage | undefined;
|
|
3478
|
+
|
|
2850
3479
|
if (
|
|
2851
3480
|
submissionRow.parent_submission_id !== null &&
|
|
2852
3481
|
submissionRow.parent_tool_call_id !== null
|
|
@@ -2906,6 +3535,7 @@ const makeServices = Effect.fn("DoSubmissionLedger.makeServices")(function* () {
|
|
|
2906
3535
|
releaseChildBudget,
|
|
2907
3536
|
scanNonterminal,
|
|
2908
3537
|
loadRecoverySnapshot,
|
|
3538
|
+
readAbortIntent: readAbortIntentForSubmission,
|
|
2909
3539
|
}),
|
|
2910
3540
|
);
|
|
2911
3541
|
});
|
|
@@ -2923,18 +3553,22 @@ export const submissionLedgerLayer: Layer.Layer<
|
|
|
2923
3553
|
|
|
2924
3554
|
/**
|
|
2925
3555
|
* A composition-root convenience Layer for the durable Submission Ledger. Point it at the
|
|
2926
|
-
* same `ctx.storage` as the
|
|
3556
|
+
* same `ctx.storage` as the ThreadStore so claims fence the same producer epochs.
|
|
2927
3557
|
*/
|
|
2928
3558
|
export const ledgerLayer = (
|
|
2929
3559
|
options: DoStorageOptions,
|
|
2930
3560
|
): Layer.Layer<SubmissionLedger, DoStorageInitializationError> =>
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
3561
|
+
Layer.unwrap(
|
|
3562
|
+
Effect.map(DoStorageConfig, (config) =>
|
|
3563
|
+
submissionLedgerLayer.pipe(
|
|
3564
|
+
Layer.provide(
|
|
3565
|
+
Layer.mergeAll(
|
|
3566
|
+
Layer.succeed(DoStorageConfig)(config),
|
|
3567
|
+
storageFailpointLayer(options),
|
|
3568
|
+
SqliteClient.layer({ storage: options.storage }),
|
|
3569
|
+
BrowserCrypto.layer,
|
|
3570
|
+
),
|
|
3571
|
+
),
|
|
2938
3572
|
),
|
|
2939
3573
|
),
|
|
2940
|
-
);
|
|
3574
|
+
).pipe(Layer.provide(storageConfigLayer(options)));
|