@effect-agent/storage-memory 0.1.0-beta.13 → 0.1.0-beta.130

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.
Files changed (34) hide show
  1. package/dist/MemoryMessageDeliveryStore.d.mts +15 -0
  2. package/dist/MemoryMessageDeliveryStore.mjs +190 -0
  3. package/dist/MemoryMessageDeliveryStore.mjs.map +1 -0
  4. package/dist/MemoryScheduleStore.d.mts +10 -0
  5. package/dist/MemoryScheduleStore.mjs +169 -0
  6. package/dist/MemoryScheduleStore.mjs.map +1 -0
  7. package/dist/MemorySemanticIndex.d.mts +21 -0
  8. package/dist/MemorySemanticIndex.mjs +222 -0
  9. package/dist/MemorySemanticIndex.mjs.map +1 -0
  10. package/dist/MemorySubmissionLedger.d.mts +24 -0
  11. package/dist/MemorySubmissionLedger.mjs +1154 -0
  12. package/dist/MemorySubmissionLedger.mjs.map +1 -0
  13. package/dist/MemorySubscriptionStore.d.mts +9 -0
  14. package/dist/MemorySubscriptionStore.mjs +849 -0
  15. package/dist/MemorySubscriptionStore.mjs.map +1 -0
  16. package/dist/MemoryThreadStore.d.mts +17 -0
  17. package/dist/MemoryThreadStore.mjs +462 -0
  18. package/dist/MemoryThreadStore.mjs.map +1 -0
  19. package/dist/index.d.mts +7 -30
  20. package/dist/index.mjs +7 -1298
  21. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  22. package/package.json +1 -45
  23. package/src/MemoryMessageDeliveryStore.ts +373 -0
  24. package/src/MemoryScheduleStore.ts +328 -0
  25. package/src/MemorySemanticIndex.ts +357 -0
  26. package/src/{memory-ledger.ts → MemorySubmissionLedger.ts} +695 -114
  27. package/src/MemorySubscriptionStore.ts +1549 -0
  28. package/src/MemoryThreadStore.ts +927 -0
  29. package/src/index.ts +6 -2
  30. package/dist/index.mjs.map +0 -1
  31. package/dist/testing.d.mts +0 -2
  32. package/dist/testing.mjs +0 -2
  33. package/src/memory-storage.ts +0 -614
  34. package/src/testing.ts +0 -10
