deepline 0.3.284 → 0.3.286

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +1 -2
  2. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  3. package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +26 -2
  4. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +7 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +836 -2234
  6. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +19 -6
  7. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +127 -789
  8. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +18 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +16 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +180 -14
  11. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +1 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +3 -22
  13. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +26 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +59 -8
  15. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +122 -18
  16. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +458 -130
  17. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +60 -9
  18. package/dist/bundling-sources/shared_libs/play-runtime/test-async-batching.ts +4 -1
  19. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +13 -2
  20. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +181 -326
  21. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +38 -151
  22. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +68 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +8 -0
  24. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +10 -1
  25. package/dist/bundling-sources/shared_libs/plays/enrich-compat-adapter.ts +2 -0
  26. package/dist/cli/index.js +30579 -34364
  27. package/dist/cli/index.mjs +30647 -34443
  28. package/dist/{client-BCXQ_Ftc.d.ts → client-B7ic-kY5.d.ts} +6 -3
  29. package/dist/{client-B0AjIkgb.d.mts → client-Dzf_KovW.d.mts} +6 -3
  30. package/dist/{compiler-manifest-B1zo3igW.d.mts → compiler-manifest-9SQtUuKb.d.mts} +1 -1
  31. package/dist/{compiler-manifest-B1zo3igW.d.ts → compiler-manifest-9SQtUuKb.d.ts} +1 -1
  32. package/dist/index.d.mts +4 -4
  33. package/dist/index.d.ts +4 -4
  34. package/dist/index.js +34 -4
  35. package/dist/index.mjs +34 -4
  36. package/dist/install-integrity.json +4 -4
  37. package/dist/plays/bundle-play-file.d.mts +2 -2
  38. package/dist/plays/bundle-play-file.d.ts +2 -2
  39. package/dist/plays/bundle-play-file.mjs +8 -1
  40. package/dist/plays/run-request.d.mts +2 -2
  41. package/dist/plays/run-request.d.ts +2 -2
  42. package/dist/release.d.mts +1 -1
  43. package/dist/release.d.ts +1 -1
  44. package/dist/release.js +1 -1
  45. package/dist/release.mjs +1 -1
  46. package/package.json +1 -1
