@yielded/agent-storage-memory 0.1.0-beta.167 → 0.1.0-beta.168

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.
@@ -1,3 +1,4 @@
1
+ import { t as MemoryThreadStoreKernel } from "./MemoryThreadStoreKernel-DYNDjlhO.mjs";
1
2
  import { Layer } from "effect";
2
3
  import { MessageDeliveryError, MessageDeliveryStore, MessageDeliveryStoreLimits } from "@yielded/agent/message-delivery";
3
4
  declare namespace MemoryMessageDeliveryStore_d_exports {
@@ -7,9 +8,12 @@ export interface MemoryMessageDeliveryStoreOptions {
7
8
  /** UTF-8 bound on the complete stored record, including a processed Settlement. */
8
9
  readonly maxStoredValueBytes?: number;
9
10
  }
10
- /** Reference adapter. All retained rows are bounded; completed deduplication evidence is never evicted. */
11
- export declare const memoryMessageDeliveryStoreLayer: (limits?: MessageDeliveryStoreLimits, options?: MemoryMessageDeliveryStoreOptions) => Layer.Layer<MessageDeliveryStore, MessageDeliveryError>;
12
- export declare const MemoryMessageDeliveryStoreLive: Layer.Layer<MessageDeliveryStore, MessageDeliveryError, never>;
11
+ /**
12
+ * Pair with MemoryThreadStoreLive so export/import sees every retained delivery obligation.
13
+ * Each retained value and live partition is bounded; completed deduplication evidence is never evicted.
14
+ */
15
+ export declare const memoryMessageDeliveryStoreLayer: (limits?: MessageDeliveryStoreLimits, options?: MemoryMessageDeliveryStoreOptions) => Layer.Layer<MessageDeliveryStore, MessageDeliveryError, MemoryThreadStoreKernel>;
16
+ export declare const MemoryMessageDeliveryStoreLive: Layer.Layer<MessageDeliveryStore, MessageDeliveryError, MemoryThreadStoreKernel>;
13
17
  //#endregion
14
18
  export { MemoryMessageDeliveryStore_d_exports as t };
15
19
  //# sourceMappingURL=MemoryMessageDeliveryStore.d.mts.map
@@ -1,14 +1,29 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
+ import { n as MemoryThreadStoreKernel, t as boundedWorkSelection } from "./WorkSelection-CmqTuws3.mjs";
2
3
  import { ScheduleInstant } from "@yielded/agent/schedule";
3
- import { Effect, Layer, Ref, Schema, Semaphore } from "effect";
4
+ import { Effect, Layer, MutableRef, Ref, Schema } from "effect";
4
5
  import { ThreadId } from "@yielded/agent/identifiers";
5
6
  import { MessageDeliveryChange, MessageDeliveryError, MessageDeliveryFailpoint, MessageDeliveryKey, MessageDeliveryPageRequest, MessageDeliveryRecord, MessageDeliveryStore, MessageDeliveryStoreLimits, applyMessageDeliveryChange, defaultMessageDeliveryStoreLimits, isWorkerUpdateDelivery, messageDeliveryCapacity, messageDeliveryDeadline, messageDeliveryKeyString, messageDeliveryUsesCapacity, sameMessageDeliveryIdentity, validateMessageDelivery } from "@yielded/agent/message-delivery";
7
+ import { validateCanonicalSettlement } from "@yielded/agent/settlement-publisher";
8
+ import { Settlement, settlementFailureFromRecord, submissionSettlementId, submissionSettlementRecordId } from "@yielded/agent/submission-ledger";
9
+ import { PreparedInput } from "@yielded/agent/subscription";
10
+ import { ThreadDeliveryImportReader, ThreadImportRejected, invalidThreadArchive } from "@yielded/agent/thread-import";
11
+ import { ThreadStoreError, makeTransferFactCheck } from "@yielded/agent/thread-store";
12
+ import { workId } from "@yielded/agent/thread-work";
6
13
  //#region src/MemoryMessageDeliveryStore.ts
7
14
  var MemoryMessageDeliveryStore_exports = /* @__PURE__ */ __exportAll({
8
15
  MemoryMessageDeliveryStoreLive: () => MemoryMessageDeliveryStoreLive,
9
16
  memoryMessageDeliveryStoreLayer: () => memoryMessageDeliveryStoreLayer
10
17
  });
11
18
  const codec = Schema.fromJsonString(MessageDeliveryRecord);
19
+ const deliveryFitsTransfer = makeTransferFactCheck(MessageDeliveryRecord);
20
+ const checkTransfer = (record) => deliveryFitsTransfer(record.key.ownerThreadId, {
21
+ ...record,
22
+ leaseUntilMillis: null
23
+ }) ? Effect.void : Effect.fail(MessageDeliveryError.make({
24
+ reason: "capacity",
25
+ operation: "transfer-page-bytes"
26
+ }));
12
27
  const decode = (text) => Schema.decodeEffect(codec)(text).pipe(Effect.mapError(() => MessageDeliveryError.make({
13
28
  reason: "corrupt",
14
29
  operation: "decode"
@@ -18,48 +33,186 @@ const Scan = Schema.Struct({
18
33
  limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)),
19
34
  ownerThreadId: Schema.optionalKey(ThreadId)
20
35
  });
21
- /** Reference adapter. All retained rows are bounded; completed deduplication evidence is never evicted. */
36
+ const position = (keys, key) => {
37
+ let low = 0;
38
+ let high = keys.length;
39
+ while (low < high) {
40
+ const mid = Math.floor((low + high) / 2);
41
+ if (keys[mid] < key) low = mid + 1;
42
+ else high = mid;
43
+ }
44
+ return low;
45
+ };
46
+ const retainKey = (keys, key) => {
47
+ const at = position(keys, key);
48
+ if (keys[at] !== key) keys.splice(at, 0, key);
49
+ };
50
+ const removeKey = (keys, key) => {
51
+ const at = position(keys, key);
52
+ if (keys[at] === key) keys.splice(at, 1);
53
+ };
54
+ const backend = () => ({
55
+ records: /* @__PURE__ */ new Map(),
56
+ work: /* @__PURE__ */ new Map(),
57
+ ordered: /* @__PURE__ */ new Map(),
58
+ pending: /* @__PURE__ */ new Map(),
59
+ counts: /* @__PURE__ */ new Map(),
60
+ metadata: /* @__PURE__ */ new Map(),
61
+ deadlines: /* @__PURE__ */ new Map(),
62
+ deadlineKeys: new Array(),
63
+ ownerDeadlineKeys: /* @__PURE__ */ new Map()
64
+ });
65
+ const ownerKey = (id) => JSON.stringify([id]);
66
+ const keysFor = (current, key) => {
67
+ let keys = current.ordered.get(key);
68
+ if (keys === void 0) current.ordered.set(key, keys = []);
69
+ return keys;
70
+ };
71
+ /**
72
+ * Pair with MemoryThreadStoreLive so export/import sees every retained delivery obligation.
73
+ * Each retained value and live partition is bounded; completed deduplication evidence is never evicted.
74
+ */
22
75
  const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLimits, options = {}) => Layer.effect(MessageDeliveryStore, Effect.gen(function* () {
76
+ const journal = yield* MemoryThreadStoreKernel;
23
77
  const config = yield* validateMessageDelivery(MessageDeliveryStoreLimits, limits, "limits");
24
78
  const maxStoredValueBytes = yield* validateMessageDelivery(Schema.Int.check(Schema.isGreaterThan(0)), options.maxStoredValueBytes ?? 16777216, "stored-value-limit");
25
- const state = yield* Ref.make({
26
- records: /* @__PURE__ */ new Map(),
27
- pending: /* @__PURE__ */ new Map(),
28
- workers: /* @__PURE__ */ new Map()
79
+ const state = yield* Ref.make(backend());
80
+ yield* journal.registerDeliveryLookup((threadId, messageId) => MutableRef.get(state.ref).records.has(messageDeliveryKeyString({
81
+ ownerThreadId: threadId,
82
+ messageId
83
+ })));
84
+ yield* journal.registerWorkOwner("deliveries", {
85
+ threads: (request) => Ref.get(state).pipe(Effect.map((current) => {
86
+ const candidates = boundedWorkSelection(request.limit, (id) => id, request.afterThreadId);
87
+ for (const threadId of current.pending.keys()) candidates.add(threadId);
88
+ const threadIds = candidates.values;
89
+ const afterThreadId = threadIds.length === request.limit ? threadIds.at(-1) : void 0;
90
+ return {
91
+ threadIds,
92
+ ...afterThreadId === void 0 ? {} : { afterThreadId }
93
+ };
94
+ })),
95
+ page: (threadId, after, limit) => Effect.gen(function* () {
96
+ const current = yield* Ref.get(state);
97
+ const candidates = boundedWorkSelection(limit + 1, (id) => id, after);
98
+ for (const id of current.pending.get(threadId) ?? []) candidates.add(id);
99
+ const ids = candidates.values;
100
+ const entries = [];
101
+ for (const messageId of ids.slice(0, limit)) {
102
+ const key = messageDeliveryKeyString({
103
+ ownerThreadId: threadId,
104
+ messageId
105
+ });
106
+ const entry = current.work.get(key);
107
+ if (entry === void 0 || !current.records.has(key)) return yield* ThreadStoreError.make({
108
+ operation: "delivery work",
109
+ message: "Pending delivery index is incomplete or corrupt"
110
+ });
111
+ entries.push(entry);
112
+ }
113
+ const next = ids.length > limit ? ids[limit - 1] : void 0;
114
+ return {
115
+ entries,
116
+ ...next === void 0 ? {} : { after: next }
117
+ };
118
+ })
29
119
  });
30
- const commit = (record, encoded) => Ref.update(state, (current) => {
120
+ const commitInto = (current, record, encoded, publish) => {
31
121
  const key = messageDeliveryKeyString(record.key);
32
- const keys = new Set(current.pending.get(record.key.ownerThreadId));
33
- if (messageDeliveryUsesCapacity(record)) keys.add(record.key.messageId);
34
- else keys.delete(record.key.messageId);
35
- const pending = new Map(current.pending);
36
- if (keys.size === 0) pending.delete(record.key.ownerThreadId);
37
- else pending.set(record.key.ownerThreadId, keys);
38
- const workers = new Map(current.workers);
122
+ const owner = record.key.ownerThreadId;
123
+ const old = current.metadata.get(key);
124
+ const update = isWorkerUpdateDelivery(record);
125
+ const pending = messageDeliveryUsesCapacity(record);
126
+ const peer = record.envelope.messageAdmission !== void 0 && "peerName" in record.envelope.messageAdmission;
127
+ let counts = current.counts.get(owner);
128
+ if (counts === void 0) current.counts.set(owner, counts = {
129
+ retained: 0,
130
+ ordinary: 0,
131
+ update: 0,
132
+ peer: 0
133
+ });
134
+ if (old === void 0) counts.retained++;
135
+ if (old?.pending) {
136
+ if (old.update) counts.update--;
137
+ else counts.ordinary--;
138
+ if (old.peer) counts.peer--;
139
+ }
140
+ if (pending) {
141
+ if (update) counts.update++;
142
+ else counts.ordinary++;
143
+ if (peer) counts.peer++;
144
+ }
145
+ let owned = current.pending.get(owner);
146
+ if (owned === void 0) current.pending.set(owner, owned = /* @__PURE__ */ new Set());
147
+ if (pending) owned.add(record.key.messageId);
148
+ else owned.delete(record.key.messageId);
149
+ if (owned.size === 0) current.pending.delete(owner);
150
+ retainKey(keysFor(current, ownerKey(owner)), record.key.messageId);
39
151
  const admission = record.envelope.workerAdmission;
40
152
  if (admission !== void 0) {
41
153
  const worker = admission.origin.worker;
42
- const startKey = JSON.stringify([
43
- record.key.ownerThreadId,
154
+ if (record.key.messageId === admission.origin.firstMessageId) retainKey(keysFor(current, JSON.stringify([
155
+ owner,
44
156
  worker.delegationId,
45
157
  worker.targetAgentId
46
- ]);
47
- if (record.key.messageId === admission.origin.firstMessageId) workers.set(startKey, /* @__PURE__ */ new Set([...workers.get(startKey) ?? [], record.key.messageId]));
48
- const pendingKey = JSON.stringify([record.key.ownerThreadId, worker.threadId]);
49
- const inputs = new Set(workers.get(pendingKey));
50
- if ((record.status === "pending" || record.status === "parked") && record.receipt === null) inputs.add(record.key.messageId);
51
- else inputs.delete(record.key.messageId);
52
- workers.set(pendingKey, inputs);
158
+ ])), record.key.messageId);
159
+ const inputs = keysFor(current, JSON.stringify([owner, worker.threadId]));
160
+ if ((record.status === "pending" || record.status === "parked") && record.receipt === null) retainKey(inputs, record.key.messageId);
161
+ else removeKey(inputs, record.key.messageId);
162
+ }
163
+ const deadline = messageDeliveryDeadline(record);
164
+ let ownerDeadlines = current.ownerDeadlineKeys.get(owner);
165
+ if (ownerDeadlines === void 0) current.ownerDeadlineKeys.set(owner, ownerDeadlines = []);
166
+ if (old?.deadlineKey !== void 0) {
167
+ current.deadlines.delete(old.deadlineKey);
168
+ removeKey(current.deadlineKeys, old.deadlineKey);
169
+ removeKey(ownerDeadlines, old.deadlineKey);
170
+ }
171
+ const deadlineKey = deadline === null ? void 0 : `${String(deadline).padStart(16, "0")}:${key}`;
172
+ if (deadlineKey !== void 0 && deadline !== null) {
173
+ current.deadlines.set(deadlineKey, {
174
+ key: record.key,
175
+ deadline
176
+ });
177
+ retainKey(current.deadlineKeys, deadlineKey);
178
+ retainKey(ownerDeadlines, deadlineKey);
53
179
  }
54
- return {
55
- records: new Map(current.records).set(key, encoded),
180
+ current.metadata.set(key, {
181
+ update,
56
182
  pending,
57
- workers
58
- };
59
- });
60
- const lock = yield* Semaphore.make(1);
183
+ peer,
184
+ ...deadlineKey === void 0 ? {} : { deadlineKey }
185
+ });
186
+ if (pending) current.work.set(key, {
187
+ id: workId("delivery", record.key.messageId),
188
+ owner: {
189
+ _tag: "Delivery",
190
+ messageId: record.key.messageId
191
+ },
192
+ partition: update ? "update" : "ordinary",
193
+ stateReference: {
194
+ _tag: "Delivery",
195
+ messageId: record.key.messageId,
196
+ version: record.version
197
+ },
198
+ state: record.status === "pending" ? "ready" : "waiting",
199
+ ...record.status === "parked" ? { wait: "parked" } : record.receipt !== null ? { wait: "destination" } : {},
200
+ ...deadline === null ? {} : { notBeforeMillis: deadline },
201
+ ...record.receipt === null ? {} : {
202
+ receiptId: record.receipt.receiptId,
203
+ queueSequence: record.receipt.queueSequence
204
+ }
205
+ });
206
+ else current.work.delete(key);
207
+ current.records.set(key, encoded);
208
+ if (publish) {
209
+ journal.initializeWork(owner);
210
+ journal.retainDelivery(owner, record.key.messageId);
211
+ }
212
+ };
213
+ const commit = (record, encoded) => Effect.sync(() => commitInto(MutableRef.get(state.ref), record, encoded, true));
61
214
  const failpoint = yield* MessageDeliveryFailpoint;
62
- const encode = Effect.fn("MemoryMessageDeliveryStore.encode")(function* (record) {
215
+ const encode = Effect.fnUntraced(function* (record) {
63
216
  const text = yield* Schema.encodeEffect(codec)(record).pipe(Effect.mapError(() => MessageDeliveryError.make({
64
217
  reason: "corrupt",
65
218
  operation: "encode"
@@ -70,10 +223,7 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
70
223
  });
71
224
  return text;
72
225
  });
73
- const all = Effect.fn("MemoryMessageDeliveryStore.all")(function* () {
74
- return yield* Effect.forEach((yield* Ref.get(state)).records.values(), decode);
75
- });
76
- const insert = Effect.fn("MemoryMessageDeliveryStore.insert")(function* (record) {
226
+ const insert = Effect.fnUntraced(function* (record) {
77
227
  const encoded = yield* encode(record);
78
228
  const input = yield* decode(encoded);
79
229
  if (input.version !== 1 || input.status !== "pending" || input.leaseUntilMillis !== null || input.retry.attempts !== 0 || input.retry.generation !== 0 || input.retry.automaticAttempts !== 0 || input.retry.nextAttemptAtMillis !== input.createdAtMillis || input.retry.lastAttemptAtMillis !== null || input.retry.lastFailure !== null || input.deadlineAtMillis !== input.initialDeadlineAtMillis) return yield* MessageDeliveryError.make({
@@ -85,7 +235,11 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
85
235
  operation: "envelope-bytes"
86
236
  });
87
237
  yield* failpoint.hit("message-delivery:insert:before");
88
- const inserted = yield* lock.withPermit(Effect.uninterruptible(Effect.gen(function* () {
238
+ const inserted = yield* journal.withMutation(Effect.uninterruptible(Effect.gen(function* () {
239
+ yield* journal.checkThreadCapacity(input.key.ownerThreadId).pipe(Effect.mapError(() => MessageDeliveryError.make({
240
+ reason: "capacity",
241
+ operation: "Thread capacity"
242
+ })));
89
243
  const { records } = yield* Ref.get(state);
90
244
  const key = messageDeliveryKeyString(input.key);
91
245
  const existingText = records.get(key);
@@ -95,11 +249,14 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
95
249
  reason: "conflict",
96
250
  operation: "insert"
97
251
  });
252
+ journal.retainDelivery(existing.key.ownerThreadId, existing.key.messageId);
98
253
  return existing;
99
254
  }
100
- const owned = (yield* all()).filter((record) => record.key.ownerThreadId === input.key.ownerThreadId && isWorkerUpdateDelivery(record) === isWorkerUpdateDelivery(input));
101
- const capacity = messageDeliveryCapacity(config, isWorkerUpdateDelivery(input));
102
- if (owned.length >= capacity.retained || owned.filter(messageDeliveryUsesCapacity).length >= capacity.pending) return yield* MessageDeliveryError.make({
255
+ yield* checkTransfer(input);
256
+ const update = isWorkerUpdateDelivery(input);
257
+ const counts = MutableRef.get(state.ref).counts.get(input.key.ownerThreadId);
258
+ const capacity = messageDeliveryCapacity(config, update);
259
+ if ((update ? counts?.update ?? 0 : counts?.ordinary ?? 0) >= capacity.pending) return yield* MessageDeliveryError.make({
103
260
  reason: "capacity",
104
261
  operation: "insert"
105
262
  });
@@ -109,23 +266,25 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
109
266
  yield* failpoint.hit("message-delivery:insert:after");
110
267
  return yield* decode(yield* encode(inserted));
111
268
  });
112
- const get = Effect.fn("MemoryMessageDeliveryStore.get")(function* (key) {
269
+ const get = Effect.fnUntraced(function* (key) {
113
270
  const input = yield* validateMessageDelivery(MessageDeliveryKey, key, "get");
114
271
  const text = (yield* Ref.get(state)).records.get(messageDeliveryKeyString(input));
115
272
  return text === void 0 ? null : yield* decode(text);
116
273
  });
117
- const change = Effect.fn("MemoryMessageDeliveryStore.change")(function* (key, change) {
274
+ const change = Effect.fnUntraced(function* (key, change) {
118
275
  const input = yield* validateMessageDelivery(MessageDeliveryChange, change, "change");
119
276
  const decodedKey = yield* validateMessageDelivery(MessageDeliveryKey, key, "change");
120
277
  const point = `message-delivery:${input._tag.toLowerCase()}`;
121
278
  yield* failpoint.hit(`${point}:before`);
122
- const changed = yield* lock.withPermit(Effect.uninterruptible(Effect.gen(function* () {
279
+ const changed = yield* journal.withMutation(Effect.uninterruptible(Effect.gen(function* () {
123
280
  const existing = yield* get(decodedKey);
124
281
  if (existing === null) return yield* MessageDeliveryError.make({
125
282
  reason: "not-found",
126
283
  operation: "change"
127
284
  });
128
285
  const next = yield* Effect.fromResult(applyMessageDeliveryChange(existing, input));
286
+ if (next === existing) return existing;
287
+ yield* checkTransfer(next);
129
288
  const encoded = yield* encode(next);
130
289
  yield* commit(next, encoded);
131
290
  return next;
@@ -133,13 +292,196 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
133
292
  yield* failpoint.hit(`${point}:after`);
134
293
  return yield* decode(yield* encode(changed));
135
294
  });
295
+ const transferDecode = (text) => decode(text).pipe(Effect.mapError(() => ThreadStoreError.make({
296
+ operation: "delivery transfer",
297
+ message: "Invalid retained delivery wire"
298
+ })));
299
+ yield* journal.registerDeliveryTransfer({
300
+ record: (threadId, messageId) => {
301
+ const text = MutableRef.get(state.ref).records.get(messageDeliveryKeyString({
302
+ ownerThreadId: threadId,
303
+ messageId
304
+ }));
305
+ return text === void 0 ? Effect.succeed(void 0) : transferDecode(text);
306
+ },
307
+ snapshot: (threadId) => {
308
+ const counts = MutableRef.get(state.ref).counts.get(threadId);
309
+ return {
310
+ idle: (MutableRef.get(state.ref).pending.get(threadId)?.size ?? 0) === 0,
311
+ deliveries: counts?.retained ?? 0
312
+ };
313
+ },
314
+ pendingPeerCount: (threadId, limit) => Math.min(MutableRef.get(state.ref).counts.get(threadId)?.peer ?? 0, limit),
315
+ facts: Effect.fnUntraced(function* (threadId, after) {
316
+ const current = MutableRef.get(state.ref);
317
+ const keys = current.ordered.get(ownerKey(threadId)) ?? [];
318
+ const messageId = keys[after === void 0 ? 0 : position(keys, after) + Number(keys[position(keys, after)] === after)];
319
+ const record = messageId === void 0 ? void 0 : yield* transferDecode(current.records.get(messageDeliveryKeyString({
320
+ ownerThreadId: threadId,
321
+ messageId: Schema.decodeSync(MessageDeliveryKey.fields.messageId)(messageId)
322
+ })) ?? "");
323
+ return {
324
+ facts: {
325
+ admissions: [],
326
+ commands: {
327
+ aborts: [],
328
+ approvals: [],
329
+ resolutions: []
330
+ },
331
+ deliveries: record === void 0 ? [] : [MessageDeliveryRecord.make({
332
+ ...record,
333
+ leaseUntilMillis: null
334
+ })]
335
+ },
336
+ after: messageId ?? after ?? ""
337
+ };
338
+ }),
339
+ startImport: Effect.fnUntraced(function* (threadId) {
340
+ const current = MutableRef.get(state.ref);
341
+ if ((current.counts.get(threadId)?.retained ?? 0) > 0) return yield* ThreadImportRejected.make({
342
+ threadId,
343
+ reason: "target-not-empty",
344
+ message: "Destination Thread owns retained deliveries"
345
+ });
346
+ const staged = backend();
347
+ return {
348
+ record: (messageId) => {
349
+ const text = staged.records.get(messageDeliveryKeyString({
350
+ ownerThreadId: threadId,
351
+ messageId
352
+ }));
353
+ return text === void 0 ? Effect.succeed(void 0) : transferDecode(text);
354
+ },
355
+ has: (messageId) => staged.records.has(messageDeliveryKeyString({
356
+ ownerThreadId: threadId,
357
+ messageId
358
+ })),
359
+ stage: Effect.fnUntraced(function* (page) {
360
+ for (const record of page.archive.deliveries) {
361
+ const key = messageDeliveryKeyString(record.key);
362
+ if (staged.records.has(key) || current.records.has(key)) return yield* invalidThreadArchive("Duplicate retained delivery", threadId);
363
+ const text = yield* encode(record).pipe(Effect.mapError(() => ThreadImportRejected.make({
364
+ threadId,
365
+ reason: "unsupported-capacity",
366
+ message: "Delivery wire exceeds destination stored-value bound"
367
+ })));
368
+ if (new TextEncoder().encode(JSON.stringify(yield* Schema.encodeEffect(PreparedInput)(record.envelope).pipe(Effect.mapError(() => invalidThreadArchive("Invalid delivery envelope", threadId))))).byteLength > config.maxEnvelopeBytes) return yield* ThreadImportRejected.make({
369
+ threadId,
370
+ reason: "unsupported-capacity",
371
+ message: "Delivery envelope exceeds destination bound"
372
+ });
373
+ const counts = staged.counts.get(threadId);
374
+ if (messageDeliveryUsesCapacity(record) && (isWorkerUpdateDelivery(record) ? counts?.update ?? 0 : counts?.ordinary ?? 0) >= messageDeliveryCapacity(config, isWorkerUpdateDelivery(record)).pending) return yield* ThreadImportRejected.make({
375
+ threadId,
376
+ reason: "unsupported-capacity",
377
+ message: "Pending delivery capacity exceeded"
378
+ });
379
+ commitInto(staged, record, text, false);
380
+ }
381
+ }),
382
+ prepareCommit: Effect.fnUntraced(function* () {
383
+ const reader = yield* ThreadDeliveryImportReader;
384
+ const read = (messageId) => {
385
+ const text = staged.records.get(messageDeliveryKeyString({
386
+ ownerThreadId: threadId,
387
+ messageId
388
+ }));
389
+ return text === void 0 ? Effect.fail(invalidThreadArchive("Missing delivery predecessor", threadId)) : transferDecode(text);
390
+ };
391
+ const visit = (messageId) => {
392
+ const metadata = staged.metadata.get(messageDeliveryKeyString({
393
+ ownerThreadId: threadId,
394
+ messageId
395
+ }));
396
+ return metadata === void 0 ? Effect.fail(invalidThreadArchive("Missing delivery predecessor metadata", threadId)) : Effect.succeed(metadata);
397
+ };
398
+ const verifyPredecessors = Effect.fnUntraced(function* (start) {
399
+ let cursor = start;
400
+ while (cursor !== void 0) {
401
+ const metadata = yield* visit(cursor);
402
+ if (metadata.importVisit === "verified") break;
403
+ if (metadata.importVisit === "visiting") return yield* invalidThreadArchive("Delivery predecessor cycle", threadId);
404
+ metadata.importVisit = "visiting";
405
+ const record = yield* read(cursor);
406
+ if (record.predecessor !== void 0 && (yield* read(record.predecessor)).createdAtMillis > record.createdAtMillis) return yield* invalidThreadArchive("Delivery predecessor has a later boundary", threadId);
407
+ cursor = record.predecessor;
408
+ }
409
+ cursor = start;
410
+ while (cursor !== void 0) {
411
+ const metadata = yield* visit(cursor);
412
+ if (metadata.importVisit === "verified") break;
413
+ metadata.importVisit = "verified";
414
+ cursor = (yield* read(cursor)).predecessor;
415
+ }
416
+ });
417
+ for (const text of staged.records.values()) {
418
+ const d = yield* transferDecode(text);
419
+ yield* verifyPredecessors(d.key.messageId);
420
+ if (d.receipt !== null) {
421
+ if (d.settlement !== null && d.settlement.settlementId !== submissionSettlementId(d.receipt.submissionId)) return yield* invalidThreadArchive("Delivery settlement identity is not canonical", threadId);
422
+ const foreign = d.receipt.threadId !== threadId;
423
+ if (foreign && d.status !== "processed") return yield* ThreadImportRejected.make({
424
+ threadId,
425
+ reason: "unsupported-obligations",
426
+ message: "Live foreign delivery receipt cannot be restored"
427
+ });
428
+ const destination = yield* reader.admission(d.receipt.threadId, d.receipt.submissionId);
429
+ if (destination === void 0) {
430
+ if (foreign && d.status === "processed") continue;
431
+ return yield* invalidThreadArchive("Destination delivery receipt evidence is missing", threadId);
432
+ }
433
+ const expected = PreparedInput.make({
434
+ schemaVersion: 1,
435
+ authorization: d.envelope.authorization,
436
+ threadId: destination.threadId,
437
+ deliveryPrincipal: destination.principal,
438
+ admissionKey: destination.idempotencyKey,
439
+ agentId: destination.agentId,
440
+ definitions: destination.agentDigests,
441
+ input: destination.inputPayload,
442
+ inputDigest: destination.inputDigest,
443
+ ...destination.admissionGroup === void 0 ? {} : { admissionGroup: destination.admissionGroup },
444
+ ...destination.admissionFence === void 0 ? {} : { admissionFence: destination.admissionFence },
445
+ ...destination.workerAdmission === void 0 ? {} : { workerAdmission: destination.workerAdmission },
446
+ ...destination.messageAdmission === void 0 ? {} : { messageAdmission: destination.messageAdmission }
447
+ });
448
+ if (d.receipt.receiptId !== destination.receiptId || d.receipt.queueSequence !== destination.queueSequence || !Schema.toEquivalence(PreparedInput)(d.envelope, expected)) return yield* invalidThreadArchive("Delivery envelope differs from destination admission", threadId);
449
+ if (d.settlement !== null) {
450
+ const record = yield* reader.record(destination.threadId, submissionSettlementRecordId(destination.submissionId));
451
+ if (record === void 0) {
452
+ if (foreign) continue;
453
+ return yield* invalidThreadArchive("Processed delivery has no destination settlement", threadId);
454
+ }
455
+ const settled = yield* validateCanonicalSettlement(record, destination).pipe(Effect.mapError(() => invalidThreadArchive("Invalid destination settlement", threadId)));
456
+ const failure = settlementFailureFromRecord(record);
457
+ const expected = Settlement.make({
458
+ submissionId: settled.submissionId,
459
+ receiptId: settled.receiptId,
460
+ settlementId: settled.settlementId,
461
+ outcome: settled.outcome,
462
+ settledAt: d.settlement.settledAt,
463
+ ...failure === void 0 ? {} : { failure },
464
+ ...d.settlement.runDisposition === void 0 || settled.runDisposition === void 0 ? {} : { runDisposition: settled.runDisposition },
465
+ ...d.settlement.usageSummary === void 0 || settled.usageSummary === void 0 ? {} : { usageSummary: settled.usageSummary }
466
+ });
467
+ if (!Schema.toEquivalence(Settlement)(d.settlement, expected)) return yield* invalidThreadArchive("Delivery settlement differs from destination evidence", threadId);
468
+ }
469
+ }
470
+ }
471
+ return () => {
472
+ for (const text of staged.records.values()) commitInto(current, Schema.decodeSync(codec)(text), text, true);
473
+ };
474
+ })
475
+ };
476
+ })
477
+ });
136
478
  return MessageDeliveryStore.of({
137
479
  limits: config,
138
480
  maxStoredValueBytes,
139
481
  insert,
140
482
  get,
141
483
  change,
142
- list: Effect.fn("MemoryMessageDeliveryStore.list")(function* (request) {
484
+ list: Effect.fnUntraced(function* (request) {
143
485
  const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, "list");
144
486
  const current = yield* Ref.get(state);
145
487
  const workerKey = input.workerStarts !== void 0 ? JSON.stringify([
@@ -147,39 +489,60 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
147
489
  input.workerStarts.delegationId,
148
490
  input.workerStarts.targetAgentId
149
491
  ]) : input.pendingWorker !== void 0 ? JSON.stringify([input.ownerThreadId, input.pendingWorker]) : void 0;
150
- const records = (workerKey !== void 0 || input.pendingOnly ? yield* Effect.forEach([...(workerKey === void 0 ? current.pending.get(input.ownerThreadId) : current.workers.get(workerKey)) ?? []].filter((messageId) => input.after === void 0 || messageId > input.after).sort().slice(0, input.limit + 1), (messageId) => decode(current.records.get(messageDeliveryKeyString({
151
- ownerThreadId: input.ownerThreadId,
152
- messageId
153
- })) ?? "")) : yield* all()).filter((record) => record.key.ownerThreadId === input.ownerThreadId && (input.after === void 0 || record.key.messageId > input.after)).sort((left, right) => left.key.messageId < right.key.messageId ? -1 : left.key.messageId > right.key.messageId ? 1 : 0);
154
- const items = records.slice(0, input.limit);
492
+ const keys = workerKey === void 0 ? input.pendingOnly ? Array.from(current.pending.get(input.ownerThreadId) ?? []).sort() : current.ordered.get(ownerKey(input.ownerThreadId)) ?? [] : current.ordered.get(workerKey) ?? [];
493
+ const start = input.after === void 0 ? 0 : position(keys, input.after) + Number(keys[position(keys, input.after)] === input.after);
494
+ const items = [];
495
+ let bytes = 0;
496
+ let at = start;
497
+ while (at < keys.length && items.length < input.limit) {
498
+ const text = current.records.get(messageDeliveryKeyString({
499
+ ownerThreadId: input.ownerThreadId,
500
+ messageId: Schema.decodeSync(MessageDeliveryKey.fields.messageId)(keys[at])
501
+ }));
502
+ if (text === void 0) return yield* MessageDeliveryError.make({
503
+ reason: "corrupt",
504
+ operation: "list locator"
505
+ });
506
+ const size = new TextEncoder().encode(text).byteLength;
507
+ if (size > 33554432) return yield* MessageDeliveryError.make({
508
+ reason: "capacity",
509
+ operation: "list byte bound"
510
+ });
511
+ if (bytes + size > 33554432) break;
512
+ items.push(yield* decode(text));
513
+ bytes += size;
514
+ at++;
515
+ }
155
516
  return {
156
517
  items,
157
- next: records.length > input.limit ? items.at(-1)?.key.messageId ?? null : null
518
+ next: at < keys.length ? items.at(-1)?.key.messageId ?? null : null
158
519
  };
159
520
  }),
160
- due: Effect.fn("MemoryMessageDeliveryStore.due")(function* (nowMillis, limit, ownerThreadId) {
521
+ due: Effect.fnUntraced(function* (nowMillis, limit, ownerThreadId) {
161
522
  const input = yield* validateMessageDelivery(Scan, {
162
523
  nowMillis,
163
524
  limit,
164
525
  ...ownerThreadId === void 0 ? {} : { ownerThreadId }
165
526
  }, "due");
166
- return (yield* all()).flatMap((record) => {
167
- const deadline = messageDeliveryDeadline(record);
168
- return deadline !== null && deadline <= input.nowMillis && (input.ownerThreadId === void 0 || input.ownerThreadId === record.key.ownerThreadId) ? [{
169
- key: record.key,
170
- deadline
171
- }] : [];
172
- }).sort((left, right) => left.deadline - right.deadline || (messageDeliveryKeyString(left.key) < messageDeliveryKeyString(right.key) ? -1 : 1)).slice(0, input.limit).map((record) => record.key);
527
+ const current = MutableRef.get(state.ref);
528
+ const keys = input.ownerThreadId === void 0 ? current.deadlineKeys : current.ownerDeadlineKeys.get(input.ownerThreadId) ?? [];
529
+ const selected = [];
530
+ for (const key of keys) {
531
+ const row = current.deadlines.get(key);
532
+ if (row === void 0) return yield* MessageDeliveryError.make({
533
+ reason: "corrupt",
534
+ operation: "deadline index"
535
+ });
536
+ if (row.deadline > input.nowMillis || selected.length === input.limit) break;
537
+ selected.push(row.key);
538
+ }
539
+ return selected;
173
540
  }),
174
- nextDeadline: Effect.fn("MemoryMessageDeliveryStore.nextDeadline")(function* (ownerThreadId) {
541
+ nextDeadline: Effect.fnUntraced(function* (ownerThreadId) {
175
542
  if (ownerThreadId !== void 0) yield* validateMessageDelivery(ThreadId, ownerThreadId, "nextDeadline");
176
- let next = null;
177
- for (const record of yield* all()) {
178
- if (ownerThreadId !== void 0 && ownerThreadId !== record.key.ownerThreadId) continue;
179
- const deadline = messageDeliveryDeadline(record);
180
- if (deadline !== null && (next === null || deadline < next)) next = deadline;
181
- }
182
- return next;
543
+ const current = MutableRef.get(state.ref);
544
+ const first = (ownerThreadId === void 0 ? current.deadlineKeys : current.ownerDeadlineKeys.get(ownerThreadId))?.[0];
545
+ return first === void 0 ? null : current.deadlines.get(first)?.deadline ?? null;
183
546
  })
184
547
  });
185
548
  }));