deepline 0.3.128 → 0.3.129

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 (128) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +34 -9
  2. package/dist/bundling-sources/sdk/src/http.ts +22 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  17. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  27. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  30. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  31. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  32. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  35. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  42. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  52. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  67. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  83. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  103. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  104. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  109. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  110. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  111. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  112. package/dist/cli/index.js +250 -38
  113. package/dist/cli/index.mjs +250 -38
  114. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  115. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  116. package/dist/index.d.mts +4 -2
  117. package/dist/index.d.ts +4 -2
  118. package/dist/index.js +231 -35
  119. package/dist/index.mjs +231 -35
  120. package/dist/install-integrity.json +31 -2
  121. package/dist/plays/bundle-play-file.d.mts +2 -2
  122. package/dist/plays/bundle-play-file.d.ts +2 -2
  123. package/dist/plays/bundle-play-file.mjs +65 -1
  124. package/dist/release.d.mts +1 -1
  125. package/dist/release.d.ts +1 -1
  126. package/dist/release.js +1 -1
  127. package/dist/release.mjs +1 -1
  128. package/package.json +1 -1
@@ -13,13 +13,31 @@ export const RUNTIME_CAPACITY_POLICY = {
13
13
  /** 32 Machines x 8 claim slots permits 256 concurrent launch/resume legs. */
14
14
  maxActiveLaneMachines: 32,
15
15
  workerSlotsPerMachine: 8,
16
- /** Queue age is an escape hatch for small-but-stuck backlogs. */
17
- scaleUpQueueAgeMs: 10_000,
16
+ /** React within one control interval when a due backlog is not draining. */
17
+ scaleUpQueueAgeMs: 1_000,
18
18
  /** Bound one Fly reconciliation without turning backlog into a stampede. */
19
19
  maxScaleUpMachinesPerReconcile: 4,
20
20
  /** Do not tear down burst capacity on the first empty observation. */
21
21
  scaleDownQuietMs: 120_000,
22
22
  },
23
+ executionRelay: {
24
+ /** Keep two relay Machines available for deploys and one-machine faults. */
25
+ minimumMachines: 2,
26
+ /** Bound relay spend and the blast radius of a bad demand signal. */
27
+ maxMachines: 16,
28
+ /** The local admission gate's per-Machine upstream permit ceiling. */
29
+ upstreamSlotsPerMachine: 192,
30
+ /** Grow before the local queue reaches its hard shed boundary. */
31
+ scaleUpUtilization: 0.75,
32
+ /** Retain a larger safety margin before removing burst capacity. */
33
+ scaleDownUtilization: 0.35,
34
+ /** One observation may add only a small bounded number of Machines. */
35
+ maxScaleUpMachinesPerReconcile: 2,
36
+ /** Reclaim burst capacity gradually after a long quiet window. */
37
+ maxScaleDownMachinesPerReconcile: 2,
38
+ /** Empty relay capacity must be quiet before it is reclaimed. */
39
+ scaleDownQuietMs: 120_000,
40
+ },
23
41
  receiptGateway: {
24
42
  admissionTimeoutMs: 10_000,
25
43
  /** End identical receipt-row work before the caller can overlap a retry. */
@@ -33,6 +51,9 @@ export const RUNTIME_CAPACITY_POLICY = {
33
51
  * once started, all eight execution slots may run concurrently.
34
52
  */
35
53
  daytonaConcurrentStartsPerProcess: 1,
54
+ /** Fleet-wide AIMD start pacing, separate from execution-slot scaling. */
55
+ daytonaInitialStartsPerMinute: 120,
56
+ daytonaMaximumStartsPerMinute: 600,
36
57
  },
37
58
  } as const;
38
59
 
