@effect-agent/storage-memory 0.1.0-beta.45 → 0.1.0-beta.46

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/index.mjs CHANGED
@@ -1,2258 +1,6 @@
1
- import { AttemptId, MemoryIndexCandidate, MemoryIndexError, MemoryIndexQuery, MemoryIndexReplacement, MemoryIndexSearch, MemoryIndexSource, MemoryKey, ReceiptId, SemanticMemoryChunk, SemanticMemoryIndex, SemanticMemoryProfile, SubmissionId, ThreadId } from "@effect-agent/core";
2
- import { AbortCommand, AbortIntent, AcceptedEvent, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionRequest, AdmissionResult, AppendConflict, AppendResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, CanonicalRecordEnvelope, CanonicalSequence, CheckpointRejected, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, DeliveryChange, Digest, EMPTY_TAIL_DIGEST, FenceRejected, FencedAppendRequest, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, LoadCheckpointRequest, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, ObservationOffset, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, PersistedJson, ProducerEpoch, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, SaveCheckpointRequest, ScheduleCapacityError, ScheduleChange, ScheduleConflict, ScheduleDueCursor, ScheduleFailpoint, ScheduleKey, ScheduleNotFound, ScheduleOwner, SchedulePageRequest, ScheduleRecord, ScheduleStorageError, ScheduleStore, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SourcePartition, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubscriptionDelivery, SubscriptionDeliveryKey, SubscriptionError, SubscriptionFailpoint, SubscriptionKey, SubscriptionLimits, SubscriptionName, SubscriptionRecord, SubscriptionScanCursors, SubscriptionStore, SuspendRequest, SuspensionSnapshot, ThreadExport, ThreadExportRequest, ThreadMaterialization, ThreadNotMaterialized, ThreadObservation, ThreadRead, ThreadStore, ThreadStoreError, ThreadTail, ThreadTailRequest, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, applyScheduleChange, applySubscriptionDeliveryChange, compareScheduleKeys, compareScheduleNames, defaultSchedulingLimits, digestCanonicalBatch, scheduleDeadline, scheduleKeyOf, scheduleKeyString, scheduleOwnerKey, scheduleUsesCapacity, settlementFailureFromRecord, subscriptionCanSelect, subscriptionDeliveryCanSelect, subscriptionDeliveryKeyString, subscriptionKeyString } from "@effect-agent/thread";
3
- import { Clock, Crypto, DateTime, Duration, Effect, Encoding, Layer, Option, PubSub, Ref, Result, Schema, Stream } from "effect";
4
- //#region src/memory-ledger.ts
5
- const MAX_SUBMISSIONS = 65536;
6
- /**
7
- * Lifecycle ordering used to advance-but-never-regress the operational state marker: a reclaimed
8
- * Attempt must not erase progress markers (input-applied, terminalizing) that an earlier Attempt
9
- * already committed.
10
- */
11
- const STATE_RANK = {
12
- admitted: 0,
13
- ready: 1,
14
- joining: 2,
15
- joined: 3,
16
- running: 4,
17
- "input-applied": 5,
18
- suspended: 6,
19
- unknown: 7,
20
- terminalizing: 8,
21
- settled: 9
22
- };
23
- /**
24
- * States in which `claim` never grants the head: the lane is host-owned (`joining`/`joined`)
25
- * or durably suspended rather than worker-claimable. Unknown heads are checked against abort
26
- * intent separately: abort authorizes cleanup and settlement, never ordinary Tool replay.
27
- */
28
- const BLOCKED_HEAD_STATES = /* @__PURE__ */ new Set([
29
- "joining",
30
- "joined",
31
- "suspended"
32
- ]);
33
- const failure = (error) => ({
34
- _tag: "failure",
35
- error
36
- });
37
- const success = (value) => ({
38
- _tag: "success",
39
- value
40
- });
41
- const ledgerError = (operation, message, cause) => cause === void 0 ? LedgerError.make({
42
- operation,
43
- message
44
- }) : LedgerError.make({
45
- operation,
46
- message,
47
- cause
48
- });
49
- const validate$2 = 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))));
50
- const decodeSubmissionId = Schema.decodeSync(SubmissionId);
51
- const decodeReceiptId = Schema.decodeSync(ReceiptId);
52
- const decodeAttemptId = Schema.decodeSync(AttemptId);
53
- const decodeOwnershipToken = Schema.decodeSync(OwnershipToken);
54
- const decodeQueueSequence = Schema.decodeSync(QueueSequence);
55
- const decodeProducerEpoch = Schema.decodeSync(ProducerEpoch);
56
- const equivalentPersistedJson = Schema.toEquivalence(PersistedJson);
57
- const equivalentUnknownResolution = Schema.toEquivalence(UnknownResolution);
58
- const utc = (millis) => DateTime.toUtc(DateTime.makeUnsafe(millis));
59
- const admissionKey = (threadId, principal, idempotencyKey) => `${threadId}\u001f${principal}\u001f${idempotencyKey}`;
60
- const toSnapshot = (row) => SubmissionSnapshot.make({
61
- submissionId: row.submissionId,
62
- threadId: row.threadId,
63
- queueSequence: row.queueSequence,
64
- principal: row.principal,
65
- idempotencyKey: row.idempotencyKey,
66
- agentId: row.agentId,
67
- agentDigests: row.agentDigests,
68
- deploymentId: row.deploymentId,
69
- inputPayload: row.inputPayload,
70
- inputDigest: row.inputDigest,
71
- receiptId: row.receiptId,
72
- state: row.state,
73
- createdAt: utc(row.createdAtMillis),
74
- ...row.settledOutcome === void 0 ? {} : { settledOutcome: row.settledOutcome },
75
- ...row.readyAtMillis === void 0 ? {} : { readyAt: utc(row.readyAtMillis) },
76
- ...row.parentLinkage === void 0 ? {} : { parentLinkage: row.parentLinkage }
77
- });
78
- const toReservationSnapshot = (row) => ChildBudgetReservationSnapshot.make({
79
- reservationId: row.reservationId,
80
- parentSubmissionId: row.parentSubmissionId,
81
- parentToolCallId: row.parentToolCallId,
82
- status: row.status,
83
- allocation: row.allocation,
84
- allocationDigest: row.allocationDigest,
85
- reservedAt: utc(row.reservedAtMillis),
86
- ...row.childSubmissionId === void 0 ? {} : { childSubmissionId: row.childSubmissionId },
87
- ...row.accounting === void 0 ? {} : { accounting: row.accounting },
88
- ...row.releaseBeganAtMillis === void 0 ? {} : { releaseBeganAt: utc(row.releaseBeganAtMillis) },
89
- ...row.releasedAtMillis === void 0 ? {} : { releasedAt: utc(row.releasedAtMillis) }
90
- });
91
- /** Linkage equality: both absent, or both present naming the same parent Tool Call. */
92
- const sameParentLinkage = (left, right) => left === void 0 ? right === void 0 : right !== void 0 && left.parentSubmissionId === right.parentSubmissionId && left.parentToolCallId === right.parentToolCallId;
93
- const laneEpoch = (state, threadId) => state.lanes.get(threadId)?.producerEpoch ?? 0;
94
- const ownershipLost = (state, stored) => OwnershipLost.make({
95
- submissionId: stored.row.submissionId,
96
- actualEpoch: decodeProducerEpoch(laneEpoch(state, stored.row.threadId))
97
- });
98
- /** The presented token owns the lane only while it matches the live ownership record. */
99
- const ownsLane = (stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken;
100
- const withSubmission = (state, stored) => ({
101
- ...state,
102
- submissions: new Map(state.submissions).set(stored.row.submissionId, stored)
103
- });
104
- const withChildReservation = (state, reservation) => ({
105
- ...state,
106
- childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
107
- });
108
- const findHead = (state, threadId) => {
109
- let head;
110
- for (const stored of state.submissions.values()) {
111
- if (stored.row.threadId !== threadId || stored.row.state === "settled") continue;
112
- if (head === void 0 || stored.row.queueSequence < head.row.queueSequence) head = stored;
113
- }
114
- return head;
115
- };
116
- /**
117
- * Reference in-memory SubmissionLedger. It implements the full port contract — atomic idempotent
118
- * admission, FIFO-head claims, producer-epoch fencing, Clock-driven ownership leases, idempotent
119
- * settlement reservation/finalization, and durable abort intent — with every transition applied
120
- * as one atomic `Ref.modify`, but its state does not survive the process (`non-durable`).
121
- *
122
- * Adapter-specific semantics within the port's latitude:
123
- *
124
- * - Time comes exclusively from the Effect `Clock` service, so `TestClock` drives lease expiry
125
- * deterministically; no wall clock is consulted.
126
- * - The ownership lease is pinned to `DEFAULT_OWNERSHIP_LEASE_DURATION` (D5); durable adapters
127
- * own the configuration seam.
128
- * - A live lease blocks claims from other producers only: the same `producerId` may reclaim its
129
- * own live lease (restart recovery), which supersedes and fences the prior Attempt's token.
130
- * - Claiming advances `ready` to `running` and otherwise preserves the recorded state, so
131
- * progress markers from an earlier Attempt survive a reclaim.
132
- * - `renewOwnership` keeps the token stable (the port allows rotation); a replayed admission
133
- * reports the Submission's current state alongside the original identities.
134
- * - `claimJoining` walks the strictly-later queue: rows already `joining`/`joined` to the
135
- * SAME host extend the claimed prefix and are skipped, and an aborted-settled row is a
136
- * closed obligation that is also skipped (P7 §7(c)); any other non-`ready` row (an
137
- * `admitted` gap, a non-aborted settled row, foreign-host linkage) breaks the prefix
138
- * conservatively.
139
- * - `markJoined` verifies the token against the HOST's live ownership (the lane is
140
- * host-owned), so a later host Attempt can repair a lost marker from history (DUR-016). The
141
- * join marker reuses the input-applied marker: the joined input IS `input:{sid}`.
142
- * - `suspend` and `markUnknown` refuse when an exact settlement is already reserved
143
- * (`SettlementConflict` with the reserved outcome) — DUR-011's reservation wins.
144
- * - `resolveAdmission` derives its answer from the single strongly consistent store, so it
145
- * never answers `Indeterminate` on its own; the test-only `resolveAdmissionFault` option
146
- * injects the `Indeterminate` classification so SUB-031 callers can be conformance-tested.
147
- * - `recordChildSettled` and `suspend(WaitingForChild)` observe child settlement directly from
148
- * the child rows (single-store latitude); no separate notification marker is stored.
149
- */
150
- const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
151
- const state = yield* Ref.make({
152
- submissions: /* @__PURE__ */ new Map(),
153
- admissionIndex: /* @__PURE__ */ new Map(),
154
- lanes: /* @__PURE__ */ new Map(),
155
- childReservations: /* @__PURE__ */ new Map(),
156
- mintCounter: 0
157
- });
158
- const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
159
- const capabilities = Effect.succeed(LedgerCapabilities.make({ durability: "non-durable" }));
160
- const admit = Effect.fn("MemorySubmissionLedger.admit")((unvalidated) => Effect.gen(function* () {
161
- const request = yield* validate$2(AdmissionRequest, "admit", unvalidated);
162
- const nowMillis = yield* Clock.currentTimeMillis;
163
- const decision = yield* Ref.modify(state, (current) => {
164
- const key = admissionKey(request.threadId, request.principal, request.idempotencyKey);
165
- const existingId = current.admissionIndex.get(key);
166
- if (existingId !== void 0) {
167
- const existing = current.submissions.get(existingId);
168
- if (existing === void 0) return [failure(ledgerError("admit", "Admission index references a missing Submission")), current];
169
- if (existing.row.inputDigest !== request.inputDigest || !sameParentLinkage(existing.row.parentLinkage, request.parentLinkage)) return [failure(AdmissionConflict.make({
170
- threadId: request.threadId,
171
- principal: request.principal,
172
- idempotencyKey: request.idempotencyKey,
173
- existingInputDigest: existing.row.inputDigest,
174
- attemptedInputDigest: request.inputDigest
175
- })), current];
176
- return [success(AdmissionResult.make({
177
- submissionId: existing.row.submissionId,
178
- receiptId: existing.row.receiptId,
179
- queueSequence: existing.row.queueSequence,
180
- state: existing.row.state,
181
- replayed: true
182
- })), current];
183
- }
184
- if (current.submissions.size >= MAX_SUBMISSIONS) return [failure(ledgerError("admit", `In-memory submission limit ${MAX_SUBMISSIONS} exceeded`)), current];
185
- const lane = current.lanes.get(request.threadId) ?? {
186
- nextQueueSequence: 1,
187
- producerEpoch: 0
188
- };
189
- const mintCounter = current.mintCounter + 1;
190
- const row = {
191
- submissionId: decodeSubmissionId(`submission-memory-${mintCounter}`),
192
- threadId: request.threadId,
193
- queueSequence: decodeQueueSequence(lane.nextQueueSequence),
194
- principal: request.principal,
195
- idempotencyKey: request.idempotencyKey,
196
- agentId: request.agentId,
197
- agentDigests: request.agentDigests,
198
- deploymentId: request.deploymentId,
199
- inputPayload: request.inputPayload,
200
- inputDigest: request.inputDigest,
201
- receiptId: decodeReceiptId(`receipt-memory-${mintCounter}`),
202
- state: "admitted",
203
- settledOutcome: void 0,
204
- createdAtMillis: nowMillis,
205
- readyAtMillis: void 0,
206
- parentLinkage: request.parentLinkage
207
- };
208
- const submissions = new Map(current.submissions).set(row.submissionId, {
209
- row,
210
- ownership: void 0,
211
- inputApplied: void 0,
212
- reservation: void 0,
213
- abortIntent: void 0,
214
- joinedHostSubmissionId: void 0,
215
- suspension: void 0,
216
- unknownMark: void 0,
217
- approvalDecisions: /* @__PURE__ */ new Map(),
218
- unknownResolutions: /* @__PURE__ */ new Map()
219
- });
220
- const admissionIndex = new Map(current.admissionIndex).set(key, row.submissionId);
221
- const lanes = new Map(current.lanes).set(request.threadId, {
222
- nextQueueSequence: lane.nextQueueSequence + 1,
223
- producerEpoch: lane.producerEpoch
224
- });
225
- return [success(AdmissionResult.make({
226
- submissionId: row.submissionId,
227
- receiptId: row.receiptId,
228
- queueSequence: row.queueSequence,
229
- state: row.state,
230
- replayed: false
231
- })), {
232
- ...current,
233
- submissions,
234
- admissionIndex,
235
- lanes,
236
- mintCounter
237
- }];
238
- });
239
- if (decision._tag === "failure") return yield* decision.error;
240
- return decision.value;
241
- }));
242
- const markReady = Effect.fn("MemorySubmissionLedger.markReady")((unvalidated) => Effect.gen(function* () {
243
- const request = yield* validate$2(MarkReadyRequest, "markReady", unvalidated);
244
- const nowMillis = yield* Clock.currentTimeMillis;
245
- const decision = yield* Ref.modify(state, (current) => {
246
- const stored = current.submissions.get(request.submissionId);
247
- if (stored === void 0) return [failure(ledgerError("markReady", `Unknown Submission ${request.submissionId}`)), current];
248
- if (stored.row.state !== "admitted") return [success(void 0), current];
249
- return [success(void 0), withSubmission(current, {
250
- ...stored,
251
- row: {
252
- ...stored.row,
253
- state: "ready",
254
- readyAtMillis: nowMillis
255
- }
256
- })];
257
- });
258
- if (decision._tag === "failure") return yield* decision.error;
259
- }));
260
- const lookup = Effect.fn("MemorySubmissionLedger.lookup")((unvalidated) => Effect.gen(function* () {
261
- const request = yield* validate$2(SubmissionLookup, "lookup", unvalidated);
262
- const current = yield* Ref.get(state);
263
- const submissionId = request._tag === "SubmissionLookupById" ? request.submissionId : current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
264
- const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
265
- return stored === void 0 ? Option.none() : Option.some(toSnapshot(stored.row));
266
- }));
267
- const resolveAdmission = Effect.fn("MemorySubmissionLedger.resolveAdmission")((unvalidated) => Effect.gen(function* () {
268
- const request = yield* validate$2(SubmissionLookupByKey, "resolveAdmission", unvalidated);
269
- if (options.resolveAdmissionFault !== void 0) {
270
- const fault = yield* options.resolveAdmissionFault;
271
- if (Option.isSome(fault)) return AdmissionIndeterminate.make({ reason: fault.value });
272
- }
273
- const current = yield* Ref.get(state);
274
- const submissionId = current.admissionIndex.get(admissionKey(request.threadId, request.principal, request.idempotencyKey));
275
- const stored = submissionId === void 0 ? void 0 : current.submissions.get(submissionId);
276
- return stored === void 0 ? AdmissionNotAdmitted.make() : AdmissionAdmitted.make({ submission: toSnapshot(stored.row) });
277
- }));
278
- const claim = Effect.fn("MemorySubmissionLedger.claim")((unvalidated) => Effect.gen(function* () {
279
- const request = yield* validate$2(ClaimRequest, "claim", unvalidated);
280
- const nowMillis = yield* Clock.currentTimeMillis;
281
- const decision = yield* Ref.modify(state, (current) => {
282
- const head = findHead(current, request.threadId);
283
- if (head === void 0) return [success(Option.none()), current];
284
- if (BLOCKED_HEAD_STATES.has(head.row.state) || head.row.state === "unknown" && head.abortIntent === void 0) return [success(Option.none()), current];
285
- if (head.ownership !== void 0 && head.ownership.leaseExpiresAtMillis > nowMillis && head.ownership.ownerProducerId !== request.producerId) return [success(Option.none()), current];
286
- const lane = current.lanes.get(request.threadId);
287
- if (lane === void 0) return [failure(ledgerError("claim", "Claimable head without a Thread lane")), current];
288
- const producerEpoch = decodeProducerEpoch(lane.producerEpoch + 1);
289
- const mintCounter = current.mintCounter + 1;
290
- const ownership = {
291
- attemptId: decodeAttemptId(`attempt-memory-${mintCounter}`),
292
- ownershipToken: decodeOwnershipToken(`ownership-memory-${mintCounter}`),
293
- producerEpoch,
294
- ownerProducerId: request.producerId,
295
- leaseExpiresAtMillis: nowMillis + leaseMillis
296
- };
297
- const row = head.row.state === "ready" ? {
298
- ...head.row,
299
- state: "running"
300
- } : head.row;
301
- const next = withSubmission(current, {
302
- ...head,
303
- row,
304
- ownership
305
- });
306
- const lanes = new Map(next.lanes).set(request.threadId, {
307
- nextQueueSequence: lane.nextQueueSequence,
308
- producerEpoch: lane.producerEpoch + 1
309
- });
310
- return [success(Option.some(Claim.make({
311
- submissionId: row.submissionId,
312
- attemptId: ownership.attemptId,
313
- ownershipToken: ownership.ownershipToken,
314
- producerEpoch,
315
- leaseExpiresAt: utc(ownership.leaseExpiresAtMillis),
316
- inputPayload: row.inputPayload
317
- }))), {
318
- ...next,
319
- lanes,
320
- mintCounter
321
- }];
322
- });
323
- if (decision._tag === "failure") return yield* decision.error;
324
- return decision.value;
325
- }));
326
- const renewOwnership = Effect.fn("MemorySubmissionLedger.renewOwnership")((unvalidated) => Effect.gen(function* () {
327
- const request = yield* validate$2(RenewOwnershipRequest, "renewOwnership", unvalidated);
328
- const nowMillis = yield* Clock.currentTimeMillis;
329
- const decision = yield* Ref.modify(state, (current) => {
330
- const stored = current.submissions.get(request.submissionId);
331
- if (stored === void 0) return [failure(ledgerError("renewOwnership", `Unknown Submission ${request.submissionId}`)), current];
332
- if (stored.ownership === void 0 || !ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
333
- const ownership = {
334
- ...stored.ownership,
335
- leaseExpiresAtMillis: nowMillis + leaseMillis
336
- };
337
- return [success(OwnershipRenewal.make({
338
- ownershipToken: ownership.ownershipToken,
339
- leaseExpiresAt: utc(ownership.leaseExpiresAtMillis)
340
- })), withSubmission(current, {
341
- ...stored,
342
- ownership
343
- })];
344
- });
345
- if (decision._tag === "failure") return yield* decision.error;
346
- return decision.value;
347
- }));
348
- const releaseOwnership = Effect.fn("MemorySubmissionLedger.releaseOwnership")((unvalidated) => Effect.gen(function* () {
349
- const request = yield* validate$2(ReleaseOwnershipRequest, "releaseOwnership", unvalidated);
350
- const decision = yield* Ref.modify(state, (current) => {
351
- const stored = current.submissions.get(request.submissionId);
352
- if (stored === void 0) return [failure(ledgerError("releaseOwnership", `Unknown Submission ${request.submissionId}`)), current];
353
- if (!ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
354
- return [success(void 0), withSubmission(current, {
355
- ...stored,
356
- ownership: void 0
357
- })];
358
- });
359
- if (decision._tag === "failure") return yield* decision.error;
360
- }));
361
- const markInputApplied = Effect.fn("MemorySubmissionLedger.markInputApplied")((unvalidated) => Effect.gen(function* () {
362
- const request = yield* validate$2(MarkInputAppliedRequest, "markInputApplied", unvalidated);
363
- const decision = yield* Ref.modify(state, (current) => {
364
- const stored = current.submissions.get(request.submissionId);
365
- if (stored === void 0) return [failure(ledgerError("markInputApplied", `Unknown Submission ${request.submissionId}`)), current];
366
- if (!ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
367
- const marker = InputAppliedMarker.make({
368
- recordId: request.recordId,
369
- sequence: request.sequence
370
- });
371
- const row = STATE_RANK[stored.row.state] < STATE_RANK["input-applied"] ? {
372
- ...stored.row,
373
- state: "input-applied"
374
- } : stored.row;
375
- return [success(void 0), withSubmission(current, {
376
- ...stored,
377
- row,
378
- inputApplied: marker
379
- })];
380
- });
381
- if (decision._tag === "failure") return yield* decision.error;
382
- }));
383
- const reserveSettlement = Effect.fn("MemorySubmissionLedger.reserveSettlement")((unvalidated) => Effect.gen(function* () {
384
- const request = yield* validate$2(SettlementReservation, "reserveSettlement", unvalidated);
385
- const decision = yield* Ref.modify(state, (current) => {
386
- const stored = current.submissions.get(request.submissionId);
387
- if (stored === void 0) return [failure(ledgerError("reserveSettlement", `Unknown Submission ${request.submissionId}`)), current];
388
- const joinedSettlement = stored.row.state === "joined" && stored.joinedHostSubmissionId !== void 0;
389
- const queuedAbortSettlement = request.outcome === "aborted" && stored.abortIntent !== void 0 && stored.ownership === void 0 && (stored.row.state === "ready" || stored.row.state === "terminalizing");
390
- if (!joinedSettlement && !queuedAbortSettlement && !ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
391
- const existing = stored.reservation;
392
- if (existing !== void 0) {
393
- if (existing.settlementId !== request.settlementId || existing.outcome !== request.outcome || existing.recordDigest !== request.recordDigest) return [failure(SettlementConflict.make({
394
- submissionId: request.submissionId,
395
- existingOutcome: existing.outcome
396
- })), current];
397
- return [success(ReservedSettlement.make({
398
- submissionId: request.submissionId,
399
- settlementId: existing.settlementId,
400
- outcome: existing.outcome,
401
- record: existing.record,
402
- recordDigest: existing.recordDigest,
403
- replayed: true
404
- })), current];
405
- }
406
- const reservation = {
407
- settlementId: request.settlementId,
408
- outcome: request.outcome,
409
- record: request.record,
410
- recordDigest: request.recordDigest,
411
- finalizedAtMillis: void 0
412
- };
413
- const row = STATE_RANK[stored.row.state] < STATE_RANK.terminalizing ? {
414
- ...stored.row,
415
- state: "terminalizing"
416
- } : stored.row;
417
- return [success(ReservedSettlement.make({
418
- submissionId: request.submissionId,
419
- settlementId: reservation.settlementId,
420
- outcome: reservation.outcome,
421
- record: reservation.record,
422
- recordDigest: reservation.recordDigest,
423
- replayed: false
424
- })), withSubmission(current, {
425
- ...stored,
426
- row,
427
- reservation
428
- })];
429
- });
430
- if (decision._tag === "failure") return yield* decision.error;
431
- return decision.value;
432
- }));
433
- const finalizeSettlement = Effect.fn("MemorySubmissionLedger.finalizeSettlement")((unvalidated) => Effect.gen(function* () {
434
- const request = yield* validate$2(SettlementFinalization, "finalizeSettlement", unvalidated);
435
- const nowMillis = yield* Clock.currentTimeMillis;
436
- const decision = yield* Ref.modify(state, (current) => {
437
- const stored = current.submissions.get(request.submissionId);
438
- if (stored === void 0) return [failure(ledgerError("finalizeSettlement", `Unknown Submission ${request.submissionId}`)), current];
439
- const reservation = stored.reservation;
440
- if (reservation === void 0) return [failure(ledgerError("finalizeSettlement", `No settlement reservation for Submission ${request.submissionId}`)), current];
441
- const settlementFailure = settlementFailureFromRecord(reservation.record);
442
- if (reservation.outcome === "failed" !== (settlementFailure !== void 0)) return [failure(ledgerError("finalizeSettlement", `Settlement reservation for Submission ${request.submissionId} has contradictory failure evidence`)), current];
443
- if (reservation.settlementId !== request.settlementId) return [failure(SettlementConflict.make({
444
- submissionId: request.submissionId,
445
- existingOutcome: reservation.outcome
446
- })), current];
447
- if (reservation.finalizedAtMillis !== void 0) return [success(Settlement.make({
448
- submissionId: stored.row.submissionId,
449
- settlementId: reservation.settlementId,
450
- receiptId: stored.row.receiptId,
451
- outcome: reservation.outcome,
452
- ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
453
- settledAt: utc(reservation.finalizedAtMillis)
454
- })), current];
455
- const next = withSubmission(current, {
456
- ...stored,
457
- row: {
458
- ...stored.row,
459
- state: "settled",
460
- settledOutcome: reservation.outcome
461
- },
462
- ownership: void 0,
463
- reservation: {
464
- ...reservation,
465
- finalizedAtMillis: nowMillis
466
- }
467
- });
468
- return [success(Settlement.make({
469
- submissionId: stored.row.submissionId,
470
- settlementId: reservation.settlementId,
471
- receiptId: stored.row.receiptId,
472
- outcome: reservation.outcome,
473
- ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
474
- settledAt: utc(nowMillis)
475
- })), next];
476
- });
477
- if (decision._tag === "failure") return yield* decision.error;
478
- return decision.value;
479
- }));
480
- const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
481
- const request = yield* validate$2(AbortCommand, "requestAbort", unvalidated);
482
- const nowMillis = yield* Clock.currentTimeMillis;
483
- const decision = yield* Ref.modify(state, (current) => {
484
- const stored = current.submissions.get(request.submissionId);
485
- if (stored === void 0) return [failure(ledgerError("requestAbort", `Unknown Submission ${request.submissionId}`)), current];
486
- if (stored.row.state === "joined") {
487
- if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("requestAbort", `Joined Submission ${request.submissionId} is missing its host linkage`)), current];
488
- return [failure(JoinedToHost.make({
489
- submissionId: request.submissionId,
490
- hostSubmissionId: stored.joinedHostSubmissionId
491
- })), current];
492
- }
493
- if (stored.row.state === "settled") {
494
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("requestAbort", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
495
- return [failure(SettlementConflict.make({
496
- submissionId: request.submissionId,
497
- existingOutcome: stored.row.settledOutcome
498
- })), current];
499
- }
500
- if (stored.abortIntent !== void 0) return [success(stored.abortIntent), current];
501
- const intent = AbortIntent.make({
502
- submissionId: request.submissionId,
503
- author: request.author,
504
- reason: request.reason,
505
- requestedAt: utc(nowMillis)
506
- });
507
- return [success(intent), withSubmission(current, {
508
- ...stored,
509
- abortIntent: intent
510
- })];
511
- });
512
- if (decision._tag === "failure") return yield* decision.error;
513
- return decision.value;
514
- }));
515
- const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
516
- const request = yield* validate$2(ClaimJoiningRequest, "claimJoining", unvalidated);
517
- const decision = yield* Ref.modify(state, (current) => {
518
- const host = current.submissions.get(request.hostSubmissionId);
519
- if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
520
- if (host.row.threadId !== request.threadId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`)), current];
521
- if (!ownsLane(host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
522
- 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);
523
- const claims = [];
524
- const submissions = new Map(current.submissions);
525
- for (const stored of later) {
526
- if (claims.length >= request.maxCount) break;
527
- if ((stored.row.state === "joining" || stored.row.state === "joined") && stored.joinedHostSubmissionId === request.hostSubmissionId) continue;
528
- if (stored.row.state === "settled" && stored.row.settledOutcome === "aborted") continue;
529
- if (stored.row.state !== "ready") break;
530
- submissions.set(stored.row.submissionId, {
531
- ...stored,
532
- row: {
533
- ...stored.row,
534
- state: "joining"
535
- },
536
- joinedHostSubmissionId: request.hostSubmissionId
537
- });
538
- claims.push(JoiningClaim.make({
539
- submissionId: stored.row.submissionId,
540
- queueSequence: stored.row.queueSequence,
541
- inputPayload: stored.row.inputPayload
542
- }));
543
- }
544
- return [success(claims), {
545
- ...current,
546
- submissions
547
- }];
548
- });
549
- if (decision._tag === "failure") return yield* decision.error;
550
- return decision.value;
551
- }));
552
- const markJoined = Effect.fn("MemorySubmissionLedger.markJoined")((unvalidated) => Effect.gen(function* () {
553
- const request = yield* validate$2(MarkJoinedRequest, "markJoined", unvalidated);
554
- const decision = yield* Ref.modify(state, (current) => {
555
- const stored = current.submissions.get(request.submissionId);
556
- if (stored === void 0) return [failure(ledgerError("markJoined", `Unknown Submission ${request.submissionId}`)), current];
557
- if (stored.joinedHostSubmissionId === void 0) return [failure(ledgerError("markJoined", `Submission ${request.submissionId} was never claimed for joining`)), current];
558
- const host = current.submissions.get(stored.joinedHostSubmissionId);
559
- if (host === void 0) return [failure(ledgerError("markJoined", `Host Submission ${stored.joinedHostSubmissionId} is missing`)), current];
560
- if (!ownsLane(host, request.ownershipToken)) return [failure(ownershipLost(current, host)), current];
561
- if (stored.inputApplied !== void 0) {
562
- if (stored.inputApplied.recordId === request.recordId && stored.inputApplied.sequence === request.sequence) return [success(void 0), current];
563
- return [failure(ledgerError("markJoined", `A different join marker is already recorded for Submission ${request.submissionId}`)), current];
564
- }
565
- 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];
566
- const marker = InputAppliedMarker.make({
567
- recordId: request.recordId,
568
- sequence: request.sequence
569
- });
570
- return [success(void 0), withSubmission(current, {
571
- ...stored,
572
- row: {
573
- ...stored.row,
574
- state: "joined"
575
- },
576
- inputApplied: marker
577
- })];
578
- });
579
- if (decision._tag === "failure") return yield* decision.error;
580
- }));
581
- const revertJoining = Effect.fn("MemorySubmissionLedger.revertJoining")((unvalidated) => Effect.gen(function* () {
582
- const request = yield* validate$2(RevertJoiningRequest, "revertJoining", unvalidated);
583
- const decision = yield* Ref.modify(state, (current) => {
584
- const stored = current.submissions.get(request.submissionId);
585
- if (stored === void 0) return [failure(ledgerError("revertJoining", `Unknown Submission ${request.submissionId}`)), current];
586
- if (stored.row.state !== "joining") return [success(void 0), current];
587
- return [success(void 0), withSubmission(current, {
588
- ...stored,
589
- row: {
590
- ...stored.row,
591
- state: "ready"
592
- },
593
- joinedHostSubmissionId: void 0
594
- })];
595
- });
596
- if (decision._tag === "failure") return yield* decision.error;
597
- }));
598
- const suspend = Effect.fn("MemorySubmissionLedger.suspend")((unvalidated) => Effect.gen(function* () {
599
- const request = yield* validate$2(SuspendRequest, "suspend", unvalidated);
600
- const nowMillis = yield* Clock.currentTimeMillis;
601
- const decision = yield* Ref.modify(state, (current) => {
602
- const stored = current.submissions.get(request.submissionId);
603
- if (stored === void 0) return [failure(ledgerError("suspend", `Unknown Submission ${request.submissionId}`)), current];
604
- if (stored.row.state === "settled") {
605
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("suspend", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
606
- return [failure(SettlementConflict.make({
607
- submissionId: request.submissionId,
608
- existingOutcome: stored.row.settledOutcome
609
- })), current];
610
- }
611
- if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
612
- submissionId: request.submissionId,
613
- existingOutcome: stored.reservation.outcome
614
- })), current];
615
- if (!ownsLane(stored, request.ownershipToken)) return [failure(ownershipLost(current, stored)), current];
616
- 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];
617
- return [success("suspended"), withSubmission(current, {
618
- ...stored,
619
- row: {
620
- ...stored.row,
621
- state: "suspended"
622
- },
623
- ownership: void 0,
624
- suspension: {
625
- reason: request.reason,
626
- suspendedAtMillis: nowMillis
627
- }
628
- })];
629
- });
630
- if (decision._tag === "failure") return yield* decision.error;
631
- return decision.value;
632
- }));
633
- const recordApprovalDecision = Effect.fn("MemorySubmissionLedger.recordApprovalDecision")((unvalidated) => Effect.gen(function* () {
634
- const command = yield* validate$2(ApprovalDecisionCommand, "recordApprovalDecision", unvalidated);
635
- const nowMillis = yield* Clock.currentTimeMillis;
636
- const decision = yield* Ref.modify(state, (current) => {
637
- const stored = current.submissions.get(command.submissionId);
638
- if (stored === void 0) return [failure(ledgerError("recordApprovalDecision", `Unknown Submission ${command.submissionId}`)), current];
639
- if (stored.row.state === "settled") {
640
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordApprovalDecision", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
641
- return [failure(SettlementConflict.make({
642
- submissionId: command.submissionId,
643
- existingOutcome: stored.row.settledOutcome
644
- })), current];
645
- }
646
- const existing = stored.approvalDecisions.get(command.toolCallId);
647
- if (existing !== void 0) {
648
- if (existing.decision !== command.decision) return [failure(ApprovalConflict.make({
649
- submissionId: command.submissionId,
650
- toolCallId: command.toolCallId,
651
- existingDecision: existing.decision
652
- })), current];
653
- return [success(existing), current];
654
- }
655
- const intent = ApprovalDecisionIntent.make({
656
- submissionId: command.submissionId,
657
- toolCallId: command.toolCallId,
658
- decision: command.decision,
659
- resolver: command.resolver,
660
- reason: command.reason,
661
- decidedAt: utc(nowMillis)
662
- });
663
- const approvalDecisions = new Map(stored.approvalDecisions).set(command.toolCallId, intent);
664
- const wakes = stored.row.state === "suspended" && stored.suspension !== void 0 && stored.suspension.reason._tag === "ApprovalPending" && stored.suspension.reason.toolCallIds.every((toolCallId) => approvalDecisions.has(toolCallId));
665
- return [success(intent), withSubmission(current, {
666
- ...stored,
667
- row: wakes ? {
668
- ...stored.row,
669
- state: "input-applied"
670
- } : stored.row,
671
- suspension: wakes ? void 0 : stored.suspension,
672
- approvalDecisions
673
- })];
674
- });
675
- if (decision._tag === "failure") return yield* decision.error;
676
- return decision.value;
677
- }));
678
- const markUnknown = Effect.fn("MemorySubmissionLedger.markUnknown")((unvalidated) => Effect.gen(function* () {
679
- const request = yield* validate$2(MarkUnknownRequest, "markUnknown", unvalidated);
680
- const decision = yield* Ref.modify(state, (current) => {
681
- const stored = current.submissions.get(request.submissionId);
682
- if (stored === void 0) return [failure(ledgerError("markUnknown", `Unknown Submission ${request.submissionId}`)), current];
683
- if (stored.row.state === "settled") {
684
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("markUnknown", `Settled Submission ${request.submissionId} is missing its outcome`)), current];
685
- return [failure(SettlementConflict.make({
686
- submissionId: request.submissionId,
687
- existingOutcome: stored.row.settledOutcome
688
- })), current];
689
- }
690
- if (stored.reservation !== void 0) return [failure(SettlementConflict.make({
691
- submissionId: request.submissionId,
692
- existingOutcome: stored.reservation.outcome
693
- })), current];
694
- const existing = stored.unknownMark;
695
- const known = new Set(existing?.toolCallIds ?? []);
696
- const merged = [...existing?.toolCallIds ?? [], ...request.toolCallIds.filter((toolCallId) => !known.has(toolCallId))];
697
- return [success(void 0), withSubmission(current, {
698
- ...stored,
699
- row: stored.row.state === "unknown" ? stored.row : {
700
- ...stored.row,
701
- state: "unknown"
702
- },
703
- unknownMark: {
704
- reason: existing?.reason ?? request.reason,
705
- toolCallIds: merged
706
- }
707
- })];
708
- });
709
- if (decision._tag === "failure") return yield* decision.error;
710
- }));
711
- const recordUnknownResolution = Effect.fn("MemorySubmissionLedger.recordUnknownResolution")((unvalidated) => Effect.gen(function* () {
712
- const command = yield* validate$2(UnknownResolutionCommand, "recordUnknownResolution", unvalidated);
713
- const nowMillis = yield* Clock.currentTimeMillis;
714
- const decision = yield* Ref.modify(state, (current) => {
715
- const stored = current.submissions.get(command.submissionId);
716
- if (stored === void 0) return [failure(ledgerError("recordUnknownResolution", `Unknown Submission ${command.submissionId}`)), current];
717
- if (stored.row.state === "settled") {
718
- if (stored.row.settledOutcome === void 0) return [failure(ledgerError("recordUnknownResolution", `Settled Submission ${command.submissionId} is missing its outcome`)), current];
719
- return [failure(SettlementConflict.make({
720
- submissionId: command.submissionId,
721
- existingOutcome: stored.row.settledOutcome
722
- })), current];
723
- }
724
- const existing = stored.unknownResolutions.get(command.toolCallId);
725
- if (existing !== void 0 && !equivalentUnknownResolution(existing.intent.resolution, command.resolution)) return [failure(UnknownResolutionConflict.make({
726
- submissionId: command.submissionId,
727
- toolCallId: command.toolCallId
728
- })), current];
729
- const intent = existing?.intent ?? UnknownResolutionIntent.make({
730
- submissionId: command.submissionId,
731
- toolCallId: command.toolCallId,
732
- author: command.author,
733
- reason: command.reason,
734
- resolution: command.resolution,
735
- resolvedAt: utc(nowMillis)
736
- });
737
- const unknownResolutions = existing !== void 0 ? stored.unknownResolutions : new Map(stored.unknownResolutions).set(command.toolCallId, { intent });
738
- const wakes = stored.row.state === "unknown" && stored.unknownMark !== void 0 && stored.unknownMark.toolCallIds.every((toolCallId) => unknownResolutions.has(toolCallId));
739
- return [success(intent), withSubmission(current, {
740
- ...stored,
741
- row: wakes ? {
742
- ...stored.row,
743
- state: "input-applied"
744
- } : stored.row,
745
- unknownMark: wakes ? void 0 : stored.unknownMark,
746
- unknownResolutions
747
- })];
748
- });
749
- if (decision._tag === "failure") return yield* decision.error;
750
- return decision.value;
751
- }));
752
- const recordChildSettled = Effect.fn("MemorySubmissionLedger.recordChildSettled")((unvalidated) => Effect.gen(function* () {
753
- const request = yield* validate$2(ChildSettledNotification, "recordChildSettled", unvalidated);
754
- const decision = yield* Ref.modify(state, (current) => {
755
- const parent = current.submissions.get(request.parentSubmissionId);
756
- if (parent === void 0) return [failure(ledgerError("recordChildSettled", `Unknown Submission ${request.parentSubmissionId}`)), current];
757
- const child = current.submissions.get(request.childSubmissionId);
758
- 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];
759
- if (parent.row.state !== "suspended" || parent.suspension === void 0 || parent.suspension.reason._tag !== "WaitingForChild") return [success("not-waiting"), current];
760
- const children = parent.suspension.reason.children;
761
- if (!children.some((entry) => entry.childSubmissionId === request.childSubmissionId)) return [success("not-waiting"), current];
762
- if (!children.every((entry) => {
763
- const listed = current.submissions.get(entry.childSubmissionId);
764
- return listed?.row.state === "settled" || listed?.row.state === "terminalizing" && listed.reservation !== void 0;
765
- })) return [success("still-waiting"), current];
766
- return [success("woken"), withSubmission(current, {
767
- ...parent,
768
- row: {
769
- ...parent.row,
770
- state: "input-applied"
771
- },
772
- suspension: void 0
773
- })];
774
- });
775
- if (decision._tag === "failure") return yield* decision.error;
776
- return decision.value;
777
- }));
778
- const reserveChildBudget = Effect.fn("MemorySubmissionLedger.reserveChildBudget")((unvalidated) => Effect.gen(function* () {
779
- const request = yield* validate$2(ChildBudgetReservationRequest, "reserveChildBudget", unvalidated);
780
- const nowMillis = yield* Clock.currentTimeMillis;
781
- const decision = yield* Ref.modify(state, (current) => {
782
- const existing = current.childReservations.get(request.reservationId);
783
- if (existing !== void 0) {
784
- if (!(existing.parentSubmissionId === request.parentSubmissionId && existing.parentToolCallId === request.parentToolCallId && existing.allocationDigest === request.allocationDigest && equivalentPersistedJson(existing.allocation, request.allocation))) return [failure(ChildReservationConflict.make({
785
- reservationId: request.reservationId,
786
- status: existing.status,
787
- message: "A reservation with this identity exists with a different parent Tool Call or allocation."
788
- })), current];
789
- return [success(ReservedChildBudget.make({
790
- reservation: toReservationSnapshot(existing),
791
- replayed: true
792
- })), current];
793
- }
794
- for (const reservation of current.childReservations.values()) if (reservation.parentSubmissionId === request.parentSubmissionId && reservation.parentToolCallId === request.parentToolCallId) return [failure(ChildReservationConflict.make({
795
- reservationId: request.reservationId,
796
- status: reservation.status,
797
- message: `Parent Tool Call ${request.parentToolCallId} already owns reservation ${reservation.reservationId}.`
798
- })), current];
799
- const parent = current.submissions.get(request.parentSubmissionId);
800
- if (parent === void 0) return [failure(ledgerError("reserveChildBudget", `Unknown Submission ${request.parentSubmissionId}`)), current];
801
- if (!ownsLane(parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
802
- const reservation = {
803
- reservationId: request.reservationId,
804
- parentSubmissionId: request.parentSubmissionId,
805
- parentToolCallId: request.parentToolCallId,
806
- childSubmissionId: void 0,
807
- status: "reserved",
808
- allocation: request.allocation,
809
- allocationDigest: request.allocationDigest,
810
- accounting: void 0,
811
- reservedAtMillis: nowMillis,
812
- releaseBeganAtMillis: void 0,
813
- releasedAtMillis: void 0
814
- };
815
- return [success(ReservedChildBudget.make({
816
- reservation: toReservationSnapshot(reservation),
817
- replayed: false
818
- })), withChildReservation(current, reservation)];
819
- });
820
- if (decision._tag === "failure") return yield* decision.error;
821
- return decision.value;
822
- }));
823
- const attachChildToReservation = Effect.fn("MemorySubmissionLedger.attachChildToReservation")((unvalidated) => Effect.gen(function* () {
824
- const request = yield* validate$2(AttachChildToReservationRequest, "attachChildToReservation", unvalidated);
825
- const decision = yield* Ref.modify(state, (current) => {
826
- const reservation = current.childReservations.get(request.reservationId);
827
- if (reservation === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown child reservation ${request.reservationId}`)), current];
828
- if (reservation.childSubmissionId !== void 0) {
829
- if (reservation.childSubmissionId === request.childSubmissionId) return [success(toReservationSnapshot(reservation)), current];
830
- return [failure(ChildReservationConflict.make({
831
- reservationId: request.reservationId,
832
- status: reservation.status,
833
- message: `Reservation ${request.reservationId} already records child ${reservation.childSubmissionId}.`
834
- })), current];
835
- }
836
- const parent = current.submissions.get(reservation.parentSubmissionId);
837
- if (parent === void 0) return [failure(ledgerError("attachChildToReservation", `Unknown Submission ${reservation.parentSubmissionId}`)), current];
838
- if (!ownsLane(parent, request.ownershipToken)) return [failure(ownershipLost(current, parent)), current];
839
- if (reservation.status !== "reserved") return [failure(ChildReservationConflict.make({
840
- reservationId: request.reservationId,
841
- status: reservation.status,
842
- message: `Cannot attach a child to a ${reservation.status} reservation.`
843
- })), current];
844
- if (!current.submissions.has(request.childSubmissionId)) return [failure(ledgerError("attachChildToReservation", `Unknown child Submission ${request.childSubmissionId}`)), current];
845
- const attached = {
846
- ...reservation,
847
- childSubmissionId: request.childSubmissionId
848
- };
849
- return [success(toReservationSnapshot(attached)), withChildReservation(current, attached)];
850
- });
851
- if (decision._tag === "failure") return yield* decision.error;
852
- return decision.value;
853
- }));
854
- const beginChildBudgetRelease = Effect.fn("MemorySubmissionLedger.beginChildBudgetRelease")((unvalidated) => Effect.gen(function* () {
855
- const request = yield* validate$2(BeginChildBudgetReleaseRequest, "beginChildBudgetRelease", unvalidated);
856
- const nowMillis = yield* Clock.currentTimeMillis;
857
- const decision = yield* Ref.modify(state, (current) => {
858
- const reservation = current.childReservations.get(request.reservationId);
859
- if (reservation === void 0) return [failure(ledgerError("beginChildBudgetRelease", `Unknown child reservation ${request.reservationId}`)), current];
860
- if (reservation.status !== "reserved") {
861
- if (reservation.accounting !== void 0 && equivalentPersistedJson(reservation.accounting, request.accounting)) return [success(toReservationSnapshot(reservation)), current];
862
- return [failure(ChildReservationConflict.make({
863
- reservationId: request.reservationId,
864
- status: reservation.status,
865
- message: "A different accounting decision is already frozen for this reservation."
866
- })), current];
867
- }
868
- const frozen = {
869
- ...reservation,
870
- status: "releasePending",
871
- accounting: request.accounting,
872
- releaseBeganAtMillis: nowMillis
873
- };
874
- return [success(toReservationSnapshot(frozen)), withChildReservation(current, frozen)];
875
- });
876
- if (decision._tag === "failure") return yield* decision.error;
877
- return decision.value;
878
- }));
879
- const releaseChildBudget = Effect.fn("MemorySubmissionLedger.releaseChildBudget")((unvalidated) => Effect.gen(function* () {
880
- const request = yield* validate$2(ReleaseChildBudgetRequest, "releaseChildBudget", unvalidated);
881
- const nowMillis = yield* Clock.currentTimeMillis;
882
- const decision = yield* Ref.modify(state, (current) => {
883
- const reservation = current.childReservations.get(request.reservationId);
884
- if (reservation === void 0) return [failure(ledgerError("releaseChildBudget", `Unknown child reservation ${request.reservationId}`)), current];
885
- if (reservation.status === "released") return [success(toReservationSnapshot(reservation)), current];
886
- if (reservation.status !== "releasePending") return [failure(ChildReservationConflict.make({
887
- reservationId: request.reservationId,
888
- status: reservation.status,
889
- message: "Cannot release a reservation whose accounting decision is not frozen."
890
- })), current];
891
- const released = {
892
- ...reservation,
893
- status: "released",
894
- releasedAtMillis: nowMillis
895
- };
896
- return [success(toReservationSnapshot(released)), withChildReservation(current, released)];
897
- });
898
- if (decision._tag === "failure") return yield* decision.error;
899
- return decision.value;
900
- }));
901
- const scanNonterminal = Stream.unwrap(Ref.get(state).pipe(Effect.map((current) => {
902
- 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((stored) => toSnapshot(stored.row));
903
- return Stream.fromIterable(snapshots);
904
- })));
905
- const loadRecoverySnapshot = Effect.fn("MemorySubmissionLedger.loadRecoverySnapshot")((unvalidated) => Effect.gen(function* () {
906
- const request = yield* validate$2(RecoverySnapshotRequest, "loadRecoverySnapshot", unvalidated);
907
- const current = yield* Ref.get(state);
908
- const stored = current.submissions.get(request.submissionId);
909
- if (stored === void 0) return yield* ledgerError("loadRecoverySnapshot", `Unknown Submission ${request.submissionId}`);
910
- const joins = [...current.submissions.values()].filter((candidate) => candidate.joinedHostSubmissionId === request.submissionId).sort((left, right) => left.row.queueSequence - right.row.queueSequence).map((candidate) => JoinSnapshot.make({
911
- submissionId: candidate.row.submissionId,
912
- state: candidate.row.state,
913
- hostSubmissionId: request.submissionId
914
- }));
915
- const byToolCallId = (left, right) => left.toolCallId < right.toolCallId ? -1 : left.toolCallId > right.toolCallId ? 1 : 0;
916
- 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);
917
- const childAttachments = [];
918
- for (const reservation of childReservations) {
919
- if (reservation.childSubmissionId === void 0) continue;
920
- const child = current.submissions.get(reservation.childSubmissionId);
921
- if (child === void 0) continue;
922
- childAttachments.push(ChildAttachmentSnapshot.make({
923
- toolCallId: reservation.parentToolCallId,
924
- childSubmissionId: reservation.childSubmissionId,
925
- childState: child.row.state,
926
- ...child.row.settledOutcome === void 0 ? {} : { childOutcome: child.row.settledOutcome }
927
- }));
928
- }
929
- return RecoverySnapshot.make({
930
- submission: toSnapshot(stored.row),
931
- joins,
932
- approvalDecisions: [...stored.approvalDecisions.values()].sort(byToolCallId),
933
- unknownResolutions: [...stored.unknownResolutions.values()].map((resolution) => resolution.intent).sort(byToolCallId),
934
- childReservations: childReservations.map(toReservationSnapshot),
935
- childAttachments,
936
- ...stored.row.parentLinkage === void 0 ? {} : { parentLinkage: stored.row.parentLinkage },
937
- ...stored.joinedHostSubmissionId === void 0 ? {} : { hostSubmissionId: stored.joinedHostSubmissionId },
938
- ...stored.suspension === void 0 ? {} : { suspension: SuspensionSnapshot.make({
939
- reason: stored.suspension.reason,
940
- suspendedAt: utc(stored.suspension.suspendedAtMillis)
941
- }) },
942
- ...stored.ownership === void 0 ? {} : { ownership: OwnershipSnapshot.make({
943
- attemptId: stored.ownership.attemptId,
944
- ownerProducerId: stored.ownership.ownerProducerId,
945
- producerEpoch: stored.ownership.producerEpoch,
946
- leaseExpiresAt: utc(stored.ownership.leaseExpiresAtMillis)
947
- }) },
948
- ...stored.inputApplied === void 0 ? {} : { inputApplied: stored.inputApplied },
949
- ...stored.reservation === void 0 ? {} : { reservation: SettlementReservationSnapshot.make({
950
- settlementId: stored.reservation.settlementId,
951
- outcome: stored.reservation.outcome,
952
- record: stored.reservation.record,
953
- recordDigest: stored.reservation.recordDigest,
954
- finalized: stored.reservation.finalizedAtMillis !== void 0
955
- }) },
956
- ...stored.abortIntent === void 0 ? {} : { abortIntent: stored.abortIntent }
957
- });
958
- }));
959
- return SubmissionLedger.of({
960
- capabilities,
961
- admit,
962
- markReady,
963
- lookup,
964
- resolveAdmission,
965
- claim,
966
- renewOwnership,
967
- releaseOwnership,
968
- markInputApplied,
969
- reserveSettlement,
970
- finalizeSettlement,
971
- requestAbort,
972
- claimJoining,
973
- markJoined,
974
- revertJoining,
975
- suspend,
976
- recordApprovalDecision,
977
- markUnknown,
978
- recordUnknownResolution,
979
- recordChildSettled,
980
- reserveChildBudget,
981
- attachChildToReservation,
982
- beginChildBudgetRelease,
983
- releaseChildBudget,
984
- scanNonterminal,
985
- loadRecoverySnapshot
986
- });
987
- });
988
- /**
989
- * In-memory reference SubmissionLedger Layer (durability `non-durable`). All state lives in one
990
- * `Ref` owned by the Layer's Scope; no daemon fibers are spawned and no wall clock is consulted.
991
- */
992
- const memorySubmissionLedgerLayer = (options = {}) => Layer.effect(SubmissionLedger, makeSubmissionLedger(options));
993
- const MemorySubmissionLedgerLive = memorySubmissionLedgerLayer();
994
- //#endregion
995
- //#region src/memory-schedule-store.ts
996
- const storageError = (operation, reason) => ScheduleStorageError.make({
997
- operation,
998
- reason
999
- });
1000
- const encodeRecord = (operation, record) => Effect.try({
1001
- try: () => Schema.encodeSync(Schema.fromJsonString(ScheduleRecord))(record),
1002
- catch: () => storageError(operation, "corrupt")
1003
- });
1004
- const decodeRecord = (operation, encoded) => Effect.try({
1005
- try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(encoded),
1006
- catch: () => storageError(operation, "corrupt")
1007
- });
1008
- const decodeInput = (operation, schema, value) => Effect.try({
1009
- try: () => Schema.decodeUnknownSync(schema)(value),
1010
- catch: () => storageError(operation, "corrupt")
1011
- });
1012
- const sameOwner = (record, owner) => scheduleOwnerKey(record.owner) === scheduleOwnerKey(owner);
1013
- const makeScheduleStore = Effect.gen(function* () {
1014
- const state = yield* Ref.make({ records: /* @__PURE__ */ new Map() });
1015
- const failpoint = yield* ScheduleFailpoint;
1016
- const insert = Effect.fn("MemoryScheduleStore.insert")((record, ownerLimit) => Effect.gen(function* () {
1017
- const encoded = yield* encodeRecord("insert", record);
1018
- yield* failpoint.hit("schedule:insert:before");
1019
- const decision = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1020
- const key = scheduleKeyString(record);
1021
- const existingText = current.records.get(key);
1022
- if (existingText !== void 0) {
1023
- const decoded = Result.try({
1024
- try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(existingText),
1025
- catch: () => storageError("insert", "corrupt")
1026
- });
1027
- if (Result.isFailure(decoded)) return [Result.fail(decoded.failure), current];
1028
- if (decoded.success.creationFingerprint === record.creationFingerprint) return [Result.succeed(decoded.success), current];
1029
- return [Result.fail(ScheduleConflict.make({
1030
- reason: "creation",
1031
- key: scheduleKeyOf(record)
1032
- })), current];
1033
- }
1034
- let ownerCount = 0;
1035
- for (const text of current.records.values()) {
1036
- const decoded = Result.try({
1037
- try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
1038
- catch: () => storageError("insert", "corrupt")
1039
- });
1040
- if (Result.isFailure(decoded)) return [Result.fail(decoded.failure), current];
1041
- if (sameOwner(decoded.success, record.owner) && scheduleUsesCapacity(decoded.success)) ownerCount += 1;
1042
- }
1043
- if (ownerCount >= ownerLimit) return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
1044
- const records = new Map(current.records);
1045
- records.set(key, encoded);
1046
- const next = { records };
1047
- return [Result.succeed(record), next];
1048
- }));
1049
- const inserted = yield* Effect.fromResult(decision);
1050
- yield* failpoint.hit("schedule:insert:after");
1051
- return yield* decodeRecord("insert", yield* encodeRecord("insert", inserted));
1052
- }));
1053
- const get = Effect.fn("MemoryScheduleStore.get")(function* (key) {
1054
- const decodedKey = yield* decodeInput("get", ScheduleKey, key);
1055
- const text = (yield* Ref.get(state)).records.get(scheduleKeyString(decodedKey));
1056
- return text === void 0 ? null : yield* decodeRecord("get", text);
1057
- });
1058
- const list = Effect.fn("MemoryScheduleStore.list")(function* (request) {
1059
- const decodedRequest = yield* decodeInput("list", SchedulePageRequest, request);
1060
- const records = [];
1061
- for (const text of (yield* Ref.get(state)).records.values()) {
1062
- const record = yield* decodeRecord("list", text);
1063
- if (sameOwner(record, decodedRequest.owner) && (decodedRequest.after === void 0 || compareScheduleNames(record.scheduleId, decodedRequest.after) > 0)) records.push(record);
1064
- }
1065
- records.sort((left, right) => compareScheduleNames(left.scheduleId, right.scheduleId));
1066
- const hasNext = records.length > decodedRequest.limit;
1067
- const items = records.slice(0, decodedRequest.limit);
1068
- return {
1069
- items,
1070
- next: hasNext ? items.at(-1)?.scheduleId ?? null : null
1071
- };
1072
- });
1073
- const change = Effect.fn("MemoryScheduleStore.change")((key, command, ownerLimit = defaultSchedulingLimits.maxSchedulesPerOwner) => Effect.gen(function* () {
1074
- const decodedKey = yield* decodeInput("change", ScheduleKey, key);
1075
- const decodedCommand = yield* decodeInput("change", ScheduleChange, command);
1076
- yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:before`);
1077
- const decision = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1078
- const storageKey = scheduleKeyString(decodedKey);
1079
- const text = current.records.get(storageKey);
1080
- if (text === void 0) return [Result.fail(ScheduleNotFound.make({ key: decodedKey })), current];
1081
- const decoded = Result.try({
1082
- try: () => Schema.decodeSync(Schema.fromJsonString(ScheduleRecord))(text),
1083
- catch: () => storageError("change", "corrupt")
1084
- });
1085
- if (Result.isFailure(decoded)) return [Result.fail(decoded.failure), current];
1086
- const applied = applyScheduleChange(decoded.success, decodedCommand);
1087
- if (Result.isFailure(applied)) return [Result.fail(applied.failure), current];
1088
- if (applied.success === decoded.success) return [Result.succeed(decoded.success), current];
1089
- if (!scheduleUsesCapacity(decoded.success) && scheduleUsesCapacity(applied.success)) {
1090
- let count = 0;
1091
- for (const text of current.records.values()) {
1092
- const candidate = Schema.decodeUnknownResult(Schema.fromJsonString(ScheduleRecord))(text);
1093
- if (Result.isFailure(candidate)) return [Result.fail(storageError("change", "corrupt")), current];
1094
- if (sameOwner(candidate.success, decodedKey.owner) && scheduleUsesCapacity(candidate.success)) count += 1;
1095
- }
1096
- if (count >= ownerLimit) return [Result.fail(ScheduleCapacityError.make({ limit: ownerLimit })), current];
1097
- }
1098
- const encoded = Result.try({
1099
- try: () => Schema.encodeSync(Schema.fromJsonString(ScheduleRecord))(applied.success),
1100
- catch: () => storageError("change", "corrupt")
1101
- });
1102
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1103
- const records = new Map(current.records);
1104
- records.set(storageKey, encoded.success);
1105
- const next = { records };
1106
- return [Result.succeed(applied.success), next];
1107
- }));
1108
- const changed = yield* Effect.fromResult(decision);
1109
- yield* failpoint.hit(`schedule:${decodedCommand._tag.toLowerCase()}:after`);
1110
- return yield* decodeRecord("change", yield* encodeRecord("change", changed));
1111
- }));
1112
- const due = Effect.fn("MemoryScheduleStore.due")(function* (nowMillis, limit, owner, after) {
1113
- const decodedOwner = owner === void 0 ? void 0 : yield* decodeInput("due", ScheduleOwner, owner);
1114
- const cursor = after === void 0 ? void 0 : yield* decodeInput("due", ScheduleDueCursor, after);
1115
- const records = [];
1116
- for (const text of (yield* Ref.get(state)).records.values()) {
1117
- const record = yield* decodeRecord("due", text);
1118
- const deadline = scheduleDeadline(record);
1119
- if (deadline !== null && deadline <= nowMillis && (decodedOwner === void 0 || sameOwner(record, decodedOwner)) && (cursor === void 0 || deadline > cursor.deadlineAtMillis || deadline === cursor.deadlineAtMillis && compareScheduleKeys(record, cursor) > 0)) records.push({
1120
- ...scheduleKeyOf(record),
1121
- deadlineAtMillis: deadline
1122
- });
1123
- }
1124
- records.sort((left, right) => {
1125
- const byDeadline = left.deadlineAtMillis - right.deadlineAtMillis;
1126
- return byDeadline !== 0 ? byDeadline : compareScheduleKeys(left, right);
1127
- });
1128
- return records.slice(0, limit);
1129
- });
1130
- const nextDeadline = Effect.fn("MemoryScheduleStore.nextDeadline")(function* (owner) {
1131
- const decodedOwner = owner === void 0 ? void 0 : yield* decodeInput("nextDeadline", ScheduleOwner, owner);
1132
- let earliest = null;
1133
- for (const text of (yield* Ref.get(state)).records.values()) {
1134
- const record = yield* decodeRecord("nextDeadline", text);
1135
- if (decodedOwner !== void 0 && !sameOwner(record, decodedOwner)) continue;
1136
- const deadline = scheduleDeadline(record);
1137
- if (deadline !== null && (earliest === null || deadline < earliest)) earliest = deadline;
1138
- }
1139
- return earliest;
1140
- });
1141
- return ScheduleStore.of({
1142
- insert,
1143
- get,
1144
- list,
1145
- change,
1146
- due,
1147
- nextDeadline
1148
- });
1149
- });
1150
- const memoryScheduleStoreLayer = () => Layer.effect(ScheduleStore, makeScheduleStore);
1151
- const MemoryScheduleStoreLive = memoryScheduleStoreLayer();
1152
- //#endregion
1153
- //#region src/memory-subscription-store.ts
1154
- const error$1 = (reason, code) => SubscriptionError.make({
1155
- reason,
1156
- code
1157
- });
1158
- const samePartition = (left, right) => left.tenantId === right.tenantId && left.address === right.address;
1159
- const sameSource$1 = (left, right) => left.name === right.name && left.version === right.version;
1160
- const encode = (schema, value, code) => Result.try({
1161
- try: () => Schema.encodeSync(Schema.fromJsonString(schema))(value),
1162
- catch: () => error$1("corrupt", code)
1163
- });
1164
- const decode = (schema, value, code) => Result.try({
1165
- try: () => Schema.decodeSync(Schema.fromJsonString(schema))(value),
1166
- catch: () => error$1("corrupt", code)
1167
- });
1168
- const decodeEffect = (schema, value, code) => Effect.fromResult(decode(schema, value, code));
1169
- const validate$1 = (schema, value, code) => Schema.decodeUnknownEffect(schema)(value).pipe(Effect.mapError(() => error$1("validation", code)));
1170
- const jsonBytes = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
1171
- const sameDeliveryIdentity = (left, right) => subscriptionDeliveryKeyString(left.key) === subscriptionDeliveryKeyString(right.key) && left.deliveryId === right.deliveryId && left.source.name === right.source.name && left.source.version === right.source.version && left.threadId === right.threadId && left.admissionKey === right.admissionKey && left.subscriptionFingerprint === right.subscriptionFingerprint && left.eventDigest === right.eventDigest;
1172
- const candidateIndexKey = (record) => JSON.stringify([
1173
- record.configuration.source.name,
1174
- record.configuration.source.version,
1175
- record.configuration.matchingKey
1176
- ]);
1177
- const eventCandidateIndexKey = (event) => JSON.stringify([
1178
- event.source.name,
1179
- event.source.version,
1180
- event.matchingKey
1181
- ]);
1182
- const sameEventIdentity = (left, right) => samePartition(left.partition, right.partition) && left.eventId === right.eventId && sameSource$1(left.source, right.source) && left.matchingKey === right.matchingKey && left.payloadDigest === right.payloadDigest;
1183
- const deliveryBelongsTo = (delivery, record, event) => subscriptionKeyString(delivery.key.subscription) === subscriptionKeyString(record.key) && delivery.key.eventId === event.eventId && sameSource$1(delivery.source, event.source);
1184
- const makeMemorySubscriptionStore = Effect.fn("makeMemorySubscriptionStore")(function* (ownedPartition) {
1185
- const partition = yield* validate$1(SourcePartition, ownedPartition, "partition");
1186
- const state = yield* Ref.make({
1187
- sequence: 0,
1188
- registrations: /* @__PURE__ */ new Map(),
1189
- events: /* @__PURE__ */ new Map(),
1190
- deliveries: /* @__PURE__ */ new Map(),
1191
- registrationIndex: /* @__PURE__ */ new Map(),
1192
- candidateIndex: /* @__PURE__ */ new Map(),
1193
- ownerRegistrationCounts: /* @__PURE__ */ new Map(),
1194
- eventIndex: /* @__PURE__ */ new Map(),
1195
- deliveryIndex: /* @__PURE__ */ new Map(),
1196
- ownerDeliveryCounts: /* @__PURE__ */ new Map(),
1197
- scanCursors: {
1198
- events: "",
1199
- deliveries: "",
1200
- recovery: 0
1201
- }
1202
- });
1203
- const failpoint = yield* SubscriptionFailpoint;
1204
- const requirePartition = (value, code) => samePartition(value.partition, partition) ? Effect.succeed(value) : Effect.fail(error$1("validation", code));
1205
- const requireKey = (key, code) => validate$1(SubscriptionKey, key, code).pipe(Effect.flatMap((decoded) => requirePartition(decoded, code)));
1206
- const register = Effect.fn("MemorySubscriptionStore.register")(function* (input, inputLimits) {
1207
- const record = yield* validate$1(SubscriptionRecord, input, "register-record");
1208
- const limits = yield* validate$1(SubscriptionLimits, inputLimits, "register-limits");
1209
- yield* requirePartition(record.key, "register-partition");
1210
- yield* failpoint.hit("subscription:register:before");
1211
- const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1212
- const key = subscriptionKeyString(record.key);
1213
- const existingText = current.registrations.get(key);
1214
- if (existingText !== void 0) {
1215
- const existing = decode(SubscriptionRecord, existingText, "register-existing");
1216
- if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1217
- return existing.success.creationFingerprint === record.creationFingerprint ? [Result.succeed(existing.success), current] : [Result.fail(error$1("conflict", "registration-identity")), current];
1218
- }
1219
- if (jsonBytes(record.configuration.context) > limits.maxContextBytes) return [Result.fail(error$1("capacity", "context-bytes")), current];
1220
- if (jsonBytes(record.configuration.parameters) > limits.maxPayloadBytes) return [Result.fail(error$1("capacity", "parameters-bytes")), current];
1221
- if (record.configuration.expiresAtMillis - record.createdAtMillis > limits.maxLifetimeMillis) return [Result.fail(error$1("capacity", "lifetime")), current];
1222
- if (current.registrations.size >= limits.maxRegistrations) return [Result.fail(error$1("capacity", "registrations")), current];
1223
- const ownerCount = current.ownerRegistrationCounts.get(record.key.ownerId) ?? 0;
1224
- if (ownerCount >= limits.maxRegistrationsPerOwner) return [Result.fail(error$1("capacity", "owner-registrations")), current];
1225
- const assigned = {
1226
- ...record,
1227
- ordinal: current.sequence + 1
1228
- };
1229
- const encoded = encode(SubscriptionRecord, assigned, "register-encode");
1230
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1231
- const registrations = new Map(current.registrations);
1232
- registrations.set(key, encoded.success);
1233
- const registrationIndex = new Map(current.registrationIndex);
1234
- registrationIndex.set(key, {
1235
- key: assigned.key,
1236
- ordinal: assigned.ordinal,
1237
- state: assigned.state,
1238
- recoveryAt: assigned.recovery?.nextAttemptAtMillis ?? null
1239
- });
1240
- const candidateIndex = new Map(current.candidateIndex);
1241
- const candidateKey = candidateIndexKey(assigned);
1242
- candidateIndex.set(candidateKey, [...candidateIndex.get(candidateKey) ?? [], key]);
1243
- const ownerRegistrationCounts = new Map(current.ownerRegistrationCounts);
1244
- ownerRegistrationCounts.set(assigned.key.ownerId, ownerCount + 1);
1245
- return [Result.succeed(assigned), {
1246
- ...current,
1247
- sequence: assigned.ordinal,
1248
- registrations,
1249
- registrationIndex,
1250
- candidateIndex,
1251
- ownerRegistrationCounts
1252
- }];
1253
- })).pipe(Effect.flatMap(Effect.fromResult));
1254
- yield* failpoint.hit("subscription:register:after");
1255
- return result;
1256
- });
1257
- const get = Effect.fn("MemorySubscriptionStore.get")(function* (input) {
1258
- const key = yield* requireKey(input, "get-key");
1259
- const text = (yield* Ref.get(state)).registrations.get(subscriptionKeyString(key));
1260
- return text === void 0 ? null : yield* decodeEffect(SubscriptionRecord, text, "get-record");
1261
- });
1262
- const list = Effect.fn("MemorySubscriptionStore.list")(function* (ownerId, after, limit) {
1263
- if (!Number.isSafeInteger(after) || after < 0 || !Number.isSafeInteger(limit) || limit <= 0) return yield* error$1("validation", "list-page");
1264
- const current = yield* Ref.get(state);
1265
- const records = [];
1266
- for (const [storageKey, indexed] of current.registrationIndex) {
1267
- if (indexed.key.ownerId !== ownerId || indexed.ordinal <= after) continue;
1268
- const text = current.registrations.get(storageKey);
1269
- if (text === void 0) return yield* error$1("corrupt", "list-index");
1270
- records.push(yield* decodeEffect(SubscriptionRecord, text, "list-record"));
1271
- }
1272
- records.sort((a, b) => a.ordinal - b.ordinal);
1273
- return records.slice(0, limit);
1274
- });
1275
- const cancel = Effect.fn("MemorySubscriptionStore.cancel")(function* (input) {
1276
- const key = yield* requireKey(input, "cancel-key");
1277
- yield* failpoint.hit("subscription:cancel:before");
1278
- const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1279
- const storageKey = subscriptionKeyString(key);
1280
- const text = current.registrations.get(storageKey);
1281
- if (text === void 0) return [Result.fail(error$1("not-found", "subscription")), current];
1282
- const decoded = decode(SubscriptionRecord, text, "cancel-record");
1283
- if (Result.isFailure(decoded)) return [decoded, current];
1284
- if (decoded.success.state === "cancelled") return [Result.succeed(decoded.success), current];
1285
- const cancelled = {
1286
- ...decoded.success,
1287
- state: "cancelled",
1288
- recovery: null
1289
- };
1290
- const encoded = encode(SubscriptionRecord, cancelled, "cancel-encode");
1291
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1292
- const registrations = new Map(current.registrations);
1293
- registrations.set(storageKey, encoded.success);
1294
- const registrationIndex = new Map(current.registrationIndex);
1295
- const indexed = registrationIndex.get(storageKey);
1296
- if (indexed === void 0) return [Result.fail(error$1("corrupt", "cancel-index")), current];
1297
- registrationIndex.set(storageKey, {
1298
- ...indexed,
1299
- state: "cancelled",
1300
- recoveryAt: null
1301
- });
1302
- return [Result.succeed(cancelled), {
1303
- ...current,
1304
- registrations,
1305
- registrationIndex
1306
- }];
1307
- })).pipe(Effect.flatMap(Effect.fromResult));
1308
- yield* failpoint.hit("subscription:cancel:after");
1309
- return result;
1310
- });
1311
- const accept = Effect.fn("MemorySubscriptionStore.accept")(function* (input, inputLimits) {
1312
- const event = yield* validate$1(AcceptedEvent, input, "accept-event");
1313
- const limits = yield* validate$1(SubscriptionLimits, inputLimits, "accept-limits");
1314
- yield* requirePartition(event, "accept-partition");
1315
- yield* failpoint.hit("subscription:accept:before");
1316
- const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1317
- const existingText = current.events.get(event.eventId);
1318
- if (existingText !== void 0) {
1319
- const existing = decode(AcceptedEvent, existingText, "accept-existing");
1320
- if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1321
- return sameEventIdentity(existing.success, event) ? [Result.succeed(existing.success), current] : [Result.fail(error$1("conflict", "event-identity")), current];
1322
- }
1323
- if (jsonBytes(event.payload) > limits.maxPayloadBytes) return [Result.fail(error$1("capacity", "payload-bytes")), current];
1324
- if (current.events.size >= limits.maxEvents) return [Result.fail(error$1("capacity", "events")), current];
1325
- const accepted = {
1326
- ...event,
1327
- cutoff: current.sequence + 1,
1328
- cursor: 0,
1329
- routingComplete: false,
1330
- routingFailure: null
1331
- };
1332
- const encoded = encode(AcceptedEvent, accepted, "accept-encode");
1333
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1334
- const events = new Map(current.events);
1335
- events.set(accepted.eventId, encoded.success);
1336
- const eventIndex = new Map(current.eventIndex);
1337
- eventIndex.set(accepted.eventId, {
1338
- routingComplete: false,
1339
- nextAttemptAtMillis: accepted.nextAttemptAtMillis
1340
- });
1341
- return [Result.succeed(accepted), {
1342
- ...current,
1343
- sequence: accepted.cutoff,
1344
- events,
1345
- eventIndex
1346
- }];
1347
- })).pipe(Effect.flatMap(Effect.fromResult));
1348
- yield* failpoint.hit("subscription:accept:after");
1349
- return result;
1350
- });
1351
- const event = Effect.fn("MemorySubscriptionStore.event")(function* (eventId) {
1352
- const text = (yield* Ref.get(state)).events.get(eventId);
1353
- return text === void 0 ? null : yield* decodeEffect(AcceptedEvent, text, "event-record");
1354
- });
1355
- const pendingEvents = Effect.fn("MemorySubscriptionStore.pendingEvents")(function* (nowMillis, after, limit) {
1356
- const events = [];
1357
- for (const [eventId, indexed] of (yield* Ref.get(state)).eventIndex) if (!indexed.routingComplete && indexed.nextAttemptAtMillis <= nowMillis && compareScheduleNames(eventId, after) > 0) events.push(eventId);
1358
- events.sort(compareScheduleNames);
1359
- return events.slice(0, limit);
1360
- });
1361
- const candidates = Effect.fn("MemorySubscriptionStore.candidates")(function* (input, limit) {
1362
- const accepted = yield* validate$1(AcceptedEvent, input, "candidates-event");
1363
- yield* requirePartition(accepted, "candidates-partition");
1364
- const stored = yield* event(accepted.eventId);
1365
- if (stored === null || !sameEventIdentity(stored, accepted)) return yield* error$1(stored === null ? "not-found" : "conflict", "event");
1366
- const current = yield* Ref.get(state);
1367
- const records = [];
1368
- for (const storageKey of current.candidateIndex.get(eventCandidateIndexKey(stored)) ?? []) {
1369
- const indexed = current.registrationIndex.get(storageKey);
1370
- if (indexed === void 0) return yield* error$1("corrupt", "candidate-index");
1371
- if (indexed.ordinal <= stored.cursor || indexed.ordinal > stored.cutoff) continue;
1372
- const text = current.registrations.get(storageKey);
1373
- if (text === void 0) return yield* error$1("corrupt", "candidate-record");
1374
- records.push(yield* decodeEffect(SubscriptionRecord, text, "candidate-record"));
1375
- }
1376
- records.sort((a, b) => a.ordinal - b.ordinal);
1377
- return records.slice(0, limit);
1378
- });
1379
- const select = Effect.fn("MemorySubscriptionStore.select")(function* (inputEvent, inputDeliveries, cursor, complete, nowMillis, inputLimits) {
1380
- const suppliedEvent = yield* validate$1(AcceptedEvent, inputEvent, "select-event");
1381
- const deliveries = yield* validate$1(Schema.Array(SubscriptionDelivery), inputDeliveries, "select-deliveries");
1382
- const limits = yield* validate$1(SubscriptionLimits, inputLimits, "select-limits");
1383
- yield* requirePartition(suppliedEvent, "select-partition");
1384
- yield* failpoint.hit("subscription:select:before");
1385
- const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
1386
- yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1387
- const eventText = current.events.get(suppliedEvent.eventId);
1388
- if (eventText === void 0) return [Result.fail(error$1("not-found", "event")), current];
1389
- const decodedEvent = decode(AcceptedEvent, eventText, "select-event-record");
1390
- if (Result.isFailure(decodedEvent)) return [Result.fail(decodedEvent.failure), current];
1391
- const accepted = decodedEvent.success;
1392
- if (!sameEventIdentity(accepted, suppliedEvent) || suppliedEvent.cursor !== accepted.cursor) return [Result.fail(error$1("conflict", "event-cursor")), current];
1393
- if (accepted.routingComplete) return [complete && cursor === accepted.cursor ? Result.void : Result.fail(error$1("conflict", "routing-complete")), current];
1394
- if (!Number.isSafeInteger(cursor) || cursor < accepted.cursor || cursor > accepted.cutoff) return [Result.fail(error$1("validation", "cursor")), current];
1395
- const additions = [];
1396
- const updates = [];
1397
- const additionIndex = [];
1398
- const registrationUpdates = [];
1399
- const owners = new Map(current.ownerDeliveryCounts);
1400
- for (const delivery of deliveries) {
1401
- const recordText = current.registrations.get(subscriptionKeyString(delivery.key.subscription));
1402
- if (recordText === void 0) return [Result.fail(error$1("not-found", "subscription")), current];
1403
- const record = decode(SubscriptionRecord, recordText, "select-registration");
1404
- if (Result.isFailure(record)) return [Result.fail(record.failure), current];
1405
- if (!deliveryBelongsTo(delivery, record.success, accepted) || !subscriptionDeliveryCanSelect(delivery, record.success, accepted) || record.success.ordinal <= accepted.cursor || record.success.ordinal > cursor) return [Result.fail(error$1("conflict", "selection")), current];
1406
- if (!subscriptionCanSelect(record.success, accepted, effectiveNowMillis, false)) continue;
1407
- const deliveryKey = subscriptionDeliveryKeyString(delivery.key);
1408
- const existingText = current.deliveries.get(deliveryKey);
1409
- if (existingText !== void 0) {
1410
- const existing = decode(SubscriptionDelivery, existingText, "select-existing");
1411
- if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1412
- if (!sameDeliveryIdentity(existing.success, delivery)) return [Result.fail(error$1("conflict", "delivery-identity")), current];
1413
- continue;
1414
- }
1415
- const encodedDelivery = encode(SubscriptionDelivery, delivery, "select-delivery-encode");
1416
- if (Result.isFailure(encodedDelivery)) return [Result.fail(encodedDelivery.failure), current];
1417
- additions.push([deliveryKey, encodedDelivery.success]);
1418
- additionIndex.push([
1419
- deliveryKey,
1420
- {
1421
- key: delivery.key,
1422
- state: delivery.state,
1423
- nextAttemptAtMillis: delivery.retry.nextAttemptAtMillis
1424
- },
1425
- record.success.key.ownerId
1426
- ]);
1427
- const ownerId = record.success.key.ownerId;
1428
- owners.set(ownerId, (owners.get(ownerId) ?? 0) + 1);
1429
- if ((owners.get(ownerId) ?? 0) > limits.maxDeliveriesPerOwner) return [Result.fail(error$1("capacity", "owner-deliveries")), current];
1430
- if (record.success.configuration.mode === "once") {
1431
- const consumed = {
1432
- ...record.success,
1433
- state: "consumed",
1434
- recovery: null
1435
- };
1436
- const encodedRecord = encode(SubscriptionRecord, consumed, "select-registration-encode");
1437
- if (Result.isFailure(encodedRecord)) return [Result.fail(encodedRecord.failure), current];
1438
- const consumedKey = subscriptionKeyString(consumed.key);
1439
- updates.push([consumedKey, encodedRecord.success]);
1440
- const indexed = current.registrationIndex.get(consumedKey);
1441
- if (indexed === void 0) return [Result.fail(error$1("corrupt", "selection-index")), current];
1442
- registrationUpdates.push([consumedKey, {
1443
- ...indexed,
1444
- state: "consumed",
1445
- recoveryAt: null
1446
- }]);
1447
- }
1448
- }
1449
- if (current.deliveries.size + additions.length > limits.maxDeliveries) return [Result.fail(error$1("capacity", "deliveries")), current];
1450
- const registrations = new Map(current.registrations);
1451
- for (const [key, value] of updates) registrations.set(key, value);
1452
- const nextDeliveries = new Map(current.deliveries);
1453
- for (const [key, value] of additions) nextDeliveries.set(key, value);
1454
- const deliveryIndex = new Map(current.deliveryIndex);
1455
- for (const [key, value] of additionIndex) deliveryIndex.set(key, value);
1456
- const registrationIndex = new Map(current.registrationIndex);
1457
- for (const [key, value] of registrationUpdates) registrationIndex.set(key, value);
1458
- const nextEvent = {
1459
- ...accepted,
1460
- cursor,
1461
- routingComplete: complete,
1462
- routingFailure: null
1463
- };
1464
- const encodedEvent = encode(AcceptedEvent, nextEvent, "select-event-encode");
1465
- if (Result.isFailure(encodedEvent)) return [Result.fail(encodedEvent.failure), current];
1466
- const events = new Map(current.events);
1467
- events.set(nextEvent.eventId, encodedEvent.success);
1468
- const eventIndex = new Map(current.eventIndex);
1469
- eventIndex.set(nextEvent.eventId, {
1470
- routingComplete: nextEvent.routingComplete,
1471
- nextAttemptAtMillis: nextEvent.nextAttemptAtMillis
1472
- });
1473
- return [Result.void, {
1474
- ...current,
1475
- registrations,
1476
- registrationIndex,
1477
- events,
1478
- eventIndex,
1479
- deliveries: nextDeliveries,
1480
- deliveryIndex,
1481
- ownerDeliveryCounts: owners
1482
- }];
1483
- })).pipe(Effect.flatMap(Effect.fromResult));
1484
- yield* failpoint.hit("subscription:select:after");
1485
- });
1486
- const catchUp = Effect.fn("MemorySubscriptionStore.catchUp")(function* (inputEvent, inputDelivery, nowMillis, inputLimits) {
1487
- const suppliedEvent = yield* validate$1(AcceptedEvent, inputEvent, "catch-up-event");
1488
- const delivery = yield* validate$1(SubscriptionDelivery, inputDelivery, "catch-up-delivery");
1489
- const limits = yield* validate$1(SubscriptionLimits, inputLimits, "catch-up-limits");
1490
- yield* requirePartition(suppliedEvent, "catch-up-partition");
1491
- yield* failpoint.hit("subscription:catch-up:before");
1492
- const effectiveNowMillis = Math.max(nowMillis, yield* Clock.currentTimeMillis);
1493
- yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1494
- const eventText = current.events.get(suppliedEvent.eventId);
1495
- const recordText = current.registrations.get(subscriptionKeyString(delivery.key.subscription));
1496
- if (eventText === void 0 || recordText === void 0) return [Result.fail(error$1("not-found", eventText === void 0 ? "event" : "subscription")), current];
1497
- const accepted = decode(AcceptedEvent, eventText, "catch-up-event-record");
1498
- if (Result.isFailure(accepted)) return [Result.fail(accepted.failure), current];
1499
- const record = decode(SubscriptionRecord, recordText, "catch-up-registration");
1500
- if (Result.isFailure(record)) return [Result.fail(record.failure), current];
1501
- if (!sameEventIdentity(accepted.success, suppliedEvent) || !deliveryBelongsTo(delivery, record.success, accepted.success) || !subscriptionDeliveryCanSelect(delivery, record.success, accepted.success) || record.success.configuration.mode !== "once") return [Result.fail(error$1("conflict", "catch-up-identity")), current];
1502
- const key = subscriptionDeliveryKeyString(delivery.key);
1503
- const existingText = current.deliveries.get(key);
1504
- if (existingText !== void 0) {
1505
- const existing = decode(SubscriptionDelivery, existingText, "catch-up-existing");
1506
- if (Result.isFailure(existing)) return [Result.fail(existing.failure), current];
1507
- return sameDeliveryIdentity(existing.success, delivery) ? [Result.void, current] : [Result.fail(error$1("conflict", "delivery-identity")), current];
1508
- }
1509
- if (!subscriptionCanSelect(record.success, accepted.success, effectiveNowMillis, true)) return [Result.fail(error$1("conflict", "catch-up-eligibility")), current];
1510
- if (current.deliveries.size >= limits.maxDeliveries) return [Result.fail(error$1("capacity", "deliveries")), current];
1511
- const ownerCount = current.ownerDeliveryCounts.get(record.success.key.ownerId) ?? 0;
1512
- if (ownerCount >= limits.maxDeliveriesPerOwner) return [Result.fail(error$1("capacity", "owner-deliveries")), current];
1513
- const encodedDelivery = encode(SubscriptionDelivery, delivery, "catch-up-delivery-encode");
1514
- if (Result.isFailure(encodedDelivery)) return [Result.fail(encodedDelivery.failure), current];
1515
- const consumed = {
1516
- ...record.success,
1517
- state: "consumed",
1518
- recovery: null
1519
- };
1520
- const encodedRecord = encode(SubscriptionRecord, consumed, "catch-up-registration-encode");
1521
- if (Result.isFailure(encodedRecord)) return [Result.fail(encodedRecord.failure), current];
1522
- const deliveries = new Map(current.deliveries);
1523
- deliveries.set(key, encodedDelivery.success);
1524
- const registrations = new Map(current.registrations);
1525
- const consumedKey = subscriptionKeyString(consumed.key);
1526
- registrations.set(consumedKey, encodedRecord.success);
1527
- const registrationIndex = new Map(current.registrationIndex);
1528
- const indexed = registrationIndex.get(consumedKey);
1529
- if (indexed === void 0) return [Result.fail(error$1("corrupt", "catch-up-index")), current];
1530
- registrationIndex.set(consumedKey, {
1531
- ...indexed,
1532
- state: "consumed",
1533
- recoveryAt: null
1534
- });
1535
- const deliveryIndex = new Map(current.deliveryIndex);
1536
- deliveryIndex.set(key, {
1537
- key: delivery.key,
1538
- state: delivery.state,
1539
- nextAttemptAtMillis: delivery.retry.nextAttemptAtMillis
1540
- });
1541
- const ownerDeliveryCounts = new Map(current.ownerDeliveryCounts);
1542
- ownerDeliveryCounts.set(consumed.key.ownerId, ownerCount + 1);
1543
- return [Result.void, {
1544
- ...current,
1545
- deliveries,
1546
- deliveryIndex,
1547
- ownerDeliveryCounts,
1548
- registrations,
1549
- registrationIndex
1550
- }];
1551
- })).pipe(Effect.flatMap(Effect.fromResult));
1552
- yield* failpoint.hit("subscription:catch-up:after");
1553
- });
1554
- const deferEvent = Effect.fn("MemorySubscriptionStore.deferEvent")(function* (eventId, nextAttemptAtMillis, code) {
1555
- const routingFailure = code === void 0 ? "routing-failed" : yield* validate$1(SubscriptionName, code, "routing-failure");
1556
- yield* failpoint.hit("subscription:defer-event:before");
1557
- yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1558
- const text = current.events.get(eventId);
1559
- if (text === void 0) return [Result.fail(error$1("not-found", "event")), current];
1560
- const accepted = decode(AcceptedEvent, text, "defer-event-record");
1561
- if (Result.isFailure(accepted)) return [Result.fail(accepted.failure), current];
1562
- const updated = {
1563
- ...accepted.success,
1564
- nextAttemptAtMillis,
1565
- routingFailure
1566
- };
1567
- const encoded = encode(AcceptedEvent, updated, "defer-event-encode");
1568
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1569
- const events = new Map(current.events);
1570
- events.set(eventId, encoded.success);
1571
- const eventIndex = new Map(current.eventIndex);
1572
- const indexed = eventIndex.get(eventId);
1573
- if (indexed === void 0) return [Result.fail(error$1("corrupt", "defer-event-index")), current];
1574
- eventIndex.set(eventId, {
1575
- ...indexed,
1576
- nextAttemptAtMillis
1577
- });
1578
- return [Result.void, {
1579
- ...current,
1580
- events,
1581
- eventIndex
1582
- }];
1583
- })).pipe(Effect.flatMap(Effect.fromResult));
1584
- yield* failpoint.hit("subscription:defer-event:after");
1585
- });
1586
- const delivery = Effect.fn("MemorySubscriptionStore.delivery")(function* (input) {
1587
- const key = yield* validate$1(SubscriptionDeliveryKey, input, "delivery-key");
1588
- yield* requirePartition(key.subscription, "delivery-partition");
1589
- const text = (yield* Ref.get(state)).deliveries.get(subscriptionDeliveryKeyString(key));
1590
- return text === void 0 ? null : yield* decodeEffect(SubscriptionDelivery, text, "delivery-record");
1591
- });
1592
- const pendingDeliveries = Effect.fn("MemorySubscriptionStore.pendingDeliveries")(function* (nowMillis, after, limit) {
1593
- const items = [];
1594
- for (const [storageKey, item] of (yield* Ref.get(state)).deliveryIndex) if (item.state !== "delivered" && item.state !== "refused" && item.nextAttemptAtMillis <= nowMillis && compareScheduleNames(storageKey, after) > 0) items.push(item.key);
1595
- items.sort((a, b) => compareScheduleNames(subscriptionDeliveryKeyString(a), subscriptionDeliveryKeyString(b)));
1596
- return items.slice(0, limit);
1597
- });
1598
- const listDeliveries = Effect.fn("MemorySubscriptionStore.listDeliveries")(function* (input, after, limit) {
1599
- const key = yield* requireKey(input, "list-deliveries-key");
1600
- const items = [];
1601
- for (const text of (yield* Ref.get(state)).deliveries.values()) {
1602
- const item = yield* decodeEffect(SubscriptionDelivery, text, "list-delivery");
1603
- const itemKey = subscriptionDeliveryKeyString(item.key);
1604
- if (subscriptionKeyString(item.key.subscription) === subscriptionKeyString(key) && compareScheduleNames(itemKey, after) > 0) items.push(item);
1605
- }
1606
- items.sort((a, b) => compareScheduleNames(subscriptionDeliveryKeyString(a.key), subscriptionDeliveryKeyString(b.key)));
1607
- return items.slice(0, limit);
1608
- });
1609
- const changeDelivery = Effect.fn("MemorySubscriptionStore.changeDelivery")(function* (inputKey, inputDeliveryId, inputChange) {
1610
- const key = yield* validate$1(SubscriptionDeliveryKey, inputKey, "change-delivery-key");
1611
- const deliveryId = yield* validate$1(Digest, inputDeliveryId, "change-delivery-id");
1612
- const change = yield* validate$1(DeliveryChange, inputChange, "change-delivery-change");
1613
- yield* requirePartition(key.subscription, "change-delivery-partition");
1614
- yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:before`);
1615
- const effectiveChange = change._tag === "Prepare" ? {
1616
- ...change,
1617
- nowMillis: Math.max(change.nowMillis, yield* Clock.currentTimeMillis)
1618
- } : change;
1619
- const result = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1620
- const storageKey = subscriptionDeliveryKeyString(key);
1621
- const text = current.deliveries.get(storageKey);
1622
- if (text === void 0) return [Result.fail(error$1("not-found", "delivery")), current];
1623
- const decoded = decode(SubscriptionDelivery, text, "change-delivery-record");
1624
- if (Result.isFailure(decoded)) return [decoded, current];
1625
- const registrationText = current.registrations.get(subscriptionKeyString(key.subscription));
1626
- if (registrationText === void 0) return [Result.fail(error$1("corrupt", "delivery-registration")), current];
1627
- const registration = decode(SubscriptionRecord, registrationText, "delivery-registration");
1628
- if (Result.isFailure(registration)) return [Result.fail(registration.failure), current];
1629
- const transition = applySubscriptionDeliveryChange(decoded.success, registration.success, deliveryId, effectiveChange);
1630
- if (Result.isFailure(transition)) return [Result.fail(transition.failure), current];
1631
- if (transition.success === decoded.success) return [Result.succeed(decoded.success), current];
1632
- const updated = transition.success;
1633
- const encoded = encode(SubscriptionDelivery, updated, "change-delivery-encode");
1634
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1635
- const deliveries = new Map(current.deliveries);
1636
- deliveries.set(storageKey, encoded.success);
1637
- const deliveryIndex = new Map(current.deliveryIndex);
1638
- const indexed = deliveryIndex.get(storageKey);
1639
- if (indexed === void 0) return [Result.fail(error$1("corrupt", "delivery-index")), current];
1640
- deliveryIndex.set(storageKey, {
1641
- ...indexed,
1642
- state: updated.state,
1643
- nextAttemptAtMillis: updated.retry.nextAttemptAtMillis
1644
- });
1645
- return [Result.succeed(updated), {
1646
- ...current,
1647
- deliveries,
1648
- deliveryIndex
1649
- }];
1650
- })).pipe(Effect.flatMap(Effect.fromResult));
1651
- yield* failpoint.hit(`subscription:delivery-${change._tag.toLowerCase()}:after`);
1652
- return result;
1653
- });
1654
- const recovering = Effect.fn("MemorySubscriptionStore.recovering")(function* (nowMillis, after, limit) {
1655
- const records = [];
1656
- for (const item of (yield* Ref.get(state)).registrationIndex.values()) if (item.ordinal > after && item.state === "active" && item.recoveryAt !== null && item.recoveryAt <= nowMillis) records.push({
1657
- key: item.key,
1658
- ordinal: item.ordinal
1659
- });
1660
- records.sort((a, b) => a.ordinal - b.ordinal);
1661
- return records.slice(0, limit);
1662
- });
1663
- const deferRecovery = Effect.fn("MemorySubscriptionStore.deferRecovery")(function* (input, recovery) {
1664
- const key = yield* requireKey(input, "defer-recovery-key");
1665
- yield* failpoint.hit("subscription:defer-recovery:before");
1666
- yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1667
- const storageKey = subscriptionKeyString(key);
1668
- const text = current.registrations.get(storageKey);
1669
- if (text === void 0) return [Result.fail(error$1("not-found", "subscription")), current];
1670
- const record = decode(SubscriptionRecord, text, "defer-recovery-record");
1671
- if (Result.isFailure(record)) return [Result.fail(record.failure), current];
1672
- const updated = {
1673
- ...record.success,
1674
- recovery: record.success.state === "active" ? recovery : null
1675
- };
1676
- const encoded = encode(SubscriptionRecord, updated, "defer-recovery-encode");
1677
- if (Result.isFailure(encoded)) return [Result.fail(encoded.failure), current];
1678
- const registrations = new Map(current.registrations);
1679
- registrations.set(storageKey, encoded.success);
1680
- const registrationIndex = new Map(current.registrationIndex);
1681
- const indexed = registrationIndex.get(storageKey);
1682
- if (indexed === void 0) return [Result.fail(error$1("corrupt", "recovery-index")), current];
1683
- registrationIndex.set(storageKey, {
1684
- ...indexed,
1685
- recoveryAt: updated.recovery?.nextAttemptAtMillis ?? null
1686
- });
1687
- return [Result.void, {
1688
- ...current,
1689
- registrations,
1690
- registrationIndex
1691
- }];
1692
- })).pipe(Effect.flatMap(Effect.fromResult));
1693
- yield* failpoint.hit("subscription:defer-recovery:after");
1694
- });
1695
- const readScanCursors = Ref.get(state).pipe(Effect.map((current) => current.scanCursors));
1696
- const advanceScanCursors = Effect.fn("MemorySubscriptionStore.advanceScanCursors")(function* (input) {
1697
- const cursors = yield* validate$1(SubscriptionScanCursors, input, "scan-cursors");
1698
- yield* failpoint.hit("subscription:advance-scan-cursors:before");
1699
- yield* Effect.uninterruptible(Ref.update(state, (current) => ({
1700
- ...current,
1701
- scanCursors: cursors
1702
- })));
1703
- yield* failpoint.hit("subscription:advance-scan-cursors:after");
1704
- });
1705
- const nextDeadline = Effect.gen(function* () {
1706
- let deadline = null;
1707
- const current = yield* Ref.get(state);
1708
- if (current.scanCursors.events !== "" || current.scanCursors.deliveries !== "" || current.scanCursors.recovery !== 0) return 0;
1709
- const consider = (value) => {
1710
- if (deadline === null || value < deadline) deadline = value;
1711
- };
1712
- for (const accepted of current.eventIndex.values()) if (!accepted.routingComplete) consider(accepted.nextAttemptAtMillis);
1713
- for (const item of current.deliveryIndex.values()) if (item.state !== "delivered" && item.state !== "refused") consider(item.nextAttemptAtMillis);
1714
- for (const record of current.registrationIndex.values()) if (record.state === "active" && record.recoveryAt !== null) consider(record.recoveryAt);
1715
- return deadline;
1716
- }).pipe(Effect.withSpan("MemorySubscriptionStore.nextDeadline"));
1717
- return SubscriptionStore.of({
1718
- partition,
1719
- register,
1720
- get,
1721
- list,
1722
- cancel,
1723
- accept,
1724
- event,
1725
- pendingEvents,
1726
- candidates,
1727
- select,
1728
- catchUp,
1729
- deferEvent,
1730
- delivery,
1731
- pendingDeliveries,
1732
- listDeliveries,
1733
- changeDelivery,
1734
- recovering,
1735
- deferRecovery,
1736
- readScanCursors,
1737
- advanceScanCursors,
1738
- nextDeadline
1739
- });
1740
- });
1741
- const memorySubscriptionStoreLayer = (partition) => Layer.effect(SubscriptionStore, makeMemorySubscriptionStore(partition));
1742
- //#endregion
1743
- //#region src/memory-storage.ts
1744
- const MAX_THREADS = 256;
1745
- const MAX_RECORDS_PER_THREAD = 65536;
1746
- const MAX_CHECKPOINTS_PER_THREAD = 1024;
1747
- const storeError = (operation, message, cause) => cause === void 0 ? ThreadStoreError.make({
1748
- operation,
1749
- message
1750
- }) : ThreadStoreError.make({
1751
- operation,
1752
- message,
1753
- cause
1754
- });
1755
- const validate = Effect.fn("MemoryThreadStore.validate")((schema, operation, value) => Schema.encodeUnknownEffect(schema)(value).pipe(Effect.flatMap(Schema.decodeUnknownEffect(schema)), Effect.mapError((error) => storeError(operation, `Invalid ${operation} request`, error))));
1756
- const decodeCanonicalSequence = Schema.decodeSync(CanonicalSequence);
1757
- const ZERO_CANONICAL_SEQUENCE = decodeCanonicalSequence(0);
1758
- const offsetSequence = Effect.fn("MemoryThreadStore.offsetSequence")((threadId, offset) => {
1759
- if (offset === void 0) return Effect.succeed(ZERO_CANONICAL_SEQUENCE);
1760
- const prefix = `memory:v1:${Encoding.encodeBase64(threadId)}:`;
1761
- const encodedSequence = offset.startsWith(prefix) ? offset.slice(prefix.length) : "";
1762
- if (!/^\d+$/.test(encodedSequence)) return Effect.fail(storeError("observe", "Malformed observation offset"));
1763
- const sequence = Number(encodedSequence);
1764
- return Number.isSafeInteger(sequence) ? Schema.decodeUnknownEffect(CanonicalSequence)(sequence).pipe(Effect.mapError(() => storeError("observe", "Malformed observation offset"))) : Effect.fail(storeError("observe", "Malformed observation offset"));
1765
- });
1766
- const observationOffset = (threadId, sequence) => Schema.decodeSync(ObservationOffset)(`memory:v1:${Encoding.encodeBase64(threadId)}:${sequence}`);
1767
- const findThread = Effect.fn("MemoryThreadStore.findThread")((state, threadId) => {
1768
- const thread = state.threads.get(threadId);
1769
- return thread === void 0 ? Effect.fail(ThreadNotMaterialized.make({ threadId })) : Effect.succeed(thread);
1770
- });
1771
- const CheckpointVersionEnvelope = Schema.Struct({ checkpoint: Schema.Struct({
1772
- threadId: ThreadId,
1773
- schemaVersion: Schema.Natural
1774
- }) });
1775
- const validateCheckpointVersion = Effect.fn("MemoryThreadStore.validateCheckpointVersion")(function* (value) {
1776
- const envelope = yield* Schema.decodeUnknownEffect(CheckpointVersionEnvelope)(value).pipe(Effect.mapError(() => storeError("saveCheckpoint", "Invalid saveCheckpoint request")));
1777
- if (envelope.checkpoint.schemaVersion !== 1) return yield* CheckpointRejected.make({
1778
- threadId: envelope.checkpoint.threadId,
1779
- reason: "unsupported-version"
1780
- });
1781
- });
1782
- const makeThreadStore = Effect.gen(function* () {
1783
- const crypto = yield* Crypto.Crypto;
1784
- const state = yield* Ref.make({ threads: /* @__PURE__ */ new Map() });
1785
- const updates = yield* PubSub.sliding(1);
1786
- yield* Effect.addFinalizer(() => PubSub.shutdown(updates));
1787
- const materialize = Effect.fn("MemoryThreadStore.materialize")((unvalidated) => Effect.gen(function* () {
1788
- const request = yield* validate(ThreadMaterialization, "materialize", unvalidated);
1789
- const decision = yield* Ref.modify(state, (current) => {
1790
- const existing = current.threads.get(request.threadId);
1791
- if (existing !== void 0) {
1792
- if (request.producerEpoch < existing.producerEpoch) return [{
1793
- _tag: "failure",
1794
- error: FenceRejected.make({
1795
- threadId: request.threadId,
1796
- actualEpoch: existing.producerEpoch,
1797
- attemptedEpoch: request.producerEpoch
1798
- })
1799
- }, current];
1800
- if (request.producerEpoch === existing.producerEpoch) return [{ _tag: "success" }, current];
1801
- const threads = new Map(current.threads);
1802
- threads.set(request.threadId, {
1803
- ...existing,
1804
- producerEpoch: request.producerEpoch
1805
- });
1806
- return [{ _tag: "success" }, { threads }];
1807
- }
1808
- if (current.threads.size >= MAX_THREADS) return [{
1809
- _tag: "failure",
1810
- error: storeError("materialize", `In-memory thread limit ${MAX_THREADS} exceeded`)
1811
- }, current];
1812
- const threads = new Map(current.threads);
1813
- threads.set(request.threadId, {
1814
- producerEpoch: request.producerEpoch,
1815
- tailSequence: ZERO_CANONICAL_SEQUENCE,
1816
- tailDigest: EMPTY_TAIL_DIGEST,
1817
- records: [],
1818
- recordIds: /* @__PURE__ */ new Set(),
1819
- batches: /* @__PURE__ */ new Map(),
1820
- tailDigests: /* @__PURE__ */ new Map([[ZERO_CANONICAL_SEQUENCE, EMPTY_TAIL_DIGEST]]),
1821
- checkpoints: /* @__PURE__ */ new Map()
1822
- });
1823
- return [{ _tag: "success" }, { threads }];
1824
- });
1825
- if (decision._tag === "failure") return yield* decision.error;
1826
- }));
1827
- const append = Effect.fn("MemoryThreadStore.append")((unvalidated) => Effect.gen(function* () {
1828
- const request = yield* validate(FencedAppendRequest, "append", unvalidated);
1829
- const digest = yield* digestCanonicalBatch(request.expectedTailDigest, request.batch).pipe(Effect.provideService(Crypto.Crypto, crypto), Effect.mapError((error) => storeError("append", error.message, error)));
1830
- const decision = yield* Effect.uninterruptible(Ref.modify(state, (current) => {
1831
- const thread = current.threads.get(request.threadId);
1832
- if (thread === void 0) return [{
1833
- _tag: "failure",
1834
- error: ThreadNotMaterialized.make({ threadId: request.threadId })
1835
- }, current];
1836
- if (request.producerEpoch !== thread.producerEpoch) return [{
1837
- _tag: "failure",
1838
- error: FenceRejected.make({
1839
- threadId: request.threadId,
1840
- actualEpoch: thread.producerEpoch,
1841
- attemptedEpoch: request.producerEpoch
1842
- })
1843
- }, current];
1844
- const previous = thread.batches.get(request.batch.batchId);
1845
- if (previous !== void 0) {
1846
- if (previous.digest !== digest) return [{
1847
- _tag: "failure",
1848
- error: AppendConflict.make({
1849
- threadId: request.threadId,
1850
- batchId: request.batch.batchId,
1851
- reason: "batch-digest"
1852
- })
1853
- }, current];
1854
- return [{
1855
- _tag: "success",
1856
- result: AppendResult.make({
1857
- firstSequence: previous.result.firstSequence,
1858
- lastSequence: previous.result.lastSequence,
1859
- tailDigest: previous.result.tailDigest,
1860
- replayed: true
1861
- }),
1862
- records: []
1863
- }, current];
1864
- }
1865
- if (request.expectedTailSequence !== thread.tailSequence || request.expectedTailDigest !== thread.tailDigest) return [{
1866
- _tag: "failure",
1867
- error: AppendConflict.make({
1868
- threadId: request.threadId,
1869
- batchId: request.batch.batchId,
1870
- reason: "tail",
1871
- actualTailSequence: thread.tailSequence,
1872
- actualTailDigest: thread.tailDigest
1873
- })
1874
- }, current];
1875
- if (thread.records.length + request.batch.records.length > MAX_RECORDS_PER_THREAD) return [{
1876
- _tag: "failure",
1877
- error: storeError("append", `In-memory record limit ${MAX_RECORDS_PER_THREAD} exceeded`)
1878
- }, current];
1879
- const batchRecordIds = /* @__PURE__ */ new Set();
1880
- for (const record of request.batch.records) {
1881
- if (thread.recordIds.has(record.recordId) || batchRecordIds.has(record.recordId)) return [{
1882
- _tag: "failure",
1883
- error: AppendConflict.make({
1884
- threadId: request.threadId,
1885
- batchId: request.batch.batchId,
1886
- reason: "record-identity"
1887
- })
1888
- }, current];
1889
- batchRecordIds.add(record.recordId);
1890
- }
1891
- const records = request.batch.records.map((record, index) => {
1892
- const sequence = decodeCanonicalSequence(thread.tailSequence + index + 1);
1893
- return CanonicalRecordEnvelope.make({
1894
- threadId: request.threadId,
1895
- batchId: request.batch.batchId,
1896
- sequence,
1897
- offset: observationOffset(request.threadId, sequence),
1898
- record
1899
- });
1900
- });
1901
- const lastSequence = decodeCanonicalSequence(thread.tailSequence + records.length);
1902
- const result = AppendResult.make({
1903
- firstSequence: decodeCanonicalSequence(thread.tailSequence + 1),
1904
- lastSequence,
1905
- tailDigest: digest,
1906
- replayed: false
1907
- });
1908
- const batches = new Map(thread.batches);
1909
- batches.set(request.batch.batchId, {
1910
- digest,
1911
- result
1912
- });
1913
- const recordIds = new Set(thread.recordIds);
1914
- for (const recordId of batchRecordIds) recordIds.add(recordId);
1915
- const tailDigests = new Map(thread.tailDigests);
1916
- tailDigests.set(lastSequence, digest);
1917
- const threads = new Map(current.threads);
1918
- threads.set(request.threadId, {
1919
- ...thread,
1920
- tailSequence: lastSequence,
1921
- tailDigest: digest,
1922
- records: [...thread.records, ...records],
1923
- recordIds,
1924
- batches,
1925
- tailDigests
1926
- });
1927
- return [{
1928
- _tag: "success",
1929
- result,
1930
- records
1931
- }, { threads }];
1932
- }).pipe(Effect.tap((decision) => decision._tag === "success" && decision.records.length > 0 ? PubSub.publish(updates, void 0) : Effect.void)));
1933
- if (decision._tag === "failure") return yield* decision.error;
1934
- return decision.result;
1935
- }));
1936
- const readSnapshot = Effect.fn("MemoryThreadStore.readSnapshot")((threadId, afterSequence, limit) => Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, threadId)), Effect.map((thread) => thread.records.filter((record) => record.sequence > (afterSequence ?? ZERO_CANONICAL_SEQUENCE)).slice(0, limit))));
1937
- const read = (unvalidated) => Stream.unwrap(Effect.gen(function* () {
1938
- const request = yield* validate(ThreadRead, "read", unvalidated);
1939
- const records = yield* readSnapshot(request.threadId, request.afterSequence, request.limit);
1940
- return Stream.fromIterable(records);
1941
- }));
1942
- const observe = (unvalidated) => Stream.unwrap(Effect.gen(function* () {
1943
- const request = yield* validate(ThreadObservation, "observe", unvalidated);
1944
- const afterSequence = yield* offsetSequence(request.threadId, request.afterOffset);
1945
- return Stream.unwrap(Effect.gen(function* () {
1946
- const subscription = yield* PubSub.subscribe(updates);
1947
- const initial = yield* readSnapshot(request.threadId, afterSequence, MAX_RECORDS_PER_THREAD);
1948
- const highWater = initial.length === 0 ? afterSequence : initial.at(-1)?.sequence ?? afterSequence;
1949
- const live = Stream.fromEffectRepeat(PubSub.take(subscription)).pipe(Stream.mapAccumEffect(() => highWater, (lastSequence) => readSnapshot(request.threadId, lastSequence, MAX_RECORDS_PER_THREAD).pipe(Effect.map((records) => [records.at(-1)?.sequence ?? lastSequence, records]))));
1950
- return Stream.fromIterable(initial).pipe(Stream.concat(live));
1951
- }));
1952
- }));
1953
- const exportThread = Effect.fn("MemoryThreadStore.export")((unvalidated) => Effect.gen(function* () {
1954
- const request = yield* validate(ThreadExportRequest, "export", unvalidated);
1955
- const thread = yield* Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, request.threadId)));
1956
- return ThreadExport.make({
1957
- format: "effect-agent/thread@1",
1958
- threadId: request.threadId,
1959
- tailSequence: thread.tailSequence,
1960
- tailDigest: thread.tailDigest,
1961
- records: thread.records
1962
- });
1963
- }));
1964
- const inspectTail = Effect.fn("MemoryThreadStore.inspectTail")((unvalidated) => Effect.gen(function* () {
1965
- const request = yield* validate(ThreadTailRequest, "inspectTail", unvalidated);
1966
- const thread = yield* Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, request.threadId)));
1967
- return ThreadTail.make({
1968
- threadId: request.threadId,
1969
- tailSequence: thread.tailSequence,
1970
- tailDigest: thread.tailDigest,
1971
- producerEpoch: thread.producerEpoch
1972
- });
1973
- }));
1974
- const saveCheckpoint = Effect.fn("MemoryThreadStore.saveCheckpoint")((unvalidated) => Effect.gen(function* () {
1975
- yield* validateCheckpointVersion(unvalidated);
1976
- const request = yield* validate(SaveCheckpointRequest, "saveCheckpoint", unvalidated);
1977
- const decision = yield* Ref.modify(state, (current) => {
1978
- const checkpoint = request.checkpoint;
1979
- const thread = current.threads.get(checkpoint.threadId);
1980
- if (thread === void 0) return [{
1981
- _tag: "failure",
1982
- error: ThreadNotMaterialized.make({ threadId: checkpoint.threadId })
1983
- }, current];
1984
- if (checkpoint.throughSequence > thread.tailSequence) return [{
1985
- _tag: "failure",
1986
- error: CheckpointRejected.make({
1987
- threadId: checkpoint.threadId,
1988
- reason: "ahead-of-tail"
1989
- })
1990
- }, current];
1991
- if (thread.tailDigests.get(checkpoint.throughSequence) !== checkpoint.tailDigest) return [{
1992
- _tag: "failure",
1993
- error: CheckpointRejected.make({
1994
- threadId: checkpoint.threadId,
1995
- reason: "digest-mismatch"
1996
- })
1997
- }, current];
1998
- if (!thread.checkpoints.has(checkpoint.throughSequence) && thread.checkpoints.size >= MAX_CHECKPOINTS_PER_THREAD) return [{
1999
- _tag: "failure",
2000
- error: storeError("saveCheckpoint", `In-memory checkpoint limit ${MAX_CHECKPOINTS_PER_THREAD} exceeded`)
2001
- }, current];
2002
- const checkpoints = new Map(thread.checkpoints);
2003
- checkpoints.set(checkpoint.throughSequence, checkpoint);
2004
- const threads = new Map(current.threads);
2005
- threads.set(checkpoint.threadId, {
2006
- ...thread,
2007
- checkpoints
2008
- });
2009
- return [{ _tag: "success" }, { threads }];
2010
- });
2011
- if (decision._tag === "failure") return yield* decision.error;
2012
- }));
2013
- const loadCheckpoint = Effect.fn("MemoryThreadStore.loadCheckpoint")((unvalidated) => Effect.gen(function* () {
2014
- const request = yield* validate(LoadCheckpointRequest, "loadCheckpoint", unvalidated);
2015
- const thread = yield* Ref.get(state).pipe(Effect.flatMap((current) => findThread(current, request.threadId)));
2016
- const maximum = request.atOrBeforeSequence ?? thread.tailSequence;
2017
- let selected;
2018
- for (const [sequence, checkpoint] of thread.checkpoints) if (sequence <= maximum && (selected === void 0 || sequence > selected.throughSequence)) selected = checkpoint;
2019
- if (selected !== void 0 && thread.tailDigests.get(selected.throughSequence) !== selected.tailDigest) return yield* CheckpointRejected.make({
2020
- threadId: request.threadId,
2021
- reason: "digest-mismatch"
2022
- });
2023
- return Option.fromNullishOr(selected);
2024
- }));
2025
- return ThreadStore.of({
2026
- materialize,
2027
- append,
2028
- read,
2029
- observe,
2030
- export: exportThread,
2031
- inspectTail,
2032
- checkpoints: {
2033
- save: saveCheckpoint,
2034
- load: loadCheckpoint
2035
- }
2036
- });
2037
- });
2038
- const MemoryThreadStoreLive = Layer.effect(ThreadStore, makeThreadStore);
2039
- /**
2040
- * In-memory canonical Thread persistence. Durable accepted work is served by the separate
2041
- * SubmissionLedger port; this Layer deliberately provides only the ThreadStore.
2042
- */
2043
- const MemoryStorageLive = MemoryThreadStoreLive;
2044
- //#endregion
2045
- //#region src/semantic-memory-index.ts
2046
- const PositiveCapacity = Schema.Int.check(Schema.isBetween({
2047
- minimum: 1,
2048
- maximum: 65536
2049
- }));
2050
- const MaxStoredVectorComponents = 16777216;
2051
- /**
2052
- * Hard per-Layer bounds for disposable semantic index state. maxChunks times profile dimensions
2053
- * must not exceed 16,777,216 vector components. maxSourceBytes bounds the aggregate UTF-8 JSON
2054
- * of retained source identities and defaults to 16 MiB; it is not a general heap limit.
2055
- */
2056
- var InMemorySemanticIndexCapacity = class extends Schema.Class("@effect-agent/storage-memory/InMemorySemanticIndexCapacity")({
2057
- maxSources: PositiveCapacity,
2058
- maxChunks: PositiveCapacity,
2059
- maxSourceBytes: Schema.optionalKey(Schema.Int.check(Schema.isBetween({
2060
- minimum: 1,
2061
- maximum: 67108864
2062
- })))
2063
- }) {};
2064
- const sameProfile = Schema.toEquivalence(SemanticMemoryProfile);
2065
- const sameSource = Schema.toEquivalence(MemoryIndexSource.Wire);
2066
- const error = (operation, reason) => MemoryIndexError.make({
2067
- operation,
2068
- reason
2069
- });
2070
- const keyString = (key) => JSON.stringify([key.namespace.address, key.id]);
2071
- const sourceIdentityBytes = (source) => Encoding.encodeHex(JSON.stringify(source)).length / 2;
2072
- const decodeBoundary = Effect.fn("InMemorySemanticIndex.decodeBoundary")(function* (schema, value, operation) {
2073
- return yield* Schema.decodeUnknownEffect(schema)(value).pipe(Effect.flatMap((decoded) => Schema.encodeEffect(schema)(decoded).pipe(Effect.as(decoded))), Effect.mapError(() => error(operation, "invalid-input")));
2074
- });
2075
- const freezeSource = (source) => Object.freeze(MemoryIndexSource.make({
2076
- key: Object.freeze(MemoryKey.make({
2077
- ...source.key,
2078
- namespace: Object.freeze({ address: source.key.namespace.address })
2079
- })),
2080
- source: Object.freeze({ ...source.source }),
2081
- sourceGeneration: source.sourceGeneration
2082
- }));
2083
- const freezeChunk = (chunk) => Object.freeze(SemanticMemoryChunk.make({
2084
- ...chunk,
2085
- vector: Object.freeze([...chunk.vector])
2086
- }));
2087
- const sourceIsFenced = (source, existing) => source.sourceGeneration < existing.source.sourceGeneration || source.sourceGeneration === existing.source.sourceGeneration && !sameSource(source, existing.source);
2088
- const squaredNorm = (vector) => {
2089
- let sum = 0;
2090
- for (const value of vector) {
2091
- sum += value * value;
2092
- if (!Number.isFinite(sum)) return null;
2093
- }
2094
- return sum > 0 ? sum : null;
2095
- };
2096
- const validVector = (vector, profile) => vector.length === profile.dimensions && squaredNorm(vector) !== null;
2097
- const validateChunks = Effect.fn("InMemorySemanticIndex.validateChunks")(function* (chunks, profile, operation) {
2098
- let nextByte = 0;
2099
- const passageIds = /* @__PURE__ */ new Set();
2100
- for (let index = 0; index < chunks.length; index++) {
2101
- const chunk = chunks[index];
2102
- const byteLength = Encoding.encodeHex(chunk.text).length / 2;
2103
- if (chunk.ordinal !== index || chunk.startByte !== nextByte || chunk.endByte <= chunk.startByte || chunk.endByte - chunk.startByte !== byteLength || byteLength > profile.maxChunkBytes || passageIds.has(chunk.passageId) || !validVector(chunk.vector, profile)) return yield* error(operation, "invalid-input");
2104
- passageIds.add(chunk.passageId);
2105
- nextByte = chunk.endByte;
2106
- }
2107
- });
2108
- const cosine = (left, right) => {
2109
- const leftNorm = Math.sqrt(squaredNorm(left) ?? 1);
2110
- const rightNorm = Math.sqrt(squaredNorm(right) ?? 1);
2111
- let score = 0;
2112
- for (let index = 0; index < left.length; index++) score += left[index] / leftNorm * (right[index] / rightNorm);
2113
- const bounded = Math.max(-1, Math.min(1, score));
2114
- return bounded === 0 ? 0 : bounded;
2115
- };
2116
- const compareText = (left, right) => left < right ? -1 : left > right ? 1 : 0;
2117
- const makeIndex = Effect.fn("InMemorySemanticIndex.make")(function* (rawProfile, rawCapacity) {
2118
- const profile = Object.freeze(SemanticMemoryProfile.make({ ...yield* decodeBoundary(SemanticMemoryProfile, rawProfile, "configure semantic memory index") }));
2119
- const capacity = yield* decodeBoundary(InMemorySemanticIndexCapacity, rawCapacity, "configure semantic memory index");
2120
- if (capacity.maxChunks * profile.dimensions > MaxStoredVectorComponents) return yield* error("configure semantic memory index", "invalid-input");
2121
- const maxSourceBytes = capacity.maxSourceBytes ?? 16777216;
2122
- const data = yield* Ref.make({
2123
- closed: false,
2124
- entries: /* @__PURE__ */ new Map(),
2125
- sourceBytes: 0
2126
- });
2127
- yield* Effect.addFinalizer(() => Ref.set(data, {
2128
- closed: true,
2129
- entries: /* @__PURE__ */ new Map(),
2130
- sourceBytes: 0
2131
- }));
2132
- const ensureOpen = Effect.fn("InMemorySemanticIndex.ensureOpen")(function* (operation) {
2133
- if ((yield* Ref.get(data)).closed) return yield* error(operation, "unavailable");
2134
- });
2135
- const replace = Effect.fn("InMemorySemanticIndex.replace")(function* (rawRequest) {
2136
- const operation = "replace semantic memory source";
2137
- yield* ensureOpen(operation);
2138
- const request = yield* decodeBoundary(MemoryIndexReplacement.Wire, rawRequest, operation);
2139
- const source = freezeSource(request.source);
2140
- const sourceBytes = sourceIdentityBytes(source);
2141
- const chunks = Object.freeze(request.chunks.map(freezeChunk));
2142
- if (source.source.id !== source.key.id) return yield* error(operation, "invalid-input");
2143
- if (!sameProfile(request.profile, profile)) return yield* error(operation, "incompatible");
2144
- yield* validateChunks(chunks, profile, operation);
2145
- const indexedAt = yield* Clock.currentTimeMillis;
2146
- const failure = yield* Ref.modify(data, (current) => {
2147
- if (current.closed) return [error(operation, "unavailable"), current];
2148
- const id = keyString(source.key);
2149
- const existing = current.entries.get(id);
2150
- if (existing !== void 0 && (existing._tag === "Withdrawn" || sourceIsFenced(source, existing))) return [error(operation, "fenced"), current];
2151
- if (existing === void 0 && current.entries.size >= capacity.maxSources) return [error(operation, "budget"), current];
2152
- const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
2153
- if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
2154
- let count = chunks.length;
2155
- for (const [entryId, entry] of current.entries) if (entryId !== id && entry._tag === "Indexed") count += entry.chunks.length;
2156
- if (count > capacity.maxChunks) return [error(operation, "budget"), current];
2157
- const entries = new Map(current.entries);
2158
- entries.set(id, {
2159
- _tag: "Indexed",
2160
- source,
2161
- sourceBytes,
2162
- chunks,
2163
- indexedAt
2164
- });
2165
- return [void 0, {
2166
- ...current,
2167
- entries,
2168
- sourceBytes: nextSourceBytes
2169
- }];
2170
- });
2171
- if (failure !== void 0) return yield* failure;
2172
- });
2173
- const withdraw = Effect.fn("InMemorySemanticIndex.withdraw")(function* (rawSource) {
2174
- const operation = "withdraw semantic memory source";
2175
- yield* ensureOpen(operation);
2176
- const source = freezeSource(yield* decodeBoundary(MemoryIndexSource.Wire, rawSource, operation));
2177
- const sourceBytes = sourceIdentityBytes(source);
2178
- if (source.source.id !== source.key.id) return yield* error(operation, "invalid-input");
2179
- const failure = yield* Ref.modify(data, (current) => {
2180
- if (current.closed) return [error(operation, "unavailable"), current];
2181
- const id = keyString(source.key);
2182
- const existing = current.entries.get(id);
2183
- if (existing !== void 0) {
2184
- if (existing._tag === "Withdrawn") return [sameSource(source, existing.source) ? void 0 : error(operation, "fenced"), current];
2185
- if (sourceIsFenced(source, existing)) return [error(operation, "fenced"), current];
2186
- } else if (current.entries.size >= capacity.maxSources) return [error(operation, "budget"), current];
2187
- const nextSourceBytes = current.sourceBytes - (existing?.sourceBytes ?? 0) + sourceBytes;
2188
- if (nextSourceBytes > maxSourceBytes) return [error(operation, "budget"), current];
2189
- const entries = new Map(current.entries);
2190
- entries.set(id, {
2191
- _tag: "Withdrawn",
2192
- source,
2193
- sourceBytes
2194
- });
2195
- return [void 0, {
2196
- ...current,
2197
- entries,
2198
- sourceBytes: nextSourceBytes
2199
- }];
2200
- });
2201
- if (failure !== void 0) return yield* failure;
2202
- });
2203
- const search = Effect.fn("InMemorySemanticIndex.search")(function* (rawQuery) {
2204
- const operation = "search semantic memory index";
2205
- yield* ensureOpen(operation);
2206
- const query = yield* decodeBoundary(MemoryIndexQuery.Wire, rawQuery, operation);
2207
- const vector = Object.freeze([...query.vector]);
2208
- if (!validVector(vector, profile)) return yield* error(operation, "invalid-input");
2209
- const current = yield* Ref.get(data);
2210
- if (current.closed) return yield* error(operation, "unavailable");
2211
- let scannedChunks = 0;
2212
- let inspectedSources = 0;
2213
- const candidates = [];
2214
- for (const entry of current.entries.values()) {
2215
- inspectedSources += 1;
2216
- if (inspectedSources % 128 === 0) yield* Effect.yieldNow;
2217
- if (entry.source.key.namespace.address !== query.namespace.address || entry._tag !== "Indexed") continue;
2218
- scannedChunks += entry.chunks.length;
2219
- if (scannedChunks > query.maxScannedChunks) return yield* error(operation, "budget");
2220
- }
2221
- for (const entry of current.entries.values()) {
2222
- if (entry.source.key.namespace.address !== query.namespace.address || entry._tag !== "Indexed") continue;
2223
- yield* Effect.yieldNow;
2224
- for (const chunk of entry.chunks) {
2225
- const score = cosine(vector, chunk.vector);
2226
- if (score < query.minScore) continue;
2227
- candidates.push(MemoryIndexCandidate.make({
2228
- ...entry.source,
2229
- passageId: chunk.passageId,
2230
- ordinal: chunk.ordinal,
2231
- startByte: chunk.startByte,
2232
- endByte: chunk.endByte,
2233
- text: chunk.text,
2234
- score,
2235
- indexedAt: entry.indexedAt
2236
- }));
2237
- }
2238
- }
2239
- candidates.sort((left, right) => right.score - left.score || compareText(left.key.id, right.key.id) || compareText(left.source.revision, right.source.revision) || left.ordinal - right.ordinal);
2240
- yield* ensureOpen(operation);
2241
- return MemoryIndexSearch.make({
2242
- candidates: candidates.slice(0, query.limit),
2243
- scannedChunks
2244
- });
2245
- });
2246
- return SemanticMemoryIndex.fromAdapter({
2247
- profile,
2248
- replace,
2249
- withdraw,
2250
- search
2251
- });
2252
- });
2253
- /** Scoped disposable semantic index. No persistent build or recovery state is retained. */
2254
- const inMemorySemanticIndexLayer = (profile, capacity) => Layer.effect(SemanticMemoryIndex, makeIndex(profile, capacity));
2255
- //#endregion
2256
- export { InMemorySemanticIndexCapacity, MemoryScheduleStoreLive, MemoryStorageLive, MemorySubmissionLedgerLive, MemoryThreadStoreLive, inMemorySemanticIndexLayer, memoryScheduleStoreLayer, memorySubmissionLedgerLayer, memorySubscriptionStoreLayer };
2257
-
2258
- //# sourceMappingURL=index.mjs.map
1
+ import { t as MemoryScheduleStore_exports } from "./MemoryScheduleStore.mjs";
2
+ import { t as MemorySemanticIndex_exports } from "./MemorySemanticIndex.mjs";
3
+ import { t as MemorySubmissionLedger_exports } from "./MemorySubmissionLedger.mjs";
4
+ import { t as MemorySubscriptionStore_exports } from "./MemorySubscriptionStore.mjs";
5
+ import { t as MemoryThreadStore_exports } from "./MemoryThreadStore.mjs";
6
+ export { MemoryScheduleStore_exports as MemoryScheduleStore, MemorySemanticIndex_exports as MemorySemanticIndex, MemorySubmissionLedger_exports as MemorySubmissionLedger, MemorySubscriptionStore_exports as MemorySubscriptionStore, MemoryThreadStore_exports as MemoryThreadStore };