deepline 0.1.201 → 0.1.202

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 (32) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +30 -9
  2. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +25 -0
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +139 -124
  4. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +17 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-dispatch.ts +52 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +152 -23
  8. package/dist/bundling-sources/shared_libs/play-runtime/coordinator-headers.ts +10 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +11 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +121 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +6 -1
  12. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-budget-state-backend.ts +22 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +86 -19
  14. package/dist/bundling-sources/shared_libs/play-runtime/governor/block-reserving-budget-state-backend.ts +141 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/governor/budget-state-backend.ts +43 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +129 -41
  17. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +40 -2
  18. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +92 -33
  19. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +13 -5
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +122 -120
  21. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +10 -8
  22. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +140 -32
  23. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +225 -29
  24. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +26 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +21 -0
  26. package/dist/bundling-sources/shared_libs/plays/dataset.ts +32 -10
  27. package/dist/cli/index.js +198 -76
  28. package/dist/cli/index.mjs +204 -82
  29. package/dist/index.js +3 -2
  30. package/dist/index.mjs +3 -2
  31. package/package.json +1 -1
  32. package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +0 -355
@@ -24,12 +24,12 @@ import {
24
24
  compileRequestsWithStrategy,
25
25
  executeChunkedRequests,
26
26
  } from './batch-runtime';
27
- import { AdaptiveToolDispatcher } from './adaptive-tool-dispatcher';
28
27
  import {
29
28
  PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
30
29
  runtimeLeaseHeartbeatIntervalFromExpiry,
31
30
  } from './lease-policy';
32
31
  import { createRuntimeReceiptHeartbeatSupervisor } from './receipt-heartbeat-supervisor';
32
+ import { dispatchBoundedSettled } from './bounded-dispatch';
33
33
  import type { PlayQueueHint } from './governor/rate-state-backend';
34
34
  import type { MapRowOutcome } from './durability-store';
35
35
  import type { WorkReceiptFailureKind } from './work-receipts';
@@ -43,6 +43,7 @@ import { RuntimeSheetRowsBlockedError } from './runtime-sheet-errors';
43
43
  import { buildChildRunId } from './child-run-id';
44
44
  import type { PlayCallGovernanceSnapshot } from './scheduler-backend';
45
45
  import {
46
+ ABSURD_RELEASE_OVERRIDE_HEADER,
46
47
  RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER,
47
48
  SYNTHETIC_RUN_HEADER,
48
49
  } from './coordinator-headers';
@@ -50,6 +51,7 @@ import {
50
51
  PLAY_RUNTIME_API_COMPAT_PATH,
51
52
  PLAY_RUNTIME_CHILD_EXECUTOR_TOKEN_COMPAT_PATH,
52
53
  } from './runtime-api-paths';
54
+ import { startRunViaAppRuntime } from './app-runtime-api';
53
55
  import { vercelProtectionBypassHeader } from './vercel-protection';
54
56
  export {
55
57
  RuntimeSheetRowsBlockedError,
@@ -163,6 +165,8 @@ import {
163
165
  createRuntimeMapRetainedRowsTracker,
164
166
  estimateRuntimeMapRowsMemory,
165
167
  resolveRuntimeMapRowAdmission,
168
+ NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER,
169
+ NODE_RUNTIME_MAP_ROW_OVERHEAD_BYTES,
166
170
  runtimeMapJsonByteLength,
167
171
  resolveRuntimeMapMemoryLimits,
168
172
  RuntimeMapMemoryLimitError,
@@ -1208,6 +1212,7 @@ export class PlayContextImpl {
1208
1212
  rootRunId: options.runId ?? options.workflowId ?? 'run',
1209
1213
  },
1210
1214
  rateState: options.rateState ?? new InMemoryRateStateBackend(),
1215
+ budgetState: options.budgetState,
1211
1216
  resolvePacing: createPacingResolver(options.getToolQueueHints),
1212
1217
  // A root run keeps depth 0 (its first child play is depth 1) but still
1213
1218
  // seeds its own play id into the ancestry/currentPlayId so the cycle guard
@@ -1367,6 +1372,16 @@ export class PlayContextImpl {
1367
1372
  [SYNTHETIC_RUN_HEADER]: '1',
1368
1373
  }
1369
1374
  : {}),
1375
+ // Defense-in-depth: pin the child to the parent worker's release lane so
1376
+ // a child always finishes on the release that launched its parent, even
1377
+ // if the resolver default (schema-derived) would drift. The run route
1378
+ // honors this only for internal ctx.runPlay child launches.
1379
+ ...(this.#options.absurdReleaseId?.trim()
1380
+ ? {
1381
+ [ABSURD_RELEASE_OVERRIDE_HEADER]:
1382
+ this.#options.absurdReleaseId.trim(),
1383
+ }
1384
+ : {}),
1370
1385
  ...(await this.vercelProtectionHeaders()),
1371
1386
  };