@@ -0,0 +1,1154 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { Cause, Clock, DateTime, Duration, Effect, Exit, Fiber, Layer, Option, Ref, Schema, Stream } from "effect";
3
+ import { AttemptId, ReceiptId, SubmissionId } from "effect-agent/identifiers";
4
+ import { InputMessage } from "effect-agent/messaging";
5
+ import { PersistedJson, ProducerEpoch, WorkerAdmission } from "effect-agent/records";
6
+ import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WorkerLedgerState, WorkerStopCommand, settlementFailureFromRecord, workerTerminalFromRecord } from "effect-agent/submission-ledger";
7
+ //#region src/MemorySubmissionLedger.ts
8
+ var MemorySubmissionLedger_exports = /* @__PURE__ */ __exportAll({
9
+ MemorySubmissionLedgerLive: () => MemorySubmissionLedgerLive,
10
+ memorySubmissionLedgerLayer: () => memorySubmissionLedgerLayer
11
+ });
12
+ const MAX_SUBMISSIONS = 65536;
13
+ /**
14
+ * Lifecycle ordering used to advance-but-never-regress the operational state marker: a reclaimed
15
+ * Attempt must not erase progress markers (input-applied, terminalizing) that an earlier Attempt
16
+ * already committed.
17
+ */
18
+ const STATE_RANK = {
19
+ admitted: 0,
20
+ ready: 1,
21
+ joining: 2,
22
+ joined: 3,
23
+ running: 4,
24
+ "input-applied": 5,
25
+ suspended: 6,
26
+ unknown: 7,
27
+ terminalizing: 8,
28
+ settled: 9
29
+ };
30
+ /**
31
+ * States in which `claim` never grants the head: the lane is host-owned (`joining`/`joined`)
32
+ * or durably suspended rather than worker-claimable. Unknown heads are checked against abort
33
+ * intent separately: abort authorizes cleanup and settlement, never ordinary Tool replay.
34
+ */
35
+ const BLOCKED_HEAD_STATES = /* @__PURE__ */ new Set([
36
+ "joining",
37
+ "joined",
38
+ "suspended"
39
+ ]);
40
+ const failure = (error) => ({
41
+ _tag: "failure",
42
+ error
43
+ });
44
+ const success = (value) => ({
45
+ _tag: "success",
46
+ value
47
+ });
48
+ const ledgerError = (operation, message, cause) => cause === void 0 ? LedgerError.make({
49
+ operation,
50
+ message
51
+ }) : LedgerError.make({
52
+ operation,
53
+ message,
54
+ cause
55
+ });
56
+ const validate = Effect.fn("MemorySubmissionLedger.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => ledgerError(operation, `Invalid ${operation} request`, error))));
57
+ const decodeSubmissionId = Schema.decodeSync(SubmissionId);
58
+ const decodeReceiptId = Schema.decodeSync(ReceiptId);
59
+ const decodeAttemptId = Schema.decodeSync(AttemptId);
60
+ const decodeOwnershipToken = Schema.decodeSync(OwnershipToken);
61
+ const decodeQueueSequence = Schema.decodeSync(QueueSequence);
62
+ const decodeProducerEpoch = Schema.decodeSync(ProducerEpoch);
63
+ const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
64
+ const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
65
+ const utc = (millis) => DateTime.toUtc(DateTime.makeUnsafe(millis));
66
+ const admissionKey = (threadId, principal, idempotencyKey) => JSON.stringify([
67
+ threadId,
68
+ principal,
69
+ idempotencyKey
70
+ ]);
71
+ const toSnapshot = (row) => SubmissionSnapshot.make({
72
+ submissionId: row.submissionId,
73
+ threadId: row.threadId,
74
+ queueSequence: row.queueSequence,
75
+ principal: row.principal,
76
+ idempotencyKey: row.idempotencyKey,
77
+ agentId: row.agentId,
78
+ agentDigests: row.agentDigests,
79
+ deploymentId: row.deploymentId,
80
+ inputPayload: row.inputPayload,
81
+ inputDigest: row.inputDigest,
82
+ receiptId: row.receiptId,
83
+ state: row.state,
84
+ createdAt: utc(row.createdAtMillis),
85
+ ...row.admissionGroup === void 0 ? {} : { admissionGroup: row.admissionGroup },
86
+ ...row.admissionFence === void 0 ? {} : { admissionFence: row.admissionFence },
87
+ ...row.workerAdmissionJson === void 0 ? {} : { workerAdmission: Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(row.workerAdmissionJson) },
88
+ ...row.messageAdmissionJson === void 0 ? {} : { messageAdmission: Schema.decodeSync(Schema.fromJsonString(InputMessage))(row.messageAdmissionJson) },
89
+ ...row.settledOutcome === void 0 ? {} : { settledOutcome: row.settledOutcome },
90
+ ...row.readyAtMillis === void 0 ? {} : { readyAt: utc(row.readyAtMillis) },
91
+ ...row.parentLinkage === void 0 ? {} : { parentLinkage: row.parentLinkage }
92
+ });
93
+ const toReservationSnapshot = (row) => ChildBudgetReservationSnapshot.make({
94
+ reservationId: row.reservationId,
95
+ parentSubmissionId: row.parentSubmissionId,
96
+ parentToolCallId: row.parentToolCallId,
97
+ status: row.status,
98
+ allocation: row.allocation,
99
+ allocationDigest: row.allocationDigest,
100
+ reservedAt: utc(row.reservedAtMillis),
101
+ ...row.childSubmissionId === void 0 ? {} : { childSubmissionId: row.childSubmissionId },
102
+ ...row.accounting === void 0 ? {} : { accounting: row.accounting },
103
+ ...row.releaseBeganAtMillis === void 0 ? {} : { releaseBeganAt: utc(row.releaseBeganAtMillis) },
104
+ ...row.releasedAtMillis === void 0 ? {} : { releasedAt: utc(row.releasedAtMillis) }
105
+ });
106
+ /** Linkage equality: both absent, or both present naming the same parent Tool Call. */
107
+ const sameParentLinkage = (left, right) => left === void 0 ? right === void 0 : right !== void 0 && left.parentSubmissionId === right.parentSubmissionId && left.parentToolCallId === right.parentToolCallId;
108
+ const laneEpoch = (state, threadId) => state.lanes.get(threadId)?.producerEpoch ?? 0;
109
+ const ownershipLost = (state, stored) => OwnershipLost.make({
110
+ submissionId: stored.row.submissionId,
111
+ actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.threadId))
112
+ });
113
+ /** A retained row token cannot outlive a newer owner of the same Thread. */
114
+ const ownsLane = (state, stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken && stored.ownership.producerEpoch === laneEpoch(state, stored.row.threadId);
115
+ const withSubmission = (state, stored) => {
116
+ const active = new Set(state.activeByThread.get(stored.row.threadId));
117
+ if (stored.row.state === "settled") active.delete(stored.row.submissionId);
118
+ else active.add(stored.row.submissionId);
119
+ return {
120
+ ...state,
121
+ submissions: new Map(state.submissions).set(stored.row.submissionId, stored),
122
+ activeByThread: new Map(state.activeByThread).set(stored.row.threadId, active)
123
+ };
124
+ };
125
+ const withChildReservation = (state, reservation) => ({
126
+ ...state,
127
+ childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
128
+ });
129
+ const findHead = (state, threadId) => {
130
+ let head;
131
+ for (const stored of state.submissions.values()) {
132
+ if (stored.row.threadId !== threadId || stored.row.state === "settled" || stored.row.state === "unknown" && stored.abortIntent === void 0) continue;
133
+ if (head === void 0 || stored.row.queueSequence < head.row.queueSequence) head = stored;
134
+ }
135
+ return head;
136
+ };
137
+ /**
138
+ * Reference in-memory SubmissionLedger. It implements the full port contract — atomic idempotent
139
+ * admission, eligible FIFO claims, producer-epoch fencing, Clock-driven ownership leases, idempotent
140
+ * settlement reservation/finalization, and durable abort intent — with every transition applied
141
+ * as one atomic `Ref.modify`, but its state does not survive the process (`non-durable`).
142
+ *
143
+ * Adapter-specific semantics within the port's latitude:
144
+ *
145
+ * - Time comes exclusively from the Effect `Clock` service, so `TestClock` drives lease expiry
146
+ * deterministically; no wall clock is consulted.
147
+ * - The ownership lease is pinned to `DEFAULT_OWNERSHIP_LEASE_DURATION` (D5); durable adapters
148
+ * own the configuration seam.
149
+ * - Any live lease in the Thread blocks every new claim, including the same `producerId`.
150
+ * Unresolved unknown work is skipped only after ownership is released or expires.
151
+ * - Claiming advances `ready` to `running` and otherwise preserves the recorded state, so
152
+ * progress markers from an earlier Attempt survive a reclaim.
153
+ * - `renewOwnership` keeps the token stable (the port allows rotation); a replayed admission
154
+ * reports the Submission's current state alongside the original identities.
155
+ * - `claimJoining` walks the strictly-later queue: rows already `joining`/`joined` to the
156
+ * SAME host extend the claimed prefix and are skipped, and an aborted-settled row is a
157
+ * closed obligation that is also skipped (P7 §7(c)); any other non-`ready` row (an
158
+ * `admitted` gap, a non-aborted settled row, foreign-host linkage) breaks the prefix
159
+ * conservatively.
160
+ * - `markJoined` verifies the token against the HOST's live ownership (the lane is
161
+ * host-owned), so a later host Attempt can repair a lost marker from history (DUR-016). The
162
+ * join marker reuses the input-applied marker: the joined input IS `input:{sid}`.
163
+ * - `suspend` and `markUnknown` refuse when an exact settlement is already reserved
164
+ * (`SettlementConflict` with the reserved outcome) — DUR-011's reservation wins.
165
+ * - `resolveAdmission` derives its answer from the single strongly consistent store, so it
166
+ * never answers `Indeterminate` on its own; the test-only `resolveAdmissionFault` option
167
+ * injects the `Indeterminate` classification so SUB-031 callers can be conformance-tested.
168
+ * - `recordChildSettled` and `suspend(WaitingForChild)` observe child settlement directly from
169
+ * the child rows (single-store latitude); no separate notification marker is stored.
170
+ */
171
+ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
172
+ const state = yield* Ref.make({
173
+ submissions: /* @__PURE__ */ new Map(),
174
+ admissionIndex: /* @__PURE__ */ new Map(),
175
+ lanes: /* @__PURE__ */ new Map(),
176
+ childReservations: /* @__PURE__ */ new Map(),
177
+ mintCounter: 0,
178
+ stoppedWorkers: /* @__PURE__ */ new Map(),
179
+ latestByThread: /* @__PURE__ */ new Map(),
180
+ activeByThread: /* @__PURE__ */ new Map()
181
+ });
182
+ const admissionFence = yield* SubmissionAdmissionFence;
183
+ const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
184
+ const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "non-durable" }));
185
+ const admit = Effect.fn("MemorySubmissionLedger.admit")((unvalidated) => Effect.gen(function* () {
186
+ const request = yield* validate(AdmissionRequest, "admit", unvalidated);
187
+ const workerAdmissionJson = request.workerAdmission === void 0 ? void 0 : yield* Schema.encodeEffect(Schema.fromJsonString(WorkerAdmission))(request.workerAdmission).pipe(Effect.mapError(() => ledgerError("admit", "Invalid worker admission metadata")));
188
+ const messageAdmissionJson = request.messageAdmission === void 0 ? void 0 : yield* Schema.encodeEffect(Schema.fromJsonString(InputMessage))(request.messageAdmission).pipe(Effect.mapError(() => ledgerError("admit", "Invalid message admission metadata")));
189
+ const nowMillis = yield* Clock.currentTimeMillis;
190
+ const services = yield* Effect.context();
191
+ const decision = yield* Ref.modify(state, (current) => {
192
+ const key = admissionKey(request.threadId, request.principal, request.idempotencyKey);
193
+ const existingId = current.admissionIndex.get(key);
194
+ if (existingId !== void 0) {
195
+ const existing = current.submissions.get(existingId);
196
+ if (existing === void 0) return [failure(ledgerError("admit", "Admission index references a missing Submission")), current];
197
+ if (existing.row.inputDigest !== request.inputDigest || !sameParentLinkage(existing.row.parentLinkage, request.parentLinkage) || existing.row.admissionGroup !== request.admissionGroup || !Schema.toEquivalence(Schema.optional(WorkerAdmission))(existing.row.workerAdmissionJson === void 0 ? void 0 : Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(existing.row.workerAdmissionJson), request.workerAdmission) || !Schema.toEquivalence(Schema.optional(InputMessage))(existing.row.messageAdmissionJson === void 0 ? void 0 : Schema.decodeSync(Schema.fromJsonString(InputMessage))(existing.row.messageAdmissionJson), request.messageAdmission) || !Schema.toEquivalence(Schema.optional(Schema.Json))(existing.row.admissionFence, request.admissionFence)) return [failure(AdmissionConflict.make({
198
+ threadId: request.threadId,
199
+ principal: request.principal,
200
+ idempotencyKey: request.idempotencyKey,
201
+ existingInputDigest: existing.row.inputDigest,
202
+ attemptedInputDigest: request.inputDigest
203
+ })), current];
204
+ return [success(AdmissionResult.make({
205
+ submissionId: existing.row.submissionId,
206
+ receiptId: existing.row.receiptId,
207
+ queueSequence: existing.row.queueSequence,
208
+ state: existing.row.state,
209
+ replayed: true
210
+ })), current];
211
+ }
212
+ if (current.stoppedWorkers.has(request.threadId)) return [failure(AdmissionPolicyError.make({
213
+ reason: "refused",
214
+ code: "worker-stopped"
215
+ })), current];
216
+ const first = [...current.submissions.values()].find(({ row }) => row.threadId === request.threadId);
217
+ if (first !== void 0) {
218
+ const previous = first.row.workerAdmissionJson === void 0 ? void 0 : Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(first.row.workerAdmissionJson);
219
+ if (!Schema.toEquivalence(Schema.optional(WorkerAdmission.fields.origin))(previous?.origin, request.workerAdmission?.origin)) return [failure(AdmissionPolicyError.make({
220
+ reason: "refused",
221
+ code: "worker-origin-conflict"
222
+ })), current];
223
+ }
224
+ const checked = Effect.runSyncExitWith(services)(admissionFence.check(request));
225
+ if (Exit.isFailure(checked)) return [{
226
+ _tag: "cause",
227
+ cause: checked.cause
228
+ }, current];
229
+ if (request.admissionGroup !== void 0 && [...current.submissions.values()].some(({ row }) => row.threadId === request.threadId && row.admissionGroup === request.admissionGroup && row.state !== "settled")) return [failure(AdmissionPolicyError.make({
230
+ reason: "occupied",
231
+ code: "admission-group"
232
+ })), current];
233
+ if (current.submissions.size >= MAX_SUBMISSIONS) return [failure(ledgerError("admit", `In-memory submission limit ${MAX_SUBMISSIONS} exceeded`)), current];
234
+ const lane = current.lanes.get(request.threadId) ?? {
235
+ nextQueueSequence: 1,
236
+ producerEpoch: 0
237
+ };
238
+ const mintCounter = current.mintCounter + 1;
239
+ const row = {
240
+ submissionId: decodeSubmissionId(`submission-memory-${mintCounter}`),
241
+ threadId: request.threadId,
242
+ queueSequence: decodeQueueSequence(lane.nextQueueSequence),
243
+ principal: request.principal,
244
+ idempotencyKey: request.idempotencyKey,
245
+ agentId: request.agentId,
246
+ agentDigests: request.agentDigests,
247
+ deploymentId: request.deploymentId,
248
+ inputPayload: request.inputPayload,
249
+ inputDigest: request.inputDigest,
250
+ receiptId: decodeReceiptId(`receipt-memory-${mintCounter}`),
251
+ state: "admitted",
252
+ settledOutcome: void 0,
253
+ createdAtMillis: nowMillis,
254
+ readyAtMillis: void 0,
255
+ parentLinkage: request.parentLinkage,
256
+ ...workerAdmissionJson === void 0 ? {} : { workerAdmissionJson },
257
+ ...messageAdmissionJson === void 0 ? {} : { messageAdmissionJson },
258
+ ...request.admissionGroup === void 0 ? {} : { admissionGroup: request.admissionGroup },
259
+ ...request.admissionFence === void 0 ? {} : { admissionFence: request.admissionFence }
260
+ };
261
+ const submissions = new Map(current.submissions).set(row.submissionId, {
262
+ row,
263
+ ownership: void 0,
264
+ inputApplied: void 0,
265
+ reservation: void 0,
266
+ abortIntent: void 0,
267
+ joinedHostSubmissionId: void 0,
268
+ suspension: void 0,
269
+ unknownMark: void 0,
270
+ approvalDecisions: /* @__PURE__ */ new Map(),
271
+ unknownResolutions: /* @__PURE__ */ new Map()
272
+ });
273
+ const admissionIndex = new Map(current.admissionIndex).set(key, row.submissionId);
274
+ const lanes = new Map(current.lanes).set(request.threadId, {
275
+ nextQueueSequence: lane.nextQueueSequence + 1,
276
+ producerEpoch: lane.producerEpoch
277
+ });
278
+ return [success(AdmissionResult.make({
279
+ submissionId: row.submissionId,
280
+ receiptId: row.receiptId,
281
+ queueSequence: row.queueSequence,
282
+ state: row.state,
283
+ replayed: false
284
+ })), {
285
+ ...current,
286
+ submissions,
287
+ admissionIndex,
288
+ lanes,
289
+ mintCounter,
290
+ latestByThread: new Map(current.latestByThread).set(request.threadId, row.submissionId),
291
+ activeByThread: new Map(current.activeByThread).set(request.threadId, /* @__PURE__ */ new Set([...current.activeByThread.get(request.threadId) ?? [], row.submissionId]))
292
+ }];
293
+ });
294
+ if (decision._tag === "failure") return yield* decision.error;
295
+ if (decision._tag === "cause") {
296
+ for (const reason of decision.cause.reasons) if (Cause.isDieReason(reason) && Cause.isAsyncFiberError(reason.defect)) {
297
+ yield* Fiber.interrupt(reason.defect.fiber);
298
+ return yield* AdmissionPolicyError.make({
299
+ reason: "unavailable",
300
+ code: "synchronous-memory-policy-required"
301
+ });
302
+ }
303
+ return yield* Effect.failCause(decision.cause);
304
+ }
305
+ return decision.value;
306
+ }), Effect.uninterruptible);
307
+ const markReady = Effect.fn("MemorySubmissionLedger.markReady")((unvalidated) => Effect.gen(function* () {
308
+ const request = yield* validate(MarkReadyRequest, "markReady", unvalidated);
309
+ const nowMillis = yield* Clock.currentTimeMillis;
310
+ const decision = yield* Ref.modify(state, (current) => {
311
+ const stored = current.submissions.get(request.submissionId);
312
+ if (stored === void 0) return [failure(ledgerError("markReady", `Unknown Submission ${request.submissionId}`)), current];
313
+ if (stored.row.state !== "admitted") return [success(void 0), current];
314
+ return [success(void 0), withSubmission(current, {
315
+ ...stored,
316
+ row: {
317
+ ...stored.row,
318
+ state: "ready",
319
+ readyAtMillis: nowMillis
320
+ }
321
+ })];
322
+ });
323
+ if (decision._tag === "failure") return yield* decision.error;
324
+ }));
325
+ const lookup = Effect.fn("MemorySubmissionLedger.lookup")((unvalidated) => Effect.gen(function* () {
326
+ const request = yield* validate(SubmissionLookup, "lookup", unvalidated);
327
+ const current = yield* Ref.get(state);
328
+ const submissionId = request._tag === "SubmissionLookupById" ? request.submissionId : current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
329
+ const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
330
+ return stored === void 0 ? Option.none() : Option.some(toSnapshot(stored.row));
331
+ }));
332
+ const resolveAdmission = Effect.fn("MemorySubmissionLedger.resolveAdmission")((unvalidated) => Effect.gen(function* () {
333
+ const request = yield* validate(SubmissionLookupByKey, "resolveAdmission", unvalidated);
334
+ if (options.resolveAdmissionFault !== void 0) {
335
+ const fault = yield* options.resolveAdmissionFault;
336
+ if (Option.isSome(fault)) return AdmissionIndeterminate.make({ reason: fault.value });
337
+ }
338
+ const current = yield* Ref.get(state);
339
+ const submissionId = current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
340
+ const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
341
+ return stored === void 0 ? AdmissionNotAdmitted.make() : AdmissionAdmitted.make({ submission: toSnapshot(stored.row) });
342
+ }));
343
+ const claim = Effect.fn("MemorySubmissionLedger.claim")((unvalidated) => Effect.gen(function* () {
344
+ const request = yield* validate(ClaimRequest, "claim", unvalidated);
345
+ const nowMillis = yield* Clock.currentTimeMillis;
346
+ const decision = yield* Ref.modify(state, (current) => {
347
+ for (const stored of current.submissions.values()) if (stored.row.threadId === request.threadId && stored.ownership !== void 0 && stored.ownership.leaseExpiresAtMillis > nowMillis) return [success(Option.none()), current];
348
+ const head = findHead(current, request.threadId);
349
+ if (head === void 0) return [success(Option.none()), current];
350
+ if (BLOCKED_HEAD_STATES.has(head.row.state)) return [success(Option.none()), current];
351
+ const lane = current.lanes.get(request.threadId);
352
+ if (lane === void 0) return [failure(ledgerError("claim", "Claimable head without a Thread lane")), current];
353
+ const producerEpoch = decodeProducerEpoch(lane.producerEpoch + 1);
354
+ const mintCounter = current.mintCounter + 1;
355
+ const ownership = {
356
+ attemptId: decodeAttemptId(`attempt-memory-${mintCounter}`),
357
+ ownershipToken: decodeOwnershipToken(`ownership-memory-${mintCounter}`),
358
+ producerEpoch,
359
+ ownerProducerId: request.producerId,
360
+ leaseExpiresAtMillis: nowMillis + leaseMillis
361
+ };
362
+ const row = head.row.state === "ready" ? {
363
+ ...head.row,
364
+ state: "running"
365
+ } : head.row;
366
+ const next = withSubmission(current, {
367
+ ...head,
368
+ row,
369
+ ownership
370
+ });
371
+ const lanes = new Map(next.lanes).set(request.threadId, {
372
+ nextQueueSequence: lane.nextQueueSequence,
373
+ producerEpoch: lane.producerEpoch + 1
374
+ });
375
+ return [success(Option.some(Claim.make({
376
+ submissionId: row.submissionId,
377
+ attemptId: ownership.attemptId,
378
+ ownershipToken: ownership.ownershipToken,
379
+ producerEpoch,
380
+ leaseExpiresAt: utc(ownership.leaseExpiresAtMillis),
381
+ inputPayload: row.inputPayload
382
+ }))), {
383
+ ...next,
384
+ lanes,
385
+ mintCounter
386
+ }];
387
+ });
388
+ if (decision._tag === "failure") return yield* decision.error;
389
+ return decision.value;
390
+ }));
391
+ const renewOwnership = Effect.fn("MemorySubmissionLedger.renewOwnership")((unvalidated) => Effect.gen(function* () {
392
+ const request = yield* validate(RenewOwnershipRequest, "renewOwnership", unvalidated);
393
+ const nowMillis = yield* Clock.currentTimeMillis;
394
+ const decision = yield* Ref.modify(state, (current) => {
395
+ const stored = current.submissions.get(request.submissionId);
396
+ if (stored === void 0) return [failure(ledgerError("renewOwnership", `Unknown Submission ${request.submissionId}`)), current];
397
+ if (stored.ownership === void 0 || !ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
398
+ const ownership = {
399
+ ...stored.ownership,
400
+ leaseExpiresAtMillis: nowMillis + leaseMillis
401
+ };
402
+ return [success(OwnershipRenewal.make({
403
+ ownershipToken: ownership.ownershipToken,
404
+ leaseExpiresAt: utc(ownership.leaseExpiresAtMillis)
405
+ })), withSubmission(current, {
406
+ ...stored,
407
+ ownership
408
+ })];
409
+ });
410
+ if (decision._tag === "failure") return yield* decision.error;
411
+ return decision.value;
412
+ }));
413
+ const releaseOwnership = Effect.fn("MemorySubmissionLedger.releaseOwnership")((unvalidated) => Effect.gen(function* () {
414
+ const request = yield* validate(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
415
+ const decision = yield* Ref.modify(state, (current) => {
416
+ const stored = current.submissions.get(request.submissionId);
417
+ if (stored === void 0) return [failure(ledgerError("releaseOwnership", `Unknown Submission ${request.submissionId}`)), current];
418
+ if (!ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
419
+ return [success(void 0), withSubmission(current, {
420
+ ...stored,
421
+ ownership: void 0
422
+ })];
423
+ });
424
+ if (decision._tag === "failure") return yield* decision.error;
425
+ }));
426
+ const markInputApplied = Effect.fn("MemorySubmissionLedger.markInputApplied")((unvalidated) => Effect.gen(function* () {
427
+ const request = yield* validate(MarkInputAppliedRequest, "markInputApplied", unvalidated);
428
+ const decision = yield* Ref.modify(state, (current) => {
429
+ const stored = current.submissions.get(request.submissionId);
430
+ if (stored === void 0) return [failure(ledgerError("markInputApplied", `Unknown Submission ${request.submissionId}`)), current];
431
+ if (!ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
432
+ if (stored.inputApplied !== void 0) {
433
+ if (stored.inputApplied.recordId === request.recordId && stored.inputApplied.sequence === request.sequence) return [success(void 0), current];
434
+ return [failure(ledgerError("markInputApplied", `A different canonical input marker is already recorded for Submission ${request.submissionId}`)), current];
435
+ }
436
+ const marker = InputAppliedMarker.make({
437
+ recordId: request.recordId,
438
+ sequence: request.sequence
439
+ });
440
+ const row = STATE_RANK[stored.row.state] < STATE_RANK["input-applied"] ? {
441
+ ...stored.row,
442
+ state: "input-applied"
443
+ } : stored.row;
444
+ return [success(void 0), withSubmission(current, {
445
+ ...stored,
446
+ row,
447
+ inputApplied: marker
448
+ })];
449
+ });
450
+ if (decision._tag === "failure") return yield* decision.error;
451
+ }));
452
+ const reserveSettlement = Effect.fn("MemorySubmissionLedger.reserveSettlement")((unvalidated) => Effect.gen(function* () {
453
+ const request = yield* validate(SettlementReservation, "reserveSettlement", unvalidated);
454
+ const decision = yield* Ref.modify(state, (current) => {
455
+ const stored = current.submissions.get(request.submissionId);
456
+ if (stored === void 0) return [failure(ledgerError("reserveSettlement", `Unknown Submission ${request.submissionId}`)), current];
457
+ const joinedSettlement = stored.row.state === "joined" && stored.joinedHostSubmissionId !== void 0;
458
+ const queuedAbortSettlement = request.outcome === "aborted" && stored.abortIntent !== void 0 && stored.ownership === void 0 && (stored.row.state === "ready" || stored.row.state === "terminalizing");
459
+ if (!joinedSettlement && !queuedAbortSettlement && !ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
460
+ const existing = stored.reservation;
461
+ if (existing !== void 0) {
462
+ if (existing.settlementId !== request.settlementId || existing.outcome !== request.outcome || existing.recordDigest !== request.recordDigest) return [failure(SettlementConflict.make({
463
+ submissionId: request.submissionId,
464
+ existingOutcome: existing.outcome
465
+ })), current];
466
+ return [success(ReservedSettlement.make({
467
+ submissionId: request.submissionId,
468
+ settlementId: existing.settlementId,
469
+ outcome: existing.outcome,
470
+ record: existing.record,
471
+ recordDigest: existing.recordDigest,
472
+ replayed: true
473
+ })), current];
474
+ }
475
+ const reservation = {
476
+ settlementId: request.settlementId,
477
+ outcome: request.outcome,
478
+ record: request.record,
479
+ recordDigest: request.recordDigest,
480
+ finalizedAtMillis: void 0
481
+ };
482
+ const row = STATE_RANK[stored.row.state] < STATE_RANK.terminalizing ? {
483
+ ...stored.row,
484
+ state: "terminalizing"
485
+ } : stored.row;
486
+ return [success(ReservedSettlement.make({
487
+ submissionId: request.submissionId,
488
+ settlementId: reservation.settlementId,
489
+ outcome: reservation.outcome,
490
+ record: reservation.record,
491
+ recordDigest: reservation.recordDigest,
492
+ replayed: false
493
+ })), withSubmission(current, {
494
+ ...stored,
495
+ row,
496
+ reservation
497
+ })];
498
+ });
499
+ if (decision._tag === "failure") return yield* decision.error;
500
+ return decision.value;
501
+ }));
502
+ const finalizeSettlement = Effect.fn("MemorySubmissionLedger.finalizeSettlement")((unvalidated) => Effect.gen(function* () {
503
+ const request = yield* validate(SettlementFinalization, "finalizeSettlement", unvalidated);
504
+ const nowMillis = yield* Clock.currentTimeMillis;
505
+ const decision = yield* Ref.modify(state, (current) => {
506
+ const stored = current.submissions.get(request.submissionId);
507
+ if (stored === void 0) return [failure(ledgerError("finalizeSettlement", `Unknown Submission ${request.submissionId}`)), current];
508
+ const reservation = stored.reservation;
509
+ if (reservation === void 0) return [failure(ledgerError("finalizeSettlement", `No settlement reservation for Submission ${request.submissionId}`)), current];
510
+ const settlementFailure = settlementFailureFromRecord(reservation.record);
511
+ if (reservation.outcome === "failed" !== (settlementFailure !== void 0)) return [failure(ledgerError("finalizeSettlement", `Settlement reservation for Submission ${request.submissionId} has contradictory failure evidence`)), current];
512
+ if (reservation.settlementId !== request.settlementId) return [failure(SettlementConflict.make({
513
+ submissionId: request.submissionId,
514
+ existingOutcome: reservation.outcome
515
+ })), current];
516
+ if (reservation.finalizedAtMillis !== void 0) return [success(Settlement.make({
517
+ submissionId: stored.row.submissionId,
518
+ settlementId: reservation.settlementId,
519
+ receiptId: stored.row.receiptId,
520
+ outcome: reservation.outcome,
521
+ ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
522
+ settledAt: utc(reservation.finalizedAtMillis)
523
+ })), current];
524
+ const terminal = stored.row.workerAdmissionJson === void 0 ? void 0 : workerTerminalFromRecord(toSnapshot(stored.row), reservation.record);
525
+ const latestId = current.latestByThread.get(stored.row.threadId);
526
+ const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
527
+ const pendingNewer = terminal === "completed" && latest !== void 0 && latest.row.queueSequence > stored.row.queueSequence && !(latest.joinedHostSubmissionId === stored.row.submissionId && latest.inputApplied !== void 0);
528
+ let sealed = current;
529
+ if (terminal !== void 0 && !pendingNewer && !current.stoppedWorkers.has(stored.row.threadId)) {
530
+ const submissions = new Map(current.submissions);
531
+ for (const id of current.activeByThread.get(stored.row.threadId) ?? []) {
532
+ const other = submissions.get(id);
533
+ if (other === void 0) continue;
534
+ if (id !== stored.row.submissionId && !(other.joinedHostSubmissionId === stored.row.submissionId && other.inputApplied !== void 0) && other.abortIntent === void 0) submissions.set(id, {
535
+ ...other,
536
+ abortIntent: AbortIntent.make({
537
+ submissionId: id,
538
+ author: stored.row.principal,
539
+ reason: `Worker assignment ${terminal}`,
540
+ requestedAt: utc(nowMillis)
541
+ })
542
+ });
543
+ }
544
+ sealed = {
545
+ ...current,
546
+ submissions,
547
+ stoppedWorkers: new Map(current.stoppedWorkers).set(stored.row.threadId, terminal)
548
+ };
549
+ }
550
+ const next = withSubmission(sealed, {
551
+ ...stored,
552
+ row: {
553
+ ...stored.row,
554
+ state: "settled",
555
+ settledOutcome: reservation.outcome
556
+ },
557
+ ownership: void 0,
558
+ reservation: {
559
+ ...reservation,
560
+ finalizedAtMillis: nowMillis
561
+ }
562
+ });
563
+ return [success(Settlement.make({
564
+ submissionId: stored.row.submissionId,
565
+ settlementId: reservation.settlementId,
566
+ receiptId: stored.row.receiptId,
567
+ outcome: reservation.outcome,
568
+ ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
569
+ settledAt: utc(nowMillis)
570
+ })), next];
571
+ });
572
+ if (decision._tag === "failure") return yield* decision.error;
573
+ return decision.value;
574
+ }));
575
+ const inspectWorker = Effect.fn("MemorySubmissionLedger.inspectWorker")(function* (threadId) {
576
+ const current = yield* Ref.get(state);
577
+ const latestId = current.latestByThread.get(threadId);
578
+ const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
579
+ const active = [...current.activeByThread.get(threadId) ?? []].flatMap((id) => {
580
+ const row = current.submissions.get(id);
581
+ return row === void 0 ? [] : [row];
582
+ }).sort((a, b) => a.row.queueSequence - b.row.queueSequence)[0];
583
+ const terminal = current.stoppedWorkers.get(threadId);
584
+ return WorkerLedgerState.make({
585
+ latest: latest === void 0 ? null : toSnapshot(latest.row),
586
+ active: active === void 0 ? null : toSnapshot(active.row),
587
+ stopped: current.stoppedWorkers.has(threadId),
588
+ ...terminal === void 0 ? {} : { terminal }
589
+ });
590
+ });
591
+ const stopWorker = Effect.fn("MemorySubmissionLedger.stopWorker")(function* (unvalidated) {
592
+ const request = yield* validate(WorkerStopCommand, "stopWorker", unvalidated);
593
+ const now = yield* Clock.currentTimeMillis;
594
+ return yield* Ref.modify(state, (current) => {
595
+ const submissions = new Map(current.submissions);
596
+ let owned = 0;
597
+ for (const id of current.activeByThread.get(request.threadId) ?? []) {
598
+ const stored = submissions.get(id);
599
+ if (stored === void 0) continue;
600
+ if (stored.ownership !== void 0) owned++;
601
+ if (stored.abortIntent === void 0) submissions.set(id, {
602
+ ...stored,
603
+ abortIntent: AbortIntent.make({
604
+ submissionId: id,
605
+ author: request.author,
606
+ reason: "Worker owner stopped the worker",
607
+ requestedAt: utc(now)
608
+ })
609
+ });
610
+ }
611
+ return [owned, {
612
+ ...current,
613
+ submissions,
614
+ stoppedWorkers: current.stoppedWorkers.has(request.threadId) ? current.stoppedWorkers : new Map(current.stoppedWorkers).set(request.threadId, void 0)
615
+ }];
616
+ });
617
+ });
618
+ const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
619
+ const request = yield* validate(AbortCommand, "requestAbort", unvalidated);
620
+ const nowMillis = yield* Clock.currentTimeMillis;
621
+ const decision = yield* Ref.modify(state, (current) => {
622
+ const stored = current.submissions.get(request.submissionId);
623
+ if (stored === void 0) return [failure(ledgerError("requestAbort", `Unknown Submission ${request.submissionId}`)), current];
624
+ if (stored.row.state === "joined") {
625
+ if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("requestAbort", `Joined Submission ${request.submissionId} is missing its host linkage`)), current];
626
+ return [failure(JoinedToHost.make({
627
+ submissionId: request.submissionId,
628
+ hostSubmissionId: stored.joinedHostSubmissionId
629
+ })), current];
630
+ }
631
+ if (stored.row.state === "settled") {
632
+ if (stored.row.settledOutcome === void 0) return [failure(ledgerError("requestAbort", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
633
+ return [failure(SettlementConflict.make({
634
+ submissionId: request.submissionId,
635
+ existingOutcome: stored.row.settledOutcome
636
+ })), current];
637
+ }
638
+ if (stored.abortIntent !== void 0) return [success(stored.abortIntent), current];
639
+ const intent = AbortIntent.make({
640
+ submissionId: request.submissionId,
641
+ author: request.author,
642
+ reason: request.reason,
643
+ requestedAt: utc(nowMillis)
644
+ });
645
+ return [success(intent), withSubmission(current, {
646
+ ...stored,
647
+ abortIntent: intent
648
+ })];
649
+ });
650
+ if (decision._tag === "failure") return yield* decision.error;
651
+ return decision.value;
652
+ }));
653
+ const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
654
+ const request = yield* validate(ClaimJoiningRequest, "claimJoining", unvalidated);
655
+ const decision = yield* Ref.modify(state, (current) => {
656
+ if (current.stoppedWorkers.has(request.threadId)) return [success([]), current];
657
+ const host = current.submissions.get(request.hostSubmissionId);
658
+ if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
659
+ if (host.row.threadId !== request.threadId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`)), current];
660
+ if (!ownsLane(current, host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
661
+ const later = [...current.submissions.values()].filter((stored) => stored.row.threadId === request.threadId && stored.row.queueSequence > host.row.queueSequence).sort((left, right) => left.row.queueSequence - right.row.queueSequence);
662
+ const claims = [];
663
+ const submissions = new Map(current.submissions);
664
+ for (const stored of later) {
665
+ if (claims.length >= request.maxCount) break;
666
+ if ((stored.row.state === "joining" || stored.row.state === "joined") && stored.joinedHostSubmissionId === request.hostSubmissionId) continue;
667
+ if (stored.row.state === "settled" && stored.row.settledOutcome === "aborted") continue;
668
+ if (stored.row.state !== "ready") break;
669
+ submissions.set(stored.row.submissionId, {
670
+ ...stored,
671
+ row: {
672
+ ...stored.row,
673
+ state: "joining"
674
+ },
675
+ joinedHostSubmissionId: request.hostSubmissionId
676
+ });
677
+ claims.push(JoiningClaim.make({
678
+ submissionId: stored.row.submissionId,
679
+ queueSequence: stored.row.queueSequence,
680
+ inputPayload: stored.row.inputPayload
681
+ }));
682
+ }
683
+ return [success(claims), {
684
+ ...current,
685
+ submissions
686
+ }];
687
+ });
688
+ if (decision._tag === "failure") return yield* decision.error;
689
+ return decision.value;
690
+ }));
691
+ const markJoined = Effect.fn("MemorySubmissionLedger.markJoined")((unvalidated) => Effect.gen(function* () {
692
+ const request = yield* validate(MarkJoinedRequest, "markJoined", unvalidated);
693
+ const decision = yield* Ref.modify(state, (current) => {
694
+ const stored = current.submissions.get(request.submissionId);
695
+ if (stored === void 0) return [failure(ledgerError("markJoined", `Unknown Submission ${request.submissionId}`)), current];
696
+ if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("markJoined", `Submission ${request.submissionId} was never claimed for joining`)), current];
697
+ const host = current.submissions.get(stored.joinedHostSubmissionId);
698
+ if (host === void 0) return [failure(ledgerError("markJoined", `Host Submission ${stored.joinedHostSubmissionId} is missing`)), current];
699
+ if (!ownsLane(current, host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
700
+ if (stored.inputApplied !== void 0) {
701
+ if (stored.inputApplied.recordId === request.recordId && stored.inputApplied.sequence === request.sequence) return [success(void 0), current];
702
+ return [failure(ledgerError("markJoined", `A different join marker is already recorded for Submission ${request.submissionId}`)), current];
703
+ }
704
+ if (stored.row.state !== "joining" && stored.row.state !== "joined") return [failure(ledgerError("markJoined", `Cannot mark Submission ${request.submissionId} joined from state ${stored.row.state}`)), current];
705
+ const marker = InputAppliedMarker.make({
706
+ recordId: request.recordId,
707
+ sequence: request.sequence
708
+ });
709
+ return [success(void 0), withSubmission(current, {
710
+ ...stored,
711
+ row: {
712
+ ...stored.row,
713
+ state: "joined"
714
+ },
715
+ inputApplied: marker
716
+ })];
717
+ });
718
+ if (decision._tag === "failure") return yield* decision.error;
719
+ }));
720
+ const revertJoining = Effect.fn("MemorySubmissionLedger.revertJoining")((unvalidated) => Effect.gen(function* () {
721
+ const request = yield* validate(RevertJoiningRequest, "revertJoining", unvalidated);
722
+ const decision = yield* Ref.modify(state, (current) => {
723
+ const stored = current.submissions.get(request.submissionId);
724
+ if (stored === void 0) return [failure(ledgerError("revertJoining", `Unknown Submission ${request.submissionId}`)), current];
725
+ if (stored.row.state !== "joining") return [success(void 0), current];
726
+ return [success(void 0), withSubmission(current, {
727
+ ...stored,
728
+ row: {
729
+ ...stored.row,
730
+ state: "ready"
731
+ },
732
+ joinedHostSubmissionId: void 0
733
+ })];
734
+ });
735
+ if (decision._tag === "failure") return yield* decision.error;
736
+ }));
737
+ const suspend = Effect.fn("MemorySubmissionLedger.suspend")((unvalidated) => Effect.gen(function* () {
738
+ const request = yield* validate(SuspendRequest, "suspend", unvalidated);
739
+ const nowMillis = yield* Clock.currentTimeMillis;
740
+ const decision = yield* Ref.modify(state, (current) => {
741
+ const stored = current.submissions.get(request.submissionId);
742
+ if (stored === void 0) return [failure(ledgerError("suspend", `Unknown Submission ${request.submissionId}`)), current];
743
+ if (stored.row.state === "settled") {
744
+ if (stored.row.settledOutcome === void 0) return [failure(ledgerError("suspend", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
745
+ return [failure(SettlementConflict.make({
746
+ submissionId: request.submissionId,
747
+ existingOutcome: stored.row.settledOutcome
748
+ })), current];
749
+ }
750
+ if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
751
+ submissionId: request.submissionId,
752
+ existingOutcome: stored.reservation.outcome
753
+ })), current];
754
+ if (!ownsLane(current, stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
755
+ if (request.reason._tag === "ApprovalPending" ? request.reason.toolCallIds.every((toolCallId) => stored.approvalDecisions.has(toolCallId)) : request.reason.children.every((child) => current.submissions.get(child.childSubmissionId)?.row.state === "settled")) return [success("resume-immediately"), current];
756
+ return [success("suspended"), withSubmission(current, {
757
+ ...stored,
758
+ row: {
759
+ ...stored.row,
760
+ state: "suspended"
761
+ },
762
+ ownership: void 0,
763
+ suspension: {
764
+ reason: request.reason,
765
+ suspendedAtMillis: nowMillis
766
+ }
767
+ })];
768
+ });
769
+ if (decision._tag === "failure") return yield* decision.error;
770
+ return decision.value;
771
+ }));
772
+ const recordApprovalDecision = Effect.fn("MemorySubmissionLedger.recordApprovalDecision")((unvalidated) => Effect.gen(function* () {
773
+ const command = yield* validate(ApprovalDecisionCommand, "recordApprovalDecision", unvalidated);
774
+ const nowMillis = yield* Clock.currentTimeMillis;
775
+ const decision = yield* Ref.modify(state, (current) => {
776
+ const stored = current.submissions.get(command.submissionId);
777
+ if (stored === void 0) return [failure(ledgerError("recordApprovalDecision", `Unknown Submission ${command.submissionId}`)), current];
778
+ if (stored.row.state === "settled") {
779
+ if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordApprovalDecision", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
780
+ return [failure(SettlementConflict.make({
781
+ submissionId: command.submissionId,
782
+ existingOutcome: stored.row.settledOutcome
783
+ })), current];
784
+ }
785
+ const existing = stored.approvalDecisions.get(command.toolCallId);
786
+ if (existing !== void 0) {
787
+ if (existing.decision !== command.decision) return [failure(ApprovalConflict.make({
788
+ submissionId: command.submissionId,
789
+ toolCallId: command.toolCallId,
790
+ existingDecision: existing.decision
791
+ })), current];
792
+ return [success(existing), current];
793
+ }
794
+ const intent = ApprovalDecisionIntent.make({
795
+ submissionId: command.submissionId,
796
+ toolCallId: command.toolCallId,
797
+ decision: command.decision,
798
+ resolver: command.resolver,
799
+ reason: command.reason,
800
+ decidedAt: utc(nowMillis)
801
+ });
802
+ const approvalDecisions = new Map(stored.approvalDecisions).set(command.toolCallId, intent);
803
+ const wakes = stored.row.state === "suspended" && stored.suspension !== void 0 && stored.suspension.reason._tag === "ApprovalPending" && stored.suspension.reason.toolCallIds.every((toolCallId) => approvalDecisions.has(toolCallId));
804
+ return [success(intent), withSubmission(current, {
805
+ ...stored,
806
+ row: wakes ? {
807
+ ...stored.row,
808
+ state: "input-applied"
809
+ } : stored.row,
810
+ suspension: wakes ? void 0 : stored.suspension,
811
+ approvalDecisions
812
+ })];
813
+ });
814
+ if (decision._tag === "failure") return yield* decision.error;
815
+ return decision.value;
816
+ }));
817
+ const markUnknown = Effect.fn("MemorySubmissionLedger.markUnknown")((unvalidated) => Effect.gen(function* () {
818
+ const request = yield* validate(MarkUnknownRequest, "markUnknown", unvalidated);
819
+ const decision = yield* Ref.modify(state, (current) => {
820
+ const stored = current.submissions.get(request.submissionId);
821
+ if (stored === void 0) return [failure(ledgerError("markUnknown", `Unknown Submission ${request.submissionId}`)), current];
822
+ if (stored.row.state === "settled") {
823
+ if (stored.row.settledOutcome === void 0) return [failure(ledgerError("markUnknown", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
824
+ return [failure(SettlementConflict.make({
825
+ submissionId: request.submissionId,
826
+ existingOutcome: stored.row.settledOutcome
827
+ })), current];
828
+ }
829
+ if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
830
+ submissionId: request.submissionId,
831
+ existingOutcome: stored.reservation.outcome
832
+ })), current];
833
+ const existing = stored.unknownMark;
834
+ const known = new Set(existing?.toolCallIds ?? []);
835
+ const merged = [...existing?.toolCallIds ?? [], ...request.toolCallIds.filter((toolCallId) => !known.has(toolCallId))];
836
+ return [success(void 0), withSubmission(current, {
837
+ ...stored,
838
+ row: stored.row.state === "unknown" ? stored.row : {
839
+ ...stored.row,
840
+ state: "unknown"
841
+ },
842
+ unknownMark: {
843
+ reason: existing?.reason ?? request.reason,
844
+ toolCallIds: merged
845
+ }
846
+ })];
847
+ });
848
+ if (decision._tag === "failure") return yield* decision.error;
849
+ }));
850
+ const recordUnknownResolution = Effect.fn("MemorySubmissionLedger.recordUnknownResolution")((unvalidated) => Effect.gen(function* () {
851
+ const command = yield* validate(UnknownResolutionCommand, "recordUnknownResolution", unvalidated);
852
+ const nowMillis = yield* Clock.currentTimeMillis;
853
+ const decision = yield* Ref.modify(state, (current) => {
854
+ const stored = current.submissions.get(command.submissionId);
855
+ if (stored === void 0) return [failure(ledgerError("recordUnknownResolution", `Unknown Submission ${command.submissionId}`)), current];
856
+ if (stored.row.state === "settled") {
857
+ if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordUnknownResolution", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
858
+ return [failure(SettlementConflict.make({
859
+ submissionId: command.submissionId,
860
+ existingOutcome: stored.row.settledOutcome
861
+ })), current];
862
+ }
863
+ const existing = stored.unknownResolutions.get(command.toolCallId);
864
+ if (existing !== void 0 && !equivalentUnknownResolution(existing.intent.resolution, command.resolution)) return [failure(UnknownResolutionConflict.make({
865
+ submissionId: command.submissionId,
866
+ toolCallId: command.toolCallId
867
+ })), current];
868
+ const intent = existing?.intent ?? UnknownResolutionIntent.make({
869
+ submissionId: command.submissionId,
870
+ toolCallId: command.toolCallId,
871
+ author: command.author,
872
+ reason: command.reason,
873
+ resolution: command.resolution,
874
+ resolvedAt: utc(nowMillis)
875
+ });
876
+ const unknownResolutions = existing !== void 0 ? stored.unknownResolutions : new Map(stored.unknownResolutions).set(command.toolCallId, { intent });
877
+ const wakes = stored.row.state === "unknown" && stored.unknownMark !== void 0 && stored.unknownMark.toolCallIds.every((toolCallId) => unknownResolutions.has(toolCallId));
878
+ return [success(intent), withSubmission(current, {
879
+ ...stored,
880
+ row: wakes ? {
881
+ ...stored.row,
882
+ state: "input-applied"
883
+ } : stored.row,
884
+ unknownMark: wakes ? void 0 : stored.unknownMark,
885
+ unknownResolutions
886
+ })];
887
+ });
888
+ if (decision._tag === "failure") return yield* decision.error;
889
+ return decision.value;
890
+ }));
891
+ const recordChildSettled = Effect.fn("MemorySubmissionLedger.recordChildSettled")((unvalidated) => Effect.gen(function* () {
892
+ const request = yield* validate(ChildSettledNotification, "recordChildSettled", unvalidated);
893
+ const decision = yield* Ref.modify(state, (current) => {
894
+ const parent = current.submissions.get(request.parentSubmissionId);
895
+ if (parent === void 0) return [failure(ledgerError("recordChildSettled", `Unknown Submission ${request.parentSubmissionId}`)), current];
896
+ const child = current.submissions.get(request.childSubmissionId);
897
+ if (!(child !== void 0 && (child.row.state === "settled" || child.row.state === "terminalizing" && child.reservation !== void 0))) return [failure(ledgerError("recordChildSettled", `Child Submission ${request.childSubmissionId} has no recorded settlement`)), current];
898
+ if (parent.row.state !== "suspended" || parent.suspension === void 0 || parent.suspension.reason._tag !== "WaitingForChild") return [success("not-waiting"), current];
899
+ const children = parent.suspension.reason.children;
900
+ if (!children.some((entry) => entry.childSubmissionId === request.childSubmissionId)) return [success("not-waiting"), current];
901
+ if (!children.every((entry) => {
902
+ const listed = current.submissions.get(entry.childSubmissionId);
903
+ return listed?.row.state === "settled" || listed?.row.state === "terminalizing" && listed.reservation !== void 0;
904
+ })) return [success("still-waiting"), current];
905
+ return [success("woken"), withSubmission(current, {
906
+ ...parent,
907
+ row: {
908
+ ...parent.row,
909
+ state: "input-applied"
910
+ },
911
+ suspension: void 0
912
+ })];
913
+ });
914
+ if (decision._tag === "failure") return yield* decision.error;
915
+ return decision.value;
916
+ }));
917
+ const reserveChildBudget = Effect.fn("MemorySubmissionLedger.reserveChildBudget")((unvalidated) => Effect.gen(function* () {
918
+ const request = yield* validate(ChildBudgetReservationRequest, "reserveChildBudget", unvalidated);
919
+ const nowMillis = yield* Clock.currentTimeMillis;
920
+ const decision = yield* Ref.modify(state, (current) => {
921
+ const existing = current.childReservations.get(request.reservationId);
922
+ if (existing !== void 0) {
923
+ if (!(existing.parentSubmissionId === request.parentSubmissionId && existing.parentToolCallId === request.parentToolCallId && existing.allocationDigest === request.allocationDigest && equivalentPersistedJson(existing.allocation, request.allocation))) return [failure(ChildReservationConflict.make({
924
+ reservationId: request.reservationId,
925
+ status: existing.status,
926
+ message: "A reservation with this identity exists with a different parent Tool Call or allocation."
927
+ })), current];
928
+ return [success(ReservedChildBudget.make({
929
+ reservation: toReservationSnapshot(existing),
930
+ replayed: true
931
+ })), current];
932
+ }
933
+ for (const reservation of current.childReservations.values()) if (reservation.parentSubmissionId === request.parentSubmissionId && reservation.parentToolCallId === request.parentToolCallId) return [failure(ChildReservationConflict.make({
934
+ reservationId: request.reservationId,
935
+ status: reservation.status,
936
+ message: `Parent Tool Call ${request.parentToolCallId} already owns reservation ${reservation.reservationId}.`
937
+ })), current];
938
+ const parent = current.submissions.get(request.parentSubmissionId);
939
+ if (parent === void 0) return [failure(ledgerError("reserveChildBudget", `Unknown Submission ${request.parentSubmissionId}`)), current];
940
+ if (!ownsLane(current, parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
941
+ const reservation = {
942
+ reservationId: request.reservationId,
943
+ parentSubmissionId: request.parentSubmissionId,
944
+ parentToolCallId: request.parentToolCallId,
945
+ childSubmissionId: void 0,
946
+ status: "reserved",
947
+ allocation: request.allocation,
948
+ allocationDigest: request.allocationDigest,
949
+ accounting: void 0,
950
+ reservedAtMillis: nowMillis,
951
+ releaseBeganAtMillis: void 0,
952
+ releasedAtMillis: void 0
953
+ };
954
+ return [success(ReservedChildBudget.make({
955
+ reservation: toReservationSnapshot(reservation),
956
+ replayed: false
957
+ })), withChildReservation(current, reservation)];
958
+ });
959
+ if (decision._tag === "failure") return yield* decision.error;
960
+ return decision.value;
961
+ }));
962
+ const attachChildToReservation = Effect.fn("MemorySubmissionLedger.attachChildToReservation")((unvalidated) => Effect.gen(function* () {
963
+ const request = yield* validate(AttachChildToReservationRequest, "attachChildToReservation", unvalidated);
964
+ const decision = yield* Ref.modify(state, (current) => {
965
+ const reservation = current.childReservations.get(request.reservationId);
966
+ if (reservation === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown child reservation ${request.reservationId}`)), current];
967
+ if (reservation.childSubmissionId !== void 0) {
968
+ if (reservation.childSubmissionId === request.childSubmissionId) return [success(toReservationSnapshot(reservation)), current];
969
+ return [failure(ChildReservationConflict.make({
970
+ reservationId: request.reservationId,
971
+ status: reservation.status,
972
+ message: `Reservation ${request.reservationId} already records child ${reservation.childSubmissionId}.`
973
+ })), current];
974
+ }
975
+ const parent = current.submissions.get(reservation.parentSubmissionId);
976
+ if (parent === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown Submission ${reservation.parentSubmissionId}`)), current];
977
+ if (!ownsLane(current, parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
978
+ if (reservation.status !== "reserved") return [failure(ChildReservationConflict.make({
979
+ reservationId: request.reservationId,
980
+ status: reservation.status,
981
+ message: `Cannot attach a child to a ${reservation.status} reservation.`
982
+ })), current];
983
+ if (!current.submissions.has(request.childSubmissionId)) return [failure(ledgerError("attachChildToReservation", `Unknown child Submission ${request.childSubmissionId}`)), current];
984
+ const attached = {
985
+ ...reservation,
986
+ childSubmissionId: request.childSubmissionId
987
+ };
988
+ return [success(toReservationSnapshot(attached)), withChildReservation(current, attached)];
989
+ });
990
+ if (decision._tag === "failure") return yield* decision.error;
991
+ return decision.value;
992
+ }));
993
+ const beginChildBudgetRelease = Effect.fn("MemorySubmissionLedger.beginChildBudgetRelease")((unvalidated) => Effect.gen(function* () {
994
+ const request = yield* validate(BeginChildBudgetReleaseRequest, "beginChildBudgetRelease", unvalidated);
995
+ const nowMillis = yield* Clock.currentTimeMillis;
996
+ const decision = yield* Ref.modify(state, (current) => {
997
+ const reservation = current.childReservations.get(request.reservationId);
998
+ if (reservation === void 0) return [failure(ledgerError("beginChildBudgetRelease", `Unknown child reservation ${request.reservationId}`)), current];
999
+ if (reservation.status !== "reserved") {
1000
+ if (reservation.accounting !== void 0 && equivalentPersistedJson(reservation.accounting, request.accounting)) return [success(toReservationSnapshot(reservation)), current];
1001
+ return [failure(ChildReservationConflict.make({
1002
+ reservationId: request.reservationId,
1003
+ status: reservation.status,
1004
+ message: "A different accounting decision is already frozen for this reservation."
1005
+ })), current];
1006
+ }
1007
+ const frozen = {
1008
+ ...reservation,
1009
+ status: "releasePending",
1010
+ accounting: request.accounting,
1011
+ releaseBeganAtMillis: nowMillis
1012
+ };
1013
+ return [success(toReservationSnapshot(frozen)), withChildReservation(current, frozen)];
1014
+ });
1015
+ if (decision._tag === "failure") return yield* decision.error;
1016
+ return decision.value;
1017
+ }));
1018
+ const releaseChildBudget = Effect.fn("MemorySubmissionLedger.releaseChildBudget")((unvalidated) => Effect.gen(function* () {
1019
+ const request = yield* validate(ReleaseChildBudgetRequest, "releaseChildBudget", unvalidated);
1020
+ const nowMillis = yield* Clock.currentTimeMillis;
1021
+ const decision = yield* Ref.modify(state, (current) => {
1022
+ const reservation = current.childReservations.get(request.reservationId);
1023
+ if (reservation === void 0) return [failure(ledgerError("releaseChildBudget", `Unknown child reservation ${request.reservationId}`)), current];
1024
+ if (reservation.status === "released") return [success(toReservationSnapshot(reservation)), current];
1025
+ if (reservation.status !== "releasePending") return [failure(ChildReservationConflict.make({
1026
+ reservationId: request.reservationId,
1027
+ status: reservation.status,
1028
+ message: "Cannot release a reservation whose accounting decision is not frozen."
1029
+ })), current];
1030
+ const released = {
1031
+ ...reservation,
1032
+ status: "released",
1033
+ releasedAtMillis: nowMillis
1034
+ };
1035
+ return [success(toReservationSnapshot(released)), withChildReservation(current, released)];
1036
+ });
1037
+ if (decision._tag === "failure") return yield* decision.error;
1038
+ return decision.value;
1039
+ }));
1040
+ const scanNonterminal = Stream.unwrap(Ref.get(state).pipe(Effect.map((current) => {
1041
+ const snapshots = [...current.submissions.values()].filter((stored) => stored.row.state !== "settled").sort((left, right) => left.row.threadId < right.row.threadId ? -1 : left.row.threadId > right.row.threadId ? 1 : left.row.queueSequence - right.row.queueSequence).map(({ row }) => SubmissionWorkItem.make({
1042
+ submissionId: row.submissionId,
1043
+ threadId: row.threadId,
1044
+ queueSequence: row.queueSequence,
1045
+ principal: row.principal,
1046
+ idempotencyKey: row.idempotencyKey,
1047
+ deploymentId: row.deploymentId,
1048
+ receiptId: row.receiptId,
1049
+ state: row.state
1050
+ }));
1051
+ return Stream.fromIterable(snapshots);
1052
+ })));
1053
+ const readAbortIntent = Effect.fn("MemorySubmissionLedger.readAbortIntent")(function* (unvalidated) {
1054
+ const request = yield* validate(AbortIntentRequest, "readAbortIntent", unvalidated);
1055
+ const stored = (yield* Ref.get(state)).submissions.get(request.submissionId);
1056
+ if (stored === void 0) return yield* ledgerError("readAbortIntent", `Unknown Submission ${request.submissionId}`);
1057
+ return stored.abortIntent;
1058
+ });
1059
+ const loadRecoverySnapshot = Effect.fn("MemorySubmissionLedger.loadRecoverySnapshot")((unvalidated) => Effect.gen(function* () {
1060
+ const request = yield* validate(RecoverySnapshotRequest, "loadRecoverySnapshot", unvalidated);
1061
+ const current = yield* Ref.get(state);
1062
+ const stored = current.submissions.get(request.submissionId);
1063
+ if (stored === void 0) return yield* ledgerError("loadRecoverySnapshot", `Unknown Submission ${request.submissionId}`);
1064
+ const joins = [...current.submissions.values()].filter((candidate) => candidate.joinedHostSubmissionId === request.submissionId).sort((left, right) => left.row.queueSequence - right.row.queueSequence).map((candidate) => JoinSnapshot.make({
1065
+ submissionId: candidate.row.submissionId,
1066
+ state: candidate.row.state,
1067
+ hostSubmissionId: request.submissionId
1068
+ }));
1069
+ const byToolCallId = (left, right) => left.toolCallId < right.toolCallId ? -1 : left.toolCallId > right.toolCallId ? 1 : 0;
1070
+ const childReservations = [...current.childReservations.values()].filter((reservation) => reservation.parentSubmissionId === request.submissionId).sort((left, right) => left.parentToolCallId < right.parentToolCallId ? -1 : left.parentToolCallId > right.parentToolCallId ? 1 : 0);
1071
+ const childAttachments = [];
1072
+ for (const reservation of childReservations) {
1073
+ if (reservation.childSubmissionId === void 0) continue;
1074
+ const child = current.submissions.get(reservation.childSubmissionId);
1075
+ if (child === void 0) continue;
1076
+ childAttachments.push(ChildAttachmentSnapshot.make({
1077
+ toolCallId: reservation.parentToolCallId,
1078
+ childSubmissionId: reservation.childSubmissionId,
1079
+ childState: child.row.state,
1080
+ ...child.row.settledOutcome === void 0 ? {} : { childOutcome: child.row.settledOutcome }
1081
+ }));
1082
+ }
1083
+ return RecoverySnapshot.make({
1084
+ submission: toSnapshot(stored.row),
1085
+ joins,
1086
+ approvalDecisions: [...stored.approvalDecisions.values()].sort(byToolCallId),
1087
+ unknownResolutions: [...stored.unknownResolutions.values()].map((resolution) => resolution.intent).sort(byToolCallId),
1088
+ childReservations: childReservations.map(toReservationSnapshot),
1089
+ childAttachments,
1090
+ ...stored.row.parentLinkage === void 0 ? {} : { parentLinkage: stored.row.parentLinkage },
1091
+ ...stored.joinedHostSubmissionId === void 0 ? {} : { hostSubmissionId: stored.joinedHostSubmissionId },
1092
+ ...stored.suspension === void 0 ? {} : { suspension: SuspensionSnapshot.make({
1093
+ reason: stored.suspension.reason,
1094
+ suspendedAt: utc(stored.suspension.suspendedAtMillis)
1095
+ }) },
1096
+ ...stored.ownership === void 0 ? {} : { ownership: OwnershipSnapshot.make({
1097
+ attemptId: stored.ownership.attemptId,
1098
+ ownerProducerId: stored.ownership.ownerProducerId,
1099
+ producerEpoch: stored.ownership.producerEpoch,
1100
+ leaseExpiresAt: utc(stored.ownership.leaseExpiresAtMillis)
1101
+ }) },
1102
+ ...stored.inputApplied === void 0 ? {} : { inputApplied: stored.inputApplied },
1103
+ ...stored.reservation === void 0 ? {} : { reservation: SettlementReservationSnapshot.make({
1104
+ settlementId: stored.reservation.settlementId,
1105
+ outcome: stored.reservation.outcome,
1106
+ record: stored.reservation.record,
1107
+ recordDigest: stored.reservation.recordDigest,
1108
+ finalized: stored.reservation.finalizedAtMillis !== void 0
1109
+ }) },
1110
+ ...stored.abortIntent === void 0 ? {} : { abortIntent: stored.abortIntent }
1111
+ });
1112
+ }));
1113
+ return SubmissionLedger.of({
1114
+ capabilities,
1115
+ admit,
1116
+ markReady,
1117
+ lookup,
1118
+ resolveAdmission,
1119
+ claim,
1120
+ renewOwnership,
1121
+ releaseOwnership,
1122
+ markInputApplied,
1123
+ reserveSettlement,
1124
+ finalizeSettlement,
1125
+ requestAbort,
1126
+ stopWorker,
1127
+ inspectWorker,
1128
+ claimJoining,
1129
+ markJoined,
1130
+ revertJoining,
1131
+ suspend,
1132
+ recordApprovalDecision,
1133
+ markUnknown,
1134
+ recordUnknownResolution,
1135
+ recordChildSettled,
1136
+ reserveChildBudget,
1137
+ attachChildToReservation,
1138
+ beginChildBudgetRelease,
1139
+ releaseChildBudget,
1140
+ scanNonterminal,
1141
+ loadRecoverySnapshot,
1142
+ readAbortIntent
1143
+ });
1144
+ });
1145
+ /**
1146
+ * In-memory reference SubmissionLedger Layer (durability `non-durable`). All state lives in one
1147
+ * `Ref` owned by the Layer's Scope; no daemon fibers are spawned and no wall clock is consulted.
1148
+ */
1149
+ const memorySubmissionLedgerLayer = (options = {}) => Layer.effect(SubmissionLedger, makeSubmissionLedger(options));
1150
+ const MemorySubmissionLedgerLive = memorySubmissionLedgerLayer();
1151
+ //#endregion
1152
+ export { MemorySubmissionLedgerLive, memorySubmissionLedgerLayer, MemorySubmissionLedger_exports as t };
1153
+
1154
+ //# sourceMappingURL=MemorySubmissionLedger.mjs.map