deepline 0.1.201 → 0.1.203

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 (33) 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 +154 -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 +16 -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-paths.ts +2 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +225 -29
  25. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +26 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +21 -0
  27. package/dist/bundling-sources/shared_libs/plays/dataset.ts +32 -10
  28. package/dist/cli/index.js +335 -77
  29. package/dist/cli/index.mjs +341 -83
  30. package/dist/index.js +3 -2
  31. package/dist/index.mjs +3 -2
  32. package/package.json +1 -1
  33. 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
  );
@@ -3961,6 +4006,7 @@ export class PlayContextImpl {
3961
4006
  {
3962
4007
  baseUrl: this.#options.baseUrl!,
3963
4008
  executorToken: this.#options.executorToken!,
4009
+ dbSessionStrategy: this.#options.dbSessionStrategy,
3964
4010
  playName: this.#options.playName!,
3965
4011
  userEmail: this.#options.userEmail,
3966
4012
  runId: this.#options.runId!,
@@ -4484,6 +4530,7 @@ export class PlayContextImpl {
4484
4530
  {
4485
4531
  baseUrl: this.#options.baseUrl!,
4486
4532
  executorToken: this.#options.executorToken!,
4533
+ dbSessionStrategy: this.#options.dbSessionStrategy,
4487
4534
  playName: this.#options.playName!,
4488
4535
  userEmail: this.#options.userEmail,
4489
4536
  runId: this.#options.runId!,
@@ -5960,6 +6007,11 @@ export class PlayContextImpl {
5960
6007
  authScopeAttempt < 2;
5961
6008
  authScopeAttempt += 1
5962
6009
  ) {
6010
+ // Capture the lease the receipt store hands to `execute` for THIS
6011
+ // attempt's key. On an auth-scope re-claim we mark that old-scope receipt
6012
+ // `skipped` (below), and the skip is fenced on this run+attempt+lease so
6013
+ // it can only supersede the receipt this attempt owns.
6014
+ let claimedReceiptLeaseId: string | null = null;
5963
6015
  try {
5964
6016
  return await this.executeWithRuntimeReceipt(
5965
6017
  'tool',
@@ -5991,7 +6043,10 @@ export class PlayContextImpl {
5991
6043
  shouldPersistFailure: (error) =>
5992
6044
  !(error instanceof ToolExecuteAuthScopeChangedError),
5993
6045
  markRunningBeforeExecute: false,
5994
- execute: ({ leaseId }) => executeTool({ leaseId }),
6046
+ execute: ({ leaseId }) => {
6047
+ claimedReceiptLeaseId = leaseId;
6048
+ return executeTool({ leaseId });
6049
+ },
5995
6050
  runningReceiptWaitMaxAttempts:
5996
6051
  resolveRuntimeToolReceiptWaitMaxAttempts(
5997
6052
  typeof options?.receiptWaitMs === 'number'
@@ -6007,6 +6062,36 @@ export class PlayContextImpl {
6007
6062
  ) {
6008
6063
  throw error;
6009
6064
  }
6065
+ // Supersede the old-scope receipt so the run's node projection does not
6066
+ // surface it as `failed`. shouldPersistFailure kept it out of the failed
6067
+ // state, but the claim left it `running` with a now-orphaned lease under
6068
+ // the OLD cache key; without an explicit terminal it projects as a failed
6069
+ // node even though the work is about to be re-done under the new scope.
6070
+ // `skipped` is the terminal for superseded work; the skip is fenced on
6071
+ // this run+attempt+lease so it can only touch the receipt this attempt
6072
+ // owns. Best-effort: a skip that cannot fence (store returns null) must
6073
+ // not mask the reclaim itself.
6074
+ const supersededReceiptKey = durableCacheKey;
6075
+ try {
6076
+ const skipped = await this.skipRuntimeStepReceipt(
6077
+ supersededReceiptKey,
6078
+ this.currentRunId,
6079
+ claimedReceiptLeaseId,
6080
+ );
6081
+ if (skipped) {
6082
+ this.log(
6083
+ `ctx.tools.execute(${toolId}): auth_scope_changed_superseded ` +
6084
+ `old-scope receipt ${toolRequestIdentity} (label: ${normalizedKey}) ` +
6085
+ `marked skipped so the reclaim does not project as a failed node.`,
6086
+ );
6087
+ }
6088
+ } catch (skipError) {
6089
+ this.log(
6090
+ `ctx.tools.execute(${toolId}): auth_scope_changed_supersede_skip_failed ` +
6091
+ `(label: ${normalizedKey}); could not mark the old-scope receipt ` +
6092
+ `skipped: ${this.formatRuntimeError(skipError)}`,
6093
+ );
6094
+ }
6010
6095
  // Make the bounded auth-scope re-claim observable in run logs instead
6011
6096
  // of a silent continuation: the credential identity changed after the
6012
6097
  // receipt key was prepared, so we evict the cached digest, re-resolve,
@@ -6213,7 +6298,7 @@ export class PlayContextImpl {
6213
6298
  input,
6214
6299
  graphHash: null,
6215
6300
  });
6216
- const childGovernance = this.governor.forkChild({
6301
+ const childGovernance = await this.governor.forkChild({
6217
6302
  childPlayName: resolvedName,
6218
6303
  childRunId,
6219
6304
  });
@@ -6338,6 +6423,12 @@ export class PlayContextImpl {
6338
6423
  this.consumePendingChildPlaySuspension() ?? suspension,
6339
6424
  );
6340
6425
  }
6426
+ await this.registerInlineChildRun({
6427
+ parentRunId: this.currentRunId,
6428
+ childRunId: childGovernance.currentRunId,
6429
+ childPlayName: resolvedName,
6430
+ staticPipeline: resolvedPlay.staticPipeline ?? null,
6431
+ });
6341
6432
  const childContext = createPlayContext({
6342
6433
  ...this.#options,
6343
6434
  playId: resolvedName,
@@ -6496,6 +6587,40 @@ export class PlayContextImpl {
6496
6587
  return body.executorToken.trim();
6497
6588
  }
6498
6589
 
6590
+ private async registerInlineChildRun(input: {
6591
+ parentRunId: string;
6592
+ childRunId: string;
6593
+ childPlayName: string;
6594
+ staticPipeline: unknown;
6595
+ }): Promise<void> {
6596
+ const baseUrl = this.#options.baseUrl?.trim();
6597
+ const executorToken = this.#options.executorToken?.trim();
6598
+ if (!baseUrl || !executorToken) return;
6599
+
6600
+ await startRunViaAppRuntime(
6601
+ {
6602
+ baseUrl,
6603
+ executorToken,
6604
+ integrationMode: this.#options.integrationMode ?? null,
6605
+ vercelProtectionBypassToken:
6606
+ this.#options.vercelProtectionBypassToken ?? null,
6607
+ },
6608
+ {
6609
+ playName: input.childPlayName,
6610
+ runId: input.childRunId,
6611
+ parentRunId: input.parentRunId,
6612
+ rootRunId: this.governance.rootRunId,
6613
+ workflowFamilyKey: this.governance.rootRunId,
6614
+ artifactHash: this.#options.artifactHash ?? null,
6615
+ graphHash: this.#options.graphHash ?? null,
6616
+ schedulerSchema: this.#options.runtimeSchedulerSchema ?? null,
6617
+ executionProfile: this.#options.childRunProfile ?? 'absurd',
6618
+ staticPipeline: input.staticPipeline,
6619
+ source: 'published',
6620
+ },
6621
+ );
6622
+ }
6623
+
6499
6624
  /**
6500
6625
  * Extract a list from a tool result.
6501
6626
  * e.g. ctx.extractList(result, 'people', ['first_name', 'last_name', 'email'])
@@ -7596,22 +7721,6 @@ export class PlayContextImpl {
7596
7721
  },
7597
7722
  });
7598
7723
  } 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
7724
  const completionBuffer: Array<{
7616
7725
  request: ToolCallRequest;
7617
7726
  result: unknown | null;
@@ -7675,8 +7784,29 @@ export class PlayContextImpl {
7675
7784
  void flushCompletionBuffer();
7676
7785
  }, 0);
7677
7786
  });
7678
- const dispatchResults = await dispatcher.dispatch(
7787
+ // Seed the dispatch width from the governor's provider-shaped
7788
+ // parallelism instead of the flat policy.concurrency.toolCalls
7789
+ // ceiling. The flat width launched every pending row at once into
7790
+ // the pacer, so unhinted providers 429'd on the first burst before
7791
+ // AIMD could halve the rate. The shaped estimate is derived from the
7792
+ // provider's RPS/maxConcurrency pacing rules (see
7793
+ // suggestedParallelism in governor.ts), floored at 1 and capped by
7794
+ // the global tool-call concurrency ceiling. The pacer still gates
7795
+ // each in-flight call, so this only trims the launch burst.
7796
+ const toolCallConcurrencyCeiling =
7797
+ this.governor.policy.concurrency.toolCalls;
7798
+ const shapedToolParallelism =
7799
+ await this.resourceGovernor.suggestedToolParallelism(
7800
+ toolId,
7801
+ toolCallConcurrencyCeiling,
7802
+ );
7803
+ const dispatchWidth = Math.min(
7804
+ toolCallConcurrencyCeiling,
7805
+ Math.max(1, shapedToolParallelism),
7806
+ );
7807
+ const dispatchResults = await dispatchBoundedSettled(
7679
7808
  pendingRequests,
7809
+ dispatchWidth,
7680
7810
  async (request) => {
7681
7811
  // Circuit breaker: do not dispatch this provider call once a
7682
7812
  // persistence failure has occurred in this run.
@@ -8021,6 +8151,7 @@ export class PlayContextImpl {
8021
8151
  }),
8022
8152
  });
8023
8153
  this.resourceGovernor.observe({
8154
+ toolId,
8024
8155
  providerResourceKey: `tool:${toolId}`,
8025
8156
  providerLatencyMs: Date.now() - providerCallStartedAt,
8026
8157
  providerSuccess: response.ok,
@@ -8044,7 +8175,7 @@ export class PlayContextImpl {
8044
8175
  const message =
8045
8176
  error instanceof Error ? error.message : String(error);
8046
8177
  if (transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS) {
8047
- this.governor.chargeBudget('retry');
8178
+ await this.governor.chargeBudget('retry');
8048
8179
  const retryAfterMs =
8049
8180
  TOOL_RETRY_AFTER_FALLBACK_MS * transportAttempt;
8050
8181
  span.setAttribute(
@@ -8102,7 +8233,7 @@ export class PlayContextImpl {
8102
8233
  }
8103
8234
  if (failure.shouldRetry) {
8104
8235
  if (failure.chargeRetryBudget) {
8105
- this.governor.chargeBudget('retry');
8236
+ await this.governor.chargeBudget('retry');
8106
8237
  }
8107
8238
  const retryAttributePrefix = failure.isRateLimit
8108
8239
  ? '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';
@@ -472,6 +473,11 @@ export interface ContextOptions {
472
473
  /** Short-lived HMAC-signed internal token for tool callbacks. Required for cloud execution. */
473
474
  executorToken?: string;
474
475
  baseUrl?: string;
476
+ /**
477
+ * Runtime-sheet transport selected by the runner. Daytona sandboxes use the
478
+ * execution gateway and must never fall back to minting direct DB sessions.
479
+ */
480
+ dbSessionStrategy?: 'preloaded' | 'sandbox_public_key' | 'gateway_only';
475
481
  vercelProtectionBypassToken?: string | null;
476
482
  /** Optional per-run integration execution mode for provider calls. */
477
483
  integrationMode?: 'live' | 'eval_stub' | 'fixture';
@@ -489,6 +495,13 @@ export interface ContextOptions {
489
495
  * to the `absurd` profile.
490
496
  */
491
497
  childRunProfile?: string | null;
498
+ /**
499
+ * The parent worker's own absurd release lane id. Threaded onto child
500
+ * `ctx.runPlay` launches via the `x-deepline-absurd-release` header so the
501
+ * child pins to the parent's lane (defense-in-depth). Null off the absurd
502
+ * path or on a pre-release parent.
503
+ */
504
+ absurdReleaseId?: string | null;
492
505
  staticPipeline?: PlayStaticPipeline | null;
493
506
  workflowId?: string;
494
507
  sessionId?: string;
@@ -561,6 +574,9 @@ export interface ContextOptions {
561
574
  * contexts omit this and use the in-process backend.
562
575
  */
563
576
  rateState?: RateStateBackend;
577
+ /** Shared root-run budget reservations. Production schedulers must provide
578
+ * the same durable backend to every child sandbox. */
579
+ budgetState?: BudgetStateBackend;
564
580
  /**
565
581
  * Loud guard for production Node substrates: when true, constructing a
566
582
  * 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
+ }