@@ -37,7 +37,7 @@ export type ToolCallReceiptCohortStore = {
37
37
  *
38
38
  * Deliberately returns nothing. A completed-result insert is immutable, so a
39
39
  * caller that already executed keeps its own result whether or not it won
40
- * the insert (see `flushPublications`). Materializing the stored rows here
40
+ * the insert. Materializing the stored rows here
41
41
  * only bought a second deserialize-and-wrap of every result, inside the
42
42
  * delivery interval a row is blocked on.
43
43
  */
@@ -46,6 +46,8 @@ export type ToolCallReceiptCohortStore = {
46
46
  ownerAttempt: number;
47
47
  receipts: Array<{
48
48
  resultReceiptKey: string;
49
+ /** Successful invocations represented by this coalesced cache write. */
50
+ memberIds?: readonly string[];
49
51
  lease: ToolCallReceiptLease;
50
52
  result: ToolExecuteResult;
51
53
  }>;
@@ -149,250 +151,210 @@ export function createToolCallReceiptCohortJob(input: {
149
151
  'Tool Call Receipt Cohort requires at least one member.',
150
152
  );
151
153
  }
152
-
153
- const groups = groupMembers(members);
154
- // A forced member deliberately discards any completed cache value and
155
- // executes a fresh provider operation. Reading it first is not merely
156
- // redundant: a large persisted result is fully hydrated and wrapped
157
- // before the force branch below ignores it. Keep completed-result cache
158
- // reads scoped to groups that can actually consume their values. This
159
- // also leaves a mixed group correct: `groupMembers` marks it forced when
160
- // any duplicate member explicitly requires fresh execution.
161
- const cacheReadKeys = [...groups.values()]
162
- .filter((group) => !group.force)
163
- .map((group) => group.resultReceiptKey);
164
- const records = byKey(
165
- cacheReadKeys.length > 0
154
+ const normalized = new Map<string, ToolCallReceiptCohortMember>();
155
+ for (const member of members) {
156
+ const memberId = required(member.memberId, 'member id');
157
+ if (normalized.has(memberId)) {
158
+ throw new Error(
159
+ `Duplicate Tool Call Receipt Cohort member ${memberId}.`,
160
+ );
161
+ }
162
+ normalized.set(memberId, {
163
+ ...member,
164
+ memberId,
165
+ resultReceiptKey: required(
166
+ member.resultReceiptKey,
167
+ 'result receipt key',
168
+ ),
169
+ });
170
+ }
171
+ const cacheReadKeys = [
172
+ ...new Set(
173
+ [...normalized.values()]
174
+ .filter((member) => !member.force)
175
+ .map((member) => member.resultReceiptKey),
176
+ ),
177
+ ];
178
+ const cached = new Map(
179
+ (cacheReadKeys.length > 0
166
180
  ? await input.store.read({ resultReceiptKeys: cacheReadKeys })
167
- : [],
181
+ : []
182
+ ).map((record) => [record.resultReceiptKey, record]),
168
183
  );
169
184
  const states = new Map<string, ToolCallReceiptCohortMemberState>();
170
- const deferred = new Map<string, Deferred<ToolExecuteResult>>();
171
- const owned = new Map<string, OwnedGroup>();
172
- const pendingPublications: PendingPublication[] = [];
173
- let publicationFlushScheduled = false;
185
+ const owned = new Map<string, OwnedMember>();
186
+ const pending: Publication[] = [];
187
+ let flushScheduled = false;
174
188
 
175
- const resolveGroup = (group: Group, result: ToolExecuteResult) => {
176
- deferredFor(group, deferred).resolve(result);
177
- for (const member of group.members) {
189
+ for (const member of normalized.values()) {
190
+ const record = !member.force && cached.get(member.resultReceiptKey);
191
+ if (record) {
178
192
  states.set(member.memberId, {
179
193
  kind: 'completed',
180
- result,
181
- source: 'owner',
194
+ result: record.result,
195
+ source: 'cache',
182
196
  });
183
- }
184
- };
185
- const rejectGroup = (group: Group, error: unknown) => {
186
- deferredFor(group, deferred).reject(error);
187
- };
188
-
189
- for (const group of groups.values()) {
190
- const cached = records.get(group.resultReceiptKey);
191
- if (!group.force && cached) {
192
- for (const member of group.members) {
193
- states.set(member.memberId, {
194
- kind: 'completed',
195
- result: cached.result,
196
- source: 'cache',
197
- });
198
- }
199
197
  continue;
200
198
  }
201
199
  const lease = {
202
200
  leaseId: COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
203
201
  ownerRunId,
204
202
  };
205
- const groupOwned = { group, lease };
206
- owned.set(group.resultReceiptKey, groupOwned);
207
- const ownerMember = group.members[0]!;
208
- states.set(ownerMember.memberId, {
203
+ const ownedMember = { member, lease };
204
+ owned.set(member.memberId, ownedMember);
205
+ states.set(member.memberId, {
209
206
  kind: 'owned',
210
- resultReceiptKey: group.resultReceiptKey,
207
+ resultReceiptKey: member.resultReceiptKey,
211
208
  lease,
212
- receipts: receiptView(ownerMember.memberId, groupOwned),
209
+ receipts: receiptView(ownedMember),
213
210
  });
214
- for (const follower of group.members.slice(1)) {
215
- states.set(follower.memberId, {
216
- kind: 'following',
217
- resultReceiptKey: group.resultReceiptKey,
218
- ownerMemberId: ownerMember.memberId,
219
- result: deferredFor(group, deferred).promise,
220
- });
221
- }
222
211
  }
223
212
 
224
- const flushPublications = async (): Promise<void> => {
225
- publicationFlushScheduled = false;
226
- const pending = pendingPublications.splice(0);
227
- if (pending.length === 0) return;
228
- // Which receipts each publication is waiting on, resolved before the
229
- // write so a publication can be settled the moment ITS receipts are
230
- // durable rather than when the whole wave is. A lease lost between the
231
- // queue and this flush is a pre-write fault of the wave, so it still
232
- // rejects every publication in it.
233
- const claims: PublicationClaim[] = [];
234
- const requestedByKey = new Map<string, RequestedReceipt>();
235
- try {
236
- for (const publication of pending) {
237
- const entries = ownedEntries(publication.entries, states, owned);
238
- for (const entry of entries) {
239
- const key = entry.group.resultReceiptKey;
240
- if (!requestedByKey.has(key)) requestedByKey.set(key, entry);
241
- }
242
- claims.push({
243
- publication,
244
- resultReceiptKeys: entries.map(
245
- (entry) => entry.group.resultReceiptKey,
246
- ),
247
- settled: false,
248
- });
249
- }
250
- } catch (error) {
251
- for (const publication of pending) publication.reject(error);
252
- return;
253
- }
254
- const requested = [...requestedByKey.values()];
255
- const settledByKey = new Map<string, ReceiptSettlement>();
256
- const results = new Map<string, ToolExecuteResult>();
213
+ const assertOwned = (memberId: string): OwnedMember => {
214
+ const current = owned.get(memberId);
215
+ if (!current) throw new ToolCallReceiptLeaseLostError(memberId);
216
+ return current;
217
+ };
257
218
 
258
- const settleReadyClaims = () => {
259
- for (const claim of claims) {
260
- if (claim.settled) continue;
261
- const settlements = claim.resultReceiptKeys.map((key) =>
262
- settledByKey.get(key),
263
- );
264
- if (settlements.some((settlement) => settlement === undefined)) {
265
- continue;
266
- }
267
- claim.settled = true;
268
- const failed = settlements.find(
269
- (settlement) => settlement?.failed === true,
270
- );
271
- if (failed) {
272
- // Only this publication: a receipt that failed to persist says
273
- // nothing about the other receipts of the same wave.
274
- claim.publication.reject(failed.error);
275
- continue;
276
- }
277
- claim.publication.resolve(
278
- new Map(
279
- claim.publication.entries.flatMap((entry) => {
280
- const result = results.get(entry.memberId);
281
- return result ? [[entry.memberId, result] as const] : [];
282
- }),
283
- ),
284
- );
219
+ const flush = async (): Promise<void> => {
220
+ flushScheduled = false;
221
+ const publications = pending.splice(0);
222
+ const writes = new Map<string, PublicationEntry>();
223
+ const entriesByKey = new Map<string, PublicationEntry[]>();
224
+ const publicationsByKey = new Map<string, Publication[]>();
225
+ for (const publication of publications) {
226
+ for (const entry of publication.entries) {
227
+ const key = entry.member.resultReceiptKey;
228
+ // Coalesce cache writes only. Every member already executed and
229
+ // retains its own result, even when this insert loses a race.
230
+ if (!writes.has(key)) writes.set(key, entry);
231
+ const matching = entriesByKey.get(key) ?? [];
232
+ matching.push(entry);
233
+ entriesByKey.set(key, matching);
285
234
  }
286
- };
287
-
288
- const settleReceipt = (
289
- resultReceiptKey: string,
290
- settlement: ReceiptSettlement,
291
- ) => {
292
- if (settledByKey.has(resultReceiptKey)) return;
293
- const entry = requestedByKey.get(resultReceiptKey);
294
- if (!entry) return;
295
- settledByKey.set(resultReceiptKey, settlement);
296
- // Per receipt, not per wave: the interval a caller opens here is
297
- // "this row's write settled -> this row resumed", and stamping it
298
- // when the last receipt of the wave landed charged an earlier row
299
- // for a later row's flight. Still stamped on failure too, so the
300
- // interval it opens is closed on both outcomes.
235
+ for (const key of publication.remaining) {
236
+ const matching = publicationsByKey.get(key) ?? [];
237
+ matching.push(publication);
238
+ publicationsByKey.set(key, matching);
239
+ }
240
+ }
241
+ const settled = new Set<string>();
242
+ const settle = (settlement: ToolCallReceiptCohortSettlement) => {
243
+ if (
244
+ !writes.has(settlement.resultReceiptKey) ||
245
+ settled.has(settlement.resultReceiptKey)
246
+ )
247
+ return;
248
+ settled.add(settlement.resultReceiptKey);
249
+ const matching = entriesByKey.get(settlement.resultReceiptKey) ?? [];
301
250
  input.timings?.onCompleteSettled?.({
302
- memberIds: entry.group.members.map((member) => member.memberId),
251
+ memberIds: matching.map((entry) => entry.member.memberId),
303
252
  atMs: Date.now(),
304
253
  });
305
- if (!settlement.failed) {
306
- // A completed-result insert is immutable: another independent
307
- // cold miss may have published first. That does not retroactively
308
- // turn this caller into a cache follower. It already executed, so
309
- // it returns its own result, and the stored row matters only to a
310
- // later cache read.
311
- owned.delete(resultReceiptKey);
312
- resolveGroup(entry.group, entry.result);
313
- for (const member of entry.group.members) {
314
- results.set(member.memberId, entry.result);
254
+ if (settlement.status === 'stored') {
255
+ for (const entry of matching) {
256
+ owned.delete(entry.member.memberId);
257
+ states.set(entry.member.memberId, {
258
+ kind: 'completed',
259
+ result: entry.result,
260
+ source: 'owner',
261
+ });
315
262
  }
316
263
  }
317
- settleReadyClaims();
264
+ for (const publication of publicationsByKey.get(
265
+ settlement.resultReceiptKey,
266
+ ) ?? []) {
267
+ publication.remaining.delete(settlement.resultReceiptKey);
268
+ if (settlement.status === 'failed')
269
+ publication.failure = settlement;
270
+ if (publication.remaining.size > 0) continue;
271
+ if (publication.failure)
272
+ publication.reject(publication.failure.error);
273
+ else
274
+ publication.resolve(
275
+ new Map(
276
+ publication.entries.map((entry) => [
277
+ entry.member.memberId,
278
+ entry.result,
279
+ ]),
280
+ ),
281
+ );
282
+ }
318
283
  };
319
-
320
284
  try {
321
285
  await input.store.complete({
322
286
  ownerRunId,
323
287
  ownerAttempt: owner.attempt,
324
- receipts: requested.map(({ group, lease, result }) => ({
325
- resultReceiptKey: group.resultReceiptKey,
326
- lease,
327
- result,
288
+ receipts: [...writes.values()].map((entry) => ({
289
+ resultReceiptKey: entry.member.resultReceiptKey,
290
+ memberIds: (
291
+ entriesByKey.get(entry.member.resultReceiptKey) ?? []
292
+ ).map((member) => member.member.memberId),
293
+ lease: entry.lease,
294
+ result: entry.result,
328
295
  })),
329
- onReceiptSettled: (settlement) =>
330
- settleReceipt(
331
- settlement.resultReceiptKey,
332
- settlement.status === 'failed'
333
- ? { failed: true, error: settlement.error }
334
- : { failed: false, error: undefined },
335
- ),
296
+ onReceiptSettled: settle,
336
297
  });
337
- for (const entry of requested) {
338
- settleReceipt(entry.group.resultReceiptKey, {
339
- failed: false,
340
- error: undefined,
341
- });
298
+ for (const resultReceiptKey of writes.keys()) {
299
+ settle({ resultReceiptKey, status: 'stored' });
342
300
  }
343
301
  } catch (error) {
344
- for (const entry of requested) {
345
- settleReceipt(entry.group.resultReceiptKey, {
346
- failed: true,
347
- error,
348
- });
302
+ for (const resultReceiptKey of writes.keys()) {
303
+ settle({ resultReceiptKey, status: 'failed', error });
349
304
  }
350
305
  }
351
306
  };
352
307
 
353
- const publish = async (
308
+ const publish = (
354
309
  entries: Array<{ memberId: string; result: ToolExecuteResult }>,
355
310
  ): Promise<Map<string, ToolExecuteResult>> => {
356
- // Validate ownership before queuing, then coalesce writes that settle
357
- // in this event-loop turn. This batches cache persistence only; it
358
- // never waits for or coordinates another run's provider call.
359
- if (ownedEntries(entries, states, owned).length === 0) {
360
- return new Map();
361
- }
362
- return await new Promise<Map<string, ToolExecuteResult>>(
311
+ if (entries.length === 0) return Promise.resolve(new Map());
312
+ const publication = new Promise<Map<string, ToolExecuteResult>>(
363
313
  (resolve, reject) => {
364
- pendingPublications.push({ entries, resolve, reject });
365
- if (publicationFlushScheduled) return;
366
- publicationFlushScheduled = true;
367
- setImmediate(() => {
368
- void flushPublications();
314
+ const publications = entries.map((entry) => ({
315
+ ...assertOwned(entry.memberId),
316
+ result: entry.result,
317
+ }));
318
+ pending.push({
319
+ entries: publications,
320
+ resolve,
321
+ reject,
322
+ remaining: new Set(
323
+ publications.map((entry) => entry.member.resultReceiptKey),
324
+ ),
369
325
  });
326
+ if (!flushScheduled) {
327
+ flushScheduled = true;
328
+ setImmediate(() => {
329
+ void flush();
330
+ });
331
+ }
370
332
  },
371
333
  );
334
+ // Siblings can settle while a caller is still awaiting another one.
335
+ // Observe rejection now, but return the original rejecting promise.
336
+ void publication.catch(() => undefined);
337
+ return publication;
372
338
  };
373
339
 
374
- function receiptView(
375
- memberId: string,
376
- groupOwned: OwnedGroup,
377
- ): ToolResultReceipts {
378
- const { group, lease } = groupOwned;
340
+ function receiptView(current: OwnedMember): ToolResultReceipts {
341
+ const { member, lease } = current;
379
342
  const assert = (candidate: {
380
343
  resultReceiptKey: string;
381
- lease: ToolCallReceiptLease;
344
+ lease?: ToolCallReceiptLease;
382
345
  }) => {
383
346
  if (
384
- candidate.resultReceiptKey !== group.resultReceiptKey ||
385
- !sameLease(candidate.lease, lease)
347
+ candidate.resultReceiptKey !== member.resultReceiptKey ||
348
+ (candidate.lease && !sameLease(candidate.lease, lease))
386
349
  ) {
387
350
  throw new ToolCallReceiptLeaseLostError(candidate.resultReceiptKey);
388
351
  }
352
+ assertOwned(member.memberId);
389
353
  };
390
354
  return {
391
355
  async claim(candidate) {
392
- if (
393
- candidate.resultReceiptKey !== group.resultReceiptKey ||
394
- candidate.ownerRunId !== ownerRunId
395
- ) {
356
+ assert(candidate);
357
+ if (candidate.ownerRunId !== ownerRunId) {
396
358
  throw new ToolCallReceiptLeaseLostError(
397
359
  candidate.resultReceiptKey,
398
360
  );
@@ -400,34 +362,31 @@ export function createToolCallReceiptCohortJob(input: {
400
362
  return { kind: 'owned', lease };
401
363
  },
402
364
  async recover(candidate) {
403
- if (
404
- candidate.resultReceiptKey !== group.resultReceiptKey ||
405
- candidate.ownerRunId !== ownerRunId
406
- ) {
407
- throw new ToolCallReceiptLeaseLostError(
408
- candidate.resultReceiptKey,
409
- );
410
- }
411
- return await deferredFor(group, deferred).promise;
365
+ throw new Error(
366
+ `An independently executing Tool Call cannot follow ${candidate.resultReceiptKey}.`,
367
+ );
412
368
  },
413
369
  async heartbeat(candidate) {
414
370
  assert(candidate);
415
- return owned.has(group.resultReceiptKey) ? 'active' : 'completed';
371
+ return 'active';
416
372
  },
417
- async complete(candidate) {
418
- assert(candidate);
419
- const results = await publish([
420
- { memberId, result: candidate.result },
421
- ]);
422
- return {
423
- kind: 'stored',
424
- result: results.get(memberId) ?? candidate.result,
425
- };
373
+ complete(candidate) {
374
+ const completion = (async () => {
375
+ assert(candidate);
376
+ const result = candidate.result;
377
+ await publish([{ memberId: member.memberId, result }]);
378
+ return {
379
+ kind: 'stored' as const,
380
+ result,
381
+ };
382
+ })();
383
+ // The member result wraps the queued promise; guard this promise too.
384
+ void completion.catch(() => undefined);
385
+ return completion;
426
386
  },
427
387
  async fail(candidate) {
428
388
  assert(candidate);
429
- owned.delete(group.resultReceiptKey);
430
- rejectGroup(group, candidate.error);
389
+ owned.delete(member.memberId);
431
390
  },
432
391
  };
433
392
  }
@@ -435,26 +394,17 @@ export function createToolCallReceiptCohortJob(input: {
435
394
  return {
436
395
  member(memberId) {
437
396
  const state = states.get(memberId);
438
- if (!state) {
397
+ if (!state)
439
398
  throw new Error(
440
399
  `Unknown Tool Call Receipt Cohort member ${memberId}.`,
441
400
  );
442
- }
443
401
  return state;
444
402
  },
445
403
  complete: publish,
446
404
  async fail(entries) {
447
405
  for (const entry of entries) {
448
- const state = states.get(entry.memberId);
449
- if (!state || state.kind !== 'owned') {
450
- throw new ToolCallReceiptLeaseLostError(entry.memberId);
451
- }
452
- const groupOwned = owned.get(state.resultReceiptKey);
453
- if (!groupOwned) {
454
- throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
455
- }
456
- owned.delete(state.resultReceiptKey);
457
- rejectGroup(groupOwned.group, entry.error);
406
+ assertOwned(entry.memberId);
407
+ owned.delete(entry.memberId);
458
408
  }
459
409
  },
460
410
  async heartbeat() {
@@ -469,113 +419,19 @@ export function createToolCallReceiptCohortJob(input: {
469
419
  };
470
420
  }
471
421
 
472
- type Group = {
473
- resultReceiptKey: string;
474
- members: ToolCallReceiptCohortMember[];
475
- force: boolean;
422
+ type OwnedMember = {
423
+ member: ToolCallReceiptCohortMember;
424
+ lease: ToolCallReceiptLease;
476
425
  };
477
-
478
- type OwnedGroup = { group: Group; lease: ToolCallReceiptLease };
479
-
480
- type Deferred<T> = {
481
- promise: Promise<T>;
482
- resolve(value: T): void;
483
- reject(error: unknown): void;
484
- };
485
-
486
- type PendingPublication = {
487
- entries: Array<{ memberId: string; result: ToolExecuteResult }>;
426
+ type PublicationEntry = OwnedMember & { result: ToolExecuteResult };
427
+ type Publication = {
428
+ entries: PublicationEntry[];
488
429
  resolve(value: Map<string, ToolExecuteResult>): void;
489
430
  reject(error: unknown): void;
431
+ remaining: Set<string>;
432
+ failure?: Extract<ToolCallReceiptCohortSettlement, { status: 'failed' }>;
490
433
  };
491
434
 
492
- type RequestedReceipt = {
493
- memberId: string;
494
- result: ToolExecuteResult;
495
- } & OwnedGroup;
496
-
497
- /** One queued publication and the receipts it is waiting to become durable. */
498
- type PublicationClaim = {
499
- publication: PendingPublication;
500
- resultReceiptKeys: string[];
501
- settled: boolean;
502
- };
503
-
504
- type ReceiptSettlement = { failed: boolean; error: unknown };
505
-
506
- function groupMembers(
507
- members: ToolCallReceiptCohortMember[],
508
- ): Map<string, Group> {
509
- const groups = new Map<string, Group>();
510
- const memberIds = new Set<string>();
511
- for (const member of members) {
512
- const memberId = required(member.memberId, 'member id');
513
- if (memberIds.has(memberId)) {
514
- throw new Error(`Duplicate Tool Call Receipt Cohort member ${memberId}.`);
515
- }
516
- memberIds.add(memberId);
517
- const resultReceiptKey = required(
518
- member.resultReceiptKey,
519
- 'result receipt key',
520
- );
521
- const group = groups.get(resultReceiptKey) ?? {
522
- resultReceiptKey,
523
- members: [],
524
- force: false,
525
- };
526
- group.members.push({ ...member, memberId, resultReceiptKey });
527
- group.force ||= member.force === true;
528
- groups.set(resultReceiptKey, group);
529
- }
530
- return groups;
531
- }
532
-
533
- function deferredFor(
534
- group: Group,
535
- deferred: Map<string, Deferred<ToolExecuteResult>>,
536
- ): Deferred<ToolExecuteResult> {
537
- let value = deferred.get(group.resultReceiptKey);
538
- if (value) return value;
539
- let resolve!: (result: ToolExecuteResult) => void;
540
- let reject!: (error: unknown) => void;
541
- const promise = new Promise<ToolExecuteResult>(
542
- (resolvePromise, rejectPromise) => {
543
- resolve = resolvePromise;
544
- reject = rejectPromise;
545
- },
546
- );
547
- void promise.catch(() => undefined);
548
- value = { promise, resolve, reject };
549
- deferred.set(group.resultReceiptKey, value);
550
- return value;
551
- }
552
-
553
- function ownedEntries<T extends { memberId: string }>(
554
- entries: T[],
555
- states: Map<string, ToolCallReceiptCohortMemberState>,
556
- owned: Map<string, OwnedGroup>,
557
- ): Array<T & OwnedGroup> {
558
- const byKey = new Map<string, T & OwnedGroup>();
559
- for (const entry of entries) {
560
- const state = states.get(entry.memberId);
561
- if (!state || state.kind !== 'owned') {
562
- throw new ToolCallReceiptLeaseLostError(entry.memberId);
563
- }
564
- const groupOwned = owned.get(state.resultReceiptKey);
565
- if (!groupOwned || !sameLease(groupOwned.lease, state.lease)) {
566
- throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
567
- }
568
- byKey.set(groupOwned.group.resultReceiptKey, { ...entry, ...groupOwned });
569
- }
570
- return [...byKey.values()];
571
- }
572
-
573
- function byKey(
574
- records: ToolCallReceiptRecord[],
575
- ): Map<string, ToolCallReceiptRecord> {
576
- return new Map(records.map((record) => [record.resultReceiptKey, record]));
577
- }
578
-
579
435
  function sameLease(
580
436
  left: ToolCallReceiptLease,
581
437
  right: ToolCallReceiptLease,
@@ -585,8 +441,7 @@ function sameLease(
585
441
 
586
442
  function required(value: string, name: string): string {
587
443
  const normalized = value?.trim();
588
- if (!normalized) {
444
+ if (!normalized)
589
445
  throw new Error(`Tool Call Receipt Cohort requires a ${name}.`);
590
- }
591
446
  return normalized;
592
447
  }