deepline 0.1.210 → 0.1.211

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.
@@ -18,6 +18,7 @@ import {
18
18
  isPlayDataset,
19
19
  iteratePlayDatasetInputPages,
20
20
  materializePlayDatasetInput,
21
+ resolveMaterializeLimitCap,
21
22
  } from '@shared_libs/plays/dataset';
22
23
  import type { PlayDataset, PlayDatasetInput } from '@shared_libs/plays/dataset';
23
24
  import {
@@ -261,6 +262,7 @@ const rowContext = new AsyncLocalStorage<{
261
262
  rowKey?: string;
262
263
  mapScope?: MapExecutionScope;
263
264
  }>();
265
+ const toolExecutionOverrides = new AsyncLocalStorage<{ force: boolean }>();
264
266
  const PROGRESS_HEARTBEAT_INTERVAL_MS = 1_000;
265
267
  const PURE_JS_HEARTBEAT_ROW_INTERVAL = 250;
266
268
  /**
@@ -819,6 +821,111 @@ function stableDigest(value: string): string {
819
821
  return sha256Hex(value);
820
822
  }
821
823
 
824
+ // Receipt keys contain canonicalized tool inputs. Runtime diagnostics must never
825
+ // emit them directly; a short digest is enough to correlate gateway, runner,
826
+ // and waiter observations for one failing run.
827
+ function runtimeReceiptKeyDigest(key: string): string {
828
+ return stableDigest(key).slice(0, 12);
829
+ }
830
+
831
+ function runtimeReceiptReadSummary(input: {
832
+ requested: readonly string[];
833
+ receipts: readonly (RuntimeStepReceipt | null | undefined)[];
834
+ resolved: ReadonlyMap<string, RuntimeStepReceipt>;
835
+ }): string {
836
+ const statuses = input.receipts.reduce<Record<string, number>>(
837
+ (counts, receipt) => {
838
+ const status = receipt?.status ?? 'missing';
839
+ counts[status] = (counts[status] ?? 0) + 1;
840
+ return counts;
841
+ },
842
+ {},
843
+ );
844
+ const positionalMismatches = input.receipts.reduce(
845
+ (count, receipt, index) =>
846
+ receipt && receipt.key.trim() !== (input.requested[index] ?? '').trim()
847
+ ? count + 1
848
+ : count,
849
+ 0,
850
+ );
851
+ const missingRequested = input.requested.filter(
852
+ (key) => !input.resolved.has(key),
853
+ );
854
+ return (
855
+ `requested=${input.requested.length} returned=${input.receipts.length} ` +
856
+ `resolved=${input.resolved.size} positional_mismatches=${positionalMismatches} ` +
857
+ `request_digests=${input.requested.map(runtimeReceiptKeyDigest).join(',')} ` +
858
+ `returned_digests=${input.receipts
859
+ .map((receipt) =>
860
+ receipt ? runtimeReceiptKeyDigest(receipt.key) : 'null',
861
+ )
862
+ .join(',')} ` +
863
+ `missing_digests=${missingRequested
864
+ .map(runtimeReceiptKeyDigest)
865
+ .join(',')} ` +
866
+ `statuses=${Object.entries(statuses)
867
+ .map(([status, count]) => `${status}:${count}`)
868
+ .join(',')}`
869
+ );
870
+ }
871
+
872
+ const runtimeReceiptReadTraceEnabled =
873
+ process.env.DEEPLINE_RUNTIME_RECEIPT_TRACE === '1';
874
+
875
+ function runtimeReceiptReadTrace(input: {
876
+ keys: readonly string[];
877
+ receipts: Array<RuntimeStepReceipt | null>;
878
+ byKey: ReadonlyMap<string, RuntimeStepReceipt>;
879
+ }): string | null {
880
+ if (!runtimeReceiptReadTraceEnabled) return null;
881
+ const digest = (value: string) => stableDigest(value).slice(0, 16);
882
+ const statusCounts = input.receipts.reduce<Record<string, number>>(
883
+ (counts, receipt) => {
884
+ if (receipt) {
885
+ counts[receipt.status] = (counts[receipt.status] ?? 0) + 1;
886
+ }
887
+ return counts;
888
+ },
889
+ {},
890
+ );
891
+ const expiredInFlightCount = input.receipts.filter((receipt) => {
892
+ if (
893
+ receipt?.status !== 'queued' &&
894
+ receipt?.status !== 'pending' &&
895
+ receipt?.status !== 'running'
896
+ ) {
897
+ return false;
898
+ }
899
+ const expiresAt = receipt.leaseExpiresAt
900
+ ? Date.parse(receipt.leaseExpiresAt)
901
+ : Number.NaN;
902
+ return Number.isFinite(expiresAt) && expiresAt <= Date.now();
903
+ }).length;
904
+ return (
905
+ '[runtime-receipt-context.read-trace] ' +
906
+ JSON.stringify({
907
+ requestedCount: input.keys.length,
908
+ returnedCount: input.receipts.filter(Boolean).length,
909
+ mapSize: input.byKey.size,
910
+ statusCounts,
911
+ expiredInFlightCount,
912
+ requestedOrderDigest: digest(input.keys.join('\u0000')),
913
+ returnedOrderDigest: digest(
914
+ input.receipts
915
+ .map((receipt) =>
916
+ receipt ? `${digest(receipt.key)}:${receipt.status}` : '-',
917
+ )
918
+ .join('\u0000'),
919
+ ),
920
+ mapOrderDigest: digest(
921
+ [...input.byKey.entries()]
922
+ .map(([key, receipt]) => `${digest(key)}:${receipt.status}`)
923
+ .join('\u0000'),
924
+ ),
925
+ })
926
+ );
927
+ }
928
+
822
929
  type DurableCtxOperation = 'step' | 'tool' | 'fetch';
823
930
 
824
931
  function durableCtxKey(input: {
@@ -1176,12 +1283,15 @@ export class PlayContextImpl {
1176
1283
  );
1177
1284
  }
1178
1285
  assertNoSecretTaint(request.input, 'ctx.tools.execute input');
1286
+ const force =
1287
+ request.force === true ||
1288
+ toolExecutionOverrides.getStore()?.force === true;
1179
1289
  return this.executeTool(
1180
1290
  request.id.trim(),
1181
1291
  request.tool,
1182
1292
  request.input,
1183
1293
  request.description ||
1184
- request.force === true ||
1294
+ force ||
1185
1295
  request.staleAfterSeconds !== undefined ||
1186
1296
  request.timeoutMs !== undefined ||
1187
1297
  request.receiptWaitMs !== undefined
@@ -1189,7 +1299,7 @@ export class PlayContextImpl {
1189
1299
  ...(request.description
1190
1300
  ? { description: request.description }
1191
1301
  : {}),
1192
- ...(request.force === true ? { force: true } : {}),
1302
+ ...(force ? { force: true } : {}),
1193
1303
  ...(request.staleAfterSeconds !== undefined
1194
1304
  ? { staleAfterSeconds: request.staleAfterSeconds }
1195
1305
  : {}),
@@ -1204,6 +1314,10 @@ export class PlayContextImpl {
1204
1314
  ) as Promise<TOutput>;
1205
1315
  },
1206
1316
  };
1317
+
1318
+ async __deeplineRunWithForcedTools<T>(run: () => Promise<T>): Promise<T> {
1319
+ return await toolExecutionOverrides.run({ force: true }, run);
1320
+ }
1207
1321
  readonly customerDb = {
1208
1322
  query: async <TRow extends object = Record<string, unknown>>(
1209
1323
  statement:
@@ -2161,6 +2275,21 @@ export class PlayContextImpl {
2161
2275
  );
2162
2276
  if (normalized) byKey.set(normalized.key, normalized);
2163
2277
  }
2278
+ if (runtimeReceiptReadTraceEnabled) {
2279
+ this.log(
2280
+ `[runtime-receipt-normalize] ${runtimeReceiptReadSummary({
2281
+ requested: uniqueKeys,
2282
+ receipts,
2283
+ resolved: byKey,
2284
+ })}`,
2285
+ );
2286
+ const trace = runtimeReceiptReadTrace({
2287
+ keys: uniqueKeys,
2288
+ receipts,
2289
+ byKey,
2290
+ });
2291
+ if (trace) this.log(trace);
2292
+ }
2164
2293
  return byKey;
2165
2294
  }
2166
2295
 
@@ -2207,6 +2336,16 @@ export class PlayContextImpl {
2207
2336
  );
2208
2337
  if (normalized) byKey.set(normalized.key, normalized);
2209
2338
  }
2339
+ if (runtimeReceiptReadTraceEnabled) {
2340
+ this.log(
2341
+ `[runtime-receipt-claim-normalize] reclaim_running=${reclaimRunning} force_refresh=${forceRefresh} force_failed_refresh=${forceFailedRefresh} ` +
2342
+ runtimeReceiptReadSummary({
2343
+ requested: uniqueKeys,
2344
+ receipts,
2345
+ resolved: byKey,
2346
+ }),
2347
+ );
2348
+ }
2210
2349
  return byKey;
2211
2350
  }
2212
2351
 
@@ -2667,6 +2806,7 @@ export class PlayContextImpl {
2667
2806
  receiptKeys: keys,
2668
2807
  store: this.durableReceiptExecutionStore(),
2669
2808
  maxAttempts,
2809
+ log: (message) => this.log(message),
2670
2810
  });
2671
2811
  }
2672
2812
 
@@ -3755,6 +3895,13 @@ export class PlayContextImpl {
3755
3895
  let duplicateReuseCount = 0;
3756
3896
  let executedCount = 0;
3757
3897
  const previewRows: Record<string, unknown>[] = [];
3898
+ // A bounded dataset may be materialized by authored code immediately
3899
+ // after run(). Keep the rows produced by this exact awaited execution so
3900
+ // that read-after-write does not depend on a separate sheet read becoming
3901
+ // visible. Datasets above the public materialize cap remain fully
3902
+ // streaming and sheet-backed.
3903
+ const materializedResultRowLimit = resolveMaterializeLimitCap();
3904
+ const immediateMaterializedRows: Record<string, unknown>[] = [];
3758
3905
 
3759
3906
  const persistMapRows = async (rows: PersistableMapRow[]) => {
3760
3907
  if (!this.#options.onMapRowsCompleted || rows.length === 0) {
@@ -4038,8 +4185,11 @@ export class PlayContextImpl {
4038
4185
  this.activeMapCellMeta = null;
4039
4186
 
4040
4187
  for (const row of mapResult.completedRows) {
4041
- if (previewRows.length >= 5) break;
4042
- previewRows.push(this.toMaterializedOutputRow(row.data));
4188
+ const materializedRow = this.toMaterializedOutputRow(row.data);
4189
+ if (previewRows.length < 5) previewRows.push(materializedRow);
4190
+ if (immediateMaterializedRows.length < materializedResultRowLimit) {
4191
+ immediateMaterializedRows.push(materializedRow);
4192
+ }
4043
4193
  }
4044
4194
  executedCount += rowsToExecute.length;
4045
4195
  successfulCount += mapResult.completedRows.length;
@@ -4100,6 +4250,42 @@ export class PlayContextImpl {
4100
4250
  return result.rows.map((row) => this.toMaterializedOutputRow(row));
4101
4251
  };
4102
4252
  const materializeRuntimeBackedMapRows = async (limit?: number) => {
4253
+ if (limit !== undefined && limit <= 0) return [];
4254
+ const availableResultRows = Math.min(
4255
+ successfulCount,
4256
+ materializedResultRowLimit,
4257
+ );
4258
+ if (
4259
+ immediateMaterializedRows.length === availableResultRows &&
4260
+ (limit !== undefined || successfulCount <= materializedResultRowLimit)
4261
+ ) {
4262
+ return limit === undefined
4263
+ ? immediateMaterializedRows.slice()
4264
+ : immediateMaterializedRows.slice(0, limit);
4265
+ }
4266
+ const pageSize = 1000;
4267
+ const materialized: Record<string, unknown>[] = [];
4268
+ let offset = 0;
4269
+ while (true) {
4270
+ const remaining =
4271
+ limit === undefined
4272
+ ? pageSize
4273
+ : Math.max(0, limit - materialized.length);
4274
+ if (remaining === 0) break;
4275
+ const rows = await readRuntimeBackedMapRows({
4276
+ limit: Math.min(pageSize, remaining),
4277
+ offset,
4278
+ });
4279
+ if (rows.length === 0) break;
4280
+ materialized.push(...rows);
4281
+ if (limit !== undefined && materialized.length >= limit) {
4282
+ return materialized.slice(0, limit);
4283
+ }
4284
+ offset += rows.length;
4285
+ }
4286
+ return materialized;
4287
+ };
4288
+ const materializeFullPersistedMapRows = async (limit?: number) => {
4103
4289
  if (limit !== undefined && limit <= 0) return [];
4104
4290
  const pageSize = 1000;
4105
4291
  const materialized: Record<string, unknown>[] = [];
@@ -4156,14 +4342,22 @@ export class PlayContextImpl {
4156
4342
  peek: async (limit) =>
4157
4343
  limit <= 0
4158
4344
  ? []
4159
- : await readRuntimeBackedMapRows({
4160
- limit,
4161
- offset: 0,
4162
- }),
4345
+ : immediateMaterializedRows.length >=
4346
+ Math.min(limit, successfulCount)
4347
+ ? immediateMaterializedRows.slice(0, limit)
4348
+ : await readRuntimeBackedMapRows({
4349
+ limit,
4350
+ offset: 0,
4351
+ }),
4163
4352
  materialize: materializeRuntimeBackedMapRows,
4353
+ materializeFullPersistedDataset: materializeFullPersistedMapRows,
4164
4354
  iterate: () =>
4165
4355
  ({
4166
4356
  async *[Symbol.asyncIterator]() {
4357
+ if (immediateMaterializedRows.length === successfulCount) {
4358
+ for (const row of immediateMaterializedRows) yield row;
4359
+ return;
4360
+ }
4167
4361
  const pageSize = 1000;
4168
4362
  let offset = 0;
4169
4363
  while (true) {
@@ -4598,39 +4792,33 @@ export class PlayContextImpl {
4598
4792
  });
4599
4793
  }
4600
4794
 
4601
- const readRuntimeBackedMapRows =
4795
+ // This map's terminal rows have already crossed the persistence barrier
4796
+ // above. Keep that verified snapshot as the authored return value instead
4797
+ // of immediately re-reading the Runtime Sheet projection. A second read
4798
+ // can observe a lagging/dynamically incomplete physical projection even
4799
+ // though the terminal write succeeded (notably for inline-child object
4800
+ // outputs on Absurd), making `materialize()` disagree with durable truth.
4801
+ // Neon remains the backing for later API/export reads of this handle.
4802
+ const terminalRows = results.map((row) =>
4803
+ this.toMaterializedOutputRow(row),
4804
+ );
4805
+ const materializeTerminalRows = async (limit?: number) =>
4806
+ limit === undefined
4807
+ ? terminalRows.slice()
4808
+ : terminalRows.slice(0, Math.max(0, limit));
4809
+ const runtimeSheetBacked =
4602
4810
  this.#options.runtimeSheetBackedMapDatasets === true &&
4603
- this.#options.baseUrl &&
4604
- this.#options.executorToken &&
4605
- this.#options.playName &&
4606
- this.#options.runId
4607
- ? async (input: { limit: number; offset: number }) => {
4608
- const result = await readRuntimeSheetDatasetRows(
4609
- {
4610
- baseUrl: this.#options.baseUrl!,
4611
- executorToken: this.#options.executorToken!,
4612
- dbSessionStrategy: this.#options.dbSessionStrategy,
4613
- playName: this.#options.playName!,
4614
- userEmail: this.#options.userEmail,
4615
- runId: this.#options.runId!,
4616
- },
4617
- {
4618
- tableNamespace: resolvedTableNamespace,
4619
- runId: this.#options.runId!,
4620
- limit: input.limit,
4621
- offset: input.offset,
4622
- },
4623
- );
4624
- return result.rows.map((row) => this.toMaterializedOutputRow(row));
4625
- }
4626
- : null;
4627
- const materializeRuntimeBackedMapRows = async (limit?: number) => {
4628
- if (!readRuntimeBackedMapRows) {
4629
- return limit === undefined
4630
- ? results.map((row) => this.toMaterializedOutputRow(row))
4631
- : results
4632
- .slice(0, Math.max(0, limit))
4633
- .map((row) => this.toMaterializedOutputRow(row));
4811
+ Boolean(
4812
+ this.#options.baseUrl &&
4813
+ this.#options.executorToken &&
4814
+ this.#options.playName &&
4815
+ this.#options.runId,
4816
+ );
4817
+ const materializeFullPersistedMapRows = async (limit?: number) => {
4818
+ if (!runtimeSheetBacked) {
4819
+ throw new Error(
4820
+ 'The full persisted dataset is unavailable because this run result has no Runtime Sheet backing.',
4821
+ );
4634
4822
  }
4635
4823
  if (limit !== undefined && limit <= 0) return [];
4636
4824
  const pageSize = 1000;
@@ -4642,10 +4830,25 @@ export class PlayContextImpl {
4642
4830
  ? pageSize
4643
4831
  : Math.max(0, limit - materialized.length);
4644
4832
  if (remaining === 0) break;
4645
- const rows = await readRuntimeBackedMapRows({
4646
- limit: Math.min(pageSize, remaining),
4647
- offset,
4648
- });
4833
+ const result = await readRuntimeSheetDatasetRows(
4834
+ {
4835
+ baseUrl: this.#options.baseUrl!,
4836
+ executorToken: this.#options.executorToken!,
4837
+ dbSessionStrategy: this.#options.dbSessionStrategy,
4838
+ playName: this.#options.playName!,
4839
+ userEmail: this.#options.userEmail,
4840
+ runId: this.#options.runId!,
4841
+ },
4842
+ {
4843
+ tableNamespace: resolvedTableNamespace,
4844
+ runId: this.#options.runId!,
4845
+ limit: Math.min(pageSize, remaining),
4846
+ offset,
4847
+ },
4848
+ );
4849
+ const rows = result.rows.map((row) =>
4850
+ this.toMaterializedOutputRow(row),
4851
+ );
4649
4852
  if (rows.length === 0) break;
4650
4853
  materialized.push(...rows);
4651
4854
  if (limit !== undefined && materialized.length >= limit) {
@@ -4663,7 +4866,7 @@ export class PlayContextImpl {
4663
4866
  resolvedTableNamespace,
4664
4867
  ),
4665
4868
  count: results.length,
4666
- backing: readRuntimeBackedMapRows
4869
+ backing: runtimeSheetBacked
4667
4870
  ? {
4668
4871
  storage: 'neon_sheet',
4669
4872
  sheet: {
@@ -4689,37 +4892,17 @@ export class PlayContextImpl {
4689
4892
  },
4690
4893
  resolvers: {
4691
4894
  count: async () => results.length,
4692
- peek: async (limit) =>
4693
- readRuntimeBackedMapRows
4694
- ? limit <= 0
4695
- ? []
4696
- : await readRuntimeBackedMapRows({
4697
- limit,
4698
- offset: 0,
4699
- })
4700
- : results
4701
- .slice(0, Math.max(0, limit))
4702
- .map((row) => this.toMaterializedOutputRow(row)),
4703
- materialize: materializeRuntimeBackedMapRows,
4895
+ peek: async (limit) => terminalRows.slice(0, Math.max(0, limit)),
4896
+ materialize: materializeTerminalRows,
4897
+ ...(runtimeSheetBacked
4898
+ ? {
4899
+ materializeFullPersistedDataset: materializeFullPersistedMapRows,
4900
+ }
4901
+ : {}),
4704
4902
  iterate: () =>
4705
4903
  ({
4706
4904
  async *[Symbol.asyncIterator]() {
4707
- if (readRuntimeBackedMapRows) {
4708
- const pageSize = 1000;
4709
- let offset = 0;
4710
- while (true) {
4711
- const rows = await readRuntimeBackedMapRows({
4712
- limit: pageSize,
4713
- offset,
4714
- });
4715
- if (rows.length === 0) return;
4716
- for (const row of rows) {
4717
- yield row;
4718
- }
4719
- offset += rows.length;
4720
- }
4721
- }
4722
- for (const row of results) {
4905
+ for (const row of terminalRows) {
4723
4906
  yield row;
4724
4907
  }
4725
4908
  },
@@ -7431,18 +7614,32 @@ export class PlayContextImpl {
7431
7614
  const forcedExisting = forcedReceiptKeys.length
7432
7615
  ? await this.getRuntimeStepReceipts(forcedReceiptKeys)
7433
7616
  : new Map<string, RuntimeStepReceipt>();
7434
- const forcedRunningReceiptKeys = new Set(
7617
+ // A forced refresh must never duplicate a provider call owned by a
7618
+ // live lease. It can, however, immediately claim pending/unleased
7619
+ // work and any expired lease. Waiting first for those receipts turns
7620
+ // an already-recoverable interruption into a five-minute stall.
7621
+ const forcedLiveReceiptKeys = new Set(
7435
7622
  [...forcedExisting]
7436
- .filter(
7437
- ([, receipt]) =>
7438
- receipt.status === 'queued' ||
7439
- receipt.status === 'pending' ||
7440
- receipt.status === 'running',
7441
- )
7623
+ .filter(([, receipt]) => {
7624
+ if (
7625
+ receipt.status !== 'queued' &&
7626
+ receipt.status !== 'pending' &&
7627
+ receipt.status !== 'running'
7628
+ ) {
7629
+ return false;
7630
+ }
7631
+ if (!receipt.leaseId?.trim()) return false;
7632
+ const expiresAt = receipt.leaseExpiresAt
7633
+ ? Date.parse(receipt.leaseExpiresAt)
7634
+ : Number.NaN;
7635
+ // Keep an unparseable leased receipt conservative. The
7636
+ // fenced claim remains the recovery path after the wait.
7637
+ return !Number.isFinite(expiresAt) || expiresAt > Date.now();
7638
+ })
7442
7639
  .map(([receiptKey]) => receiptKey),
7443
7640
  );
7444
7641
  const claimableForcedReceiptKeys = forcedReceiptKeys.filter(
7445
- (receiptKey) => !forcedRunningReceiptKeys.has(receiptKey),
7642
+ (receiptKey) => !forcedLiveReceiptKeys.has(receiptKey),
7446
7643
  );
7447
7644
  const claims =
7448
7645
  normalReceiptKeys.length > 0
@@ -7706,16 +7903,39 @@ export class PlayContextImpl {
7706
7903
  const processExistingRunningReceiptWaits =
7707
7904
  async (): Promise<void> => {
7708
7905
  if (durableExistingRunningWaits.length === 0) return;
7906
+ const waitingReceiptKeys = durableExistingRunningWaits.map(
7907
+ (wait) => wait.receiptKey,
7908
+ );
7709
7909
  const waitMaxAttempts = Math.max(
7710
7910
  ...durableExistingRunningWaits.map(
7711
7911
  (wait) => wait.waitMaxAttempts ?? 0,
7712
7912
  ),
7713
7913
  );
7914
+ if (runtimeReceiptReadTraceEnabled) {
7915
+ this.log(
7916
+ `[runtime-receipt-wait] phase=start requested=${waitingReceiptKeys.length} ` +
7917
+ `request_digests=${waitingReceiptKeys
7918
+ .map(runtimeReceiptKeyDigest)
7919
+ .join(',')} max_attempts=${waitMaxAttempts || 'default'}`,
7920
+ );
7921
+ }
7714
7922
  const waited = await this.waitForCompletedRuntimeToolReceipts(
7715
- durableExistingRunningWaits.map((wait) => wait.receiptKey),
7923
+ waitingReceiptKeys,
7716
7924
  waitMaxAttempts > 0 ? waitMaxAttempts : undefined,
7717
7925
  );
7718
- await Promise.allSettled(
7926
+ if (runtimeReceiptReadTraceEnabled) {
7927
+ this.log(
7928
+ `[runtime-receipt-wait] phase=settled completed=${waited.completed.size} ` +
7929
+ `failed=${waited.failed.size} timed_out=${waited.timedOut.size} ` +
7930
+ `completed_digests=${[...waited.completed.keys()]
7931
+ .map(runtimeReceiptKeyDigest)
7932
+ .join(',')} ` +
7933
+ `timed_out_digests=${[...waited.timedOut]
7934
+ .map(runtimeReceiptKeyDigest)
7935
+ .join(',')}`,
7936
+ );
7937
+ }
7938
+ const deliverySettlements = await Promise.allSettled(
7719
7939
  durableExistingRunningWaits.map(async (wait) => {
7720
7940
  const failed = waited.failed.get(wait.receiptKey);
7721
7941
  if (failed) {
@@ -7737,6 +7957,28 @@ export class PlayContextImpl {
7737
7957
  );
7738
7958
  }),
7739
7959
  );
7960
+ if (runtimeReceiptReadTraceEnabled) {
7961
+ const rejected = deliverySettlements.flatMap(
7962
+ (settlement, index) => {
7963
+ if (settlement.status !== 'rejected') return [];
7964
+ const receiptKey =
7965
+ durableExistingRunningWaits[index]?.receiptKey;
7966
+ return receiptKey
7967
+ ? [
7968
+ `${runtimeReceiptKeyDigest(receiptKey)}:${
7969
+ settlement.reason instanceof Error
7970
+ ? settlement.reason.name
7971
+ : 'non_error'
7972
+ }`,
7973
+ ]
7974
+ : ['unknown'];
7975
+ },
7976
+ );
7977
+ this.log(
7978
+ `[runtime-receipt-wait] phase=delivery settled=${deliverySettlements.length} ` +
7979
+ `rejected=${rejected.length} rejected_digests=${rejected.join(',')}`,
7980
+ );
7981
+ }
7740
7982
  };
7741
7983
 
7742
7984
  for (const [receiptKey, group] of requestsByReceiptKey) {
@@ -8222,11 +8464,17 @@ export class PlayContextImpl {
8222
8464
  // The Governor's tool slot is the single seam for tool-call budget + the
8223
8465
  // global tool-concurrency backstop + per-(org, provider) pacing. It blocks
8224
8466
  // until all three are satisfied and is held (across retries) until release.
8467
+ const admissionStartedAt = Date.now();
8225
8468
  const toolSlot = await this.resourceGovernor.acquireTool({
8226
8469
  orgId: this.#options.orgId ?? null,
8227
8470
  providerResourceKey: `tool:${toolId}`,
8228
8471
  toolId,
8229
8472
  });
8473
+ if (runtimeReceiptReadTraceEnabled) {
8474
+ this.log(
8475
+ `[perf] tool call id=${toolId} phase=governor_admission elapsed_ms=${Date.now() - admissionStartedAt}`,
8476
+ );
8477
+ }
8230
8478
  try {
8231
8479
  return await withActiveSpan(
8232
8480
  'plays.tool.execute',
@@ -8286,7 +8534,13 @@ export class PlayContextImpl {
8286
8534
  : null;
8287
8535
  try {
8288
8536
  const providerCallStartedAt = Date.now();
8537
+ const ownershipStartedAt = Date.now();
8289
8538
  await options?.beforeProviderCall?.();
8539
+ if (runtimeReceiptReadTraceEnabled) {
8540
+ this.log(
8541
+ `[perf] tool call id=${toolId} phase=before_provider_ownership elapsed_ms=${Date.now() - ownershipStartedAt}`,
8542
+ );
8543
+ }
8290
8544
  const heartbeatIntervalMs = hasReceiptHeartbeat
8291
8545
  ? runtimeLeaseHeartbeatIntervalFromExpiry({
8292
8546
  leaseExpiresAt: options?.receiptLeaseExpiresAt,
@@ -8316,6 +8570,7 @@ export class PlayContextImpl {
8316
8570
  : null;
8317
8571
  receiptHeartbeatSupervisor?.start();
8318
8572
  try {
8573
+ const integrationFetchStartedAt = Date.now();
8319
8574
  response = await fetch(url, {
8320
8575
  method: 'POST',
8321
8576
  signal: abortController?.signal,
@@ -8380,6 +8635,11 @@ export class PlayContextImpl {
8380
8635
  : {}),
8381
8636
  }),
8382
8637
  });
8638
+ if (runtimeReceiptReadTraceEnabled) {
8639
+ this.log(
8640
+ `[perf] tool call id=${toolId} phase=integration_fetch_headers elapsed_ms=${Date.now() - integrationFetchStartedAt} status=${response.status}`,
8641
+ );
8642
+ }
8383
8643
  this.resourceGovernor.observe({
8384
8644
  toolId,
8385
8645
  providerResourceKey: `tool:${toolId}`,