@@ -95,6 +116,55 @@ export function desiredAbsurdLaneMachines(input: {
95
116
  return desired;
96
117
  }
97
118
 
119
+ /**
120
+ * Execution-relay demand is the exact upstream admission state, not request
121
+ * count. Active permits consume capacity and waiting requests are an early
122
+ * backpressure signal. Scale up before the local gate begins shedding, but
123
+ * only by a bounded amount per controller observation. Scale down only after
124
+ * the aggregate relay is empty for the quiet window; the controller applies
125
+ * its own per-pass stop bound when it materializes this target.
126
+ */
127
+ export function desiredExecutionRelayMachines(input: {
128
+ currentMachines?: number;
129
+ inFlight: number;
130
+ waiting: number;
131
+ idleForMs?: number;
132
+ }): number {
133
+ const policy = RUNTIME_CAPACITY_POLICY.executionRelay;
134
+ const currentMachines = Math.max(
135
+ policy.minimumMachines,
136
+ nonNegativeInteger(input.currentMachines ?? policy.minimumMachines),
137
+ );
138
+ const inFlight = nonNegativeInteger(input.inFlight);
139
+ const waiting = nonNegativeInteger(input.waiting);
140
+ const targetSlots =
141
+ policy.upstreamSlotsPerMachine * policy.scaleUpUtilization;
142
+ let desired = Math.max(
143
+ policy.minimumMachines,
144
+ Math.ceil((inFlight + waiting) / targetSlots),
145
+ );
146
+
147
+ // A waiter means at least one Machine is already at its local admission
148
+ // boundary. Aggregate active work may still look below the fleet target
149
+ // when the other Machine(s) are quiet, so give the queue one extra slot.
150
+ if (waiting > 0) desired = Math.max(desired, currentMachines + 1);
151
+
152
+ desired = Math.min(
153
+ desired,
154
+ currentMachines + policy.maxScaleUpMachinesPerReconcile,
155
+ policy.maxMachines,
156
+ );
157
+
158
+ if (
159
+ inFlight === 0 &&
160
+ waiting === 0 &&
161
+ nonNegativeNumber(input.idleForMs ?? 0) < policy.scaleDownQuietMs
162
+ ) {
163
+ desired = Math.max(desired, currentMachines);
164
+ }
165
+ return desired;
166
+ }
167
+
98
168
  function nonNegativeInteger(value: number): number {
99
169
  return Math.max(0, Math.floor(nonNegativeNumber(value)));
100
170
  }
@@ -116,6 +186,19 @@ export function assertRuntimeCapacityPolicy(): void {
116
186
  'Daytona sandbox-start concurrency must be a positive integer.',
117
187
  );
118
188
  }
189
+ const sandboxAcquisition = RUNTIME_CAPACITY_POLICY.sandboxAcquisition;
190
+ if (
191
+ !Number.isInteger(sandboxAcquisition.daytonaInitialStartsPerMinute) ||
192
+ !Number.isInteger(sandboxAcquisition.daytonaMaximumStartsPerMinute) ||
193
+ sandboxAcquisition.daytonaInitialStartsPerMinute <= 0 ||
194
+ sandboxAcquisition.daytonaInitialStartsPerMinute >
195
+ sandboxAcquisition.daytonaMaximumStartsPerMinute ||
196
+ sandboxAcquisition.daytonaMaximumStartsPerMinute > 600
197
+ ) {
198
+ throw new Error(
199
+ 'Daytona sandbox-start AIMD rates must be positive integers with initial <= maximum <= 600/minute.',
200
+ );
201
+ }
119
202
  if (
120
203
  RUNTIME_CAPACITY_POLICY.receiptGateway.requestTimeoutMs <=
121
204
  RUNTIME_CAPACITY_POLICY.receiptGateway.admissionTimeoutMs
@@ -140,6 +223,19 @@ export function assertRuntimeCapacityPolicy(): void {
140
223
  'Active-lane minimum Machines cannot exceed the active-lane maximum.',
141
224
  );
142
225
  }
226
+ const relay = RUNTIME_CAPACITY_POLICY.executionRelay;
227
+ if (
228
+ relay.minimumMachines > relay.maxMachines ||
229
+ relay.upstreamSlotsPerMachine <= 0 ||
230
+ relay.scaleUpUtilization <= 0 ||
231
+ relay.scaleUpUtilization > 1 ||
232
+ relay.scaleDownUtilization <= 0 ||
233
+ relay.scaleDownUtilization > relay.scaleUpUtilization
234
+ ) {
235
+ throw new Error(
236
+ 'Execution-relay capacity policy has invalid floor, ceiling, or utilization bounds.',
237
+ );
238
+ }
143
239
  }
144
240
 
145
241
  assertRuntimeCapacityPolicy();
@@ -58,10 +58,11 @@ export function resolveLegacyRuntimeReceiptWireFormat(
58
58
  }
