deepline 0.1.231 → 0.1.232

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.
@@ -1454,7 +1454,7 @@ async function executeTool(
1454
1454
  providerIdempotencyKey?: string | null;
1455
1455
  },
1456
1456
  workflowStep?: WorkflowStep,
1457
- onProviderBackpressure?: (retryAfterMs: number) => void,
1457
+ onProviderBackpressure?: (retryAfterMs: number) => void | Promise<void>,
1458
1458
  onRetryAttempt?: () => void | Promise<void>,
1459
1459
  transientHttpRetrySafe = false,
1460
1460
  abortSignal?: AbortSignal,
@@ -1500,7 +1500,7 @@ async function executeToolWithLifecycle(
1500
1500
  },
1501
1501
  workflowStep: WorkflowStep | undefined,
1502
1502
  callbacks: WorkerCtxCallbacks | undefined,
1503
- onProviderBackpressure?: (retryAfterMs: number) => void,
1503
+ onProviderBackpressure?: (retryAfterMs: number) => void | Promise<void>,
1504
1504
  onRetryAttempt?: () => void | Promise<void>,
1505
1505
  transientHttpRetrySafe = false,
1506
1506
  abortSignal?: AbortSignal,
@@ -1690,7 +1690,7 @@ async function callToolDirect(
1690
1690
  executionAuthScopeDigest?: string | null;
1691
1691
  providerIdempotencyKey?: string | null;
1692
1692
  },
1693
- onProviderBackpressure?: (retryAfterMs: number) => void,
1693
+ onProviderBackpressure?: (retryAfterMs: number) => void | Promise<void>,
1694
1694
  // Invoked once per in-process retry attempt (429 / retryable 5xx / synthetic
1695
1695
  // transient) so the Governor charges chargeBudget('retry') per attempt — the
1696
1696
  // same runaway guard the cjs runner applies (context.ts charges retry on each