1372
1387
  }
@@ -1777,6 +1792,30 @@ export class PlayContextImpl {
1777
1792
  };
1778
1793
  }
1779
1794
 
1795
+ private async skipRuntimeStepReceipt(
1796
+ key: string,
1797
+ runId: string,
1798
+ leaseId?: string | null,
1799
+ ): Promise<RuntimeStepReceipt | null> {
1800
+ if (!this.#options.skipRuntimeStepReceipt) {
1801
+ return null;
1802
+ }
1803
+ const skipped = await this.#options.skipRuntimeStepReceipt({
1804
+ key,
1805
+ runId,
1806
+ runAttempt: this.currentRunAttempt,
1807
+ ...(leaseId ? { leaseId } : {}),
1808
+ });
1809
+ if (!skipped || typeof skipped.key !== 'string' || !skipped.key.trim()) {
1810
+ return null;
1811
+ }
1812
+ return {
1813
+ ...skipped,
1814
+ key: skipped.key.trim(),
1815
+ runId: skipped.runId ?? null,
1816
+ };
1817
+ }
1818
+
1780
1819
  private async heartbeatRuntimeStepReceipt(
1781
1820
  key: string,
1782
1821
  runId: string,
@@ -3701,7 +3740,13 @@ export class PlayContextImpl {
3701
3740
  await flushChunk();
3702
3741
  };
3703
3742
 
3704
- for await (const page of iteratePlayDatasetInputPages(items)) {
3743
+ for await (const page of iteratePlayDatasetInputPages(items, {
3744
+ maxPageBytes: mapMemoryLimits.materializedBudgetBytes,
3745
+ estimateRowBytes: (row) =>
3746
+ runtimeMapJsonByteLength(row) *
3747
+ NODE_RUNTIME_MAP_MATERIALIZED_MEMORY_MULTIPLIER +
3748
+ NODE_RUNTIME_MAP_ROW_OVERHEAD_BYTES,
3749
+ })) {
3705
3750
  const pageRawItems = page.rows.map((item) =>
3706
3751
  this.toOutputRow(item as Record<string, unknown>),
3707
3752
  );
@@ -5960,6 +6005,11 @@ export class PlayContextImpl {
5960
6005
  authScopeAttempt < 2;
5961
6006
  authScopeAttempt += 1
5962
6007
  ) {
6008
+ // Capture the lease the receipt store hands to `execute` for THIS
6009
+ // attempt's key. On an auth-scope re-claim we mark that old-scope receipt
6010
+ // `skipped` (below), and the skip is fenced on this run+attempt+lease so
6011
+ // it can only supersede the receipt this attempt owns.
6012
+ let claimedReceiptLeaseId: string | null = null;
5963
6013
  try {
5964
6014
  return await this.executeWithRuntimeReceipt(
5965
6015
  'tool',
@@ -5991,7 +6041,10 @@ export class PlayContextImpl {
5991
6041
  shouldPersistFailure: (error) =>
5992
6042
  !(error instanceof ToolExecuteAuthScopeChangedError),
5993
6043
  markRunningBeforeExecute: false,
5994
- execute: ({ leaseId }) => executeTool({ leaseId }),
6044
+ execute: ({ leaseId }) => {
6045
+ claimedReceiptLeaseId = leaseId;
6046
+ return executeTool({ leaseId });
6047
+ },
5995
6048
  runningReceiptWaitMaxAttempts:
5996
6049
  resolveRuntimeToolReceiptWaitMaxAttempts(
5997
6050
  typeof options?.receiptWaitMs === 'number'
@@ -6007,6 +6060,36 @@ export class PlayContextImpl {
6007
6060
  ) {
6008
6061
  throw error;
6009
6062
  }
6063
+ // Supersede the old-scope receipt so the run's node projection does not
6064
+ // surface it as `failed`. shouldPersistFailure kept it out of the failed
6065
+ // state, but the claim left it `running` with a now-orphaned lease under
6066
+ // the OLD cache key; without an explicit terminal it projects as a failed
6067
+ // node even though the work is about to be re-done under the new scope.
6068
+ // `skipped` is the terminal for superseded work; the skip is fenced on
6069
+ // this run+attempt+lease so it can only touch the receipt this attempt
6070
+ // owns. Best-effort: a skip that cannot fence (store returns null) must
6071
+ // not mask the reclaim itself.
6072
+ const supersededReceiptKey = durableCacheKey;
6073
+ try {
6074
+ const skipped = await this.skipRuntimeStepReceipt(
6075
+ supersededReceiptKey,
6076
+ this.currentRunId,
6077
+ claimedReceiptLeaseId,
6078
+ );
6079
+ if (skipped) {
6080
+ this.log(
6081
+ `ctx.tools.execute(${toolId}): auth_scope_changed_superseded ` +
6082
+ `old-scope receipt ${toolRequestIdentity} (label: ${normalizedKey}) ` +
6083
+ `marked skipped so the reclaim does not project as a failed node.`,
6084
+ );
6085
+ }
6086
+ } catch (skipError) {
6087
+ this.log(
6088
+ `ctx.tools.execute(${toolId}): auth_scope_changed_supersede_skip_failed ` +
6089
+ `(label: ${normalizedKey}); could not mark the old-scope receipt ` +
6090
+ `skipped: ${this.formatRuntimeError(skipError)}`,
6091
+ );
6092
+ }
6010
6093
  // Make the bounded auth-scope re-claim observable in run logs instead
6011
6094
  // of a silent continuation: the credential identity changed after the
6012
6095
  // receipt key was prepared, so we evict the cached digest, re-resolve,
@@ -6213,7 +6296,7 @@ export class PlayContextImpl {
6213
6296
  input,
6214
6297
  graphHash: null,
6215
6298
  });
6216
- const childGovernance = this.governor.forkChild({
6299
+ const childGovernance = await this.governor.forkChild({
6217
6300
  childPlayName: resolvedName,
6218
6301
  childRunId,
6219
6302
  });
@@ -6338,6 +6421,12 @@ export class PlayContextImpl {
6338
6421
  this.consumePendingChildPlaySuspension() ?? suspension,
6339
6422
  );
6340
6423
  }
6424
+ await this.registerInlineChildRun({
6425
+ parentRunId: this.currentRunId,
6426
+ childRunId: childGovernance.currentRunId,
6427
+ childPlayName: resolvedName,
6428
+ staticPipeline: resolvedPlay.staticPipeline ?? null,
6429
+ });
6341
6430
  const childContext = createPlayContext({
6342
6431
  ...this.#options,
6343
6432
  playId: resolvedName,
@@ -6496,6 +6585,40 @@ export class PlayContextImpl {
6496
6585
  return body.executorToken.trim();
6497
6586
  }
6498
6587
 
6588
+ private async registerInlineChildRun(input: {
6589
+ parentRunId: string;
6590
+ childRunId: string;
6591
+ childPlayName: string;
6592
+ staticPipeline: unknown;
6593
+ }): Promise<void> {
6594
+ const baseUrl = this.#options.baseUrl?.trim();
6595
+ const executorToken = this.#options.executorToken?.trim();
6596
+ if (!baseUrl || !executorToken) return;
6597
+
6598
+ await startRunViaAppRuntime(
6599
+ {
6600
+ baseUrl,
6601
+ executorToken,
6602
+ integrationMode: this.#options.integrationMode ?? null,
6603
+ vercelProtectionBypassToken:
6604
+ this.#options.vercelProtectionBypassToken ?? null,
6605
+ },
6606
+ {
6607
+ playName: input.childPlayName,
6608
+ runId: input.childRunId,
6609
+ parentRunId: input.parentRunId,
6610
+ rootRunId: this.governance.rootRunId,
6611
+ workflowFamilyKey: this.governance.rootRunId,
6612
+ artifactHash: this.#options.artifactHash ?? null,
6613
+ graphHash: this.#options.graphHash ?? null,
6614
+ schedulerSchema: this.#options.runtimeSchedulerSchema ?? null,
6615
+ executionProfile: this.#options.childRunProfile ?? 'absurd',
6616
+ staticPipeline: input.staticPipeline,
6617
+ source: 'published',
6618
+ },
6619
+ );
6620
+ }
6621
+
6499
6622
  /**
6500
6623
  * Extract a list from a tool result.
6501
6624
  * e.g. ctx.extractList(result, 'people', ['first_name', 'last_name', 'email'])
@@ -7596,22 +7719,6 @@ export class PlayContextImpl {
7596
7719
  },
7597
7720
  });
7598
7721
  } else {
7599
- const initialRequestsPerSecond =
7600
- await this.resourceGovernor.suggestedToolParallelism(
7601
- toolId,
7602
- this.governor.policy.pacing.suggestedMaxParallelism,
7603
- );
7604
- const dispatcher = new AdaptiveToolDispatcher<
7605
- ToolCallRequest,
7606
- unknown
7607
- >({
7608
- initialRequestsPerSecond,
7609
- maxRequestsPerSecond:
7610
- this.governor.policy.pacing.suggestedMaxParallelism,
7611
- maxConcurrency: this.governor.policy.concurrency.toolCalls,
7612
- defaultLatencyMs: 1_000,
7613
- safetyFactor: 1.5,
7614
- });
7615
7722
  const completionBuffer: Array<{
7616
7723
  request: ToolCallRequest;
7617
7724
  result: unknown | null;
@@ -7675,8 +7782,29 @@ export class PlayContextImpl {
7675
7782
  void flushCompletionBuffer();
7676
7783
  }, 0);
7677
7784
  });
7678
- const dispatchResults = await dispatcher.dispatch(
7785
+ // Seed the dispatch width from the governor's provider-shaped
7786
+ // parallelism instead of the flat policy.concurrency.toolCalls
7787
+ // ceiling. The flat width launched every pending row at once into
7788
+ // the pacer, so unhinted providers 429'd on the first burst before
7789
+ // AIMD could halve the rate. The shaped estimate is derived from the
7790
+ // provider's RPS/maxConcurrency pacing rules (see
7791
+ // suggestedParallelism in governor.ts), floored at 1 and capped by
7792
+ // the global tool-call concurrency ceiling. The pacer still gates
7793
+ // each in-flight call, so this only trims the launch burst.
7794
+ const toolCallConcurrencyCeiling =
7795
+ this.governor.policy.concurrency.toolCalls;
7796
+ const shapedToolParallelism =
7797
+ await this.resourceGovernor.suggestedToolParallelism(
7798
+ toolId,
7799
+ toolCallConcurrencyCeiling,
7800
+ );
7801
+ const dispatchWidth = Math.min(
7802
+ toolCallConcurrencyCeiling,
7803
+ Math.max(1, shapedToolParallelism),
7804
+ );
7805
+ const dispatchResults = await dispatchBoundedSettled(
7679
7806
  pendingRequests,
7807
+ dispatchWidth,
7680
7808
  async (request) => {
7681
7809
  // Circuit breaker: do not dispatch this provider call once a
7682
7810
  // persistence failure has occurred in this run.
@@ -8021,6 +8149,7 @@ export class PlayContextImpl {
8021
8149
  }),
8022
8150
  });
8023
8151
  this.resourceGovernor.observe({
8152
+ toolId,
8024
8153
  providerResourceKey: `tool:${toolId}`,
8025
8154
  providerLatencyMs: Date.now() - providerCallStartedAt,
8026
8155
  providerSuccess: response.ok,
@@ -8044,7 +8173,7 @@ export class PlayContextImpl {
8044
8173
  const message =
8045
8174
  error instanceof Error ? error.message : String(error);
8046
8175
  if (transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS) {
8047
- this.governor.chargeBudget('retry');
8176
+ await this.governor.chargeBudget('retry');
8048
8177
  const retryAfterMs =
8049
8178
  TOOL_RETRY_AFTER_FALLBACK_MS * transportAttempt;
8050
8179
  span.setAttribute(
@@ -8102,7 +8231,7 @@ export class PlayContextImpl {
8102
8231
  }
8103
8232
  if (failure.shouldRetry) {
8104
8233
  if (failure.chargeRetryBudget) {
8105
- this.governor.chargeBudget('retry');
8234
+ await this.governor.chargeBudget('retry');
8106
8235
  }
8107
8236
  const retryAttributePrefix = failure.isRateLimit
8108
8237
  ? 'rate_limit'
@@ -28,6 +28,16 @@ export const WORKER_CALLBACK_URL_OVERRIDE_HEADER =
28
28
  'x-deepline-worker-callback-url';
29
29
  export const RUNTIME_SCHEDULER_SCHEMA_OVERRIDE_HEADER =
30
30
  'x-deepline-runtime-scheduler-schema';
31
+ /**
32
+ * Child-launch defense-in-depth: an Absurd worker sets this to its own
33
+ * `currentAbsurdReleaseId()` when it launches a `ctx.runPlay` child via
34
+ * `/api/v2/plays/run`. The run route pins the child to this release lane over
35
+ * the resolver's schema-derived default, so a child always finishes on the
36
+ * release that launched its parent. Honored ONLY for internal `internalRunPlay`
37
+ * child launches (executor-token authed, lineage-validated). Absent =>
38
+ * inherit-when-absent back-compat: the resolver default applies unchanged.
39
+ */
40
+ export const ABSURD_RELEASE_OVERRIDE_HEADER = 'x-deepline-absurd-release';
31
41
  /**
32
42
  * CLI→app marker (NOT a coordinator header — it lives here only because both
33
43
  * the SDK HTTP client and the run route already import this module). Set by
@@ -13,6 +13,7 @@ import type {
13
13
  RateStateBackend,
14
14
  } from './governor/rate-state-backend';
15
15
  import type { GovernanceSnapshot } from './governor/governor';
16
+ import type { BudgetStateBackend } from './governor/budget-state-backend';
16
17
  import type { RuntimeMapMemoryLimits } from './map-memory-limits';
17
18
  import type { AnyBatchOperationStrategy } from './batching-types';
18
19
  import type { ToolResultMetadataInput } from './tool-result';
@@ -489,6 +490,13 @@ export interface ContextOptions {
489
490
  * to the `absurd` profile.
490
491
  */
491
492
  childRunProfile?: string | null;
493
+ /**
494
+ * The parent worker's own absurd release lane id. Threaded onto child
495
+ * `ctx.runPlay` launches via the `x-deepline-absurd-release` header so the
496
+ * child pins to the parent's lane (defense-in-depth). Null off the absurd
497
+ * path or on a pre-release parent.
498
+ */
499
+ absurdReleaseId?: string | null;
492
500
  staticPipeline?: PlayStaticPipeline | null;
493
501
  workflowId?: string;
494
502
  sessionId?: string;
@@ -561,6 +569,9 @@ export interface ContextOptions {
561
569
  * contexts omit this and use the in-process backend.
562
570
  */
563
571
  rateState?: RateStateBackend;
572
+ /** Shared root-run budget reservations. Production schedulers must provide
573
+ * the same durable backend to every child sandbox. */
574
+ budgetState?: BudgetStateBackend;
564
575
  /**
565
576
  * Loud guard for production Node substrates: when true, constructing a
566
577
  * context without a shared rate-state backend is a configuration error.
@@ -0,0 +1,121 @@
1
+ export const RUNTIME_EXECUTION_CAPABILITIES = [
2
+ 'artifact.read',
3
+ 'file.read',
4
+ 'receipt.read',
5
+ 'receipt.write',
6
+ 'tool.inspect',
7
+ 'tool.execute',
8
+ 'event.wait',
9
+ 'run.child',
10
+ 'run.progress',
11
+ 'sheet.read',
12
+ 'sheet.write',
13
+ 'db.session',
14
+ 'rate.acquire',
15
+ 'budget.charge',
16
+ 'billing.write',
17
+ 'egress.fetch',
18
+ 'secret.resolve',
19
+ ] as const;
20
+
21
+ export type RuntimeExecutionCapability =
22
+ (typeof RUNTIME_EXECUTION_CAPABILITIES)[number];
23
+
24
+ const CAPABILITY_SET = new Set<string>(RUNTIME_EXECUTION_CAPABILITIES);
25
+
26
+ /**
27
+ * Immutable authority persisted with a durable launch. It contains facts, not
28
+ * a bearer credential, so a scheduler can mint one short-lived token per leg.
29
+ */
30
+ export type RuntimeAuthorityDescriptor = {
31
+ orgId: string;
32
+ workflowId: string;
33
+ runId: string;
34
+ billingRunId?: string | null;
35
+ playName: string;
36
+ allowedSecrets: string[];
37
+ maxCreditsPerRun?: number | null;
38
+ integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
39
+ synthetic?: boolean | null;
40
+ actorUserId?: string | null;
41
+ actorEmail?: string | null;
42
+ capabilities: RuntimeExecutionCapability[];
43
+ };
44
+
45
+ export const DEFAULT_RUNTIME_EXECUTION_CAPABILITIES = [
46
+ ...RUNTIME_EXECUTION_CAPABILITIES,
47
+ ] as const satisfies readonly RuntimeExecutionCapability[];
48
+
49
+ /** Daytona receives these safe-to-steal capabilities, never raw DB access. */
50
+ export const DAYTONA_RUNTIME_EXECUTION_CAPABILITIES =
51
+ DEFAULT_RUNTIME_EXECUTION_CAPABILITIES.filter(
52
+ (capability) => capability !== 'db.session',
53
+ );
54
+
55
+ export function isRuntimeExecutionCapability(
56
+ value: unknown,
57
+ ): value is RuntimeExecutionCapability {
58
+ return typeof value === 'string' && CAPABILITY_SET.has(value);
59
+ }
60
+
61
+ export function normalizeRuntimeExecutionCapabilities(
62
+ values: readonly unknown[] | null | undefined,
63
+ ): RuntimeExecutionCapability[] {
64
+ if (!Array.isArray(values)) return [];
65
+ return [...new Set(values.filter(isRuntimeExecutionCapability))].sort();
66
+ }
67
+
68
+ export function hasRuntimeExecutionCapability(
69
+ values: readonly string[] | null | undefined,
70
+ capability: RuntimeExecutionCapability,
71
+ ): boolean {
72
+ return Array.isArray(values) && values.includes(capability);
73
+ }
74
+
75
+ export function requireRuntimeAuthorityDescriptor(
76
+ value: unknown,
77
+ ): RuntimeAuthorityDescriptor {
78
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
79
+ throw new Error(
80
+ 'Durable play launch is missing runtimeAuthority. Re-submit the run with the current runtime contract.',
81
+ );
82
+ }
83
+ const descriptor = value as Partial<RuntimeAuthorityDescriptor>;
84
+ const requiredStrings = [
85
+ descriptor.orgId,
86
+ descriptor.workflowId,
87
+ descriptor.runId,
88
+ descriptor.playName,
89
+ ];
90
+ const capabilities = normalizeRuntimeExecutionCapabilities(
91
+ descriptor.capabilities,
92
+ );
93
+ if (
94
+ requiredStrings.some(
95
+ (entry) => typeof entry !== 'string' || entry.trim().length === 0,
96
+ ) ||
97
+ capabilities.length === 0
98
+ ) {
99
+ throw new Error(
100
+ 'Durable play launch contains an invalid runtimeAuthority descriptor.',
101
+ );
102
+ }
103
+ return {
104
+ orgId: descriptor.orgId!,
105
+ workflowId: descriptor.workflowId!,
106
+ runId: descriptor.runId!,
107
+ billingRunId: descriptor.billingRunId ?? null,
108
+ playName: descriptor.playName!,
109
+ allowedSecrets: Array.isArray(descriptor.allowedSecrets)
110
+ ? descriptor.allowedSecrets.filter(
111
+ (entry): entry is string => typeof entry === 'string',
112
+ )
113
+ : [],
114
+ maxCreditsPerRun: descriptor.maxCreditsPerRun ?? null,
115
+ integrationMode: descriptor.integrationMode ?? null,
116
+ synthetic: descriptor.synthetic === true,
117
+ actorUserId: descriptor.actorUserId ?? null,
118
+ actorEmail: descriptor.actorEmail ?? null,
119
+ capabilities,
120
+ };
121
+ }
@@ -53,6 +53,7 @@ type ProviderState = {
53
53
  retryAfterUntilMs: number | null;
54
54
  latencyMsEwma: number | null;
55
55
  fallbackRules: PacingRule[];
56
+ hasSeenBackpressure: boolean;
56
57
  };
57
58
 
58
59
  function finitePositive(value: number, fallback: number): number {
@@ -118,6 +119,7 @@ export function createInMemoryAdaptiveAdmission(
118
119
  retryAfterUntilMs: null,
119
120
  latencyMsEwma: null,
120
121
  fallbackRules: [...input.fallbackRules],
122
+ hasSeenBackpressure: false,
121
123
  };
122
124
  states.set(key, state);
123
125
  }
@@ -178,7 +180,9 @@ export function createInMemoryAdaptiveAdmission(
178
180
  ) {
179
181
  state.currentRequestsPerSecond = Math.min(
180
182
  maxRequestsPerSecond,
181
- state.currentRequestsPerSecond + additiveIncreasePerWindow,
183
+ state.hasSeenBackpressure
184
+ ? state.currentRequestsPerSecond + additiveIncreasePerWindow
185
+ : state.currentRequestsPerSecond * 2,
182
186
  );
183
187
  state.lastIncreaseAtMs = currentTime;
184
188
  state.successCountAtLastIncrease = state.successCount;
@@ -189,6 +193,7 @@ export function createInMemoryAdaptiveAdmission(
189
193
  const state = getExistingState(input.orgId, input.provider);
190
194
  if (!state) return;
191
195
  state.rateLimitCount += 1;
196
+ state.hasSeenBackpressure = true;
192
197
  state.currentRequestsPerSecond = Math.max(
193
198
  minRequestsPerSecond,
194
199
  Math.floor(state.currentRequestsPerSecond / 2),
@@ -0,0 +1,22 @@
1
+ import {
2
+ chargeGovernorBudgetViaAppRuntime,
3
+ type WorkerRuntimeApiContext,
4
+ } from '../app-runtime-api';
5
+ import { BlockReservingBudgetStateBackend } from './block-reserving-budget-state-backend';
6
+
7
+ export class AppRuntimeBudgetStateBackend extends BlockReservingBudgetStateBackend {
8
+ constructor(
9
+ context: WorkerRuntimeApiContext,
10
+ schedulerSchema?: string | null,
11
+ ) {
12
+ super(
13
+ async (input) =>
14
+ await chargeGovernorBudgetViaAppRuntime(context, {
15
+ rootRunId: input.rootRunId,
16
+ reservationId: input.reservationId,
17
+ charges: [...input.charges],
18
+ schedulerSchema: schedulerSchema ?? null,
19
+ }),
20
+ );
21
+ }
22
+ }