59
59
 
60
60
  export function runtimeReceiptCompletionResponse<
61
- T extends { output?: unknown },
61
+ T extends { output?: unknown; outputRef?: unknown },
62
62
  >(wireFormat: LegacyRuntimeReceiptWireFormat, receipt: T | null): T | null {
63
63
  if (!receipt || wireFormat < 3) return receipt;
64
- const { output, ...ack } = receipt;
64
+ const { output, outputRef, ...ack } = receipt;
65
65
  void output;
66
+ void outputRef;
66
67
  return ack as T;
67
68
  }
@@ -10,9 +10,13 @@ export type PlayRuntimeSelection = {
10
10
  namespace: string;
11
11
  /** Explicit managed-sandbox executor. Omission preserves Daytona compatibility. */
12
12
  backend?: Extract<PlayRuntimeBackendId, 'daytona' | 'modal'>;
13
+ /** Durable placement contract for the selected Preview worker lane. */
14
+ sandboxPlacementPolicy?: string;
13
15
  };
14
16
 
15
17
  const PLAY_RUNTIME_NAMESPACE_PATTERN = /^[a-z][a-z0-9-]{0,30}$/;
18
+ const PLAY_RUNTIME_SANDBOX_PLACEMENT_POLICY_PATTERN =
19
+ /^[a-z][a-z0-9_]*@[1-9][0-9]*$/;
16
20
 
17
21
  export function normalizePlayRuntimeNamespace(value: unknown): string | null {
18
22
  if (typeof value !== 'string') return null;
@@ -20,6 +24,21 @@ export function normalizePlayRuntimeNamespace(value: unknown): string | null {
20
24
  return PLAY_RUNTIME_NAMESPACE_PATTERN.test(normalized) ? normalized : null;
21
25
  }
22
26
 
27
+ /**
28
+ * Policy membership is owned by the server-side policy catalogue. The shared
29
+ * selector only accepts a stable versioned id shape, so unknown policies fail
30
+ * loudly at the authority that knows which worker releases support them.
31
+ */
32
+ export function normalizePlayRuntimeSandboxPlacementPolicy(
33
+ value: unknown,
34
+ ): string | null {
35
+ if (typeof value !== 'string') return null;
36
+ const normalized = value.trim();
37
+ return PLAY_RUNTIME_SANDBOX_PLACEMENT_POLICY_PATTERN.test(normalized)
38
+ ? normalized
39
+ : null;
40
+ }
41
+
23
42
  /** Validate the selector as one value so the SDK and server cannot drift. */
24
43
  export function normalizePlayRuntimeSelection(
25
44
  value: unknown,
@@ -29,7 +48,10 @@ export function normalizePlayRuntimeSelection(
29
48
  if (
30
49
  Object.keys(record).some(
31
50
  (key) =>
32
- key !== 'environment' && key !== 'namespace' && key !== 'backend',
51
+ key !== 'environment' &&
52
+ key !== 'namespace' &&
53
+ key !== 'backend' &&
54
+ key !== 'sandboxPlacementPolicy',
33
55
  ) ||
34
56
  record.environment !== 'preview'
35
57
  ) {
@@ -37,16 +59,31 @@ export function normalizePlayRuntimeSelection(
37
59
  }
38
60
  const namespace = normalizePlayRuntimeNamespace(record.namespace);
39
61
  if (!namespace) return null;
40
- if (record.backend === undefined) {
41
- return { environment: 'preview', namespace };
62
+ const sandboxPlacementPolicy =
63
+ record.sandboxPlacementPolicy === undefined
64
+ ? undefined
65
+ : normalizePlayRuntimeSandboxPlacementPolicy(
66
+ record.sandboxPlacementPolicy,
67
+ );
68
+ if (
69
+ record.sandboxPlacementPolicy !== undefined &&
70
+ sandboxPlacementPolicy === null
71
+ ) {
72
+ return null;
42
73
  }
43
74
  if (
75
+ record.backend !== undefined &&
44
76
  record.backend !== PLAY_RUNTIME_BACKENDS.daytona &&
45
77
  record.backend !== PLAY_RUNTIME_BACKENDS.modal
46
78
  ) {
47
79
  return null;
48
80
  }
49
- return { environment: 'preview', namespace, backend: record.backend };
81
+ return {
82
+ environment: 'preview',
83
+ namespace,
84
+ ...(record.backend === undefined ? {} : { backend: record.backend }),
85
+ ...(sandboxPlacementPolicy ? { sandboxPlacementPolicy } : {}),
86
+ };
50
87
  }
51
88
 
52
89
  export function normalizePlayRuntimeEnvironment(
@@ -0,0 +1,448 @@
1
+ /**
2
+ * Private runtime admission composition.
3
+ *
4
+ * A flow is deliberately smaller than a queue: it owns no payloads, retries,
5
+ * workers, or durable state. It acquires the capacity already owned by its
6
+ * stages, and releases every acquired stage in reverse order. That gives a
7
+ * map row, provider dispatch, or result handoff one idiomatic permit without
8
+ * inventing a second runtime scheduler.
9
+ */
10
+
11
+ export type RuntimeFlowLease = {
12
+ release(): void;
13
+ };
14
+
15
+ export type RuntimeFlowStage<Input> = {
16
+ /** Stable diagnostic name. It never changes execution behavior. */
17
+ name: string;
18
+ acquire(input: Input): Promise<RuntimeFlowLease>;
19
+ };
20
+
21
+ export type RuntimeFlow<Input> = {
22
+ /**
23
+ * Admit and run one logical unit through every capacity boundary in order.
24
+ *
25
+ * A later boundary rejecting returns earlier permits immediately. The permits
26
+ * stay private to the callback and return in reverse order on every exit.
27
+ */
28
+ run<Output>(
29
+ input: Input,
30
+ work: () => Output | Promise<Output>,
31
+ ): Promise<Output>;
32
+ };
33
+
34
+ export function createRuntimeFlow<Input>(input: {
35
+ stages: readonly RuntimeFlowStage<Input>[];
36
+ }): RuntimeFlow<Input> {
37
+ const stages = [...input.stages];
38
+ if (stages.length === 0) {
39
+ throw new Error('Runtime flow requires at least one admission stage.');
40
+ }
41
+ const names = new Set<string>();
42
+ for (const stage of stages) {
43
+ const name = stage.name.trim();
44
+ if (!name) throw new Error('Runtime flow stages require a name.');
45
+ if (names.has(name)) {
46
+ throw new Error(`Runtime flow stage "${name}" was registered twice.`);
47
+ }
48
+ names.add(name);
49
+ }
50
+
51
+ const admit = async (value: Input): Promise<RuntimeFlowLease[]> => {
52
+ const leases: RuntimeFlowLease[] = [];
53
+ try {
54
+ for (const stage of stages) {
55
+ leases.push(await stage.acquire(value));
56
+ }
57
+ } catch (error) {
58
+ releaseRuntimeFlowLeases(leases);
59
+ throw error;
60
+ }
61
+
62
+ return leases;
63
+ };
64
+
65
+ return {
66
+ async run<Output>(
67
+ value: Input,
68
+ work: () => Output | Promise<Output>,
69
+ ): Promise<Output> {
70
+ const leases = await admit(value);
71
+ try {
72
+ return await work();
73
+ } finally {
74
+ releaseRuntimeFlowLeases(leases);
75
+ }
76
+ },
77
+ };
78
+ }
79
+
80
+ /**
81
+ * A provider permit has a distinct pre-dispatch cancellation path. `park`,
82
+ * when present, returns a due permit that lost the local execution-slot race
83
+ * to its source for immediate local re-draw instead of refunding it.
84
+ */
85
+ export type RuntimeDispatchProviderPermit = RuntimeFlowLease & {
86
+ cancel(): Promise<void>;
87
+ park?(): Promise<void>;
88
+ };
89
+
90
+ export type RuntimeDispatchFlowInput = {
91
+ providerResourceKey?: string | null;
92
+ signal?: AbortSignal;
93
+ /** Fixtures consume provider capacity but acquire app transport after delay. */
94
+ requiresTransportPermit: boolean;
95
+ /** Emits the request-side transport wait observed by the caller's metrics. */
96
+ onTransportAdmitted?: (waitMs: number) => void;
97
+ };
98
+
99
+ type RuntimeDispatchPermit<
100
+ ProviderPermit extends RuntimeDispatchProviderPermit,
101
+ > = {
102
+ providerPermit: ProviderPermit;
103
+ executionSlot: RuntimeFlowLease;
104
+ integrationRequestLease: RuntimeFlowLease | null;
105
+ };
106
+
107
+ /**
108
+ * The only capability exposed while a provider-dispatch flow is admitted.
109
+ *
110
+ * `request` brackets the physical HTTP exchange, including consuming its
111
+ * body. It never exposes the provider, execution, or transport leases, so a
112
+ * caller cannot accidentally retain capacity after that exchange or return a
113
+ * provider permit before it knows whether the request began.
114
+ */
115
+ export type RuntimeDispatchScope = {
116
+ /**
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.
120
+ */
121
+ request<Output>(
122
+ work: () => Output | Promise<Output>,
123
+ beforeRelease?: () => void | Promise<void>,
124
+ ): Promise<Output>;
125
+ };
126
+
127
+ export type RuntimeDispatchFlow<
128
+ ProviderPermit extends RuntimeDispatchProviderPermit,
129
+ > = {
130
+ /**
131
+ * Admit and run exactly one physical provider request.
132
+ *
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.
137
+ */
138
+ run<Output>(
139
+ input: RuntimeDispatchFlowInput,
140
+ work: (scope: RuntimeDispatchScope) => Output | Promise<Output>,
141
+ ): Promise<Output>;
142
+ };
143
+
144
+ /**
145
+ * Compose provider, sandbox-execution, and app-transport capacity at the
146
+ * dispatch edge. A provider permit is only useful when the local execution
147
+ * slot (and, for a live call, app transport) is immediately available. If a
148
+ * later stage is full, this flow returns every acquired permit and observes
149
+ * that stage's next availability. An execution slot won that way is kept
150
+ * only if a provider permit is already due locally (`tryAcquireProviderPermit`);
151
+ * otherwise the flow releases it and admits from the provider boundary
152
+ * again. No execution slot is ever held across a blocking provider acquire.
153
+ *
154
+ * It is not a queue and retains no tool input or result. The caller receives
155
+ * only a scoped request callback; Flow releases or cancels every lease based
156
+ * on whether that callback actually began.
157
+ */
158
+ export function createRuntimeDispatchFlow<
159
+ ProviderPermit extends RuntimeDispatchProviderPermit,
160
+ >(input: {
161
+ acquireProviderPermit: (input: {
162
+ signal?: AbortSignal;
163
+ }) => Promise<ProviderPermit>;
164
+ /** Synchronous local-only admission; must never block or go remote. */
165
+ tryAcquireProviderPermit?: () => ProviderPermit | null;
166
+ tryAcquireExecutionSlot: (input: {
167
+ providerResourceKey?: string | null;
168
+ }) => RuntimeFlowLease | null;
169
+ waitForExecutionSlot: (input: {
170
+ providerResourceKey?: string | null;
171
+ signal?: AbortSignal;
172
+ }) => Promise<RuntimeFlowLease>;
173
+ tryAcquireTransportSlot: () => RuntimeFlowLease | null;
174
+ waitForTransportSlot: (input: {
175
+ signal?: AbortSignal;
176
+ }) => Promise<RuntimeFlowLease>;
177
+ }): RuntimeDispatchFlow<ProviderPermit> {
178
+ const admit = async (
179
+ admission: RuntimeDispatchFlowInput,
180
+ ): Promise<RuntimeDispatchPermit<ProviderPermit>> => {
181
+ while (true) {
182
+ let providerPermit: ProviderPermit | null = null;
183
+ let executionSlot: RuntimeFlowLease | null = null;
184
+ const releaseExecutionSlot = (): void => {
185
+ const slot = executionSlot;
186
+ executionSlot = null;
187
+ slot?.release();
188
+ };
189
+ const cancelProviderPermit = async (): Promise<void> => {
190
+ const permit = providerPermit;
191
+ providerPermit = null;
192
+ await permit?.cancel();
193
+ };
194
+ // The execution-slot wait is brief and the permit is already due, so
195
+ // keep it local for the retry instead of refunding it to the store.
196
+ const parkProviderPermit = async (): Promise<void> => {
197
+ const permit = providerPermit;
198
+ providerPermit = null;
199
+ if (!permit) return;
200
+ if (permit.park) await permit.park();
201
+ else await permit.cancel();
202
+ };
203
+ try {
204
+ providerPermit = await input.acquireProviderPermit({
205
+ signal: admission.signal,
206
+ });
207
+ executionSlot = input.tryAcquireExecutionSlot({
208
+ providerResourceKey: admission.providerResourceKey,
209
+ });
210
+ if (!executionSlot) {
211
+ await parkProviderPermit();
212
+ executionSlot = await input.waitForExecutionSlot({
213
+ providerResourceKey: admission.providerResourceKey,
214
+ signal: admission.signal,
215
+ });
216
+ // Releasing this slot would hand it to the next FIFO waiter, so
217
+ // keep it if the parked (or any due) permit can be re-drawn without
218
+ // blocking. Otherwise the blocking provider acquire must not run
219
+ // while a slot is held.
220
+ providerPermit = input.tryAcquireProviderPermit
221
+ ? input.tryAcquireProviderPermit()
222
+ : null;
223
+ if (!providerPermit) {
224
+ releaseExecutionSlot();
225
+ continue;
226
+ }
227
+ }
228
+
229
+ if (!admission.requiresTransportPermit) {
230
+ return {
231
+ providerPermit,
232
+ executionSlot,
233
+ integrationRequestLease: null,
234
+ };
235
+ }
236
+
237
+ const integrationRequestLease = input.tryAcquireTransportSlot();
238
+ if (integrationRequestLease) {
239
+ return {
240
+ providerPermit,
241
+ executionSlot,
242
+ integrationRequestLease,
243
+ };
244
+ }
245
+
246
+ releaseExecutionSlot();
247
+ await cancelProviderPermit();
248
+ const availability = await input.waitForTransportSlot({
249
+ signal: admission.signal,
250
+ });
251
+ availability.release();
252
+ } catch (error) {
253
+ await cleanupRuntimeDispatchAdmissionFailure({
254
+ cause: error,
255
+ executionSlot,
256
+ providerPermit,
257
+ });
258
+ }
259
+ }
260
+ };
261
+
262
+ return {
263
+ async run<Output>(
264
+ admission: RuntimeDispatchFlowInput,
265
+ work: (scope: RuntimeDispatchScope) => Output | Promise<Output>,
266
+ ): Promise<Output> {
267
+ const permit = await admit(admission);
268
+ let transportLease = permit.integrationRequestLease;
269
+ let requestStarted = false;
270
+ let requestUsed = false;
271
+ let scopeOpen = true;
272
+ let requestPromise: Promise<unknown> | null = null;
273
+ let permitReleased = false;
274
+ const releasePermit = async (): Promise<void> => {
275
+ if (permitReleased) return;
276
+ permitReleased = true;
277
+ await releaseRuntimeDispatchPermit({
278
+ transportLease,
279
+ executionSlot: permit.executionSlot,
280
+ providerPermit: permit.providerPermit,
281
+ requestStarted,
282
+ });
283
+ };
284
+ try {
285
+ const output = await work({
286
+ request: <RequestOutput>(
287
+ request: () => RequestOutput | Promise<RequestOutput>,
288
+ beforeRelease?: () => void | Promise<void>,
289
+ ): Promise<RequestOutput> => {
290
+ if (!scopeOpen) {
291
+ return Promise.reject(
292
+ new Error('Runtime dispatch flow scope has already closed.'),
293
+ );
294
+ }
295
+ if (requestUsed) {
296
+ return Promise.reject(
297
+ new Error(
298
+ 'Runtime dispatch flow permits exactly one physical request.',
299
+ ),
300
+ );
301
+ }
302
+ requestUsed = true;
303
+ requestPromise = (async () => {
304
+ try {
305
+ if (transportLease) {
306
+ admission.onTransportAdmitted?.(0);
307
+ } else {
308
+ const transportStartedAt = Date.now();
309
+ transportLease = await input.waitForTransportSlot({
310
+ signal: admission.signal,
311
+ });
312
+ admission.onTransportAdmitted?.(
313
+ Date.now() - transportStartedAt,
314
+ );
315
+ }
316
+ requestStarted = true;
317
+ return await request();
318
+ } finally {
319
+ // Feedback is response-bound, but it is not allowed to strand
320
+ // the capacity that made the response possible. Preserve the
321
+ // ordering (feedback while the provider lease is valid) and
322
+ // always release all leases even when feedback persistence
323
+ // fails.
324
+ try {
325
+ if (requestStarted) await beforeRelease?.();
326
+ } finally {
327
+ await releasePermit();
328
+ }
329
+ }
330
+ })();
331
+ return requestPromise as Promise<RequestOutput>;
332
+ },
333
+ });
334
+ // A caller normally returns `await request(...)`. Waiting here also
335
+ // makes an accidental fire-and-forget request drain before its scope
336
+ // can close and prevents an early release of its capacity.
337
+ await requestPromise;
338
+ return output;
339
+ } finally {
340
+ scopeOpen = false;
341
+ // `work` may throw after it deliberately started a fire-and-forget
342
+ // request. That request has already claimed this permit's ownership,
343
+ // even when it is still waiting to acquire transport. Drain it before
344
+ // the outer cleanup: releasing first would make the request's own
345
+ // finally a no-op and leak a transport lease it acquires afterwards.
346
+ // Its rejection cannot replace the original work failure, but its
347
+ // cleanup must finish before any permit is returned.
348
+ if (requestPromise) {
349
+ try {
350
+ await requestPromise;
351
+ } catch {
352
+ // The normal path above already surfaces request failures. This
353
+ // path only preserves an earlier failure from `work`.
354
+ }
355
+ }
356
+ await releasePermit();
357
+ }
358
+ },
359
+ };
360
+ }
361
+
362
+ async function cleanupRuntimeDispatchAdmissionFailure<
363
+ ProviderPermit extends RuntimeDispatchProviderPermit,
364
+ >(input: {
365
+ cause: unknown;
366
+ executionSlot: RuntimeFlowLease | null;
367
+ providerPermit: ProviderPermit | null;
368
+ }): Promise<never> {
369
+ const failures = [input.cause];
370
+ try {
371
+ input.executionSlot?.release();
372
+ } catch (error) {
373
+ failures.push(error);
374
+ }
375
+ try {
376
+ await input.providerPermit?.cancel();
377
+ } catch (error) {
378
+ failures.push(error);
379
+ }
380
+ if (failures.length === 1) {
381
+ throw input.cause;
382
+ }
383
+ throw new AggregateError(
384
+ failures,
385
+ 'Runtime dispatch admission and cleanup both failed.',
386
+ );
387
+ }
388
+
389
+ function releaseRuntimeFlowLeases(leases: readonly RuntimeFlowLease[]): void {
390
+ let failure: unknown = null;
391
+ let hasFailure = false;
392
+ for (let index = leases.length - 1; index >= 0; index -= 1) {
393
+ try {
394
+ leases[index]?.release();
395
+ } catch (error) {
396
+ if (!hasFailure) {
397
+ failure = error;
398
+ hasFailure = true;
399
+ }
400
+ }
401
+ }
402
+ if (hasFailure) {
403
+ throw failure;
404
+ }
405
+ }
406
+
407
+ async function releaseRuntimeDispatchPermit<
408
+ ProviderPermit extends RuntimeDispatchProviderPermit,
409
+ >(input: {
410
+ transportLease: RuntimeFlowLease | null;
411
+ executionSlot: RuntimeFlowLease;
412
+ providerPermit: ProviderPermit;
413
+ requestStarted: boolean;
414
+ }): Promise<void> {
415
+ let failure: unknown = null;
416
+ let hasFailure = false;
417
+ const release = (work: () => void): void => {
418
+ try {
419
+ work();
420
+ } catch (error) {
421
+ if (!hasFailure) {
422
+ failure = error;
423
+ hasFailure = true;
424
+ }
425
+ }
426
+ };
427
+ const releaseAsync = async (work: () => Promise<void>): Promise<void> => {
428
+ try {
429
+ await work();
430
+ } catch (error) {
431
+ if (!hasFailure) {
432
+ failure = error;
433
+ hasFailure = true;
434
+ }
435
+ }
436
+ };
437
+
438
+ release(() => input.transportLease?.release());
439
+ release(() => input.executionSlot.release());
440
+ if (input.requestStarted) {
441
+ release(() => input.providerPermit.release());
442
+ } else {
443
+ await releaseAsync(async () => await input.providerPermit.cancel());
444
+ }
445
+ if (hasFailure) {
446
+ throw failure;
447
+ }
448
+ }