@@ -1877,7 +1877,7 @@ async function callToolDirect(
1877
1877
  if (failure.backpressureDelayMs !== null) {
1878
1878
  // Feed the provider's backpressure into the shared pacer even on the
1879
1879
  // final attempt so the (org, provider) bucket backs off across isolates.
1880
- onProviderBackpressure?.(failure.backpressureDelayMs);
1880
+ await onProviderBackpressure?.(failure.backpressureDelayMs);
1881
1881
  }
1882
1882
  if (!failure.shouldRetry) {
1883
1883
  throw lastError;
@@ -2616,7 +2616,7 @@ function isWorkerFetchResponse(value: unknown): value is WorkerFetchResponse {
2616
2616
  async function postRuntimeEgressFetch(
2617
2617
  req: RunRequest,
2618
2618
  payload: RuntimeEgressFetchPayload,
2619
- onProviderBackpressure?: (retryAfterMs: number) => void,
2619
+ onProviderBackpressure?: (retryAfterMs: number) => void | Promise<void>,
2620
2620
  onRetryAttempt?: () => void | Promise<void>,
2621
2621
  ): Promise<WorkerFetchResponse> {
2622
2622
  let lastError: Error | null = null;
@@ -2667,7 +2667,7 @@ async function postRuntimeEgressFetch(
2667
2667
  Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0
2668
2668
  ? Math.ceil(retryAfterSeconds * 1000)
2669
2669
  : 1_000;
2670
- onProviderBackpressure?.(retryAfterMs);
2670
+ await onProviderBackpressure?.(retryAfterMs);
2671
2671
  if (attempt >= TOOL_EXECUTE_RATE_LIMIT_MAX_ATTEMPTS) {
2672
2672
  throw lastError;
2673
2673
  }
@@ -3776,7 +3776,7 @@ function createWorkerToolAuthScopeDigestResolver(req: RunRequest) {
3776
3776
  // (RuntimeToolAuthScopeDigestResolver), whose `invalidate` member is
3777
3777
  // REQUIRED — the AUTH_SCOPE_CHANGED re-claim wiring below calls it directly,
3778
3778
  // and a resolver without eviction support must be a type error.
3779
- return createRuntimeToolAuthScopeDigestResolver({
3779
+ const digestResolver = createRuntimeToolAuthScopeDigestResolver({
3780
3780
  missingToolIdMessage: 'Tool auth scope needs tool id.',
3781
3781
  fetchDigest: async (normalized) => {
3782
3782
  const runScopedPath =
@@ -3807,12 +3807,19 @@ function createWorkerToolAuthScopeDigestResolver(req: RunRequest) {
3807
3807
  return digest;
3808
3808
  },
3809
3809
  });
3810
+ return digestResolver;
3810
3811
  }
3811
3812
 
3812
3813
  function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
3813
3814
  const cache = new Map<
3814
3815
  string,
3815
- Promise<(ResolvedPacingPolicy & { retrySafeTransientHttp: boolean }) | null>
3816
+ Promise<
3817
+ | (ResolvedPacingPolicy & {
3818
+ retrySafeTransientHttp: boolean;
3819
+ r: { bucketId: string; token: string } | null;
3820
+ })
3821
+ | null
3822
+ >
3816
3823
  >();
3817
3824
  return (toolId: string) => {
3818
3825
  const normalized = String(toolId || '').trim();
@@ -3840,6 +3847,7 @@ function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
3840
3847
  provider?: unknown;
3841
3848
  queueHints?: unknown;
3842
3849
  retry?: unknown;
3850
+ r?: { bucketId: string; token: string };
3843
3851
  } | null;
3844
3852
  if (!body) return null;
3845
3853
  const pacing = pacingPolicyFromUnknownQueueHints(body.queueHints);
@@ -3857,6 +3865,7 @@ function createWorkerPacingResolver(req: RunRequest): WorkerPacingResolver {
3857
3865
  : '',
3858
3866
  rules: [],
3859
3867
  }),
3868
+ r: body.r ?? null,
3860
3869
  retrySafeTransientHttp: retry.retrySafeTransientHttp === true,
3861
3870
  };
3862
3871
  })();
@@ -3883,11 +3892,13 @@ function resumeGovernanceFromRequest(req: RunRequest): GovernanceSnapshot {
3883
3892
  };
3884
3893
  }
3885
3894
 
3886
- function createGovernorForRun(req: RunRequest): {
3895
+ function createGovernorForRun(
3896
+ req: RunRequest,
3897
+ resolvePacing: WorkerPacingResolver,
3898
+ ): {
3887
3899
  governor: PlayExecutionGovernor;
3888
3900
  resolvePacing: WorkerPacingResolver;
3889
3901
  } {
3890
- const resolvePacing = createWorkerPacingResolver(req);
3891
3902
  const governor = createPlayExecutionGovernor({
3892
3903
  adapter: 'esm_workers',
3893
3904
  scope: {
@@ -3906,6 +3917,8 @@ function createGovernorForRun(req: RunRequest): {
3906
3917
  }),
3907
3918
  ),
3908
3919
  resolvePacing,
3920
+ resolveRateScope: async (toolId) =>
3921
+ (await resolvePacing(toolId))?.r ?? null,
3909
3922
  resume: resumeGovernanceFromRequest(req),
3910
3923
  });
3911
3924
  return { governor, resolvePacing };
@@ -3944,12 +3957,15 @@ function createMinimalWorkerCtx(
3944
3957
  leasedSheetNamespaces?: Set<string>,
3945
3958
  receiptSalvage?: ReceiptSalvageBuffer,
3946
3959
  ): unknown {
3947
- const { governor, resolvePacing: resolveToolPacing } =
3948
- createGovernorForRun(req);
3949
- const resolveToolActionCacheVersion =
3950
- createWorkerToolActionCacheVersionResolver(req);
3960
+ const resolveToolPacing = createWorkerPacingResolver(req);
3961
+ const { governor } = createGovernorForRun(req, resolveToolPacing);
3951
3962
  const resolveToolAuthScopeDigest =
3952
3963
  createWorkerToolAuthScopeDigestResolver(req);
3964
+ const resolveToolActionCacheVersion =
3965
+ createWorkerToolActionCacheVersionResolver(req);
3966
+ // Play-call depth/count/per-parent budgets, child-play concurrency, and the
3967
+ // lineage snapshot are owned by the Governor (createGovernorForRun above).
3968
+ // The worker keeps only substrate mechanism here.
3953
3969
  const stepCallCounts: Record<string, number> = {};
3954
3970
  const secretRedactor = createSecretRedactionContext();
3955
3971
 
@@ -4004,7 +4020,7 @@ function createMinimalWorkerCtx(
4004
4020
  input: Record<string, unknown>;
4005
4021
  workflowStep?: unknown;
4006
4022
  callbacks?: WorkerCtxCallbacks;
4007
- onProviderBackpressure?: (retryAfterMs: number) => void;
4023
+ onProviderBackpressure?: (retryAfterMs: number) => void | Promise<void>;
4008
4024
  onRetryAttempt?: () => void | Promise<void>;
4009
4025
  transientHttpRetrySafe?: boolean;
4010
4026
  durableCallReceiptKey?: string | null;
@@ -73,7 +73,11 @@ import type { WorkerWorkBudgetMeter } from './work-budget';
73
73
  export type WorkerPacingResolver = (
74
74
  toolId: string,
75
75
  ) => Promise<
76
- (ResolvedPacingPolicy & { retrySafeTransientHttp: boolean }) | null
76
+ | (ResolvedPacingPolicy & {
77
+ retrySafeTransientHttp: boolean;
78
+ r: { bucketId: string; token: string } | null;
79
+ })
80
+ | null
77
81
  >;
78
82
 
79
83
  export type WorkerToolActionCacheVersionResolver = (
@@ -111,7 +115,7 @@ export type WorkerToolDispatchExecuteTool = (input: {
111
115
  input: Record<string, unknown>;
112
116
  workflowStep?: WorkflowStepLike;
113
117
  callbacks?: WorkerToolDispatchCallbacks;
114
- onProviderBackpressure?: (retryAfterMs: number) => void;
118
+ onProviderBackpressure?: (retryAfterMs: number) => void | Promise<void>;
115
119
  onRetryAttempt?: () => void | Promise<void>;
116
120
  transientHttpRetrySafe?: boolean;
117
121
  durableCallReceiptKey?: string | null;
@@ -561,17 +565,19 @@ export class WorkerToolBatchScheduler {
561
565
  * isolates. Provider comes from the same pacing resolver the Governor uses
562
566
  * (the worker has no local catalog), so callers pass only the toolId.
563
567
  */
564
- private reportBackpressure(toolId: string, retryAfterMs: number): void {
568
+ private async reportBackpressure(
569
+ toolId: string,
570
+ retryAfterMs: number,
571
+ ): Promise<void> {
565
572
  if (retryAfterMs <= 0) return;
566
- void (async () => {
567
- const pacing = await this.options.resolvePacing(toolId).catch(() => null);
568
- if (pacing?.provider) {
569
- this.options.governor.reportProviderBackpressure({
570
- provider: pacing.provider,
571
- retryAfterMs,
572
- });
573
- }
574
- })();
573
+ const pacing = await this.options.resolvePacing(toolId).catch(() => null);
574
+ if (pacing?.provider) {
575
+ await this.options.governor.reportProviderBackpressure({
576
+ provider: pacing.provider,
577
+ toolId,
578
+ retryAfterMs,
579
+ });
580
+ }
575
581
  }
576
582
 
577
583
  async execute(
@@ -108,10 +108,10 @@ export const SDK_RELEASE = {
108
108
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
109
109
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
110
110
  // automatically without blocking their current command.
111
- version: '0.1.231',
111
+ version: '0.1.232',
112
112
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
113
113
  supportPolicy: {
114
- latest: '0.1.231',
114
+ latest: '0.1.232',
115
115
  minimumSupported: '0.1.53',
116
116
  deprecatedBelow: '0.1.219',
117
117
  commandMinimumSupported: [
@@ -57,10 +57,12 @@ export {
57
57
  import {
58
58
  createDefaultGovernanceSnapshot,
59
59
  createPlayExecutionGovernor,
60
+ defaultPacingForTool,
60
61
  type GovernanceSnapshot,
61
62
  type PacingResolver,
62
63
  type PlayExecutionGovernor,
63
64
  } from './governor/governor';
65
+ import { resolveExecutionPolicy } from './governor/policy';
64
66
  import {
65
67
  createRuntimeResourceGovernor,
66
68
  type RuntimeResourceGovernor,
@@ -1142,12 +1144,20 @@ function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
1142
1144
  */
1143
1145
  function createPacingResolver(
1144
1146
  getToolQueueHints: ContextOptions['getToolQueueHints'],
1147
+ getToolProvider: ContextOptions['getToolProvider'],
1145
1148
  ): PacingResolver {
1146
1149
  return async (toolId: string) => {
1147
1150
  const builtin = pacingPolicyForTool(toolId, []);
1148
1151
  if (builtin) return builtin;
1149
1152
  const hints = getToolQueueHints ? await getToolQueueHints(toolId) : [];
1150
- return pacingPolicyForTool(toolId, hints);
1153
+ const declared = pacingPolicyForTool(toolId, hints);
1154
+ if (declared) return declared;
1155
+ const provider = (await getToolProvider?.(toolId))?.trim();
1156
+ if (!provider) return null;
1157
+ return {
1158
+ ...defaultPacingForTool(toolId, resolveExecutionPolicy('cjs_node20')),
1159
+ provider,
1160
+ };
1151
1161
  };
1152
1162
  }
1153
1163
 
@@ -1375,7 +1385,11 @@ export class PlayContextImpl {
1375
1385
  },
1376
1386
  rateState: options.rateState ?? new InMemoryRateStateBackend(),
1377
1387
  budgetState: options.budgetState,
1378
- resolvePacing: createPacingResolver(options.getToolQueueHints),
1388
+ resolvePacing: createPacingResolver(
1389
+ options.getToolQueueHints,
1390
+ options.getToolProvider,
1391
+ ),
1392
+ resolveRateScope: options.getToolRateScope,
1379
1393
  // A root run keeps depth 0 (its first child play is depth 1) but still
1380
1394
  // seeds its own play id into the ancestry/currentPlayId so the cycle guard
1381
1395
  // catches a child re-invoking the root. createDefaultGovernanceSnapshot
@@ -8533,8 +8547,9 @@ export class PlayContextImpl {
8533
8547
  ? await this.#options.getToolQueueHints(toolId)
8534
8548
  : [];
8535
8549
  const provider = hints[0]?.provider?.trim() || `tool:${toolId}`;
8536
- this.resourceGovernor.reportProviderBackpressure({
8550
+ await this.resourceGovernor.reportProviderBackpressure({
8537
8551
  provider,
8552
+ toolId,
8538
8553
  retryAfterMs,
8539
8554
  });
8540
8555
  }
@@ -641,12 +641,20 @@ export interface ContextOptions {
641
641
  runtimeSheetBackedMapDatasets?: boolean;
642
642
  resolvePlay?: (playRef: string) => Promise<ResolvedPlayExecution | null>;
643
643
  getToolQueueHints?: (toolId: string) => Promise<readonly PlayQueueHint[]>;
644
+ getToolProvider?: (toolId: string) => Promise<string | null>;
644
645
  getToolRetryPolicy?: (toolId: string) => Promise<{
645
646
  retrySafeTransientHttp?: boolean;
646
647
  requiresExecutionFence?: boolean;
647
648
  } | null>;
648
649
  getToolActionCacheVersion?: (toolId: string) => Promise<string> | string;
649
650
  getToolAuthScopeDigest?: (toolId: string) => Promise<string> | string;
651
+ getToolRateScope?: (
652
+ toolId: string,
653
+ provider: string,
654
+ ) =>
655
+ | Promise<{ bucketId: string; token: string } | null>
656
+ | { bucketId: string; token: string }
657
+ | null;
650
658
  invalidateToolAuthScopeDigest?: (toolId: string) => void;
651
659
  getToolTargetGetters?: (
652
660
  toolId: string,
@@ -89,20 +89,8 @@ export interface RateStateCooldown {
89
89
  }
90
90
 
91
91
  interface LeasedBlock {
92
- /**
93
- * Concurrency reservations, one per locally-cached permit. Only rules with a
94
- * `maxConcurrency` mint lease tokens; each id maps to a durable slot that must
95
- * be released (or TTL-expired) so the shared concurrency count drains.
96
- */
97
- leaseIds: string[];
98
- /**
99
- * Pure-rate (RPS-only) permits with no concurrency slot behind them. The
100
- * server decremented the provider window by this count already, so drawing
101
- * one locally is a no-op that needs no release token. Tracked separately from
102
- * `leaseIds` so a pure-RPS block (the common `defaultPacingForTool` shape)
103
- * actually amortizes its whole grant across acquires instead of burning it.
104
- */
105
- windowPermits: number;
92
+ permits: Array<[scheduledAtMs: number, leaseId: string | null]>;
93
+ rateScopeToken: string | null;
106
94
  expiresAt: number;
107
95
  rulesKey: string;
108
96
  rules: PacingRule[];
@@ -110,6 +98,7 @@ interface LeasedBlock {
110
98
 
111
99
  interface PendingRelease {
112
100
  bucketId: string;
101
+ rateScopeToken: string | null;
113
102
  rules: PacingRule[];
114
103
  leaseIds: string[];
115
104
  flushing: boolean;
@@ -156,6 +145,8 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
156
145
  private readonly waiters = new Map<string, number>();
157
146
  /** Last-known server cooldown per bucket (surfaced for PROVIDER_EXHAUSTED). */
158
147
  private readonly cooldowns = new Map<string, RateStateCooldown>();
148
+ private readonly effectiveRps = new Map<string, number>();
149
+ private readonly pendingSuccesses = new Map<string, number>();
159
150
  private pendingReleaseFailure: Error | null = null;
160
151
 
161
152
  constructor(context: WorkerRuntimeApiContext, options: Options = {}) {
@@ -220,6 +211,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
220
211
 
221
212
  async acquire(input: {
222
213
  bucketId: string;
214
+ rateScopeToken?: string | null;
223
215
  rules: readonly PacingRule[];
224
216
  signal?: AbortSignal;
225
217
  }): Promise<PacingPermit> {
@@ -249,13 +241,29 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
249
241
  : new Error('Rate-state acquire aborted.');
250
242
  }
251
243
  const localPermit = this.drawFromBlock(bucketId, rulesKey);
252
- if (localPermit) return localPermit;
244
+ if (localPermit) {
245
+ const waitMs = localPermit.scheduledAtMs - this.now();
246
+ if (waitMs > 0) await this.sleep(waitMs);
247
+ if (signal?.aborted) {
248
+ localPermit.permit.release();
249
+ throw signal.reason instanceof Error
250
+ ? signal.reason
251
+ : new Error('Rate-state acquire aborted.');
252
+ }
253
+ return localPermit.permit;
254
+ }
253
255
  // Count this acquirer as waiting on the refill so the single-flight refill
254
256
  // can size its block to the live wave. Decremented once a block lands (or
255
257
  // the refill errors) — see the finally in `refillBlock`.
256
258
  this.waiters.set(bucketId, (this.waiters.get(bucketId) ?? 0) + 1);
257
259
  try {
258
- await this.refillBlock(bucketId, ordered, rulesKey, signal);
260
+ await this.refillBlock(
261
+ bucketId,
262
+ input.rateScopeToken?.trim() || null,
263
+ ordered,
264
+ rulesKey,
265
+ signal,
266
+ );
259
267
  } finally {
260
268
  const remaining = (this.waiters.get(bucketId) ?? 1) - 1;
261
269
  if (remaining > 0) this.waiters.set(bucketId, remaining);
@@ -279,7 +287,15 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
279
287
  ): number {
280
288
  const floor = APP_RUNTIME_RATE_STATE_LEASE_BLOCK_SIZE;
281
289
  if (rules.some((rule) => rule.maxConcurrency != null)) return floor;
290
+ const configuredRps = Math.min(
291
+ ...rules.map((rule) => (rule.requestsPerWindow / rule.windowMs) * 1_000),
292
+ );
293
+ const horizonBudget = Math.max(
294
+ 1,
295
+ Math.ceil(this.effectiveRps.get(bucketId) ?? configuredRps),
296
+ );
282
297
  const windowBudget = Math.min(
298
+ horizonBudget,
283
299
  ...rules
284
300
  .map((rule) => rule.requestsPerWindow)
285
301
  .filter((value) => Number.isFinite(value) && value > 0),
@@ -353,6 +369,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
353
369
  */
354
370
  private refillBlock(
355
371
  bucketId: string,
372
+ rateScopeToken: string | null,
356
373
  ordered: PacingRule[],
357
374
  rulesKey: string,
358
375
  signal?: AbortSignal,
@@ -361,6 +378,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
361
378
  if (existing) return existing;
362
379
  const promise = this.performRefill(
363
380
  bucketId,
381
+ rateScopeToken,
364
382
  ordered,
365
383
  rulesKey,
366
384
  signal,
@@ -375,6 +393,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
375
393
 
376
394
  private async performRefill(
377
395
  bucketId: string,
396
+ rateScopeToken: string | null,
378
397
  ordered: PacingRule[],
379
398
  rulesKey: string,
380
399
  signal?: AbortSignal,
@@ -397,19 +416,45 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
397
416
  if (signal?.aborted) return;
398
417
  }
399
418
  try {
419
+ const observedSuccesses = this.pendingSuccesses.get(bucketId) ?? 0;
400
420
  const response = await this.port.acquire({
401
421
  bucketId,
422
+ rateScopeToken,
402
423
  // Stamp adaptiveMaxRps on default-pacing rules so the DB row's ceiling
403
424
  // matches the old in-memory AIMD range.
404
425
  rules: withAdaptiveMaxRps(ordered),
405
426
  requested: this.requestedForBucket(bucketId, ordered),
427
+ observedSuccesses,
406
428
  schedulerSchema: this.schedulerSchema,
407
429
  });
430
+ if (observedSuccesses > 0) {
431
+ const remaining = Math.max(
432
+ 0,
433
+ (this.pendingSuccesses.get(bucketId) ?? 0) - observedSuccesses,
434
+ );
435
+ if (remaining > 0) this.pendingSuccesses.set(bucketId, remaining);
436
+ else this.pendingSuccesses.delete(bucketId);
437
+ }
408
438
  this.recordCooldown(bucketId, response);
439
+ if (
440
+ Number.isFinite(response.effectiveRequestsPerSecond) &&
441
+ Number(response.effectiveRequestsPerSecond) > 0
442
+ ) {
443
+ this.effectiveRps.set(
444
+ bucketId,
445
+ Number(response.effectiveRequestsPerSecond),
446
+ );
447
+ }
409
448
  if (response.granted > 0) {
410
449
  // Merge the whole grant into the block and let acquirers re-draw. No
411
450
  // permit is handed out here, so nothing is double-drawn.
412
- this.mergeGrantedBlock(bucketId, ordered, rulesKey, response);
451
+ this.mergeGrantedBlock(
452
+ bucketId,
453
+ rateScopeToken,
454
+ ordered,
455
+ rulesKey,
456
+ response,
457
+ );
413
458
  return;
414
459
  }
415
460
  // Live path: the server told us to wait past the threshold. Fail fast
@@ -456,9 +501,14 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
456
501
  */
457
502
  private mergeGrantedBlock(
458
503
  bucketId: string,
504
+ rateScopeToken: string | null,
459
505
  ordered: PacingRule[],
460
506
  rulesKey: string,
461
- response: { granted: number; leaseIds?: unknown },
507
+ response: {
508
+ granted: number;
509
+ leaseIds?: unknown;
510
+ scheduledAtMs?: unknown;
511
+ },
462
512
  ): void {
463
513
  const hasConcurrency = ordered.some((rule) => rule.maxConcurrency != null);
464
514
  const leaseIds = Array.isArray(response.leaseIds)
@@ -477,38 +527,71 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
477
527
  `Rate-state store returned ${leaseIds.length} lease tokens for pure-rate bucket ${bucketId}.`,
478
528
  );
479
529
  }
530
+ // Rolling deploy compatibility: an older gateway returns no schedule. It
531
+ // has already debited the block under the legacy token-bucket contract, so
532
+ // keep those permits immediately usable until every gateway serves the new
533
+ // globally reserved timestamps.
534
+ const scheduledAtMs = Array.isArray(response.scheduledAtMs)
535
+ ? response.scheduledAtMs.map(Number)
536
+ : Array.from({ length: response.granted }, () => this.now());
537
+ if (
538
+ scheduledAtMs.length !== response.granted ||
539
+ scheduledAtMs.some((value) => !Number.isFinite(value) || value < 0)
540
+ ) {
541
+ throw new Error(
542
+ `Rate-state store granted ${response.granted} permits for ${bucketId} with ${scheduledAtMs.length} dispatch timestamps.`,
543
+ );
544
+ }
480
545
  this.mergeBlock(
481
546
  bucketId,
482
- hasConcurrency ? leaseIds : [],
483
- hasConcurrency ? 0 : response.granted,
547
+ rateScopeToken,
548
+ scheduledAtMs.map(
549
+ (scheduledAtMs, index): [number, string | null] => [
550
+ scheduledAtMs,
551
+ hasConcurrency ? (leaseIds[index] ?? null) : null,
552
+ ],
553
+ ),
484
554
  rulesKey,
485
555
  ordered,
486
556
  );
487
557
  }
488
558
 
489
- penalize(input: { bucketId: string; cooldownMs: number }): void {
559
+ async penalize(input: {
560
+ bucketId: string;
561
+ rateScopeToken?: string | null;
562
+ cooldownMs: number;
563
+ }): Promise<void> {
490
564
  if (input.cooldownMs <= 0) return;
491
565
  this.drainBlock(input.bucketId);
492
- void this.port
493
- .penalize({
566
+ try {
567
+ await this.port.penalize({
494
568
  bucketId: input.bucketId,
569
+ rateScopeToken: input.rateScopeToken?.trim() || null,
495
570
  cooldownMs: input.cooldownMs,
496
571
  schedulerSchema: this.schedulerSchema,
497
- })
498
- .catch((error) => {
499
- const normalized = normalizeError(error);
500
- this.pendingReleaseFailure = new Error(
501
- `Rate-state penalize failed for ${input.bucketId}: ${normalized.message}`,
502
- );
503
- this.onStoreFailure({
504
- bucketId: input.bucketId,
505
- error: normalized.message,
506
- });
507
572
  });
573
+ } catch (error) {
574
+ const normalized = normalizeError(error);
575
+ this.onStoreFailure({
576
+ bucketId: input.bucketId,
577
+ error: normalized.message,
578
+ });
579
+ throw new Error(
580
+ `Rate-state penalize failed for ${input.bucketId}: ${normalized.message}`,
581
+ );
582
+ }
583
+ }
584
+
585
+ observeSuccess(input: { bucketId: string }): void {
586
+ this.pendingSuccesses.set(
587
+ input.bucketId,
588
+ Math.min(1_000_000, (this.pendingSuccesses.get(input.bucketId) ?? 0) + 1),
589
+ );
508
590
  }
509
591
 
510
592
  private permitFor(
511
593
  bucketId: string,
594
+ rateScopeToken: string | null,
512
595
  rules: PacingRule[],
513
596
  leaseId: string | null,
514
597
  ): PacingPermit {
@@ -520,35 +603,49 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
520
603
  release: () => {
521
604
  if (released) return;
522
605
  released = true;
523
- this.releaseReserved(bucketId, rules, leaseId ? [leaseId] : []);
606
+ this.releaseReserved(
607
+ bucketId,
608
+ rateScopeToken,
609
+ rules,
610
+ leaseId ? [leaseId] : [],
611
+ );
524
612
  },
525
613
  };
526
614
  }
527
615
 
528
616
  private mergeBlock(
529
617
  bucketId: string,
530
- leaseIds: string[],
531
- windowPermits: number,
618
+ rateScopeToken: string | null,
619
+ permits: LeasedBlock['permits'],
532
620
  rulesKey: string,
533
621
  rules: PacingRule[],
534
622
  ): void {
535
- const freshExpiresAt = this.now() + leasedBlockTtlMs(rules);
623
+ const lastScheduledAtMs = permits.at(-1)?.[0] ?? this.now();
624
+ const freshExpiresAt =
625
+ Math.max(this.now(), lastScheduledAtMs) + leasedBlockTtlMs(rules);
536
626
  const existing = this.blocks.get(bucketId);
537
627
  if (
538
628
  existing &&
539
629
  existing.rulesKey === rulesKey &&
540
630
  existing.expiresAt > this.now()
541
631
  ) {
542
- existing.leaseIds.push(...leaseIds);
543
- existing.windowPermits += windowPermits;
544
- existing.expiresAt = Math.min(existing.expiresAt, freshExpiresAt);
632
+ existing.permits.push(...permits);
633
+ existing.permits.sort((a, b) => a[0] - b[0]);
634
+ existing.expiresAt = Math.max(existing.expiresAt, freshExpiresAt);
545
635
  return;
546
636
  }
547
637
  if (existing)
548
- this.releaseReserved(bucketId, existing.rules, existing.leaseIds);
638
+ this.releaseReserved(
639
+ bucketId,
640
+ existing.rateScopeToken,
641
+ existing.rules,
642
+ existing.permits.flatMap(([, leaseId]) =>
643
+ leaseId ? [leaseId] : [],
644
+ ),
645
+ );
549
646
  this.blocks.set(bucketId, {
550
- leaseIds,
551
- windowPermits,
647
+ permits,
648
+ rateScopeToken,
552
649
  expiresAt: freshExpiresAt,
553
650
  rulesKey,
554
651
  rules,
@@ -558,7 +655,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
558
655
  private drawFromBlock(
559
656
  bucketId: string,
560
657
  rulesKey: string,
561
- ): PacingPermit | null {
658
+ ): { permit: PacingPermit; scheduledAtMs: number } | null {
562
659
  const block = this.blocks.get(bucketId);
563
660
  if (!block) return null;
564
661
  if (block.rulesKey !== rulesKey || block.expiresAt <= this.now()) {
@@ -567,30 +664,50 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
567
664
  // decremented the provider window for them, and the rule-scoped TTL is
568
665
  // capped so at most one block's worth is stranded per bucket/window).
569
666
  // Concurrency leases must be released so the shared slot count drains.
570
- this.releaseReserved(bucketId, block.rules, block.leaseIds);
667
+ this.releaseReserved(
668
+ bucketId,
669
+ block.rateScopeToken,
670
+ block.rules,
671
+ block.permits.flatMap(([, leaseId]) =>
672
+ leaseId ? [leaseId] : [],
673
+ ),
674
+ );
571
675
  return null;
572
676
  }
573
- const hasConcurrency = block.rules.some(
574
- (rule) => rule.maxConcurrency != null,
575
- );
576
- // Concurrency blocks draw a lease token (release semantics); pure-rate
577
- // blocks draw an untracked window permit that the server already accounted.
578
- const leaseId = hasConcurrency ? (block.leaseIds.pop() ?? null) : null;
579
- if (!hasConcurrency && block.windowPermits > 0) block.windowPermits -= 1;
580
- if (block.leaseIds.length === 0 && block.windowPermits === 0)
677
+ const scheduled = block.permits.shift();
678
+ if (!scheduled) {
581
679
  this.blocks.delete(bucketId);
582
- return this.permitFor(bucketId, block.rules, leaseId);
680
+ return null;
681
+ }
682
+ if (block.permits.length === 0) this.blocks.delete(bucketId);
683
+ return {
684
+ permit: this.permitFor(
685
+ bucketId,
686
+ block.rateScopeToken,
687
+ block.rules,
688
+ scheduled[1],
689
+ ),
690
+ scheduledAtMs: scheduled[0],
691
+ };
583
692
  }
584
693
 
585
694
  private drainBlock(bucketId: string): void {
586
695
  const block = this.blocks.get(bucketId);
587
696
  if (!block) return;
588
697
  this.blocks.delete(bucketId);
589
- this.releaseReserved(bucketId, block.rules, block.leaseIds);
698
+ this.releaseReserved(
699
+ bucketId,
700
+ block.rateScopeToken,
701
+ block.rules,
702
+ block.permits.flatMap(([, leaseId]) =>
703
+ leaseId ? [leaseId] : [],
704
+ ),
705
+ );
590
706
  }
591
707
 
592
708
  private releaseReserved(
593
709
  bucketId: string,
710
+ rateScopeToken: string | null,
594
711
  rules: readonly PacingRule[],
595
712
  leaseIds: readonly string[],
596
713
  ): void {
@@ -603,6 +720,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
603
720
  const key = `${bucketId}\0${rulesSignature(ordered)}`;
604
721
  const pending = this.pendingReleases.get(key) ?? {
605
722
  bucketId,
723
+ rateScopeToken,
606
724
  rules: ordered,
607
725
  leaseIds: [],
608
726
  flushing: false,
@@ -623,6 +741,7 @@ export class AppRuntimeRateStateBackend implements RateStateBackend {
623
741
  try {
624
742
  await this.port.release({
625
743
  bucketId: pending.bucketId,
744
+ rateScopeToken: pending.rateScopeToken,
626
745
  rules: pending.rules,
627
746
  leaseIds,
628
747
  schedulerSchema: this.schedulerSchema,
@@ -167,22 +167,26 @@ export class CoordinatorRateStateBackend implements RateStateBackend {
167
167
  }
168
168
  }
169
169
 
170
- penalize(input: { bucketId: string; cooldownMs: number }): void {
170
+ async penalize(input: {
171
+ bucketId: string;
172
+ cooldownMs: number;
173
+ }): Promise<void> {
171
174
  if (input.cooldownMs <= 0) return;
172
175
  // Drop any cached block for this bucket so the cooldown takes effect on the
173
176
  // very next acquire instead of being masked by already-leased permits.
174
177
  this.blocks.delete(input.bucketId);
175
- void this.port
176
- .ratePenalize({
178
+ try {
179
+ await this.port.ratePenalize({
177
180
  bucketId: input.bucketId,
178
181
  cooldownMs: input.cooldownMs,
179
- })
180
- .catch((error) => {
181
- this.onDegraded({
182
- bucketId: input.bucketId,
183
- error: error instanceof Error ? error.message : String(error),
184
- });
185
182
  });
183
+ } catch (error) {
184
+ this.onDegraded({
185
+ bucketId: input.bucketId,
186
+ error: error instanceof Error ? error.message : String(error),
187
+ });
188
+ throw error;
189
+ }
186
190
  }
187
191
 
188
192
  /**
@@ -70,6 +70,14 @@ export type PacingResolver = (
70
70
  toolId: string,
71
71
  ) => Promise<{ provider: string; rules: PacingRule[] } | null>;
72
72
 
73
+ export type RateScopeResolver = (
74
+ toolId: string,
75
+ provider: string,
76
+ ) =>
77
+ | Promise<{ bucketId: string; token: string } | null>
78
+ | { bucketId: string; token: string }
79
+ | null;
80
+
73
81
  export function defaultPacingForTool(
74
82
  toolId: string,
75
83
  policy: ResolvedExecutionPolicy,
@@ -96,12 +104,9 @@ export interface PlayExecutionGovernor {
96
104
  /** Block until a map-row slot is free. */
97
105
  acquireRowSlot(opts?: { signal?: AbortSignal }): Promise<WorkLease>;
98
106
  /**
99
- * Block until a global tool-concurrency slot AND the per-(org,provider) pacer
100
- * permit are free, then charge the tool-call budget and return a lease. Order:
101
- * concurrency slot provider pace tool budget (charged last so a
102
- * failed/aborted acquire never consumes budget). A run over tool budget still
103
- * acquires and holds a slot + pacing permit before the breach is detected; the
104
- * breach surfaces only once the call is otherwise cleared to run.
107
+ * Block until the per-provider pacer permit and then a global
108
+ * tool-concurrency slot are free, charge the tool-call budget, and return a
109
+ * lease. Parked future permits never consume scarce execution slots.
105
110
  */
106
111
  acquireToolSlot(
107
112
  toolId: string,
@@ -132,12 +137,19 @@ export interface PlayExecutionGovernor {
132
137
  /** Feed a provider's Retry-After back into the shared pacer. */
133
138
  reportProviderBackpressure(input: {
134
139
  provider: string;
140
+ /**
141
+ * The tool whose provider call received the 429. Credential-scoped rate
142
+ * buckets are resolved per tool, so delayed feedback must retain this
143
+ * identity instead of selecting the latest scope for the provider.
144
+ */
145
+ toolId?: string;
135
146
  retryAfterMs: number;
136
- }): void;
147
+ }): Promise<void>;
137
148
 
138
149
  /** Feed clean provider calls back into adaptive admission. */
139
150
  observeProviderSuccess(input: {
140
151
  provider: string;
152
+ toolId?: string;
141
153
  latencyMs?: number | null;
142
154
  }): void;
143
155
  /** Feed success back using the same tool-to-provider resolution as acquire. */
@@ -155,6 +167,7 @@ interface GovernorInput {
155
167
  rateState: RateStateBackend;
156
168
  budgetState?: BudgetStateBackend;
157
169
  resolvePacing: PacingResolver;
170
+ resolveRateScope?: RateScopeResolver;
158
171
  adaptiveAdmission?: RuntimeAdaptiveAdmission;
159
172
  resume?: GovernanceSnapshot;
160
173
  }
@@ -246,6 +259,7 @@ export function createPlayExecutionGovernor(
246
259
  });
247
260
  const budgetState = input.budgetState ?? new InMemoryBudgetStateBackend();
248
261
  const providerByTool = new Map<string, string>();
262
+ const scopeByTool = new Map<string, [string, string]>();
249
263
 
250
264
  // When the rate-state backend owns pacing authoritatively (the Absurd/Node
251
265
  // app-runtime Postgres pacer runs the whole token bucket + AIMD in the row),
@@ -256,7 +270,20 @@ export function createPlayExecutionGovernor(
256
270
  // undefined and keep the in-memory adaptive admission (byte-identical).
257
271
  const dbAuthoritativePacing = input.rateState.kind === 'app_runtime_postgres';
258
272
 
259
- const bucketId = (provider: string) => `${input.scope.orgId}:${provider}`;
273
+ const orgBucket = (provider: string) => `${input.scope.orgId}:${provider}`;
274
+
275
+ async function resolveScope(toolId: string, provider: string) {
276
+ const resolved = await input.resolveRateScope?.(toolId, provider);
277
+ const scope = [
278
+ resolved?.bucketId ?? orgBucket(provider),
279
+ resolved?.token ?? '',
280
+ ] as [string, string];
281
+ scopeByTool.set(toolId, scope);
282
+ return scope;
283
+ }
284
+
285
+ const scopeFor = (toolId: string | undefined, provider: string) =>
286
+ scopeByTool.get(toolId ?? '') ?? ([orgBucket(provider), ''] as [string, string]);
260
287
 
261
288
  async function resolveAdaptivePacing(toolId: string): Promise<{
262
289
  provider: string;
@@ -348,23 +375,26 @@ export function createPlayExecutionGovernor(
348
375
 
349
376
  acquireRowSlot: (opts) => rowSlots.acquire(opts?.signal),
350
377
  async acquireToolSlot(toolId, opts) {
351
- // 1. global tool-concurrency slot.
352
- const slot = await toolSlots.acquire(opts?.signal);
353
- // 2. per-(org,provider) pacing. The provider comes from the pacing
378
+ // 1. Per-provider pacing. The provider comes from the pacing
354
379
  // resolver, so callers only need the toolId. No rules → no pacing.
355
- let permit: { release(): void };
380
+ const pacing = await resolveAdaptivePacing(toolId);
381
+ const rateScope = await resolveScope(toolId, pacing.provider);
382
+ const permit = await input.rateState.acquire({
383
+ bucketId: rateScope[0],
384
+ rateScopeToken: rateScope[1],
385
+ rules: pacing.rules,
386
+ signal: opts?.signal,
387
+ });
388
+ // 2. Acquire a scarce execution slot only after the provider permit is
389
+ // due. If the slot wait aborts, refund any concurrency-bearing permit.
390
+ let slot: WorkLease;
356
391
  try {
357
- const pacing = await resolveAdaptivePacing(toolId);
358
- permit = await input.rateState.acquire({
359
- bucketId: bucketId(pacing.provider),
360
- rules: pacing.rules,
361
- signal: opts?.signal,
362
- });
392
+ slot = await toolSlots.acquire(opts?.signal);
363
393
  } catch (error) {
364
- slot.release();
394
+ permit.release();
365
395
  throw error;
366
396
  }
367
- // 3. charge the budget only once the call is actually cleared to run, so a
397
+ // 3. Charge the budget only once the call is actually cleared to run, so a
368
398
  // failed/aborted acquisition never permanently consumes tool budget.
369
399
  try {
370
400
  await chargeBudget('toolCall');
@@ -409,7 +439,7 @@ export function createPlayExecutionGovernor(
409
439
  resolveRowConcurrency: (requested) =>
410
440
  resolveRowConcurrency(policy, requested),
411
441
 
412
- reportProviderBackpressure(bp) {
442
+ async reportProviderBackpressure(bp) {
413
443
  // DB-authoritative pacer: route backpressure ONLY to the row via
414
444
  // penalize. The in-memory adaptive admission must not also halve, or the
415
445
  // provider would be throttled twice (once in the row, once in memory).
@@ -420,13 +450,22 @@ export function createPlayExecutionGovernor(
420
450
  retryAfterMs: bp.retryAfterMs,
421
451
  });
422
452
  }
423
- input.rateState.penalize({
424
- bucketId: bucketId(bp.provider),
453
+ const rateScope = scopeFor(bp.toolId, bp.provider);
454
+ await input.rateState.penalize({
455
+ bucketId: rateScope[0],
456
+ ...(rateScope[1] ? { rateScopeToken: rateScope[1] } : {}),
425
457
  cooldownMs: bp.retryAfterMs,
426
458
  });
427
459
  },
428
460
 
429
461
  observeProviderSuccess(success) {
462
+ if (dbAuthoritativePacing) {
463
+ const rateScope = scopeFor(success.toolId, success.provider);
464
+ input.rateState.observeSuccess?.({
465
+ bucketId: rateScope[0],
466
+ });
467
+ return;
468
+ }
430
469
  adaptiveAdmission.observeProviderSuccess({
431
470
  orgId: input.scope.orgId,
432
471
  provider: success.provider,
@@ -438,9 +477,9 @@ export function createPlayExecutionGovernor(
438
477
  const provider =
439
478
  providerByTool.get(success.toolId) ??
440
479
  defaultPacingForTool(success.toolId, policy).provider;
441
- adaptiveAdmission.observeProviderSuccess({
442
- orgId: input.scope.orgId,
480
+ governor.observeProviderSuccess({
443
481
  provider,
482
+ toolId: success.toolId,
444
483
  latencyMs: success.latencyMs,
445
484
  });
446
485
  },
@@ -507,8 +546,8 @@ function createInlineChildGovernor(
507
546
  root.suggestedParallelism(toolId, fallback),
508
547
  chargeBudget: (kind, amount) => root.chargeBudget(kind, amount),
509
548
  resolveRowConcurrency: (requested) => root.resolveRowConcurrency(requested),
510
- reportProviderBackpressure: (input) =>
511
- root.reportProviderBackpressure(input),
549
+ reportProviderBackpressure: async (input) =>
550
+ await root.reportProviderBackpressure(input),
512
551
  observeProviderSuccess: (input) => root.observeProviderSuccess(input),
513
552
  observeToolSuccess: (input) => root.observeToolSuccess(input),
514
553
  async forkInlineChild(input) {
@@ -86,6 +86,7 @@ export interface RateStateBackend {
86
86
  */
87
87
  acquire(input: {
88
88
  bucketId: string;
89
+ rateScopeToken?: string | null;
89
90
  rules: readonly PacingRule[];
90
91
  signal?: AbortSignal;
91
92
  }): Promise<PacingPermit>;
@@ -94,7 +95,17 @@ export interface RateStateBackend {
94
95
  * Feed a server-observed Retry-After back so future acquires for this bucket
95
96
  * back off. Advisory and idempotent; never un-charges an in-flight call.
96
97
  */
97
- penalize(input: { bucketId: string; cooldownMs: number }): void;
98
+ penalize(input: {
99
+ bucketId: string;
100
+ rateScopeToken?: string | null;
101
+ cooldownMs: number;
102
+ }): void | Promise<void>;
103
+
104
+ /** Record confirmed provider success for adaptive recovery. */
105
+ observeSuccess?(input: {
106
+ bucketId: string;
107
+ rateScopeToken?: string | null;
108
+ }): void;
98
109
  }
99
110
 
100
111
  const NOOP_PERMIT: PacingPermit = { release() {} };
@@ -25,14 +25,24 @@ export type PlayRunnerRateStateBackendConfig =
25
25
 
26
26
  export type PlayRunnerRateStateAcquireInput = {
27
27
  bucketId: string;
28
+ rateScopeToken?: string | null;
28
29
  rules: PacingRule[];
29
30
  requested: number;
31
+ /** Confirmed provider successes since this runner's previous reservation. */
32
+ observedSuccesses?: number;
30
33
  schedulerSchema?: string | null;
31
34
  };
32
35
 
33
36
  export type PlayRunnerRateStateAcquireResult = {
34
37
  granted: number;
35
38
  waitMs: number;
39
+ /**
40
+ * One globally reserved dispatch timestamp per granted permit. The runtime
41
+ * may cache the block, but must not dispatch a permit before its timestamp.
42
+ */
43
+ scheduledAtMs?: number[];
44
+ /** Live shared rate after adaptive backpressure/recovery. */
45
+ effectiveRequestsPerSecond?: number;
36
46
  /** One independently-expiring concurrency reservation per granted permit. */
37
47
  leaseIds?: string[];
38
48
  /**
@@ -50,6 +60,7 @@ export type PlayRunnerRateStateAcquireResult = {
50
60
 
51
61
  export type PlayRunnerRateStateReleaseInput = {
52
62
  bucketId: string;
63
+ rateScopeToken?: string | null;
53
64
  rules: PacingRule[];
54
65
  leaseIds: string[];
55
66
  schedulerSchema?: string | null;
@@ -57,6 +68,7 @@ export type PlayRunnerRateStateReleaseInput = {
57
68
 
58
69
  export type PlayRunnerRateStatePenalizeInput = {
59
70
  bucketId: string;
71
+ rateScopeToken?: string | null;
60
72
  cooldownMs: number;
61
73
  schedulerSchema?: string | null;
62
74
  };
@@ -67,8 +67,9 @@ export interface RuntimeResourceGovernor {
67
67
  resolveRowConcurrency(requested?: number): number;
68
68
  reportProviderBackpressure(input: {
69
69
  provider: string;
70
+ toolId?: string;
70
71
  retryAfterMs: number;
71
- }): void;
72
+ }): Promise<void>;
72
73
  observe(input: RuntimeResourceObservation): void;
73
74
  snapshot(): RuntimeResourceSnapshot;
74
75
  }
@@ -308,8 +309,8 @@ export function createRuntimeResourceGovernor(input: {
308
309
  return input.executionGovernor.resolveRowConcurrency(requested);
309
310
  },
310
311
 
311
- reportProviderBackpressure(backpressure) {
312
- input.executionGovernor.reportProviderBackpressure(backpressure);
312
+ async reportProviderBackpressure(backpressure) {
313
+ await input.executionGovernor.reportProviderBackpressure(backpressure);
313
314
  observe({
314
315
  providerResourceKey: backpressure.provider,
315
316
  provider429: true,
package/dist/cli/index.js CHANGED
@@ -625,10 +625,10 @@ var SDK_RELEASE = {
625
625
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
626
626
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
627
627
  // automatically without blocking their current command.
628
- version: "0.1.231",
628
+ version: "0.1.232",
629
629
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
630
630
  supportPolicy: {
631
- latest: "0.1.231",
631
+ latest: "0.1.232",
632
632
  minimumSupported: "0.1.53",
633
633
  deprecatedBelow: "0.1.219",
634
634
  commandMinimumSupported: [
@@ -610,10 +610,10 @@ var SDK_RELEASE = {
610
610
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
611
611
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
612
612
  // automatically without blocking their current command.
613
- version: "0.1.231",
613
+ version: "0.1.232",
614
614
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
615
615
  supportPolicy: {
616
- latest: "0.1.231",
616
+ latest: "0.1.232",
617
617
  minimumSupported: "0.1.53",
618
618
  deprecatedBelow: "0.1.219",
619
619
  commandMinimumSupported: [
package/dist/index.js CHANGED
@@ -424,10 +424,10 @@ var SDK_RELEASE = {
424
424
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
425
425
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
426
426
  // automatically without blocking their current command.
427
- version: "0.1.231",
427
+ version: "0.1.232",
428
428
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
429
429
  supportPolicy: {
430
- latest: "0.1.231",
430
+ latest: "0.1.232",
431
431
  minimumSupported: "0.1.53",
432
432
  deprecatedBelow: "0.1.219",
433
433
  commandMinimumSupported: [
package/dist/index.mjs CHANGED
@@ -354,10 +354,10 @@ var SDK_RELEASE = {
354
354
  // 0.1.175 ships loud `deepline enrich` empty-waterfall reporting.
355
355
  // 0.1.220 deprecates SDK CLI versions below 0.1.219 and updates them
356
356
  // automatically without blocking their current command.
357
- version: "0.1.231",
357
+ version: "0.1.232",
358
358
  apiContract: "2026-06-dataset-handle-results-hard-cutover",
359
359
  supportPolicy: {
360
- latest: "0.1.231",
360
+ latest: "0.1.232",
361
361
  minimumSupported: "0.1.53",
362
362
  deprecatedBelow: "0.1.219",
363
363
  commandMinimumSupported: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.1.231",
3
+ "version": "0.1.232",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {