@effect-agent/storage-memory 0.1.0-beta.9 → 0.1.0-beta.91
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/MemoryMessageDeliveryStore.d.mts +15 -0
- package/dist/MemoryMessageDeliveryStore.mjs +147 -0
- package/dist/MemoryMessageDeliveryStore.mjs.map +1 -0
- package/dist/MemoryScheduleStore.d.mts +10 -0
- package/dist/MemoryScheduleStore.mjs +169 -0
- package/dist/MemoryScheduleStore.mjs.map +1 -0
- package/dist/MemorySemanticIndex.d.mts +21 -0
- package/dist/MemorySemanticIndex.mjs +222 -0
- package/dist/MemorySemanticIndex.mjs.map +1 -0
- package/dist/MemorySubmissionLedger.d.mts +24 -0
- package/dist/MemorySubmissionLedger.mjs +1058 -0
- package/dist/MemorySubmissionLedger.mjs.map +1 -0
- package/dist/MemorySubscriptionStore.d.mts +9 -0
- package/dist/MemorySubscriptionStore.mjs +849 -0
- package/dist/MemorySubscriptionStore.mjs.map +1 -0
- package/dist/MemoryThreadStore.d.mts +17 -0
- package/dist/MemoryThreadStore.mjs +377 -0
- package/dist/MemoryThreadStore.mjs.map +1 -0
- package/dist/index.d.mts +7 -30
- package/dist/index.mjs +7 -1298
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -45
- package/src/MemoryMessageDeliveryStore.ts +293 -0
- package/src/MemoryScheduleStore.ts +328 -0
- package/src/MemorySemanticIndex.ts +357 -0
- package/src/{memory-ledger.ts → MemorySubmissionLedger.ts} +478 -86
- package/src/MemorySubscriptionStore.ts +1549 -0
- package/src/MemoryThreadStore.ts +766 -0
- package/src/index.ts +6 -2
- package/dist/index.mjs.map +0 -1
- package/dist/testing.d.mts +0 -2
- package/dist/testing.mjs +0 -2
- package/src/memory-storage.ts +0 -614
- package/src/testing.ts +0 -10
|
@@ -1,19 +1,48 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Clock,
|
|
3
|
+
Cause,
|
|
4
|
+
Exit,
|
|
5
|
+
Fiber,
|
|
6
|
+
DateTime,
|
|
7
|
+
Duration,
|
|
8
|
+
Effect,
|
|
9
|
+
Layer,
|
|
10
|
+
Option,
|
|
11
|
+
Ref,
|
|
12
|
+
Schema,
|
|
13
|
+
Stream,
|
|
14
|
+
} from "effect";
|
|
15
|
+
import {
|
|
16
|
+
type ToolCallId,
|
|
2
17
|
AttemptId,
|
|
3
18
|
ReceiptId,
|
|
4
19
|
SubmissionId,
|
|
5
|
-
ToolCallId,
|
|
6
20
|
type AgentId,
|
|
7
|
-
type
|
|
21
|
+
type ThreadId,
|
|
8
22
|
type SettlementId,
|
|
9
|
-
} from "
|
|
23
|
+
} from "effect-agent/identifiers";
|
|
24
|
+
import { InputMessage } from "effect-agent/messaging";
|
|
25
|
+
import {
|
|
26
|
+
PersistedJson,
|
|
27
|
+
WorkerAdmission,
|
|
28
|
+
ProducerEpoch,
|
|
29
|
+
type DefinitionDigests,
|
|
30
|
+
type DeploymentId,
|
|
31
|
+
type Digest,
|
|
32
|
+
type ProducerId,
|
|
33
|
+
type RecordEnvelope,
|
|
34
|
+
type SettlementOutcome,
|
|
35
|
+
} from "effect-agent/records";
|
|
10
36
|
import {
|
|
37
|
+
type ParentLinkage,
|
|
11
38
|
AbortCommand,
|
|
12
39
|
AdmissionAdmitted,
|
|
13
40
|
AdmissionConflict,
|
|
41
|
+
AdmissionPolicyError,
|
|
14
42
|
AdmissionIndeterminate,
|
|
15
43
|
AdmissionNotAdmitted,
|
|
16
44
|
AdmissionRequest,
|
|
45
|
+
SubmissionAdmissionFence,
|
|
17
46
|
AdmissionResult,
|
|
18
47
|
ApprovalConflict,
|
|
19
48
|
ApprovalDecisionCommand,
|
|
@@ -43,8 +72,6 @@ import {
|
|
|
43
72
|
OwnershipRenewal,
|
|
44
73
|
OwnershipSnapshot,
|
|
45
74
|
OwnershipToken,
|
|
46
|
-
ParentLinkage,
|
|
47
|
-
ProducerEpoch,
|
|
48
75
|
QueueSequence,
|
|
49
76
|
RecoverySnapshot,
|
|
50
77
|
RecoverySnapshotRequest,
|
|
@@ -59,7 +86,9 @@ import {
|
|
|
59
86
|
SettlementFinalization,
|
|
60
87
|
SettlementReservation,
|
|
61
88
|
SettlementReservationSnapshot,
|
|
89
|
+
settlementFailureFromRecord,
|
|
62
90
|
AbortIntent,
|
|
91
|
+
AbortIntentRequest,
|
|
63
92
|
SubmissionLedger,
|
|
64
93
|
SubmissionLookup,
|
|
65
94
|
SubmissionLookupByKey,
|
|
@@ -73,20 +102,12 @@ import {
|
|
|
73
102
|
type ChildReservationId,
|
|
74
103
|
type ChildReservationStatus,
|
|
75
104
|
type ChildSettledOutcome,
|
|
76
|
-
type DefinitionDigests,
|
|
77
|
-
type DeploymentId,
|
|
78
|
-
type Digest,
|
|
79
105
|
type IdempotencyKey,
|
|
80
|
-
type PersistedJson,
|
|
81
106
|
type Principal,
|
|
82
|
-
type ProducerId,
|
|
83
|
-
type RecordEnvelope,
|
|
84
|
-
type SettlementOutcome,
|
|
85
107
|
type SubmissionState,
|
|
86
108
|
type SuspensionOutcome,
|
|
87
109
|
type SuspensionReason,
|
|
88
|
-
} from "
|
|
89
|
-
import { Clock, DateTime, Duration, Effect, Layer, Option, Ref, Schema, Stream } from "effect";
|
|
110
|
+
} from "effect-agent/submission-ledger";
|
|
90
111
|
|
|
91
112
|
const MAX_SUBMISSIONS = 65_536;
|
|
92
113
|
|
|
@@ -110,7 +131,7 @@ const STATE_RANK: Record<SubmissionState, number> = {
|
|
|
110
131
|
|
|
111
132
|
interface SubmissionRow {
|
|
112
133
|
readonly submissionId: SubmissionId;
|
|
113
|
-
readonly
|
|
134
|
+
readonly threadId: ThreadId;
|
|
114
135
|
readonly queueSequence: QueueSequence;
|
|
115
136
|
readonly principal: Principal;
|
|
116
137
|
readonly idempotencyKey: IdempotencyKey;
|
|
@@ -126,6 +147,10 @@ interface SubmissionRow {
|
|
|
126
147
|
readonly readyAtMillis: number | undefined;
|
|
127
148
|
/** Immutable child-side lineage recorded at admission (spec §12 step 5). */
|
|
128
149
|
readonly parentLinkage: ParentLinkage | undefined;
|
|
150
|
+
readonly admissionGroup?: string;
|
|
151
|
+
readonly admissionFence?: AdmissionRequest["admissionFence"];
|
|
152
|
+
readonly workerAdmissionJson?: string;
|
|
153
|
+
readonly messageAdmissionJson?: string;
|
|
129
154
|
}
|
|
130
155
|
|
|
131
156
|
interface StoredOwnership {
|
|
@@ -156,8 +181,6 @@ interface StoredUnknownMark {
|
|
|
156
181
|
|
|
157
182
|
interface StoredUnknownResolution {
|
|
158
183
|
readonly intent: UnknownResolutionIntent;
|
|
159
|
-
/** Canonical JSON of the Schema-encoded resolution, for divergent re-resolution detection. */
|
|
160
|
-
readonly resolutionJson: string;
|
|
161
184
|
}
|
|
162
185
|
|
|
163
186
|
interface StoredSubmission {
|
|
@@ -176,13 +199,13 @@ interface StoredSubmission {
|
|
|
176
199
|
|
|
177
200
|
/**
|
|
178
201
|
* States in which `claim` never grants the head: the lane is host-owned (`joining`/`joined`)
|
|
179
|
-
* or durably
|
|
202
|
+
* or durably suspended rather than worker-claimable. Unknown heads are checked against abort
|
|
203
|
+
* intent separately: abort authorizes cleanup and settlement, never ordinary Tool replay.
|
|
180
204
|
*/
|
|
181
205
|
const BLOCKED_HEAD_STATES: ReadonlySet<SubmissionState> = new Set([
|
|
182
206
|
"joining",
|
|
183
207
|
"joined",
|
|
184
208
|
"suspended",
|
|
185
|
-
"unknown",
|
|
186
209
|
]);
|
|
187
210
|
|
|
188
211
|
interface LaneState {
|
|
@@ -198,12 +221,8 @@ interface StoredChildReservation {
|
|
|
198
221
|
readonly childSubmissionId: SubmissionId | undefined;
|
|
199
222
|
readonly status: ChildReservationStatus;
|
|
200
223
|
readonly allocation: PersistedJson;
|
|
201
|
-
/** Canonical JSON of the allocation, for divergent-replay detection. */
|
|
202
|
-
readonly allocationJson: string;
|
|
203
224
|
readonly allocationDigest: Digest;
|
|
204
225
|
readonly accounting: PersistedJson | undefined;
|
|
205
|
-
/** Canonical JSON of the frozen accounting decision, for divergent-freeze detection. */
|
|
206
|
-
readonly accountingJson: string | undefined;
|
|
207
226
|
readonly reservedAtMillis: number;
|
|
208
227
|
readonly releaseBeganAtMillis: number | undefined;
|
|
209
228
|
readonly releasedAtMillis: number | undefined;
|
|
@@ -212,7 +231,7 @@ interface StoredChildReservation {
|
|
|
212
231
|
interface LedgerState {
|
|
213
232
|
readonly submissions: ReadonlyMap<SubmissionId, StoredSubmission>;
|
|
214
233
|
readonly admissionIndex: ReadonlyMap<string, SubmissionId>;
|
|
215
|
-
readonly lanes: ReadonlyMap<
|
|
234
|
+
readonly lanes: ReadonlyMap<ThreadId, LaneState>;
|
|
216
235
|
readonly childReservations: ReadonlyMap<ChildReservationId, StoredChildReservation>;
|
|
217
236
|
readonly mintCounter: number;
|
|
218
237
|
}
|
|
@@ -247,19 +266,21 @@ const decodeAttemptId = Schema.decodeSync(AttemptId);
|
|
|
247
266
|
const decodeOwnershipToken = Schema.decodeSync(OwnershipToken);
|
|
248
267
|
const decodeQueueSequence = Schema.decodeSync(QueueSequence);
|
|
249
268
|
const decodeProducerEpoch = Schema.decodeSync(ProducerEpoch);
|
|
269
|
+
const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
|
|
270
|
+
const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
|
|
250
271
|
|
|
251
272
|
const utc = (millis: number): DateTime.Utc => DateTime.toUtc(DateTime.makeUnsafe(millis));
|
|
252
273
|
|
|
253
274
|
const admissionKey = (
|
|
254
|
-
|
|
275
|
+
threadId: ThreadId,
|
|
255
276
|
principal: Principal,
|
|
256
277
|
idempotencyKey: IdempotencyKey,
|
|
257
|
-
): string =>
|
|
278
|
+
): string => JSON.stringify([threadId, principal, idempotencyKey]);
|
|
258
279
|
|
|
259
280
|
const toSnapshot = (row: SubmissionRow): SubmissionSnapshot =>
|
|
260
281
|
SubmissionSnapshot.make({
|
|
261
282
|
submissionId: row.submissionId,
|
|
262
|
-
|
|
283
|
+
threadId: row.threadId,
|
|
263
284
|
queueSequence: row.queueSequence,
|
|
264
285
|
principal: row.principal,
|
|
265
286
|
idempotencyKey: row.idempotencyKey,
|
|
@@ -271,6 +292,22 @@ const toSnapshot = (row: SubmissionRow): SubmissionSnapshot =>
|
|
|
271
292
|
receiptId: row.receiptId,
|
|
272
293
|
state: row.state,
|
|
273
294
|
createdAt: utc(row.createdAtMillis),
|
|
295
|
+
...(row.admissionGroup === undefined ? {} : { admissionGroup: row.admissionGroup }),
|
|
296
|
+
...(row.admissionFence === undefined ? {} : { admissionFence: row.admissionFence }),
|
|
297
|
+
...(row.workerAdmissionJson === undefined
|
|
298
|
+
? {}
|
|
299
|
+
: {
|
|
300
|
+
workerAdmission: Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(
|
|
301
|
+
row.workerAdmissionJson,
|
|
302
|
+
),
|
|
303
|
+
}),
|
|
304
|
+
...(row.messageAdmissionJson === undefined
|
|
305
|
+
? {}
|
|
306
|
+
: {
|
|
307
|
+
messageAdmission: Schema.decodeSync(Schema.fromJsonString(InputMessage))(
|
|
308
|
+
row.messageAdmissionJson,
|
|
309
|
+
),
|
|
310
|
+
}),
|
|
274
311
|
...(row.settledOutcome === undefined ? {} : { settledOutcome: row.settledOutcome }),
|
|
275
312
|
...(row.readyAtMillis === undefined ? {} : { readyAt: utc(row.readyAtMillis) }),
|
|
276
313
|
...(row.parentLinkage === undefined ? {} : { parentLinkage: row.parentLinkage }),
|
|
@@ -304,13 +341,13 @@ const sameParentLinkage = (
|
|
|
304
341
|
left.parentSubmissionId === right.parentSubmissionId &&
|
|
305
342
|
left.parentToolCallId === right.parentToolCallId;
|
|
306
343
|
|
|
307
|
-
const laneEpoch = (state: LedgerState,
|
|
308
|
-
state.lanes.get(
|
|
344
|
+
const laneEpoch = (state: LedgerState, threadId: ThreadId): number =>
|
|
345
|
+
state.lanes.get(threadId)?.producerEpoch ?? 0;
|
|
309
346
|
|
|
310
347
|
const ownershipLost = (state: LedgerState, stored: StoredSubmission): OwnershipLost =>
|
|
311
348
|
OwnershipLost.make({
|
|
312
349
|
submissionId: stored.row.submissionId,
|
|
313
|
-
actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.
|
|
350
|
+
actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.threadId)),
|
|
314
351
|
});
|
|
315
352
|
|
|
316
353
|
/** The presented token owns the lane only while it matches the live ownership record. */
|
|
@@ -330,15 +367,14 @@ const withChildReservation = (
|
|
|
330
367
|
childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation),
|
|
331
368
|
});
|
|
332
369
|
|
|
333
|
-
const findHead = (
|
|
334
|
-
state: LedgerState,
|
|
335
|
-
conversationId: ConversationId,
|
|
336
|
-
): StoredSubmission | undefined => {
|
|
370
|
+
const findHead = (state: LedgerState, threadId: ThreadId): StoredSubmission | undefined => {
|
|
337
371
|
let head: StoredSubmission | undefined;
|
|
372
|
+
|
|
338
373
|
for (const stored of state.submissions.values()) {
|
|
339
|
-
if (stored.row.
|
|
374
|
+
if (stored.row.threadId !== threadId || stored.row.state === "settled") continue;
|
|
340
375
|
if (head === undefined || stored.row.queueSequence < head.row.queueSequence) head = stored;
|
|
341
376
|
}
|
|
377
|
+
|
|
342
378
|
return head;
|
|
343
379
|
};
|
|
344
380
|
|
|
@@ -385,6 +421,8 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
385
421
|
childReservations: new Map(),
|
|
386
422
|
mintCounter: 0,
|
|
387
423
|
});
|
|
424
|
+
|
|
425
|
+
const admissionFence = yield* SubmissionAdmissionFence;
|
|
388
426
|
const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
|
|
389
427
|
|
|
390
428
|
const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "non-durable" }));
|
|
@@ -393,23 +431,45 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
393
431
|
(unvalidated) =>
|
|
394
432
|
Effect.gen(function* () {
|
|
395
433
|
const request = yield* validate(AdmissionRequest, "admit", unvalidated);
|
|
434
|
+
|
|
435
|
+
const workerAdmissionJson =
|
|
436
|
+
request.workerAdmission === undefined
|
|
437
|
+
? undefined
|
|
438
|
+
: yield* Schema.encodeEffect(Schema.fromJsonString(WorkerAdmission))(
|
|
439
|
+
request.workerAdmission,
|
|
440
|
+
).pipe(
|
|
441
|
+
Effect.mapError(() => ledgerError("admit", "Invalid worker admission metadata")),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
const messageAdmissionJson =
|
|
445
|
+
request.messageAdmission === undefined
|
|
446
|
+
? undefined
|
|
447
|
+
: yield* Schema.encodeEffect(Schema.fromJsonString(InputMessage))(
|
|
448
|
+
request.messageAdmission,
|
|
449
|
+
).pipe(
|
|
450
|
+
Effect.mapError(() => ledgerError("admit", "Invalid message admission metadata")),
|
|
451
|
+
);
|
|
452
|
+
|
|
396
453
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
454
|
+
const services = yield* Effect.context<never>();
|
|
455
|
+
|
|
397
456
|
const decision = yield* Ref.modify(
|
|
398
457
|
state,
|
|
399
458
|
(
|
|
400
459
|
current,
|
|
401
460
|
): readonly [
|
|
402
|
-
|
|
461
|
+
(
|
|
462
|
+
| Decision<AdmissionResult, AdmissionConflict | AdmissionPolicyError | LedgerError>
|
|
463
|
+
| { readonly _tag: "cause"; readonly cause: Cause.Cause<AdmissionPolicyError> }
|
|
464
|
+
),
|
|
403
465
|
LedgerState,
|
|
404
466
|
] => {
|
|
405
|
-
const key = admissionKey(
|
|
406
|
-
request.conversationId,
|
|
407
|
-
request.principal,
|
|
408
|
-
request.idempotencyKey,
|
|
409
|
-
);
|
|
467
|
+
const key = admissionKey(request.threadId, request.principal, request.idempotencyKey);
|
|
410
468
|
const existingId = current.admissionIndex.get(key);
|
|
469
|
+
|
|
411
470
|
if (existingId !== undefined) {
|
|
412
471
|
const existing = current.submissions.get(existingId);
|
|
472
|
+
|
|
413
473
|
if (existing === undefined) {
|
|
414
474
|
return [
|
|
415
475
|
failure(
|
|
@@ -422,12 +482,33 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
422
482
|
// (or its absence): linkage is immutable lineage (spec §12 step 5, SUB-016).
|
|
423
483
|
if (
|
|
424
484
|
existing.row.inputDigest !== request.inputDigest ||
|
|
425
|
-
!sameParentLinkage(existing.row.parentLinkage, request.parentLinkage)
|
|
485
|
+
!sameParentLinkage(existing.row.parentLinkage, request.parentLinkage) ||
|
|
486
|
+
existing.row.admissionGroup !== request.admissionGroup ||
|
|
487
|
+
!Schema.toEquivalence(Schema.optional(WorkerAdmission))(
|
|
488
|
+
existing.row.workerAdmissionJson === undefined
|
|
489
|
+
? undefined
|
|
490
|
+
: Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(
|
|
491
|
+
existing.row.workerAdmissionJson,
|
|
492
|
+
),
|
|
493
|
+
request.workerAdmission,
|
|
494
|
+
) ||
|
|
495
|
+
!Schema.toEquivalence(Schema.optional(InputMessage))(
|
|
496
|
+
existing.row.messageAdmissionJson === undefined
|
|
497
|
+
? undefined
|
|
498
|
+
: Schema.decodeSync(Schema.fromJsonString(InputMessage))(
|
|
499
|
+
existing.row.messageAdmissionJson,
|
|
500
|
+
),
|
|
501
|
+
request.messageAdmission,
|
|
502
|
+
) ||
|
|
503
|
+
!Schema.toEquivalence(Schema.optional(Schema.Json))(
|
|
504
|
+
existing.row.admissionFence,
|
|
505
|
+
request.admissionFence,
|
|
506
|
+
)
|
|
426
507
|
) {
|
|
427
508
|
return [
|
|
428
509
|
failure(
|
|
429
510
|
AdmissionConflict.make({
|
|
430
|
-
|
|
511
|
+
threadId: request.threadId,
|
|
431
512
|
principal: request.principal,
|
|
432
513
|
idempotencyKey: request.idempotencyKey,
|
|
433
514
|
existingInputDigest: existing.row.inputDigest,
|
|
@@ -437,6 +518,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
437
518
|
current,
|
|
438
519
|
];
|
|
439
520
|
}
|
|
521
|
+
|
|
440
522
|
return [
|
|
441
523
|
success(
|
|
442
524
|
AdmissionResult.make({
|
|
@@ -450,6 +532,58 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
450
532
|
current,
|
|
451
533
|
];
|
|
452
534
|
}
|
|
535
|
+
|
|
536
|
+
// A Thread's first admission fixes its worker origin before canonical materialization.
|
|
537
|
+
const first = [...current.submissions.values()].find(
|
|
538
|
+
({ row }) => row.threadId === request.threadId,
|
|
539
|
+
);
|
|
540
|
+
|
|
541
|
+
if (first !== undefined) {
|
|
542
|
+
const previous =
|
|
543
|
+
first.row.workerAdmissionJson === undefined
|
|
544
|
+
? undefined
|
|
545
|
+
: Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(
|
|
546
|
+
first.row.workerAdmissionJson,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
if (
|
|
550
|
+
!Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin))(
|
|
551
|
+
previous?.origin,
|
|
552
|
+
request.workerAdmission?.origin,
|
|
553
|
+
)
|
|
554
|
+
)
|
|
555
|
+
return [
|
|
556
|
+
failure(
|
|
557
|
+
AdmissionPolicyError.make({
|
|
558
|
+
reason: "refused",
|
|
559
|
+
code: "worker-origin-conflict",
|
|
560
|
+
}),
|
|
561
|
+
),
|
|
562
|
+
current,
|
|
563
|
+
];
|
|
564
|
+
}
|
|
565
|
+
// A memory policy and the ledger mutation share one synchronous critical section.
|
|
566
|
+
// An asynchronous policy cannot fence this Ref and therefore fails closed.
|
|
567
|
+
const checked = Effect.runSyncExitWith(services)(admissionFence.check(request));
|
|
568
|
+
|
|
569
|
+
if (Exit.isFailure(checked)) {
|
|
570
|
+
return [{ _tag: "cause", cause: checked.cause }, current];
|
|
571
|
+
}
|
|
572
|
+
if (
|
|
573
|
+
request.admissionGroup !== undefined &&
|
|
574
|
+
[...current.submissions.values()].some(
|
|
575
|
+
({ row }) =>
|
|
576
|
+
row.threadId === request.threadId &&
|
|
577
|
+
row.admissionGroup === request.admissionGroup &&
|
|
578
|
+
row.state !== "settled",
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
return [
|
|
582
|
+
failure(
|
|
583
|
+
AdmissionPolicyError.make({ reason: "occupied", code: "admission-group" }),
|
|
584
|
+
),
|
|
585
|
+
current,
|
|
586
|
+
];
|
|
453
587
|
if (current.submissions.size >= MAX_SUBMISSIONS) {
|
|
454
588
|
return [
|
|
455
589
|
failure(
|
|
@@ -458,14 +592,17 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
458
592
|
current,
|
|
459
593
|
];
|
|
460
594
|
}
|
|
461
|
-
|
|
595
|
+
|
|
596
|
+
const lane = current.lanes.get(request.threadId) ?? {
|
|
462
597
|
nextQueueSequence: 1,
|
|
463
598
|
producerEpoch: 0,
|
|
464
599
|
};
|
|
600
|
+
|
|
465
601
|
const mintCounter = current.mintCounter + 1;
|
|
602
|
+
|
|
466
603
|
const row: SubmissionRow = {
|
|
467
604
|
submissionId: decodeSubmissionId(`submission-memory-${mintCounter}`),
|
|
468
|
-
|
|
605
|
+
threadId: request.threadId,
|
|
469
606
|
queueSequence: decodeQueueSequence(lane.nextQueueSequence),
|
|
470
607
|
principal: request.principal,
|
|
471
608
|
idempotencyKey: request.idempotencyKey,
|
|
@@ -480,7 +617,16 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
480
617
|
createdAtMillis: nowMillis,
|
|
481
618
|
readyAtMillis: undefined,
|
|
482
619
|
parentLinkage: request.parentLinkage,
|
|
620
|
+
...(workerAdmissionJson === undefined ? {} : { workerAdmissionJson }),
|
|
621
|
+
...(messageAdmissionJson === undefined ? {} : { messageAdmissionJson }),
|
|
622
|
+
...(request.admissionGroup === undefined
|
|
623
|
+
? {}
|
|
624
|
+
: { admissionGroup: request.admissionGroup }),
|
|
625
|
+
...(request.admissionFence === undefined
|
|
626
|
+
? {}
|
|
627
|
+
: { admissionFence: request.admissionFence }),
|
|
483
628
|
};
|
|
629
|
+
|
|
484
630
|
const submissions = new Map(current.submissions).set(row.submissionId, {
|
|
485
631
|
row,
|
|
486
632
|
ownership: undefined,
|
|
@@ -493,11 +639,14 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
493
639
|
approvalDecisions: new Map<ToolCallId, ApprovalDecisionIntent>(),
|
|
494
640
|
unknownResolutions: new Map<ToolCallId, StoredUnknownResolution>(),
|
|
495
641
|
});
|
|
642
|
+
|
|
496
643
|
const admissionIndex = new Map(current.admissionIndex).set(key, row.submissionId);
|
|
497
|
-
|
|
644
|
+
|
|
645
|
+
const lanes = new Map(current.lanes).set(request.threadId, {
|
|
498
646
|
nextQueueSequence: lane.nextQueueSequence + 1,
|
|
499
647
|
producerEpoch: lane.producerEpoch,
|
|
500
648
|
});
|
|
649
|
+
|
|
501
650
|
return [
|
|
502
651
|
success(
|
|
503
652
|
AdmissionResult.make({
|
|
@@ -512,9 +661,26 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
512
661
|
];
|
|
513
662
|
},
|
|
514
663
|
);
|
|
664
|
+
|
|
515
665
|
if (decision._tag === "failure") return yield* decision.error;
|
|
666
|
+
if (decision._tag === "cause") {
|
|
667
|
+
for (const reason of decision.cause.reasons) {
|
|
668
|
+
if (Cause.isDieReason(reason) && Cause.isAsyncFiberError(reason.defect)) {
|
|
669
|
+
yield* Fiber.interrupt(reason.defect.fiber);
|
|
670
|
+
|
|
671
|
+
return yield* AdmissionPolicyError.make({
|
|
672
|
+
reason: "unavailable",
|
|
673
|
+
code: "synchronous-memory-policy-required",
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
return yield* Effect.failCause(decision.cause);
|
|
679
|
+
}
|
|
680
|
+
|
|
516
681
|
return decision.value;
|
|
517
682
|
}),
|
|
683
|
+
Effect.uninterruptible,
|
|
518
684
|
);
|
|
519
685
|
|
|
520
686
|
const markReady: SubmissionLedger["Service"]["markReady"] = Effect.fn(
|
|
@@ -523,10 +689,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
523
689
|
Effect.gen(function* () {
|
|
524
690
|
const request = yield* validate(MarkReadyRequest, "markReady", unvalidated);
|
|
525
691
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
692
|
+
|
|
526
693
|
const decision = yield* Ref.modify(
|
|
527
694
|
state,
|
|
528
695
|
(current): readonly [Decision<void, LedgerError>, LedgerState] => {
|
|
529
696
|
const stored = current.submissions.get(request.submissionId);
|
|
697
|
+
|
|
530
698
|
if (stored === undefined) {
|
|
531
699
|
return [
|
|
532
700
|
failure(ledgerError("markReady", `Unknown Submission ${request.submissionId}`)),
|
|
@@ -534,6 +702,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
534
702
|
];
|
|
535
703
|
}
|
|
536
704
|
if (stored.row.state !== "admitted") return [success(undefined), current];
|
|
705
|
+
|
|
537
706
|
return [
|
|
538
707
|
success(undefined),
|
|
539
708
|
withSubmission(current, {
|
|
@@ -543,6 +712,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
543
712
|
];
|
|
544
713
|
},
|
|
545
714
|
);
|
|
715
|
+
|
|
546
716
|
if (decision._tag === "failure") return yield* decision.error;
|
|
547
717
|
}),
|
|
548
718
|
);
|
|
@@ -553,14 +723,17 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
553
723
|
Effect.gen(function* () {
|
|
554
724
|
const request = yield* validate(SubmissionLookup, "lookup", unvalidated);
|
|
555
725
|
const current = yield* Ref.get(state);
|
|
726
|
+
|
|
556
727
|
const submissionId =
|
|
557
728
|
request._tag === "SubmissionLookupById"
|
|
558
729
|
? request.submissionId
|
|
559
730
|
: current.admissionIndex.get(
|
|
560
|
-
admissionKey(request.
|
|
731
|
+
admissionKey(request.threadId, request.principal, request.idempotencyKey),
|
|
561
732
|
);
|
|
733
|
+
|
|
562
734
|
const stored =
|
|
563
735
|
submissionId === undefined ? undefined : current.submissions.get(submissionId);
|
|
736
|
+
|
|
564
737
|
return stored === undefined ? Option.none() : Option.some(toSnapshot(stored.row));
|
|
565
738
|
}),
|
|
566
739
|
);
|
|
@@ -570,20 +743,25 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
570
743
|
)((unvalidated) =>
|
|
571
744
|
Effect.gen(function* () {
|
|
572
745
|
const request = yield* validate(SubmissionLookupByKey, "resolveAdmission", unvalidated);
|
|
746
|
+
|
|
573
747
|
// Test-only fault seam: lets suites exercise the Indeterminate classification that a
|
|
574
748
|
// single strongly consistent store never produces on its own (SUB-031, P6 honesty).
|
|
575
749
|
if (options.resolveAdmissionFault !== undefined) {
|
|
576
750
|
const fault = yield* options.resolveAdmissionFault;
|
|
751
|
+
|
|
577
752
|
if (Option.isSome(fault)) {
|
|
578
753
|
return AdmissionIndeterminate.make({ reason: fault.value });
|
|
579
754
|
}
|
|
580
755
|
}
|
|
581
756
|
const current = yield* Ref.get(state);
|
|
757
|
+
|
|
582
758
|
const submissionId = current.admissionIndex.get(
|
|
583
|
-
admissionKey(request.
|
|
759
|
+
admissionKey(request.threadId, request.principal, request.idempotencyKey),
|
|
584
760
|
);
|
|
761
|
+
|
|
585
762
|
const stored =
|
|
586
763
|
submissionId === undefined ? undefined : current.submissions.get(submissionId);
|
|
764
|
+
|
|
587
765
|
return stored === undefined
|
|
588
766
|
? AdmissionNotAdmitted.make()
|
|
589
767
|
: AdmissionAdmitted.make({ submission: toSnapshot(stored.row) });
|
|
@@ -595,14 +773,18 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
595
773
|
Effect.gen(function* () {
|
|
596
774
|
const request = yield* validate(ClaimRequest, "claim", unvalidated);
|
|
597
775
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
776
|
+
|
|
598
777
|
const decision = yield* Ref.modify(
|
|
599
778
|
state,
|
|
600
779
|
(current): readonly [Decision<Option.Option<Claim>, LedgerError>, LedgerState] => {
|
|
601
|
-
const head = findHead(current, request.
|
|
780
|
+
const head = findHead(current, request.threadId);
|
|
781
|
+
|
|
602
782
|
if (head === undefined) return [success(Option.none()), current];
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
783
|
+
if (
|
|
784
|
+
BLOCKED_HEAD_STATES.has(head.row.state) ||
|
|
785
|
+
(head.row.state === "unknown" && head.abortIntent === undefined)
|
|
786
|
+
)
|
|
787
|
+
return [success(Option.none()), current];
|
|
606
788
|
if (
|
|
607
789
|
head.ownership !== undefined &&
|
|
608
790
|
head.ownership.leaseExpiresAtMillis > nowMillis &&
|
|
@@ -610,15 +792,17 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
610
792
|
) {
|
|
611
793
|
return [success(Option.none()), current];
|
|
612
794
|
}
|
|
613
|
-
const lane = current.lanes.get(request.
|
|
795
|
+
const lane = current.lanes.get(request.threadId);
|
|
796
|
+
|
|
614
797
|
if (lane === undefined) {
|
|
615
798
|
return [
|
|
616
|
-
failure(ledgerError("claim", "Claimable head without a
|
|
799
|
+
failure(ledgerError("claim", "Claimable head without a Thread lane")),
|
|
617
800
|
current,
|
|
618
801
|
];
|
|
619
802
|
}
|
|
620
803
|
const producerEpoch = decodeProducerEpoch(lane.producerEpoch + 1);
|
|
621
804
|
const mintCounter = current.mintCounter + 1;
|
|
805
|
+
|
|
622
806
|
const ownership: StoredOwnership = {
|
|
623
807
|
attemptId: decodeAttemptId(`attempt-memory-${mintCounter}`),
|
|
624
808
|
ownershipToken: decodeOwnershipToken(`ownership-memory-${mintCounter}`),
|
|
@@ -626,13 +810,17 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
626
810
|
ownerProducerId: request.producerId,
|
|
627
811
|
leaseExpiresAtMillis: nowMillis + leaseMillis,
|
|
628
812
|
};
|
|
813
|
+
|
|
629
814
|
const row: SubmissionRow =
|
|
630
815
|
head.row.state === "ready" ? { ...head.row, state: "running" } : head.row;
|
|
816
|
+
|
|
631
817
|
const next = withSubmission(current, { ...head, row, ownership });
|
|
632
|
-
|
|
818
|
+
|
|
819
|
+
const lanes = new Map(next.lanes).set(request.threadId, {
|
|
633
820
|
nextQueueSequence: lane.nextQueueSequence,
|
|
634
821
|
producerEpoch: lane.producerEpoch + 1,
|
|
635
822
|
});
|
|
823
|
+
|
|
636
824
|
return [
|
|
637
825
|
success(
|
|
638
826
|
Option.some(
|
|
@@ -650,7 +838,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
650
838
|
];
|
|
651
839
|
},
|
|
652
840
|
);
|
|
841
|
+
|
|
653
842
|
if (decision._tag === "failure") return yield* decision.error;
|
|
843
|
+
|
|
654
844
|
return decision.value;
|
|
655
845
|
}),
|
|
656
846
|
);
|
|
@@ -661,12 +851,14 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
661
851
|
Effect.gen(function* () {
|
|
662
852
|
const request = yield* validate(RenewOwnershipRequest, "renewOwnership", unvalidated);
|
|
663
853
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
854
|
+
|
|
664
855
|
const decision = yield* Ref.modify(
|
|
665
856
|
state,
|
|
666
857
|
(
|
|
667
858
|
current,
|
|
668
859
|
): readonly [Decision<OwnershipRenewal, OwnershipLost | LedgerError>, LedgerState] => {
|
|
669
860
|
const stored = current.submissions.get(request.submissionId);
|
|
861
|
+
|
|
670
862
|
if (stored === undefined) {
|
|
671
863
|
return [
|
|
672
864
|
failure(
|
|
@@ -678,10 +870,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
678
870
|
if (stored.ownership === undefined || !ownsLane(stored, request.ownershipToken)) {
|
|
679
871
|
return [failure(ownershipLost(current, stored)), current];
|
|
680
872
|
}
|
|
873
|
+
|
|
681
874
|
const ownership: StoredOwnership = {
|
|
682
875
|
...stored.ownership,
|
|
683
876
|
leaseExpiresAtMillis: nowMillis + leaseMillis,
|
|
684
877
|
};
|
|
878
|
+
|
|
685
879
|
return [
|
|
686
880
|
success(
|
|
687
881
|
OwnershipRenewal.make({
|
|
@@ -693,7 +887,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
693
887
|
];
|
|
694
888
|
},
|
|
695
889
|
);
|
|
890
|
+
|
|
696
891
|
if (decision._tag === "failure") return yield* decision.error;
|
|
892
|
+
|
|
697
893
|
return decision.value;
|
|
698
894
|
}),
|
|
699
895
|
);
|
|
@@ -703,10 +899,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
703
899
|
)((unvalidated) =>
|
|
704
900
|
Effect.gen(function* () {
|
|
705
901
|
const request = yield* validate(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
|
|
902
|
+
|
|
706
903
|
const decision = yield* Ref.modify(
|
|
707
904
|
state,
|
|
708
905
|
(current): readonly [Decision<void, OwnershipLost | LedgerError>, LedgerState] => {
|
|
709
906
|
const stored = current.submissions.get(request.submissionId);
|
|
907
|
+
|
|
710
908
|
if (stored === undefined) {
|
|
711
909
|
return [
|
|
712
910
|
failure(
|
|
@@ -718,12 +916,14 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
718
916
|
if (!ownsLane(stored, request.ownershipToken)) {
|
|
719
917
|
return [failure(ownershipLost(current, stored)), current];
|
|
720
918
|
}
|
|
919
|
+
|
|
721
920
|
return [
|
|
722
921
|
success(undefined),
|
|
723
922
|
withSubmission(current, { ...stored, ownership: undefined }),
|
|
724
923
|
];
|
|
725
924
|
},
|
|
726
925
|
);
|
|
926
|
+
|
|
727
927
|
if (decision._tag === "failure") return yield* decision.error;
|
|
728
928
|
}),
|
|
729
929
|
);
|
|
@@ -733,10 +933,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
733
933
|
)((unvalidated) =>
|
|
734
934
|
Effect.gen(function* () {
|
|
735
935
|
const request = yield* validate(MarkInputAppliedRequest, "markInputApplied", unvalidated);
|
|
936
|
+
|
|
736
937
|
const decision = yield* Ref.modify(
|
|
737
938
|
state,
|
|
738
939
|
(current): readonly [Decision<void, OwnershipLost | LedgerError>, LedgerState] => {
|
|
739
940
|
const stored = current.submissions.get(request.submissionId);
|
|
941
|
+
|
|
740
942
|
if (stored === undefined) {
|
|
741
943
|
return [
|
|
742
944
|
failure(
|
|
@@ -748,20 +950,43 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
748
950
|
if (!ownsLane(stored, request.ownershipToken)) {
|
|
749
951
|
return [failure(ownershipLost(current, stored)), current];
|
|
750
952
|
}
|
|
953
|
+
|
|
954
|
+
if (stored.inputApplied !== undefined) {
|
|
955
|
+
if (
|
|
956
|
+
stored.inputApplied.recordId === request.recordId &&
|
|
957
|
+
stored.inputApplied.sequence === request.sequence
|
|
958
|
+
) {
|
|
959
|
+
return [success(undefined), current];
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
return [
|
|
963
|
+
failure(
|
|
964
|
+
ledgerError(
|
|
965
|
+
"markInputApplied",
|
|
966
|
+
`A different canonical input marker is already recorded for Submission ${request.submissionId}`,
|
|
967
|
+
),
|
|
968
|
+
),
|
|
969
|
+
current,
|
|
970
|
+
];
|
|
971
|
+
}
|
|
972
|
+
|
|
751
973
|
const marker = InputAppliedMarker.make({
|
|
752
974
|
recordId: request.recordId,
|
|
753
975
|
sequence: request.sequence,
|
|
754
976
|
});
|
|
977
|
+
|
|
755
978
|
const row: SubmissionRow =
|
|
756
979
|
STATE_RANK[stored.row.state] < STATE_RANK["input-applied"]
|
|
757
980
|
? { ...stored.row, state: "input-applied" }
|
|
758
981
|
: stored.row;
|
|
982
|
+
|
|
759
983
|
return [
|
|
760
984
|
success(undefined),
|
|
761
985
|
withSubmission(current, { ...stored, row, inputApplied: marker }),
|
|
762
986
|
];
|
|
763
987
|
},
|
|
764
988
|
);
|
|
989
|
+
|
|
765
990
|
if (decision._tag === "failure") return yield* decision.error;
|
|
766
991
|
}),
|
|
767
992
|
);
|
|
@@ -771,6 +996,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
771
996
|
)((unvalidated) =>
|
|
772
997
|
Effect.gen(function* () {
|
|
773
998
|
const request = yield* validate(SettlementReservation, "reserveSettlement", unvalidated);
|
|
999
|
+
|
|
774
1000
|
const decision = yield* Ref.modify(
|
|
775
1001
|
state,
|
|
776
1002
|
(
|
|
@@ -780,6 +1006,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
780
1006
|
LedgerState,
|
|
781
1007
|
] => {
|
|
782
1008
|
const stored = current.submissions.get(request.submissionId);
|
|
1009
|
+
|
|
783
1010
|
if (stored === undefined) {
|
|
784
1011
|
return [
|
|
785
1012
|
failure(
|
|
@@ -788,11 +1015,13 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
788
1015
|
current,
|
|
789
1016
|
];
|
|
790
1017
|
}
|
|
1018
|
+
|
|
791
1019
|
// A `joined` Submission settles WITH its host (plan §2.5) and its lane is never
|
|
792
1020
|
// worker-claimable, so no ownership token can exist for it: the recorded host
|
|
793
1021
|
// linkage authorizes the reservation and the presented token is not consulted.
|
|
794
1022
|
const joinedSettlement =
|
|
795
1023
|
stored.row.state === "joined" && stored.joinedHostSubmissionId !== undefined;
|
|
1024
|
+
|
|
796
1025
|
// P7 §7(c): an aborted, never-claimed, still-queued Submission likewise has no
|
|
797
1026
|
// live ownership to fence against — its durable abort intent authorizes exactly
|
|
798
1027
|
// its ABORTED settlement (`terminalizing` is the same pass's crash replay). Every
|
|
@@ -802,6 +1031,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
802
1031
|
stored.abortIntent !== undefined &&
|
|
803
1032
|
stored.ownership === undefined &&
|
|
804
1033
|
(stored.row.state === "ready" || stored.row.state === "terminalizing");
|
|
1034
|
+
|
|
805
1035
|
if (
|
|
806
1036
|
!joinedSettlement &&
|
|
807
1037
|
!queuedAbortSettlement &&
|
|
@@ -810,6 +1040,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
810
1040
|
return [failure(ownershipLost(current, stored)), current];
|
|
811
1041
|
}
|
|
812
1042
|
const existing = stored.reservation;
|
|
1043
|
+
|
|
813
1044
|
if (existing !== undefined) {
|
|
814
1045
|
if (
|
|
815
1046
|
existing.settlementId !== request.settlementId ||
|
|
@@ -826,6 +1057,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
826
1057
|
current,
|
|
827
1058
|
];
|
|
828
1059
|
}
|
|
1060
|
+
|
|
829
1061
|
return [
|
|
830
1062
|
success(
|
|
831
1063
|
ReservedSettlement.make({
|
|
@@ -840,6 +1072,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
840
1072
|
current,
|
|
841
1073
|
];
|
|
842
1074
|
}
|
|
1075
|
+
|
|
843
1076
|
const reservation: StoredReservation = {
|
|
844
1077
|
settlementId: request.settlementId,
|
|
845
1078
|
outcome: request.outcome,
|
|
@@ -847,10 +1080,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
847
1080
|
recordDigest: request.recordDigest,
|
|
848
1081
|
finalizedAtMillis: undefined,
|
|
849
1082
|
};
|
|
1083
|
+
|
|
850
1084
|
const row: SubmissionRow =
|
|
851
1085
|
STATE_RANK[stored.row.state] < STATE_RANK.terminalizing
|
|
852
1086
|
? { ...stored.row, state: "terminalizing" }
|
|
853
1087
|
: stored.row;
|
|
1088
|
+
|
|
854
1089
|
return [
|
|
855
1090
|
success(
|
|
856
1091
|
ReservedSettlement.make({
|
|
@@ -866,7 +1101,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
866
1101
|
];
|
|
867
1102
|
},
|
|
868
1103
|
);
|
|
1104
|
+
|
|
869
1105
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1106
|
+
|
|
870
1107
|
return decision.value;
|
|
871
1108
|
}),
|
|
872
1109
|
);
|
|
@@ -877,12 +1114,14 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
877
1114
|
Effect.gen(function* () {
|
|
878
1115
|
const request = yield* validate(SettlementFinalization, "finalizeSettlement", unvalidated);
|
|
879
1116
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1117
|
+
|
|
880
1118
|
const decision = yield* Ref.modify(
|
|
881
1119
|
state,
|
|
882
1120
|
(
|
|
883
1121
|
current,
|
|
884
1122
|
): readonly [Decision<Settlement, SettlementConflict | LedgerError>, LedgerState] => {
|
|
885
1123
|
const stored = current.submissions.get(request.submissionId);
|
|
1124
|
+
|
|
886
1125
|
if (stored === undefined) {
|
|
887
1126
|
return [
|
|
888
1127
|
failure(
|
|
@@ -892,6 +1131,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
892
1131
|
];
|
|
893
1132
|
}
|
|
894
1133
|
const reservation = stored.reservation;
|
|
1134
|
+
|
|
895
1135
|
if (reservation === undefined) {
|
|
896
1136
|
return [
|
|
897
1137
|
failure(
|
|
@@ -903,6 +1143,19 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
903
1143
|
current,
|
|
904
1144
|
];
|
|
905
1145
|
}
|
|
1146
|
+
const settlementFailure = settlementFailureFromRecord(reservation.record);
|
|
1147
|
+
|
|
1148
|
+
if ((reservation.outcome === "failed") !== (settlementFailure !== undefined)) {
|
|
1149
|
+
return [
|
|
1150
|
+
failure(
|
|
1151
|
+
ledgerError(
|
|
1152
|
+
"finalizeSettlement",
|
|
1153
|
+
`Settlement reservation for Submission ${request.submissionId} has contradictory failure evidence`,
|
|
1154
|
+
),
|
|
1155
|
+
),
|
|
1156
|
+
current,
|
|
1157
|
+
];
|
|
1158
|
+
}
|
|
906
1159
|
if (reservation.settlementId !== request.settlementId) {
|
|
907
1160
|
return [
|
|
908
1161
|
failure(
|
|
@@ -922,18 +1175,21 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
922
1175
|
settlementId: reservation.settlementId,
|
|
923
1176
|
receiptId: stored.row.receiptId,
|
|
924
1177
|
outcome: reservation.outcome,
|
|
1178
|
+
...(settlementFailure === undefined ? {} : { failure: settlementFailure }),
|
|
925
1179
|
settledAt: utc(reservation.finalizedAtMillis),
|
|
926
1180
|
}),
|
|
927
1181
|
),
|
|
928
1182
|
current,
|
|
929
1183
|
];
|
|
930
1184
|
}
|
|
1185
|
+
|
|
931
1186
|
const next = withSubmission(current, {
|
|
932
1187
|
...stored,
|
|
933
1188
|
row: { ...stored.row, state: "settled", settledOutcome: reservation.outcome },
|
|
934
1189
|
ownership: undefined,
|
|
935
1190
|
reservation: { ...reservation, finalizedAtMillis: nowMillis },
|
|
936
1191
|
});
|
|
1192
|
+
|
|
937
1193
|
return [
|
|
938
1194
|
success(
|
|
939
1195
|
Settlement.make({
|
|
@@ -941,6 +1197,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
941
1197
|
settlementId: reservation.settlementId,
|
|
942
1198
|
receiptId: stored.row.receiptId,
|
|
943
1199
|
outcome: reservation.outcome,
|
|
1200
|
+
...(settlementFailure === undefined ? {} : { failure: settlementFailure }),
|
|
944
1201
|
settledAt: utc(nowMillis),
|
|
945
1202
|
}),
|
|
946
1203
|
),
|
|
@@ -948,7 +1205,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
948
1205
|
];
|
|
949
1206
|
},
|
|
950
1207
|
);
|
|
1208
|
+
|
|
951
1209
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1210
|
+
|
|
952
1211
|
return decision.value;
|
|
953
1212
|
}),
|
|
954
1213
|
);
|
|
@@ -959,6 +1218,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
959
1218
|
Effect.gen(function* () {
|
|
960
1219
|
const request = yield* validate(AbortCommand, "requestAbort", unvalidated);
|
|
961
1220
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1221
|
+
|
|
962
1222
|
const decision = yield* Ref.modify(
|
|
963
1223
|
state,
|
|
964
1224
|
(
|
|
@@ -968,6 +1228,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
968
1228
|
LedgerState,
|
|
969
1229
|
] => {
|
|
970
1230
|
const stored = current.submissions.get(request.submissionId);
|
|
1231
|
+
|
|
971
1232
|
if (stored === undefined) {
|
|
972
1233
|
return [
|
|
973
1234
|
failure(ledgerError("requestAbort", `Unknown Submission ${request.submissionId}`)),
|
|
@@ -989,6 +1250,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
989
1250
|
current,
|
|
990
1251
|
];
|
|
991
1252
|
}
|
|
1253
|
+
|
|
992
1254
|
return [
|
|
993
1255
|
failure(
|
|
994
1256
|
JoinedToHost.make({
|
|
@@ -1011,6 +1273,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1011
1273
|
current,
|
|
1012
1274
|
];
|
|
1013
1275
|
}
|
|
1276
|
+
|
|
1014
1277
|
return [
|
|
1015
1278
|
failure(
|
|
1016
1279
|
SettlementConflict.make({
|
|
@@ -1022,16 +1285,20 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1022
1285
|
];
|
|
1023
1286
|
}
|
|
1024
1287
|
if (stored.abortIntent !== undefined) return [success(stored.abortIntent), current];
|
|
1288
|
+
|
|
1025
1289
|
const intent = AbortIntent.make({
|
|
1026
1290
|
submissionId: request.submissionId,
|
|
1027
1291
|
author: request.author,
|
|
1028
1292
|
reason: request.reason,
|
|
1029
1293
|
requestedAt: utc(nowMillis),
|
|
1030
1294
|
});
|
|
1295
|
+
|
|
1031
1296
|
return [success(intent), withSubmission(current, { ...stored, abortIntent: intent })];
|
|
1032
1297
|
},
|
|
1033
1298
|
);
|
|
1299
|
+
|
|
1034
1300
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1301
|
+
|
|
1035
1302
|
return decision.value;
|
|
1036
1303
|
}),
|
|
1037
1304
|
);
|
|
@@ -1041,6 +1308,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1041
1308
|
)((unvalidated) =>
|
|
1042
1309
|
Effect.gen(function* () {
|
|
1043
1310
|
const request = yield* validate(ClaimJoiningRequest, "claimJoining", unvalidated);
|
|
1311
|
+
|
|
1044
1312
|
const decision = yield* Ref.modify(
|
|
1045
1313
|
state,
|
|
1046
1314
|
(
|
|
@@ -1050,6 +1318,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1050
1318
|
LedgerState,
|
|
1051
1319
|
] => {
|
|
1052
1320
|
const host = current.submissions.get(request.hostSubmissionId);
|
|
1321
|
+
|
|
1053
1322
|
if (host === undefined) {
|
|
1054
1323
|
return [
|
|
1055
1324
|
failure(
|
|
@@ -1058,12 +1327,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1058
1327
|
current,
|
|
1059
1328
|
];
|
|
1060
1329
|
}
|
|
1061
|
-
if (host.row.
|
|
1330
|
+
if (host.row.threadId !== request.threadId) {
|
|
1062
1331
|
return [
|
|
1063
1332
|
failure(
|
|
1064
1333
|
ledgerError(
|
|
1065
1334
|
"claimJoining",
|
|
1066
|
-
`Host Submission ${request.hostSubmissionId} does not belong to
|
|
1335
|
+
`Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`,
|
|
1067
1336
|
),
|
|
1068
1337
|
),
|
|
1069
1338
|
current,
|
|
@@ -1072,15 +1341,18 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1072
1341
|
if (!ownsLane(host, request.ownershipToken)) {
|
|
1073
1342
|
return [failure(ownershipLost(current, host)), current];
|
|
1074
1343
|
}
|
|
1344
|
+
|
|
1075
1345
|
const later = [...current.submissions.values()]
|
|
1076
1346
|
.filter(
|
|
1077
1347
|
(stored) =>
|
|
1078
|
-
stored.row.
|
|
1348
|
+
stored.row.threadId === request.threadId &&
|
|
1079
1349
|
stored.row.queueSequence > host.row.queueSequence,
|
|
1080
1350
|
)
|
|
1081
1351
|
.sort((left, right) => left.row.queueSequence - right.row.queueSequence);
|
|
1352
|
+
|
|
1082
1353
|
const claims: Array<JoiningClaim> = [];
|
|
1083
1354
|
const submissions = new Map(current.submissions);
|
|
1355
|
+
|
|
1084
1356
|
for (const stored of later) {
|
|
1085
1357
|
if (claims.length >= request.maxCount) break;
|
|
1086
1358
|
// Rows already claimed by THIS host extend its contiguous prefix and are skipped;
|
|
@@ -1113,10 +1385,13 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1113
1385
|
}),
|
|
1114
1386
|
);
|
|
1115
1387
|
}
|
|
1388
|
+
|
|
1116
1389
|
return [success(claims), { ...current, submissions }];
|
|
1117
1390
|
},
|
|
1118
1391
|
);
|
|
1392
|
+
|
|
1119
1393
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1394
|
+
|
|
1120
1395
|
return decision.value;
|
|
1121
1396
|
}),
|
|
1122
1397
|
);
|
|
@@ -1126,10 +1401,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1126
1401
|
)((unvalidated) =>
|
|
1127
1402
|
Effect.gen(function* () {
|
|
1128
1403
|
const request = yield* validate(MarkJoinedRequest, "markJoined", unvalidated);
|
|
1404
|
+
|
|
1129
1405
|
const decision = yield* Ref.modify(
|
|
1130
1406
|
state,
|
|
1131
1407
|
(current): readonly [Decision<void, OwnershipLost | LedgerError>, LedgerState] => {
|
|
1132
1408
|
const stored = current.submissions.get(request.submissionId);
|
|
1409
|
+
|
|
1133
1410
|
if (stored === undefined) {
|
|
1134
1411
|
return [
|
|
1135
1412
|
failure(ledgerError("markJoined", `Unknown Submission ${request.submissionId}`)),
|
|
@@ -1148,6 +1425,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1148
1425
|
];
|
|
1149
1426
|
}
|
|
1150
1427
|
const host = current.submissions.get(stored.joinedHostSubmissionId);
|
|
1428
|
+
|
|
1151
1429
|
if (host === undefined) {
|
|
1152
1430
|
return [
|
|
1153
1431
|
failure(
|
|
@@ -1171,6 +1449,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1171
1449
|
) {
|
|
1172
1450
|
return [success(undefined), current];
|
|
1173
1451
|
}
|
|
1452
|
+
|
|
1174
1453
|
return [
|
|
1175
1454
|
failure(
|
|
1176
1455
|
ledgerError(
|
|
@@ -1192,10 +1471,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1192
1471
|
current,
|
|
1193
1472
|
];
|
|
1194
1473
|
}
|
|
1474
|
+
|
|
1195
1475
|
const marker = InputAppliedMarker.make({
|
|
1196
1476
|
recordId: request.recordId,
|
|
1197
1477
|
sequence: request.sequence,
|
|
1198
1478
|
});
|
|
1479
|
+
|
|
1199
1480
|
return [
|
|
1200
1481
|
success(undefined),
|
|
1201
1482
|
withSubmission(current, {
|
|
@@ -1206,6 +1487,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1206
1487
|
];
|
|
1207
1488
|
},
|
|
1208
1489
|
);
|
|
1490
|
+
|
|
1209
1491
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1210
1492
|
}),
|
|
1211
1493
|
);
|
|
@@ -1215,10 +1497,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1215
1497
|
)((unvalidated) =>
|
|
1216
1498
|
Effect.gen(function* () {
|
|
1217
1499
|
const request = yield* validate(RevertJoiningRequest, "revertJoining", unvalidated);
|
|
1500
|
+
|
|
1218
1501
|
const decision = yield* Ref.modify(
|
|
1219
1502
|
state,
|
|
1220
1503
|
(current): readonly [Decision<void, LedgerError>, LedgerState] => {
|
|
1221
1504
|
const stored = current.submissions.get(request.submissionId);
|
|
1505
|
+
|
|
1222
1506
|
if (stored === undefined) {
|
|
1223
1507
|
return [
|
|
1224
1508
|
failure(ledgerError("revertJoining", `Unknown Submission ${request.submissionId}`)),
|
|
@@ -1228,6 +1512,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1228
1512
|
// Idempotent and recovery-only: only a still-`joining` Submission reverts; an
|
|
1229
1513
|
// already-joined (or already-reverted) Submission is a no-op (DUR-016).
|
|
1230
1514
|
if (stored.row.state !== "joining") return [success(undefined), current];
|
|
1515
|
+
|
|
1231
1516
|
return [
|
|
1232
1517
|
success(undefined),
|
|
1233
1518
|
withSubmission(current, {
|
|
@@ -1238,6 +1523,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1238
1523
|
];
|
|
1239
1524
|
},
|
|
1240
1525
|
);
|
|
1526
|
+
|
|
1241
1527
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1242
1528
|
}),
|
|
1243
1529
|
);
|
|
@@ -1248,6 +1534,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1248
1534
|
Effect.gen(function* () {
|
|
1249
1535
|
const request = yield* validate(SuspendRequest, "suspend", unvalidated);
|
|
1250
1536
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1537
|
+
|
|
1251
1538
|
const decision = yield* Ref.modify(
|
|
1252
1539
|
state,
|
|
1253
1540
|
(
|
|
@@ -1257,6 +1544,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1257
1544
|
LedgerState,
|
|
1258
1545
|
] => {
|
|
1259
1546
|
const stored = current.submissions.get(request.submissionId);
|
|
1547
|
+
|
|
1260
1548
|
if (stored === undefined) {
|
|
1261
1549
|
return [
|
|
1262
1550
|
failure(ledgerError("suspend", `Unknown Submission ${request.submissionId}`)),
|
|
@@ -1275,6 +1563,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1275
1563
|
current,
|
|
1276
1564
|
];
|
|
1277
1565
|
}
|
|
1566
|
+
|
|
1278
1567
|
return [
|
|
1279
1568
|
failure(
|
|
1280
1569
|
SettlementConflict.make({
|
|
@@ -1301,6 +1590,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1301
1590
|
if (!ownsLane(stored, request.ownershipToken)) {
|
|
1302
1591
|
return [failure(ownershipLost(current, stored)), current];
|
|
1303
1592
|
}
|
|
1593
|
+
|
|
1304
1594
|
// A covering event that raced ahead of the suspend transaction (an approval decision,
|
|
1305
1595
|
// or a child settlement observed directly from the child's row in this single store)
|
|
1306
1596
|
// resumes the caller immediately WITHOUT releasing the lane (plan §2.6, spec §12).
|
|
@@ -1313,9 +1603,11 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1313
1603
|
(child) =>
|
|
1314
1604
|
current.submissions.get(child.childSubmissionId)?.row.state === "settled",
|
|
1315
1605
|
);
|
|
1606
|
+
|
|
1316
1607
|
if (alreadyCovered) {
|
|
1317
1608
|
return [success("resume-immediately" as const), current];
|
|
1318
1609
|
}
|
|
1610
|
+
|
|
1319
1611
|
return [
|
|
1320
1612
|
success("suspended" as const),
|
|
1321
1613
|
withSubmission(current, {
|
|
@@ -1327,7 +1619,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1327
1619
|
];
|
|
1328
1620
|
},
|
|
1329
1621
|
);
|
|
1622
|
+
|
|
1330
1623
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1624
|
+
|
|
1331
1625
|
return decision.value;
|
|
1332
1626
|
}),
|
|
1333
1627
|
);
|
|
@@ -1341,7 +1635,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1341
1635
|
"recordApprovalDecision",
|
|
1342
1636
|
unvalidated,
|
|
1343
1637
|
);
|
|
1638
|
+
|
|
1344
1639
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1640
|
+
|
|
1345
1641
|
const decision = yield* Ref.modify(
|
|
1346
1642
|
state,
|
|
1347
1643
|
(
|
|
@@ -1351,6 +1647,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1351
1647
|
LedgerState,
|
|
1352
1648
|
] => {
|
|
1353
1649
|
const stored = current.submissions.get(command.submissionId);
|
|
1650
|
+
|
|
1354
1651
|
if (stored === undefined) {
|
|
1355
1652
|
return [
|
|
1356
1653
|
failure(
|
|
@@ -1374,6 +1671,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1374
1671
|
current,
|
|
1375
1672
|
];
|
|
1376
1673
|
}
|
|
1674
|
+
|
|
1377
1675
|
return [
|
|
1378
1676
|
failure(
|
|
1379
1677
|
SettlementConflict.make({
|
|
@@ -1385,6 +1683,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1385
1683
|
];
|
|
1386
1684
|
}
|
|
1387
1685
|
const existing = stored.approvalDecisions.get(command.toolCallId);
|
|
1686
|
+
|
|
1388
1687
|
if (existing !== undefined) {
|
|
1389
1688
|
if (existing.decision !== command.decision) {
|
|
1390
1689
|
return [
|
|
@@ -1398,8 +1697,10 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1398
1697
|
current,
|
|
1399
1698
|
];
|
|
1400
1699
|
}
|
|
1700
|
+
|
|
1401
1701
|
return [success(existing), current];
|
|
1402
1702
|
}
|
|
1703
|
+
|
|
1403
1704
|
const intent = ApprovalDecisionIntent.make({
|
|
1404
1705
|
submissionId: command.submissionId,
|
|
1405
1706
|
toolCallId: command.toolCallId,
|
|
@@ -1408,10 +1709,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1408
1709
|
reason: command.reason,
|
|
1409
1710
|
decidedAt: utc(nowMillis),
|
|
1410
1711
|
});
|
|
1712
|
+
|
|
1411
1713
|
const approvalDecisions = new Map(stored.approvalDecisions).set(
|
|
1412
1714
|
command.toolCallId,
|
|
1413
1715
|
intent,
|
|
1414
1716
|
);
|
|
1717
|
+
|
|
1415
1718
|
// Once every pending call of an ApprovalPending suspension is decided, the lane
|
|
1416
1719
|
// wakes: suspended → input-applied (plan §2.6). A WaitingForChild suspension wakes
|
|
1417
1720
|
// only through recordChildSettled.
|
|
@@ -1422,6 +1725,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1422
1725
|
stored.suspension.reason.toolCallIds.every((toolCallId) =>
|
|
1423
1726
|
approvalDecisions.has(toolCallId),
|
|
1424
1727
|
);
|
|
1728
|
+
|
|
1425
1729
|
return [
|
|
1426
1730
|
success(intent),
|
|
1427
1731
|
withSubmission(current, {
|
|
@@ -1433,7 +1737,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1433
1737
|
];
|
|
1434
1738
|
},
|
|
1435
1739
|
);
|
|
1740
|
+
|
|
1436
1741
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1742
|
+
|
|
1437
1743
|
return decision.value;
|
|
1438
1744
|
}),
|
|
1439
1745
|
);
|
|
@@ -1443,10 +1749,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1443
1749
|
)((unvalidated) =>
|
|
1444
1750
|
Effect.gen(function* () {
|
|
1445
1751
|
const request = yield* validate(MarkUnknownRequest, "markUnknown", unvalidated);
|
|
1752
|
+
|
|
1446
1753
|
const decision = yield* Ref.modify(
|
|
1447
1754
|
state,
|
|
1448
1755
|
(current): readonly [Decision<void, SettlementConflict | LedgerError>, LedgerState] => {
|
|
1449
1756
|
const stored = current.submissions.get(request.submissionId);
|
|
1757
|
+
|
|
1450
1758
|
if (stored === undefined) {
|
|
1451
1759
|
return [
|
|
1452
1760
|
failure(ledgerError("markUnknown", `Unknown Submission ${request.submissionId}`)),
|
|
@@ -1465,6 +1773,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1465
1773
|
current,
|
|
1466
1774
|
];
|
|
1467
1775
|
}
|
|
1776
|
+
|
|
1468
1777
|
return [
|
|
1469
1778
|
failure(
|
|
1470
1779
|
SettlementConflict.make({
|
|
@@ -1492,10 +1801,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1492
1801
|
// set while the first recorded reason is kept.
|
|
1493
1802
|
const existing = stored.unknownMark;
|
|
1494
1803
|
const known = new Set(existing?.toolCallIds ?? []);
|
|
1804
|
+
|
|
1495
1805
|
const merged = [
|
|
1496
1806
|
...(existing?.toolCallIds ?? []),
|
|
1497
1807
|
...request.toolCallIds.filter((toolCallId) => !known.has(toolCallId)),
|
|
1498
1808
|
];
|
|
1809
|
+
|
|
1499
1810
|
return [
|
|
1500
1811
|
success(undefined),
|
|
1501
1812
|
withSubmission(current, {
|
|
@@ -1507,6 +1818,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1507
1818
|
];
|
|
1508
1819
|
},
|
|
1509
1820
|
);
|
|
1821
|
+
|
|
1510
1822
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1511
1823
|
}),
|
|
1512
1824
|
);
|
|
@@ -1519,15 +1831,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1519
1831
|
"recordUnknownResolution",
|
|
1520
1832
|
unvalidated,
|
|
1521
1833
|
);
|
|
1834
|
+
|
|
1522
1835
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1523
|
-
|
|
1524
|
-
command.resolution,
|
|
1525
|
-
).pipe(
|
|
1526
|
-
Effect.mapError((error) =>
|
|
1527
|
-
ledgerError("recordUnknownResolution", "Invalid unknown-outcome resolution", error),
|
|
1528
|
-
),
|
|
1529
|
-
);
|
|
1530
|
-
const resolutionJson = JSON.stringify(encodedResolution);
|
|
1836
|
+
|
|
1531
1837
|
const decision = yield* Ref.modify(
|
|
1532
1838
|
state,
|
|
1533
1839
|
(
|
|
@@ -1540,6 +1846,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1540
1846
|
LedgerState,
|
|
1541
1847
|
] => {
|
|
1542
1848
|
const stored = current.submissions.get(command.submissionId);
|
|
1849
|
+
|
|
1543
1850
|
if (stored === undefined) {
|
|
1544
1851
|
return [
|
|
1545
1852
|
failure(
|
|
@@ -1563,6 +1870,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1563
1870
|
current,
|
|
1564
1871
|
];
|
|
1565
1872
|
}
|
|
1873
|
+
|
|
1566
1874
|
return [
|
|
1567
1875
|
failure(
|
|
1568
1876
|
SettlementConflict.make({
|
|
@@ -1574,7 +1882,11 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1574
1882
|
];
|
|
1575
1883
|
}
|
|
1576
1884
|
const existing = stored.unknownResolutions.get(command.toolCallId);
|
|
1577
|
-
|
|
1885
|
+
|
|
1886
|
+
if (
|
|
1887
|
+
existing !== undefined &&
|
|
1888
|
+
!equivalentUnknownResolution(existing.intent.resolution, command.resolution)
|
|
1889
|
+
) {
|
|
1578
1890
|
return [
|
|
1579
1891
|
failure(
|
|
1580
1892
|
UnknownResolutionConflict.make({
|
|
@@ -1585,6 +1897,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1585
1897
|
current,
|
|
1586
1898
|
];
|
|
1587
1899
|
}
|
|
1900
|
+
|
|
1588
1901
|
const intent =
|
|
1589
1902
|
existing?.intent ??
|
|
1590
1903
|
UnknownResolutionIntent.make({
|
|
@@ -1595,13 +1908,14 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1595
1908
|
resolution: command.resolution,
|
|
1596
1909
|
resolvedAt: utc(nowMillis),
|
|
1597
1910
|
});
|
|
1911
|
+
|
|
1598
1912
|
const unknownResolutions =
|
|
1599
1913
|
existing !== undefined
|
|
1600
1914
|
? stored.unknownResolutions
|
|
1601
1915
|
: new Map(stored.unknownResolutions).set(command.toolCallId, {
|
|
1602
1916
|
intent,
|
|
1603
|
-
resolutionJson,
|
|
1604
1917
|
});
|
|
1918
|
+
|
|
1605
1919
|
// The lane reopens only when EVERY marked open call has a durable resolution
|
|
1606
1920
|
// intent: unknown → input-applied (DUR-017). Replays re-run the coverage check so
|
|
1607
1921
|
// a recovering caller can wake the lane idempotently.
|
|
@@ -1611,6 +1925,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1611
1925
|
stored.unknownMark.toolCallIds.every((toolCallId) =>
|
|
1612
1926
|
unknownResolutions.has(toolCallId),
|
|
1613
1927
|
);
|
|
1928
|
+
|
|
1614
1929
|
return [
|
|
1615
1930
|
success(intent),
|
|
1616
1931
|
withSubmission(current, {
|
|
@@ -1622,7 +1937,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1622
1937
|
];
|
|
1623
1938
|
},
|
|
1624
1939
|
);
|
|
1940
|
+
|
|
1625
1941
|
if (decision._tag === "failure") return yield* decision.error;
|
|
1942
|
+
|
|
1626
1943
|
return decision.value;
|
|
1627
1944
|
}),
|
|
1628
1945
|
);
|
|
@@ -1636,10 +1953,12 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1636
1953
|
"recordChildSettled",
|
|
1637
1954
|
unvalidated,
|
|
1638
1955
|
);
|
|
1956
|
+
|
|
1639
1957
|
const decision = yield* Ref.modify(
|
|
1640
1958
|
state,
|
|
1641
1959
|
(current): readonly [Decision<ChildSettledOutcome, LedgerError>, LedgerState] => {
|
|
1642
1960
|
const parent = current.submissions.get(request.parentSubmissionId);
|
|
1961
|
+
|
|
1643
1962
|
if (parent === undefined) {
|
|
1644
1963
|
return [
|
|
1645
1964
|
failure(
|
|
@@ -1651,10 +1970,18 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1651
1970
|
current,
|
|
1652
1971
|
];
|
|
1653
1972
|
}
|
|
1654
|
-
// The
|
|
1655
|
-
//
|
|
1973
|
+
// The caller may notify after the canonical Settlement append but before ledger
|
|
1974
|
+
// finalization. In a single store, an exact reservation plus `terminalizing` is the
|
|
1975
|
+
// narrow durable prefix that makes that ordering admissible; earlier states remain
|
|
1976
|
+
// a caller error.
|
|
1656
1977
|
const child = current.submissions.get(request.childSubmissionId);
|
|
1657
|
-
|
|
1978
|
+
|
|
1979
|
+
const announced =
|
|
1980
|
+
child !== undefined &&
|
|
1981
|
+
(child.row.state === "settled" ||
|
|
1982
|
+
(child.row.state === "terminalizing" && child.reservation !== undefined));
|
|
1983
|
+
|
|
1984
|
+
if (!announced) {
|
|
1658
1985
|
return [
|
|
1659
1986
|
failure(
|
|
1660
1987
|
ledgerError(
|
|
@@ -1673,16 +2000,24 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1673
2000
|
return [success("not-waiting" as const), current];
|
|
1674
2001
|
}
|
|
1675
2002
|
const children = parent.suspension.reason.children;
|
|
2003
|
+
|
|
1676
2004
|
if (!children.some((entry) => entry.childSubmissionId === request.childSubmissionId)) {
|
|
1677
2005
|
return [success("not-waiting" as const), current];
|
|
1678
2006
|
}
|
|
1679
|
-
|
|
1680
|
-
//
|
|
1681
|
-
// idempotently.
|
|
1682
|
-
const allSettled = children.every(
|
|
1683
|
-
|
|
1684
|
-
|
|
2007
|
+
|
|
2008
|
+
// Every listed child must be either finalized or canonically announced from the
|
|
2009
|
+
// exact terminalizing reservation. Replays re-run this coverage check idempotently.
|
|
2010
|
+
const allSettled = children.every((entry) => {
|
|
2011
|
+
const listed = current.submissions.get(entry.childSubmissionId);
|
|
2012
|
+
|
|
2013
|
+
return (
|
|
2014
|
+
listed?.row.state === "settled" ||
|
|
2015
|
+
(listed?.row.state === "terminalizing" && listed.reservation !== undefined)
|
|
2016
|
+
);
|
|
2017
|
+
});
|
|
2018
|
+
|
|
1685
2019
|
if (!allSettled) return [success("still-waiting" as const), current];
|
|
2020
|
+
|
|
1686
2021
|
return [
|
|
1687
2022
|
success("woken" as const),
|
|
1688
2023
|
withSubmission(current, {
|
|
@@ -1693,7 +2028,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1693
2028
|
];
|
|
1694
2029
|
},
|
|
1695
2030
|
);
|
|
2031
|
+
|
|
1696
2032
|
if (decision._tag === "failure") return yield* decision.error;
|
|
2033
|
+
|
|
1697
2034
|
return decision.value;
|
|
1698
2035
|
}),
|
|
1699
2036
|
);
|
|
@@ -1707,8 +2044,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1707
2044
|
"reserveChildBudget",
|
|
1708
2045
|
unvalidated,
|
|
1709
2046
|
);
|
|
2047
|
+
|
|
1710
2048
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1711
|
-
|
|
2049
|
+
|
|
1712
2050
|
const decision = yield* Ref.modify(
|
|
1713
2051
|
state,
|
|
1714
2052
|
(
|
|
@@ -1718,6 +2056,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1718
2056
|
LedgerState,
|
|
1719
2057
|
] => {
|
|
1720
2058
|
const existing = current.childReservations.get(request.reservationId);
|
|
2059
|
+
|
|
1721
2060
|
if (existing !== undefined) {
|
|
1722
2061
|
// Identical replays short-circuit before the fence, mirroring reserveSettlement:
|
|
1723
2062
|
// a replay creates nothing, so a recovering caller resumes rather than duplicates.
|
|
@@ -1725,7 +2064,8 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1725
2064
|
existing.parentSubmissionId === request.parentSubmissionId &&
|
|
1726
2065
|
existing.parentToolCallId === request.parentToolCallId &&
|
|
1727
2066
|
existing.allocationDigest === request.allocationDigest &&
|
|
1728
|
-
existing.
|
|
2067
|
+
equivalentPersistedJson(existing.allocation, request.allocation);
|
|
2068
|
+
|
|
1729
2069
|
if (!identical) {
|
|
1730
2070
|
return [
|
|
1731
2071
|
failure(
|
|
@@ -1739,6 +2079,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1739
2079
|
current,
|
|
1740
2080
|
];
|
|
1741
2081
|
}
|
|
2082
|
+
|
|
1742
2083
|
return [
|
|
1743
2084
|
success(
|
|
1744
2085
|
ReservedChildBudget.make({
|
|
@@ -1767,6 +2108,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1767
2108
|
}
|
|
1768
2109
|
}
|
|
1769
2110
|
const parent = current.submissions.get(request.parentSubmissionId);
|
|
2111
|
+
|
|
1770
2112
|
if (parent === undefined) {
|
|
1771
2113
|
return [
|
|
1772
2114
|
failure(
|
|
@@ -1783,6 +2125,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1783
2125
|
if (!ownsLane(parent, request.ownershipToken)) {
|
|
1784
2126
|
return [failure(ownershipLost(current, parent)), current];
|
|
1785
2127
|
}
|
|
2128
|
+
|
|
1786
2129
|
const reservation: StoredChildReservation = {
|
|
1787
2130
|
reservationId: request.reservationId,
|
|
1788
2131
|
parentSubmissionId: request.parentSubmissionId,
|
|
@@ -1790,14 +2133,13 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1790
2133
|
childSubmissionId: undefined,
|
|
1791
2134
|
status: "reserved",
|
|
1792
2135
|
allocation: request.allocation,
|
|
1793
|
-
allocationJson,
|
|
1794
2136
|
allocationDigest: request.allocationDigest,
|
|
1795
2137
|
accounting: undefined,
|
|
1796
|
-
accountingJson: undefined,
|
|
1797
2138
|
reservedAtMillis: nowMillis,
|
|
1798
2139
|
releaseBeganAtMillis: undefined,
|
|
1799
2140
|
releasedAtMillis: undefined,
|
|
1800
2141
|
};
|
|
2142
|
+
|
|
1801
2143
|
return [
|
|
1802
2144
|
success(
|
|
1803
2145
|
ReservedChildBudget.make({
|
|
@@ -1809,7 +2151,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1809
2151
|
];
|
|
1810
2152
|
},
|
|
1811
2153
|
);
|
|
2154
|
+
|
|
1812
2155
|
if (decision._tag === "failure") return yield* decision.error;
|
|
2156
|
+
|
|
1813
2157
|
return decision.value;
|
|
1814
2158
|
}),
|
|
1815
2159
|
);
|
|
@@ -1822,6 +2166,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1822
2166
|
"attachChildToReservation",
|
|
1823
2167
|
unvalidated,
|
|
1824
2168
|
);
|
|
2169
|
+
|
|
1825
2170
|
const decision = yield* Ref.modify(
|
|
1826
2171
|
state,
|
|
1827
2172
|
(
|
|
@@ -1834,6 +2179,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1834
2179
|
LedgerState,
|
|
1835
2180
|
] => {
|
|
1836
2181
|
const reservation = current.childReservations.get(request.reservationId);
|
|
2182
|
+
|
|
1837
2183
|
if (reservation === undefined) {
|
|
1838
2184
|
return [
|
|
1839
2185
|
failure(
|
|
@@ -1850,6 +2196,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1850
2196
|
if (reservation.childSubmissionId === request.childSubmissionId) {
|
|
1851
2197
|
return [success(toReservationSnapshot(reservation)), current];
|
|
1852
2198
|
}
|
|
2199
|
+
|
|
1853
2200
|
return [
|
|
1854
2201
|
failure(
|
|
1855
2202
|
ChildReservationConflict.make({
|
|
@@ -1862,6 +2209,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1862
2209
|
];
|
|
1863
2210
|
}
|
|
1864
2211
|
const parent = current.submissions.get(reservation.parentSubmissionId);
|
|
2212
|
+
|
|
1865
2213
|
if (parent === undefined) {
|
|
1866
2214
|
return [
|
|
1867
2215
|
failure(
|
|
@@ -1901,17 +2249,21 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1901
2249
|
current,
|
|
1902
2250
|
];
|
|
1903
2251
|
}
|
|
2252
|
+
|
|
1904
2253
|
const attached: StoredChildReservation = {
|
|
1905
2254
|
...reservation,
|
|
1906
2255
|
childSubmissionId: request.childSubmissionId,
|
|
1907
2256
|
};
|
|
2257
|
+
|
|
1908
2258
|
return [
|
|
1909
2259
|
success(toReservationSnapshot(attached)),
|
|
1910
2260
|
withChildReservation(current, attached),
|
|
1911
2261
|
];
|
|
1912
2262
|
},
|
|
1913
2263
|
);
|
|
2264
|
+
|
|
1914
2265
|
if (decision._tag === "failure") return yield* decision.error;
|
|
2266
|
+
|
|
1915
2267
|
return decision.value;
|
|
1916
2268
|
}),
|
|
1917
2269
|
);
|
|
@@ -1924,8 +2276,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1924
2276
|
"beginChildBudgetRelease",
|
|
1925
2277
|
unvalidated,
|
|
1926
2278
|
);
|
|
2279
|
+
|
|
1927
2280
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
1928
|
-
|
|
2281
|
+
|
|
1929
2282
|
const decision = yield* Ref.modify(
|
|
1930
2283
|
state,
|
|
1931
2284
|
(
|
|
@@ -1935,6 +2288,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1935
2288
|
LedgerState,
|
|
1936
2289
|
] => {
|
|
1937
2290
|
const reservation = current.childReservations.get(request.reservationId);
|
|
2291
|
+
|
|
1938
2292
|
if (reservation === undefined) {
|
|
1939
2293
|
return [
|
|
1940
2294
|
failure(
|
|
@@ -1949,9 +2303,13 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1949
2303
|
if (reservation.status !== "reserved") {
|
|
1950
2304
|
// The accounting decision was already frozen exactly once; an identical replay is
|
|
1951
2305
|
// a no-op and a divergent decision conflicts (spec §12 join step 6).
|
|
1952
|
-
if (
|
|
2306
|
+
if (
|
|
2307
|
+
reservation.accounting !== undefined &&
|
|
2308
|
+
equivalentPersistedJson(reservation.accounting, request.accounting)
|
|
2309
|
+
) {
|
|
1953
2310
|
return [success(toReservationSnapshot(reservation)), current];
|
|
1954
2311
|
}
|
|
2312
|
+
|
|
1955
2313
|
return [
|
|
1956
2314
|
failure(
|
|
1957
2315
|
ChildReservationConflict.make({
|
|
@@ -1964,20 +2322,23 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1964
2322
|
current,
|
|
1965
2323
|
];
|
|
1966
2324
|
}
|
|
2325
|
+
|
|
1967
2326
|
const frozen: StoredChildReservation = {
|
|
1968
2327
|
...reservation,
|
|
1969
2328
|
status: "releasePending",
|
|
1970
2329
|
accounting: request.accounting,
|
|
1971
|
-
accountingJson,
|
|
1972
2330
|
releaseBeganAtMillis: nowMillis,
|
|
1973
2331
|
};
|
|
2332
|
+
|
|
1974
2333
|
return [
|
|
1975
2334
|
success(toReservationSnapshot(frozen)),
|
|
1976
2335
|
withChildReservation(current, frozen),
|
|
1977
2336
|
];
|
|
1978
2337
|
},
|
|
1979
2338
|
);
|
|
2339
|
+
|
|
1980
2340
|
if (decision._tag === "failure") return yield* decision.error;
|
|
2341
|
+
|
|
1981
2342
|
return decision.value;
|
|
1982
2343
|
}),
|
|
1983
2344
|
);
|
|
@@ -1991,7 +2352,9 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
1991
2352
|
"releaseChildBudget",
|
|
1992
2353
|
unvalidated,
|
|
1993
2354
|
);
|
|
2355
|
+
|
|
1994
2356
|
const nowMillis = yield* Clock.currentTimeMillis;
|
|
2357
|
+
|
|
1995
2358
|
const decision = yield* Ref.modify(
|
|
1996
2359
|
state,
|
|
1997
2360
|
(
|
|
@@ -2001,6 +2364,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2001
2364
|
LedgerState,
|
|
2002
2365
|
] => {
|
|
2003
2366
|
const reservation = current.childReservations.get(request.reservationId);
|
|
2367
|
+
|
|
2004
2368
|
if (reservation === undefined) {
|
|
2005
2369
|
return [
|
|
2006
2370
|
failure(
|
|
@@ -2030,18 +2394,22 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2030
2394
|
current,
|
|
2031
2395
|
];
|
|
2032
2396
|
}
|
|
2397
|
+
|
|
2033
2398
|
const released: StoredChildReservation = {
|
|
2034
2399
|
...reservation,
|
|
2035
2400
|
status: "released",
|
|
2036
2401
|
releasedAtMillis: nowMillis,
|
|
2037
2402
|
};
|
|
2403
|
+
|
|
2038
2404
|
return [
|
|
2039
2405
|
success(toReservationSnapshot(released)),
|
|
2040
2406
|
withChildReservation(current, released),
|
|
2041
2407
|
];
|
|
2042
2408
|
},
|
|
2043
2409
|
);
|
|
2410
|
+
|
|
2044
2411
|
if (decision._tag === "failure") return yield* decision.error;
|
|
2412
|
+
|
|
2045
2413
|
return decision.value;
|
|
2046
2414
|
}),
|
|
2047
2415
|
);
|
|
@@ -2052,18 +2420,32 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2052
2420
|
const snapshots = [...current.submissions.values()]
|
|
2053
2421
|
.filter((stored) => stored.row.state !== "settled")
|
|
2054
2422
|
.sort((left, right) =>
|
|
2055
|
-
left.row.
|
|
2423
|
+
left.row.threadId < right.row.threadId
|
|
2056
2424
|
? -1
|
|
2057
|
-
: left.row.
|
|
2425
|
+
: left.row.threadId > right.row.threadId
|
|
2058
2426
|
? 1
|
|
2059
2427
|
: left.row.queueSequence - right.row.queueSequence,
|
|
2060
2428
|
)
|
|
2061
2429
|
.map((stored) => toSnapshot(stored.row));
|
|
2430
|
+
|
|
2062
2431
|
return Stream.fromIterable(snapshots);
|
|
2063
2432
|
}),
|
|
2064
2433
|
),
|
|
2065
2434
|
);
|
|
2066
2435
|
|
|
2436
|
+
const readAbortIntent: SubmissionLedger["Service"]["readAbortIntent"] = Effect.fn(
|
|
2437
|
+
"MemorySubmissionLedger.readAbortIntent",
|
|
2438
|
+
)(function* (unvalidated) {
|
|
2439
|
+
const request = yield* validate(AbortIntentRequest, "readAbortIntent", unvalidated);
|
|
2440
|
+
const stored = (yield* Ref.get(state)).submissions.get(request.submissionId);
|
|
2441
|
+
|
|
2442
|
+
if (stored === undefined) {
|
|
2443
|
+
return yield* ledgerError("readAbortIntent", `Unknown Submission ${request.submissionId}`);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
return stored.abortIntent;
|
|
2447
|
+
});
|
|
2448
|
+
|
|
2067
2449
|
const loadRecoverySnapshot: SubmissionLedger["Service"]["loadRecoverySnapshot"] = Effect.fn(
|
|
2068
2450
|
"MemorySubmissionLedger.loadRecoverySnapshot",
|
|
2069
2451
|
)((unvalidated) =>
|
|
@@ -2073,14 +2455,17 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2073
2455
|
"loadRecoverySnapshot",
|
|
2074
2456
|
unvalidated,
|
|
2075
2457
|
);
|
|
2458
|
+
|
|
2076
2459
|
const current = yield* Ref.get(state);
|
|
2077
2460
|
const stored = current.submissions.get(request.submissionId);
|
|
2461
|
+
|
|
2078
2462
|
if (stored === undefined) {
|
|
2079
2463
|
return yield* ledgerError(
|
|
2080
2464
|
"loadRecoverySnapshot",
|
|
2081
2465
|
`Unknown Submission ${request.submissionId}`,
|
|
2082
2466
|
);
|
|
2083
2467
|
}
|
|
2468
|
+
|
|
2084
2469
|
const joins = [...current.submissions.values()]
|
|
2085
2470
|
.filter((candidate) => candidate.joinedHostSubmissionId === request.submissionId)
|
|
2086
2471
|
.sort((left, right) => left.row.queueSequence - right.row.queueSequence)
|
|
@@ -2091,11 +2476,13 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2091
2476
|
hostSubmissionId: request.submissionId,
|
|
2092
2477
|
}),
|
|
2093
2478
|
);
|
|
2479
|
+
|
|
2094
2480
|
const byToolCallId = <A extends { readonly toolCallId: ToolCallId }>(
|
|
2095
2481
|
left: A,
|
|
2096
2482
|
right: A,
|
|
2097
2483
|
): number =>
|
|
2098
2484
|
left.toolCallId < right.toolCallId ? -1 : left.toolCallId > right.toolCallId ? 1 : 0;
|
|
2485
|
+
|
|
2099
2486
|
// Parent-side subagent view: this Submission's child budget reservations in parent Tool
|
|
2100
2487
|
// Call order, plus each attached child's current lane state (a disposable derived view;
|
|
2101
2488
|
// the canonical records stay the recovery truth, DUR-015).
|
|
@@ -2108,10 +2495,13 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2108
2495
|
? 1
|
|
2109
2496
|
: 0,
|
|
2110
2497
|
);
|
|
2498
|
+
|
|
2111
2499
|
const childAttachments: Array<ChildAttachmentSnapshot> = [];
|
|
2500
|
+
|
|
2112
2501
|
for (const reservation of childReservations) {
|
|
2113
2502
|
if (reservation.childSubmissionId === undefined) continue;
|
|
2114
2503
|
const child = current.submissions.get(reservation.childSubmissionId);
|
|
2504
|
+
|
|
2115
2505
|
if (child === undefined) continue;
|
|
2116
2506
|
childAttachments.push(
|
|
2117
2507
|
ChildAttachmentSnapshot.make({
|
|
@@ -2124,6 +2514,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2124
2514
|
}),
|
|
2125
2515
|
);
|
|
2126
2516
|
}
|
|
2517
|
+
|
|
2127
2518
|
return RecoverySnapshot.make({
|
|
2128
2519
|
submission: toSnapshot(stored.row),
|
|
2129
2520
|
joins,
|
|
@@ -2201,6 +2592,7 @@ const makeSubmissionLedger = (options: MemorySubmissionLedgerOptions = {}) =>
|
|
|
2201
2592
|
releaseChildBudget,
|
|
2202
2593
|
scanNonterminal,
|
|
2203
2594
|
loadRecoverySnapshot,
|
|
2595
|
+
readAbortIntent,
|
|
2204
2596
|
});
|
|
2205
2597
|
});
|
|
2206
2598
|
|