deepline 0.3.183 → 0.3.185

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 (34) hide show
  1. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  2. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +53 -0
  3. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +751 -193
  4. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +4 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +3 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +1 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +437 -150
  8. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +47 -4
  9. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +3 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +18 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +3 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +22 -2
  13. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +6 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +11 -5
  15. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +102 -12
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +8 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +136 -28
  18. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts +80 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +897 -207
  20. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +33 -1
  21. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +21 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +120 -34
  23. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +82 -2
  24. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +7 -2
  25. package/dist/cli/index.js +28 -3
  26. package/dist/cli/index.mjs +28 -3
  27. package/dist/index.js +1 -1
  28. package/dist/index.mjs +1 -1
  29. package/dist/install-integrity.json +1 -0
  30. package/dist/release.d.mts +1 -1
  31. package/dist/release.d.ts +1 -1
  32. package/dist/release.js +1 -1
  33. package/dist/release.mjs +1 -1
  34. package/package.json +1 -1
@@ -21,6 +21,7 @@ import {
21
21
  resolveRowConcurrency,
22
22
  } from './policy';
23
23
  import {
24
+ DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS,
24
25
  type PacingPermit,
25
26
  type PacingRule,
26
27
  type ProviderConcurrencyGrant,
@@ -50,6 +51,8 @@ export interface ProviderPermit extends WorkLease {
50
51
  readonly concurrencyGrant?: ProviderConcurrencyGrant | null;
51
52
  /** Effective rate-controller reading for this physical provider permit. */
52
53
  readonly pacingGrant?: ProviderPacingGrant | null;
54
+ /** Signed shared-scope proof consumed by the execution gateway after queueing. */
55
+ readonly gatewayDispatchProof?: PacingPermit['gatewayDispatchProof'];
53
56
  /**
54
57
  * Return an acquired provider permit before an outbound request started.
55
58
  * Unlike `release`, this returns the rate reservation as well as any
@@ -64,6 +67,8 @@ export interface ProviderPermit extends WorkLease {
64
67
  * backend's lease block when that block is still live; otherwise refunds.
65
68
  */
66
69
  park(): Promise<void>;
70
+ /** Recheck shared provider state immediately before network dispatch. */
71
+ validate?(): Promise<boolean>;
67
72
  }
68
73
 
69
74
  export type BudgetKind = 'toolCall' | 'retry' | 'waterfallStep';
@@ -174,7 +179,7 @@ export interface PlayExecutionGovernor {
174
179
  */
175
180
  acquireProviderPermit(
176
181
  toolId: string,
177
- opts?: { signal?: AbortSignal },
182
+ opts?: { signal?: AbortSignal; yieldProviderHolds?: boolean },
178
183
  ): Promise<ProviderPermit>;
179
184
 
180
185
  /**
@@ -226,6 +231,8 @@ export interface PlayExecutionGovernor {
226
231
  */
227
232
  toolId?: string;
228
233
  retryAfterMs: number;
234
+ /** Absolute provider retry deadline from the observed response. */
235
+ retryAtMs?: number;
229
236
  /** Provider residence for the rejected physical request, when observed. */
230
237
  latencyMs?: number | null;
231
238
  /** Server-authored context for attributing this physical provider 429. */
@@ -434,6 +441,15 @@ export function createPlayExecutionGovernor(
434
441
  provider,
435
442
  concurrencyGrant: rateLease.concurrencyGrant ?? null,
436
443
  pacingGrant: rateLease.pacingGrant ?? null,
444
+ gatewayDispatchProof: rateLease.gatewayDispatchProof ?? null,
445
+ ...(rateLease.validate
446
+ ? {
447
+ validate: async () => {
448
+ if (settled) return false;
449
+ return await rateLease.validate!();
450
+ },
451
+ }
452
+ : {}),
437
453
  release() {
438
454
  if (settled) return;
439
455
  settled = true;
@@ -487,16 +503,37 @@ export function createPlayExecutionGovernor(
487
503
  // Passthrough: hand the DB-backed pacer the declared rules verbatim (or
488
504
  // the default-pacing shape when undeclared). No in-memory admission — the
489
505
  // row is authoritative.
490
- const pacing = declaredPacing ?? defaultPacingForTool(toolId, policy);
506
+ const fallback = defaultPacingForTool(toolId, policy);
507
+ const pacing = declaredPacing ?? {
508
+ ...fallback,
509
+ provider: resolvedPacing?.provider ?? fallback.provider,
510
+ };
511
+ if (!declaredPacing) {
512
+ // bucketId is the authenticated quota scope. Unknown tools sharing a
513
+ // credential must not each mint an independent copy of the default rate.
514
+ // Start its one shared lane at one probe/sec; the DB-owned AIMD quickly
515
+ // ramps on sustained success and reacts to Retry-After if the provider
516
+ // tells us the quota is lower.
517
+ pacing.rules = pacing.rules.map((rule) => ({
518
+ ...rule,
519
+ ruleId: 'default:provider-scope',
520
+ requestsPerWindow: DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS,
521
+ windowMs: 1_000,
522
+ }));
523
+ }
491
524
  providerByTool.set(toolId, pacing.provider);
492
525
  pacingRulesByTool.set(toolId, pacing.rules);
493
526
  return pacing;
494
527
  }
528
+ const fallback = defaultPacingForTool(toolId, policy);
495
529
  const pacing = adaptiveAdmission.resolvePacing({
496
530
  orgId: input.scope.orgId,
497
531
  toolId,
498
532
  declaredPacing,
499
- fallbackPacing: defaultPacingForTool(toolId, policy),
533
+ fallbackPacing: {
534
+ ...fallback,
535
+ provider: resolvedPacing?.provider ?? fallback.provider,
536
+ },
500
537
  });
501
538
  providerByTool.set(toolId, pacing.provider);
502
539
  pacingRulesByTool.set(toolId, pacing.rules);
@@ -623,8 +660,13 @@ export function createPlayExecutionGovernor(
623
660
  rateScopeToken: rateScope[1],
624
661
  rules: pacing.rules,
625
662
  signal: opts?.signal,
663
+ yieldProviderHolds: opts?.yieldProviderHolds,
626
664
  });
627
- return providerPermitFrom(pacing.provider, rateLease, localConcurrencySlot);
665
+ return providerPermitFrom(
666
+ pacing.provider,
667
+ rateLease,
668
+ localConcurrencySlot,
669
+ );
628
670
  } catch (error) {
629
671
  localConcurrencySlot?.release();
630
672
  throw error;
@@ -727,6 +769,7 @@ export function createPlayExecutionGovernor(
727
769
  bucketId: rateScope[0],
728
770
  ...(rateScope[1] ? { rateScopeToken: rateScope[1] } : {}),
729
771
  cooldownMs: bp.retryAfterMs,
772
+ ...(bp.retryAtMs != null ? { retryAtMs: bp.retryAtMs } : {}),
730
773
  ...(rules ? { rules } : {}),
731
774
  ...(bp.latencyMs != null ? { latencyMs: bp.latencyMs } : {}),
732
775
  ...(bp.concurrencyGrant
@@ -188,7 +188,9 @@ export const SHARED_EXECUTION_POLICY: ResolvedExecutionPolicy = {
188
188
  maxWaterfallStepExecutions: 5_000_000,
189
189
  },
190
190
  pacing: {
191
- // Undeclared providers; declared providers (rate-limit-definitions.ts) win.
191
+ // Keep the generic fallback rate. The shared Postgres backend separately
192
+ // cold-starts unknown provider scopes at one probe/sec.
193
+ // Declared providers (rate-limit-definitions.ts) keep their own rates.
192
194
  defaultProviderRequestsPerSecond: 10,
193
195
  // This is the scheduler's resident-call envelope, not an RPS target.
194
196
  // Keep it aligned with the explicit run ceiling so an RPS-only provider
@@ -14,6 +14,12 @@
14
14
  */
15
15
  export const DEFAULT_ADVISORY_PROVIDER_MAX_RPS = 100;
16
16
 
17
+ /** Cold-start rate for an unknown provider bucket shared across a fleet key. */
18
+ export const DEFAULT_UNKNOWN_PROVIDER_COLD_START_RPS = 1;
19
+
20
+ /** AIMD state cooldown cap; provider Retry-After holds remain uncapped. */
21
+ export const PROVIDER_AIMD_COOLDOWN_CAP_MS = 45 * 60 * 1_000;
22
+
17
23
  /** A single resolved rate rule for a bucket (from rate-limit-definitions). */
18
24
  export interface PacingRule {
19
25
  readonly ruleId: string;
@@ -83,10 +89,18 @@ export interface PacingPermit {
83
89
  * Absent on backends that keep no local block; callers use `cancel` then.
84
90
  */
85
91
  park?(): Promise<void>;
92
+ /** Recheck the durable provider-scope hold immediately before dispatch. */
93
+ validate?(): Promise<boolean>;
86
94
  /** Server-authored context for attributing this request's provider 429. */
87
95
  readonly concurrencyGrant?: ProviderConcurrencyGrant | null;
88
96
  /** Effective shared rate for every app-runtime provider permit. */
89
97
  readonly pacingGrant?: ProviderPacingGrant | null;
98
+ /** Signed shared-scope proof for revalidation after the execution-gateway queue. */
99
+ readonly gatewayDispatchProof?: {
100
+ readonly bucketId: string;
101
+ readonly rateScopeToken: string;
102
+ readonly retryAtMsAtGrant: number;
103
+ } | null;
90
104
  }
91
105
 
92
106
  /**
@@ -118,6 +132,8 @@ export interface RateStateBackend {
118
132
  rateScopeToken?: string | null;
119
133
  rules: readonly PacingRule[];
120
134
  signal?: AbortSignal;
135
+ /** Managed Plays hand a shared provider hold to durable row suspension. */
136
+ yieldProviderHolds?: boolean;
121
137
  }): Promise<PacingPermit>;
122
138
 
123
139
  /**
@@ -140,6 +156,8 @@ export interface RateStateBackend {
140
156
  bucketId: string;
141
157
  rateScopeToken?: string | null;
142
158
  cooldownMs: number;
159
+ /** Absolute upstream retry deadline, stable across transport retries. */
160
+ retryAtMs?: number;
143
161
  /** Resolved provider rules for the rejected physical attempt. */
144
162
  rules?: readonly PacingRule[];
145
163
  /** Provider residence measured at the HTTP boundary. */
@@ -15,6 +15,9 @@ export function cloneMapFrame(frame: MapExecutionFrame): MapExecutionFrame {
15
15
  suspendedInputRows: frame.suspendedInputRows.map((row) => ({
16
16
  inputIndex: row.inputIndex,
17
17
  data: { ...row.data },
18
+ ...(row.authoredInputDigest
19
+ ? { authoredInputDigest: row.authoredInputDigest }
20
+ : {}),
18
21
  })),
19
22
  }
20
23
  : {}),
@@ -79,6 +79,8 @@ export type PlayRunnerRateStateConcurrencyGrant = {
79
79
  export type PlayRunnerRateStateAcquireResult = {
80
80
  granted: number;
81
81
  waitMs: number;
82
+ /** This receiver serves rate_state_validate. Absent on pre-expansion readers. */
83
+ scopeValidationSupported?: boolean;
82
84
  /**
83
85
  * Gateway clock used to produce `scheduledAtMs`. Runners translate the
84
86
  * schedule into relative delays so cross-machine clock skew cannot compress
@@ -108,8 +110,8 @@ export type PlayRunnerRateStateAcquireResult = {
108
110
  */
109
111
  coolUntilMs?: number;
110
112
  /**
111
- * The most recent server Retry-After stored verbatim (uncapped) as an absolute
112
- * epoch-ms deadline. Purely observational — never gates a grant on its own.
113
+ * Most recent provider Retry-After deadline, surfaced for diagnostics. The
114
+ * authoritative admission gate is the durable provider-scope hold row.
113
115
  */
114
116
  claimedRetryAtMs?: number;
115
117
  };
@@ -128,6 +130,12 @@ export type PlayRunnerRateStatePenalizeInput = {
128
130
  bucketId: string;
129
131
  rateScopeToken?: string | null;
130
132
  cooldownMs: number;
133
+ /** Absolute provider retry deadline captured with the observed response. */
134
+ retryAtMs?: number;
135
+ /** Unflushed confirmed successes, sharing acquire's replay-safe identity. */
136
+ observedSuccesses?: number;
137
+ concurrencyObservations?: PlayRunnerRateStateConcurrencyObservation[];
138
+ concurrencyObservationBatch?: PlayRunnerRateStateConcurrencyObservationBatch;
131
139
  /**
132
140
  * The same resolved rules that admitted the attempt. Optional during the
133
141
  * rolling deploy window: old runners retain rate-only backpressure handling.
@@ -140,6 +148,18 @@ export type PlayRunnerRateStatePenalizeInput = {
140
148
  schedulerSchema?: string | null;
141
149
  };
142
150
 
151
+ export type PlayRunnerRateStateValidateInput = {
152
+ bucketId: string;
153
+ rateScopeToken?: string | null;
154
+ schedulerSchema?: string | null;
155
+ };
156
+
157
+ export type PlayRunnerRateStateValidateResult = {
158
+ allowed: boolean;
159
+ serverNowMs: number;
160
+ retryAtMs: number;
161
+ };
162
+
143
163
  export type PlayRunnerBudgetCharge = {
144
164
  key: string;
145
165
  amount: number;
@@ -711,6 +711,7 @@ export interface RuntimeResourceGovernor {
711
711
  acquireProviderPermit(input: {
712
712
  toolId: string;
713
713
  signal?: AbortSignal;
714
+ yieldProviderHolds?: boolean;
714
715
  }): Promise<RuntimeProviderPermit>;
715
716
  /**
716
717
  * Non-blocking provider admission from capacity this process already holds.
@@ -748,6 +749,7 @@ export interface RuntimeResourceGovernor {
748
749
  provider: string;
749
750
  toolId?: string;
750
751
  retryAfterMs: number;
752
+ retryAtMs?: number;
751
753
  /** Provider residence for the rejected physical request, when observed. */
752
754
  latencyMs?: number | null;
753
755
  /** Server-authored context for attributing this physical provider 429. */
@@ -1585,7 +1587,10 @@ export function createRuntimeResourceGovernor(input: {
1585
1587
  const startedAt = Date.now();
1586
1588
  const lease = await input.executionGovernor.acquireProviderPermit(
1587
1589
  providerInput.toolId,
1588
- { signal: providerInput.signal },
1590
+ {
1591
+ signal: providerInput.signal,
1592
+ yieldProviderHolds: providerInput.yieldProviderHolds,
1593
+ },
1589
1594
  );
1590
1595
  observe({
1591
1596
  providerResourceKey: lease.provider,
@@ -241,15 +241,17 @@ export function providerExhaustedMessage(input: {
241
241
  }
242
242
 
243
243
  /**
244
- * Thrown by the DB-authoritative pacer (`app_runtime_postgres` rate-state
245
- * backend) when a provider's hold exceeds the tolerable wait
246
- * (PROVIDER_EXHAUSTED_MAX_WAIT_MS). The call is skipped BEFORE any tool
247
- * execution request, so nothing is dispatched and nothing is billed. Row-failure
248
- * isolation still applies: this is a row/step outcome, not a run-fatal error.
244
+ * Control signal from the DB-authoritative pacer when a provider hold exceeds
245
+ * the in-process wait threshold. The next provider call is not dispatched.
246
+ * Managed durable Plays translate this into a persisted run/row wait and resume
247
+ * after the deadline; callers without durable boundaries receive a repairable
248
+ * provider-exhausted outcome. This signal is not a terminal row failure in the
249
+ * managed durable path.
249
250
  */
250
251
  export class ProviderExhaustedError extends Error {
251
252
  readonly code = PROVIDER_EXHAUSTED_CODE;
252
253
  readonly provider: string;
254
+ readonly publicDetails: ToolExecutionError['publicDetails'];
253
255
  /** ISO 8601 retry deadline (from the server Retry-After / cooldown). */
254
256
  readonly retryAt: string;
255
257
  constructor(input: { provider: string; retryAtMs: number; cause?: unknown }) {
@@ -264,6 +266,10 @@ export class ProviderExhaustedError extends Error {
264
266
  );
265
267
  this.name = 'ProviderExhaustedError';
266
268
  this.provider = input.provider;
269
+ this.publicDetails =
270
+ input.cause instanceof ToolExecutionError
271
+ ? input.cause.publicDetails
272
+ : null;
267
273
  this.retryAt = Number.isFinite(Number(input.retryAtMs))
268
274
  ? new Date(Number(input.retryAtMs)).toISOString()
269
275
  : new Date(0).toISOString();
@@ -85,6 +85,8 @@ export function createRuntimeFlow<Input>(input: {
85
85
  export type RuntimeDispatchProviderPermit = RuntimeFlowLease & {
86
86
  cancel(): Promise<void>;
87
87
  park?(): Promise<void>;
88
+ /** Recheck a shared provider hold at the physical dispatch boundary. */
89
+ validate?(): Promise<boolean>;
88
90
  };
89
91
 
90
92
  export type RuntimeDispatchFlowInput = {
@@ -92,6 +94,11 @@ export type RuntimeDispatchFlowInput = {
92
94
  signal?: AbortSignal;
93
95
  /** Fixtures consume provider capacity but acquire app transport after delay. */
94
96
  requiresTransportPermit: boolean;
97
+ /**
98
+ * Defer shared-hold validation until the request callback reaches its actual
99
+ * outbound send. The callback must call the supplied boundary exactly once.
100
+ */
101
+ deferProviderPermitValidation?: boolean;
95
102
  /** Emits the request-side transport wait observed by the caller's metrics. */
96
103
  onTransportAdmitted?: (waitMs: number) => void;
97
104
  };
@@ -114,12 +121,18 @@ type RuntimeDispatchPermit<
114
121
  */
115
122
  export type RuntimeDispatchScope = {
116
123
  /**
117
- * Run the one physical request. `beforeRelease` executes after the response
118
- * is known but while the provider lease is still valid, so a caller can
119
- * durably record response-bound feedback that the lease authorizes.
124
+ * Run the one physical request. Deferred-validation callers must invoke
125
+ * `beforeProviderDispatch` immediately before the outbound send, after any
126
+ * async setup/queueing. `beforeRelease` executes after the response is known
127
+ * but while the provider lease is still valid, so a caller can durably
128
+ * record response-bound feedback that the lease authorizes.
120
129
  */
121
130
  request<Output>(
122
- work: () => Output | Promise<Output>,
131
+ work: (
132
+ beforeProviderDispatch: (
133
+ onPermitInvalidated?: () => void,
134
+ ) => boolean | Promise<boolean>,
135
+ ) => Output | Promise<Output>,
123
136
  beforeRelease?: () => void | Promise<void>,
124
137
  ): Promise<Output>;
125
138
  };
@@ -130,10 +143,12 @@ export type RuntimeDispatchFlow<
130
143
  /**
131
144
  * Admit and run exactly one physical provider request.
132
145
  *
133
- * Work that exits before `request` returns the provider permit through its
134
- * cancellation path. Once `request` starts, the permit is released after
135
- * the request body is consumed, even if that body cannot be verified. This
136
- * preserves the ambiguity boundary while making resource lifetime private.
146
+ * Work must call `beforeProviderDispatch` at the actual outbound-send edge.
147
+ * That is where shared Retry-After state is checked: not before async
148
+ * request preparation. Work that exits before that call returns the provider
149
+ * permit through its cancellation path. Once the send starts, the permit is
150
+ * released after the response body is consumed, even if it cannot be
151
+ * verified.
137
152
  */
138
153
  run<Output>(
139
154
  input: RuntimeDispatchFlowInput,
@@ -264,8 +279,10 @@ export function createRuntimeDispatchFlow<
264
279
  admission: RuntimeDispatchFlowInput,
265
280
  work: (scope: RuntimeDispatchScope) => Output | Promise<Output>,
266
281
  ): Promise<Output> {
267
- const permit = await admit(admission);
282
+ let permit = await admit(admission);
268
283
  let transportLease = permit.integrationRequestLease;
284
+ let requiresTransportPermitForReacquire =
285
+ admission.requiresTransportPermit;
269
286
  let requestStarted = false;
270
287
  let requestUsed = false;
271
288
  let scopeOpen = true;
@@ -284,7 +301,11 @@ export function createRuntimeDispatchFlow<
284
301
  try {
285
302
  const output = await work({
286
303
  request: <RequestOutput>(
287
- request: () => RequestOutput | Promise<RequestOutput>,
304
+ request: (
305
+ beforeProviderDispatch: (
306
+ onPermitInvalidated?: () => void,
307
+ ) => boolean | Promise<boolean>,
308
+ ) => RequestOutput | Promise<RequestOutput>,
288
309
  beforeRelease?: () => void | Promise<void>,
289
310
  ): Promise<RequestOutput> => {
290
311
  if (!scopeOpen) {
@@ -301,10 +322,69 @@ export function createRuntimeDispatchFlow<
301
322
  }
302
323
  requestUsed = true;
303
324
  requestPromise = (async () => {
325
+ let dispatchStarted = false;
326
+ const beforeProviderDispatch = (
327
+ onPermitInvalidated?: () => void,
328
+ ): boolean | Promise<boolean> => {
329
+ if (dispatchStarted) {
330
+ throw new Error(
331
+ 'Runtime dispatch flow permits exactly one physical provider dispatch.',
332
+ );
333
+ }
334
+ if (!permit.providerPermit.validate) {
335
+ // Preserve the zero-round-trip in-memory path: deferred
336
+ // callers still cross the exact send boundary, but do not
337
+ // pay an artificial microtask when there is nothing shared
338
+ // to revalidate.
339
+ dispatchStarted = true;
340
+ requestStarted = true;
341
+ return false;
342
+ }
343
+ return validateAndStartProviderDispatch(onPermitInvalidated);
344
+ };
345
+ const validateAndStartProviderDispatch = async (
346
+ onPermitInvalidated?: () => void,
347
+ ): Promise<boolean> => {
348
+ let replaced = false;
349
+ while (
350
+ permit.providerPermit.validate &&
351
+ !(await permit.providerPermit.validate())
352
+ ) {
353
+ requestStarted = false;
354
+ onPermitInvalidated?.();
355
+ await releasePermit();
356
+ permit = await admit({
357
+ ...admission,
358
+ requiresTransportPermit:
359
+ requiresTransportPermitForReacquire,
360
+ });
361
+ transportLease = permit.integrationRequestLease;
362
+ requestStarted = false;
363
+ permitReleased = false;
364
+ replaced = true;
365
+ }
366
+ dispatchStarted = true;
367
+ if (admission.deferProviderPermitValidation) {
368
+ requestStarted = true;
369
+ }
370
+ return replaced;
371
+ };
304
372
  try {
373
+ if (!admission.deferProviderPermitValidation) {
374
+ // Preserve the ordinary API: its request callback begins
375
+ // the physical send immediately. Callers with asynchronous
376
+ // preparation opt into deferred validation and invoke this
377
+ // same boundary at the final outbound edge.
378
+ if (permit.providerPermit.validate) {
379
+ await beforeProviderDispatch();
380
+ } else {
381
+ dispatchStarted = true;
382
+ }
383
+ }
305
384
  if (transportLease) {
306
385
  admission.onTransportAdmitted?.(0);
307
386
  } else {
387
+ requiresTransportPermitForReacquire = true;
308
388
  const transportStartedAt = Date.now();
309
389
  transportLease = await input.waitForTransportSlot({
310
390
  signal: admission.signal,
@@ -313,8 +393,18 @@ export function createRuntimeDispatchFlow<
313
393
  Date.now() - transportStartedAt,
314
394
  );
315
395
  }
316
- requestStarted = true;
317
- return await request();
396
+ if (!admission.deferProviderPermitValidation) {
397
+ // Preserve the cancellation boundary for a permit whose
398
+ // transport wait fails before the request callback begins.
399
+ requestStarted = true;
400
+ }
401
+ const result = await request(beforeProviderDispatch);
402
+ if (!dispatchStarted) {
403
+ throw new Error(
404
+ 'Runtime dispatch request completed without crossing its provider-dispatch boundary.',
405
+ );
406
+ }
407
+ return result;
318
408
  } finally {
319
409
  // Feedback is response-bound, but it is not allowed to strand
320
410
  // the capacity that made the response possible. Preserve the
@@ -47,6 +47,7 @@ export const RUNTIME_OPERATION_CAPABILITIES = {
47
47
  rate_state_acquire: 'rate.acquire',
48
48
  rate_state_release: 'rate.acquire',
49
49
  rate_state_penalize: 'rate.acquire',
50
+ rate_state_validate: 'rate.acquire',
50
51
  get_runtime_traffic_policy: 'rate.acquire',
51
52
  governor_budget_charge: 'budget.charge',
52
53
  resolve_secret: 'secret.resolve',
@@ -69,6 +70,13 @@ export const RUNTIME_OPERATION_CONTRACT: DeployedOperationContract =
69
70
  kind: 'equivalent-fallback',
70
71
  action: 'complete_runtime_step_receipts',
71
72
  },
73
+ // Permit validation cannot safely fall back to a cached ticket: an old
74
+ // receiver would ignore the durable provider Retry-After hold. New
75
+ // workers therefore fail closed with this typed compatibility result.
76
+ rate_state_validate: {
77
+ kind: 'feature-unavailable',
78
+ code: 'RATE_STATE_VALIDATE_UNAVAILABLE',
79
+ },
72
80
  },
73
81
  });
74
82