deepline 0.3.128 → 0.3.130

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 (130) 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/daytona-sandbox-janitor.ts +4 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  18. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  27. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  31. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  32. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  36. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  43. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  55. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  67. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +114 -4
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  83. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +28 -8
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +146 -15
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +904 -77
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  103. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  104. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  105. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  109. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  110. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  111. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  112. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  113. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  114. package/dist/cli/index.js +250 -38
  115. package/dist/cli/index.mjs +250 -38
  116. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  117. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  118. package/dist/index.d.mts +4 -2
  119. package/dist/index.d.ts +4 -2
  120. package/dist/index.js +231 -35
  121. package/dist/index.mjs +231 -35
  122. package/dist/install-integrity.json +31 -2
  123. package/dist/plays/bundle-play-file.d.mts +2 -2
  124. package/dist/plays/bundle-play-file.d.ts +2 -2
  125. package/dist/plays/bundle-play-file.mjs +65 -1
  126. package/dist/release.d.mts +1 -1
  127. package/dist/release.d.ts +1 -1
  128. package/dist/release.js +1 -1
  129. package/dist/release.mjs +1 -1
  130. package/package.json +1 -1
@@ -33,6 +33,7 @@ import {
33
33
  PLAY_ADMISSION_MAX_ACQUIRE_QUEUE_WAIT_MS,
34
34
  } from '../admission-transport-policy';
35
35
  import { RUNTIME_RELIABILITY_POLICY } from '../runtime-reliability-policy';
36
+ import { RUNTIME_CAPACITY_POLICY } from '../runtime-capacity-policy';
36
37
  export {
37
38
  isRuntimeSchedulerCapacityError,
38
39
  parseRuntimeSchedulerCapacityError,
@@ -126,8 +127,8 @@ export function assertRuntimeSchedulerHotPathUrl(url: string): void {
126
127
  }
127
128
 
128
129
  export const RUNTIME_SCHEDULER_POOL_DEFAULTS = {
129
- /** Per-process client cap. Workers override via options; keep small on serverless. */
130
- maxClients: 8,
130
+ /** One shared per-process client cap for scheduler control-plane lanes. */
131
+ maxClients: RUNTIME_CAPACITY_POLICY.runtimePostgres.schedulerPoolMaxClients,
131
132
  /** Refuse to construct a pool above this — protects the shared PgBouncer/Postgres. */
132
133
  maxClientsHardCap: 32,
133
134
  /** Max time a caller waits for a pooled connection before erroring. */
@@ -163,7 +164,11 @@ export const RUNTIME_SCHEDULER_POOL_DEFAULTS = {
163
164
  * once. This shared cap keeps horizontally scaled workers and the receipt
164
165
  * gateway from consuming PgBouncer's global client budget independently.
165
166
  */
166
- export const RUNTIME_SCHEDULER_CONTROL_PLANE_MAX_CLIENTS = 2;
167
+ export const RUNTIME_SCHEDULER_CONTROL_PLANE_MAX_CLIENTS =
168
+ RUNTIME_CAPACITY_POLICY.runtimePostgres.schedulerPoolMaxClients;
169
+
170
+ export const RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS = 2_000;
171
+ export const RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS = 15_000;
167
172
 
168
173
  export type RuntimeSchedulerPoolOptions = {
169
174
  url: string;
@@ -188,6 +193,11 @@ export type RuntimeSchedulerPoolOptions = {
188
193
  maxAcquireQueueWaitMs?: number;
189
194
  };
190
195
 
196
+ export type RuntimeSchedulerOperationOptions = {
197
+ signal?: AbortSignal;
198
+ maxAttempts?: number;
199
+ };
200
+
191
201
  export type RuntimeSchedulerQueryClient = {
192
202
  query<Row extends Record<string, unknown> = Record<string, unknown>>(
193
203
  sql: string,
@@ -195,6 +205,22 @@ export type RuntimeSchedulerQueryClient = {
195
205
  ): Promise<{ rows: Row[] }>;
196
206
  };
197
207
 
208
+ export function isRuntimeSchedulerQueryCancellationError(
209
+ error: unknown,
210
+ ): boolean {
211
+ if (!error || typeof error !== 'object') return false;
212
+ if ((error as { code?: unknown }).code === '57014') return true;
213
+ const name = (error as { name?: unknown }).name;
214
+ if (name === 'TimeoutError' || name === 'AbortError') return true;
215
+ const message = (error as { message?: unknown }).message;
216
+ return (
217
+ typeof message === 'string' &&
218
+ /(?:statement timeout|canceling statement due to user request|query read timeout)/i.test(
219
+ message,
220
+ )
221
+ );
222
+ }
223
+
198
224
  export class RuntimeSchedulerCircuitOpenError extends Error {
199
225
  readonly code = 'RUNTIME_SCHEDULER_UNAVAILABLE';
200
226
 
@@ -206,6 +232,47 @@ export class RuntimeSchedulerCircuitOpenError extends Error {
206
232
  }
207
233
  }
208
234
 
235
+ /**
236
+ * Errors can cross the app/worker package boundary, where `instanceof` is not
237
+ * reliable. Keep this guard tied to the one canonical error name and message
238
+ * instead of making every status endpoint duplicate a string match.
239
+ */
240
+ export function isRuntimeSchedulerCircuitOpenError(
241
+ error: unknown,
242
+ ): error is RuntimeSchedulerCircuitOpenError {
243
+ if (error instanceof RuntimeSchedulerCircuitOpenError) return true;
244
+ return (
245
+ error instanceof Error &&
246
+ error.name === 'RuntimeSchedulerCircuitOpenError' &&
247
+ error.message.startsWith(
248
+ 'Runtime scheduler DB circuit breaker is open after repeated connection failures:',
249
+ )
250
+ );
251
+ }
252
+
253
+ type SchedulerCircuitWait = (delayMs: number) => Promise<void>;
254
+
255
+ /**
256
+ * Give the existing scheduler-pool breaker one half-open trial before an
257
+ * idempotent control-plane read is surfaced as unavailable. This is not a
258
+ * second retry policy or congestion controller: it waits exactly the breaker
259
+ * cooldown already enforced by `withRuntimeSchedulerClient`, retries once,
260
+ * and only accepts read operations supplied by the caller.
261
+ */
262
+ export async function retryOnceAfterRuntimeSchedulerCircuitOpen<T>(
263
+ read: () => Promise<T>,
264
+ options: { wait?: SchedulerCircuitWait } = {},
265
+ ): Promise<T> {
266
+ try {
267
+ return await read();
268
+ } catch (error) {
269
+ if (!isRuntimeSchedulerCircuitOpenError(error)) throw error;
270
+ const wait = options.wait ?? sleep;
271
+ await wait(RUNTIME_SCHEDULER_POOL_DEFAULTS.breaker.openMs);
272
+ return read();
273
+ }
274
+ }
275
+
209
276
  type LatencyRing = {
210
277
  samples: number[];
211
278
  next: number;
@@ -697,11 +764,21 @@ export async function withRuntimeSchedulerClient<T>(
697
764
  phase: string;
698
765
  priority?: RuntimeSchedulerPoolPriority;
699
766
  },
700
- operationOptions: { signal?: AbortSignal } = {},
767
+ operationOptions: RuntimeSchedulerOperationOptions = {},
701
768
  ): Promise<T> {
702
769
  const signal = operationOptions.signal;
703
770
  throwIfSchedulerAborted(signal);
704
771
  const entry = getPoolEntry(options);
772
+ const maxAttempts = Math.max(
773
+ 1,
774
+ Math.min(
775
+ RUNTIME_SCHEDULER_POOL_DEFAULTS.retry.attempts,
776
+ Math.floor(
777
+ operationOptions.maxAttempts ??
778
+ RUNTIME_SCHEDULER_POOL_DEFAULTS.retry.attempts,
779
+ ),
780
+ ),
781
+ );
705
782
  const operationStartedAt = performance.now();
706
783
  const admissionStartedAt = performance.now();
707
784
  const releaseAdmission = await entry.admission.acquire(
@@ -712,11 +789,7 @@ export async function withRuntimeSchedulerClient<T>(
712
789
  let lastError: unknown = null;
713
790
  try {
714
791
  throwIfSchedulerAborted(signal);
715
- for (
716
- let attempt = 0;
717
- attempt < RUNTIME_SCHEDULER_POOL_DEFAULTS.retry.attempts;
718
- attempt += 1
719
- ) {
792
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
720
793
  throwIfSchedulerAborted(signal);
721
794
  breakerGate(entry);
722
795
  if (entry.pool.waitingCount >= entry.maxAcquireQueueDepth) {
@@ -755,7 +828,7 @@ export async function withRuntimeSchedulerClient<T>(
755
828
  recordBreakerFailure(entry, error);
756
829
  entry.stats.connectionErrors += 1;
757
830
  lastError = error;
758
- if (attempt + 1 < RUNTIME_SCHEDULER_POOL_DEFAULTS.retry.attempts) {
831
+ if (attempt + 1 < maxAttempts) {
759
832
  entry.stats.retries += 1;
760
833
  await sleep(jitteredBackoffMs(attempt));
761
834
  throwIfSchedulerAborted(signal);
@@ -805,9 +878,18 @@ export async function withRuntimeSchedulerClient<T>(
805
878
  const queryStartedAt = performance.now();
806
879
  queryCount += 1;
807
880
  try {
808
- const queryResult = await poolClient.query(sql, params);
809
- throwIfSchedulerAborted(signal);
810
- return { rows: queryResult.rows };
881
+ try {
882
+ const queryResult = await poolClient.query(sql, params);
883
+ throwIfSchedulerAborted(signal);
884
+ return { rows: queryResult.rows };
885
+ } catch (error) {
886
+ if (isRuntimeSchedulerQueryCancellationError(error)) {
887
+ releasePoolClient(
888
+ error instanceof Error ? error : new Error(String(error)),
889
+ );
890
+ }
891
+ throw error;
892
+ }
811
893
  } finally {
812
894
  queryMs += performance.now() - queryStartedAt;
813
895
  }
@@ -873,6 +955,14 @@ export async function withRuntimeSchedulerClient<T>(
873
955
  error instanceof Error ? error.message : String(error),
874
956
  );
875
957
  }
958
+ if (isRuntimeSchedulerQueryCancellationError(error)) {
959
+ entry.stats.queryErrors += 1;
960
+ recordBreakerSuccess(entry);
961
+ releasePoolClient(
962
+ error instanceof Error ? error : new Error(String(error)),
963
+ );
964
+ throw error;
965
+ }
876
966
  if (isConnectionCategoryError(error)) {
877
967
  recordBreakerFailure(entry, error);
878
968
  entry.stats.connectionErrors += 1;
@@ -881,7 +971,7 @@ export async function withRuntimeSchedulerClient<T>(
881
971
  releasePoolClient(
882
972
  error instanceof Error ? error : new Error(String(error)),
883
973
  );
884
- if (attempt + 1 < RUNTIME_SCHEDULER_POOL_DEFAULTS.retry.attempts) {
974
+ if (attempt + 1 < maxAttempts) {
885
975
  entry.stats.retries += 1;
886
976
  await sleep(jitteredBackoffMs(attempt));
887
977
  throwIfSchedulerAborted(signal);
@@ -899,7 +989,7 @@ export async function withRuntimeSchedulerClient<T>(
899
989
  releasePoolClient(
900
990
  error instanceof Error ? error : new Error(String(error)),
901
991
  );
902
- if (attempt + 1 < RUNTIME_SCHEDULER_POOL_DEFAULTS.retry.attempts) {
992
+ if (attempt + 1 < maxAttempts) {
903
993
  entry.stats.retries += 1;
904
994
  await sleep(jitteredBackoffMs(attempt));
905
995
  throwIfSchedulerAborted(signal);
@@ -923,6 +1013,47 @@ export async function withRuntimeSchedulerClient<T>(
923
1013
  }
924
1014
  }
925
1015
 
1016
+ export async function withRuntimeSchedulerTransaction<T>(
1017
+ options: RuntimeSchedulerPoolOptions,
1018
+ timeouts: { lockTimeoutMs: number; statementTimeoutMs: number },
1019
+ fn: (client: RuntimeSchedulerQueryClient) => Promise<T>,
1020
+ trace?: {
1021
+ runId: string;
1022
+ phase: string;
1023
+ priority?: RuntimeSchedulerPoolPriority;
1024
+ },
1025
+ operationOptions: RuntimeSchedulerOperationOptions = {},
1026
+ ): Promise<T> {
1027
+ const lockTimeoutMs = Math.max(1, Math.floor(timeouts.lockTimeoutMs));
1028
+ const statementTimeoutMs = Math.max(
1029
+ lockTimeoutMs,
1030
+ Math.floor(timeouts.statementTimeoutMs),
1031
+ );
1032
+ return withRuntimeSchedulerClient(
1033
+ options,
1034
+ async (client) => {
1035
+ await client.query('BEGIN');
1036
+ try {
1037
+ await client.query(
1038
+ `SELECT set_config('lock_timeout', $1, true),
1039
+ set_config('statement_timeout', $2, true)`,
1040
+ [`${lockTimeoutMs}ms`, `${statementTimeoutMs}ms`],
1041
+ );
1042
+ const result = await fn(client);
1043
+ await client.query('COMMIT');
1044
+ return result;
1045
+ } catch (error) {
1046
+ if (!isRuntimeSchedulerQueryCancellationError(error)) {
1047
+ await client.query('ROLLBACK').catch(() => undefined);
1048
+ }
1049
+ throw error;
1050
+ }
1051
+ },
1052
+ trace,
1053
+ { ...operationOptions, maxAttempts: operationOptions.maxAttempts ?? 1 },
1054
+ );
1055
+ }
1056
+
926
1057
  export type RuntimeSchedulerPoolStats = {
927
1058
  url: string;
928
1059
  maxClients: number;
@@ -19,12 +19,18 @@ import {
19
19
  } from '../app-runtime-api';
20
20
  import type { PlayVisualNodeProgressMap } from '../worker-api-types';
21
21
  import type { PlayExecutionEvent } from '../ctx-types';
22
+ import type { PlayActivityObservation } from '../activity-observation';
22
23
  import type {
23
24
  PlayRunnerLogEvent,
24
25
  PlayRunnerResult,
25
26
  } from '../protocol';
26
27
  import type { PlaySchedulerSubmitInput } from '../scheduler-backend';
27
28
  import type { DocflowObservationRecord } from '../docflow-observation';
29
+ import {
30
+ buildPlayRunLedgerEventsFromStatusPatch,
31
+ createEmptyPlayRunLedgerSnapshot,
32
+ type PlayRunLedgerEvent,
33
+ } from '../run-ledger';
28
34
  import {
29
35
  getTopLevelPipelineSubsteps,
30
36
  type PlayStaticPipeline,
@@ -248,7 +254,16 @@ function shouldPublishTerminalResultLog(
248
254
  mode: PostgresSchedulerDurableLogMode,
249
255
  line: string,
250
256
  ): boolean {
251
- return mode !== 'direct_worker' || CTX_LOG_TIMESTAMP_PREFIX_RE.test(line);
257
+ // Direct workers keep host lifecycle noise out of the customer Run Log
258
+ // Stream. The resource summary is one bounded, machine-readable terminal
259
+ // diagnostic rather than a host log, though: the fixture harness needs it
260
+ // after the runner has gone away and the normal terminal result is no longer
261
+ // available through the status projection.
262
+ return (
263
+ mode !== 'direct_worker' ||
264
+ CTX_LOG_TIMESTAMP_PREFIX_RE.test(line) ||
265
+ line.startsWith('[runtime.phase_summary] ')
266
+ );
252
267
  }
253
268
 
254
269
  type TerminalLogReplayPayload = NonNullable<
@@ -323,14 +338,34 @@ export function createPostgresSchedulerProgressReporter(input: {
323
338
  durableLogMode?: PostgresSchedulerDurableLogMode;
324
339
  producerAttempt?: number;
325
340
  isCurrentAttempt?: () => boolean | Promise<boolean>;
341
+ /**
342
+ * The caller already read the durable attempt fence for THIS request and it
343
+ * was current. The first projection flush then reuses that read instead of
344
+ * paying a second identical round trip. A reporter lives for exactly one
345
+ * request, and the credit is consumed by the first flush, so a later flush —
346
+ * and every other request — re-reads the fence.
347
+ */
348
+ attemptAlreadyVerified?: boolean;
326
349
  /** Durable log position before this reporter's first event. Push runners own
327
350
  * this cursor so successive batches may land on different gateway Machines. */
328
351
  initialLiveLogTotalCount?: number;
352
+ /** Override the app-runtime transport when a caller needs to capture the
353
+ * folded terminal patch for a durable scheduler outbox instead of posting
354
+ * it synchronously. */
355
+ publishStatusUpdate?: (update: RuntimeStatusUpdate) => Promise<void>;
329
356
  }): PostgresSchedulerProgressReporter {
330
357
  const launch = input.launch;
331
358
  const durableLogMode = input.durableLogMode ?? 'progress_reporter';
332
359
  const producerAttempt = input.producerAttempt;
333
- const isCurrentAttempt = input.isCurrentAttempt ?? (() => true);
360
+ const readAttemptFence = input.isCurrentAttempt ?? (() => true);
361
+ let verifiedAttemptCredit = input.attemptAlreadyVerified === true;
362
+ const isCurrentAttempt = async (): Promise<boolean> => {
363
+ if (verifiedAttemptCredit) {
364
+ verifiedAttemptCredit = false;
365
+ return true;
366
+ }
367
+ return await readAttemptFence();
368
+ };
334
369
  const orderedNodes = buildOrderedNodeList(launch.contractSnapshot);
335
370
  const mapNodeLookup = buildMapNodeLookup(launch.contractSnapshot);
336
371
  const runtimeApi = {
@@ -338,6 +373,10 @@ export function createPostgresSchedulerProgressReporter(input: {
338
373
  executorToken: launch.executorToken,
339
374
  vercelProtectionBypassToken: launch.vercelProtectionBypassToken ?? null,
340
375
  };
376
+ const publishStatusUpdate =
377
+ input.publishStatusUpdate ??
378
+ ((update: RuntimeStatusUpdate) =>
379
+ updateRunStatusViaAppRuntime(runtimeApi, update));
341
380
 
342
381
  let liveNodeProgress: PlayVisualNodeProgressMap = {};
343
382
  let liveNodeProgressDirty = false;
@@ -427,7 +466,7 @@ export function createPostgresSchedulerProgressReporter(input: {
427
466
 
428
467
  try {
429
468
  if (await isCurrentAttempt()) {
430
- await updateRunStatusViaAppRuntime(runtimeApi, {
469
+ await publishStatusUpdate({
431
470
  playId: launch.runId,
432
471
  status: request.status,
433
472
  liveNodeProgress: nextProgress,
@@ -1246,10 +1285,102 @@ export const createDirectWorkerProgressReporter = (input: {
1246
1285
  launch: PlaySchedulerSubmitInput;
1247
1286
  producerAttempt?: number;
1248
1287
  isCurrentAttempt?: () => boolean | Promise<boolean>;
1288
+ /** See `createPostgresSchedulerProgressReporter`: one request, one credit. */
1289
+ attemptAlreadyVerified?: boolean;
1249
1290
  initialLiveLogTotalCount?: number;
1291
+ publishStatusUpdate?: (update: RuntimeStatusUpdate) => Promise<void>;
1250
1292
  }) =>
1251
1293
  createPostgresSchedulerProgressReporter({
1252
1294
  ...input,
1253
1295
  durableLogMode: 'direct_worker',
1254
1296
  });
1255
1297
  export type DirectWorkerProgressReporter = PostgresSchedulerProgressReporter;
1298
+
1299
+ /**
1300
+ * Fold a detached runner's final progress without crossing the app-runtime
1301
+ * network boundary. The returned events are stored in the same Postgres
1302
+ * terminal outbox row as `run.completed`, so the projector can append the
1303
+ * final step/log/activity facts and the terminal fact in one ordered Convex
1304
+ * mutation. This is the completed-run fast path; failed/suspended runs retain
1305
+ * the compatibility projection until their parked worker resumes.
1306
+ */
1307
+ export async function collectGatewayRunnerTerminalProgressLedgerEvents(input: {
1308
+ launch: PlaySchedulerSubmitInput;
1309
+ attempt: number;
1310
+ result: PlayRunnerResult;
1311
+ events: unknown[];
1312
+ liveLogOffset: number;
1313
+ }): Promise<PlayRunLedgerEvent[]> {
1314
+ const captured: { update?: RuntimeStatusUpdate } = {};
1315
+ const reporter = createDirectWorkerProgressReporter({
1316
+ launch: input.launch,
1317
+ producerAttempt: input.attempt,
1318
+ initialLiveLogTotalCount: input.liveLogOffset,
1319
+ isCurrentAttempt: () => true,
1320
+ attemptAlreadyVerified: true,
1321
+ publishStatusUpdate: async (update) => {
1322
+ captured.update = update;
1323
+ },
1324
+ });
1325
+ for (const event of input.events) reporter.onRunnerEvent(event);
1326
+ await reporter.finalize(input.result, {
1327
+ status: 'running',
1328
+ required: true,
1329
+ skipTerminalLogReplay: true,
1330
+ terminalLogProducerAttempt: input.attempt,
1331
+ });
1332
+ const finalUpdate = captured.update;
1333
+ if (!finalUpdate) return [];
1334
+
1335
+ const liveLogs = finalUpdate.liveLogs ?? [];
1336
+ const liveLogTotalCount =
1337
+ finalUpdate.liveLogTotalCount ?? liveLogs.length + input.liveLogOffset;
1338
+ const progressEvents = buildPlayRunLedgerEventsFromStatusPatch({
1339
+ patch: {
1340
+ runId: input.launch.runId,
1341
+ status: 'running',
1342
+ lastCheckpointAt: finalUpdate.lastCheckpointAt,
1343
+ liveNodeProgress: finalUpdate.liveNodeProgress,
1344
+ liveLogs,
1345
+ liveLogsChannelOffset: Math.max(0, liveLogTotalCount - liveLogs.length),
1346
+ liveLogsProducerAttempt:
1347
+ finalUpdate.liveLogProducerAttempt ?? input.attempt,
1348
+ },
1349
+ previousSnapshot: createEmptyPlayRunLedgerSnapshot({
1350
+ runId: input.launch.runId,
1351
+ status: 'queued',
1352
+ }),
1353
+ source: 'worker',
1354
+ });
1355
+ const datasetEvents: PlayRunLedgerEvent[] = (
1356
+ finalUpdate.datasetLifecycleEvents ?? []
1357
+ ).map((event) => ({
1358
+ type: 'dataset.lifecycle',
1359
+ runId: input.launch.runId,
1360
+ source: 'worker',
1361
+ occurredAt: event.at,
1362
+ datasetId: event.datasetId,
1363
+ path: event.path,
1364
+ tableNamespace: event.tableNamespace,
1365
+ phase: event.phase,
1366
+ ...(event.persistedRows === undefined
1367
+ ? {}
1368
+ : { persistedRows: event.persistedRows }),
1369
+ ...(event.succeededRows === undefined
1370
+ ? {}
1371
+ : { succeededRows: event.succeededRows }),
1372
+ ...(event.failedRows === undefined ? {} : { failedRows: event.failedRows }),
1373
+ ...(event.complete === undefined ? {} : { complete: event.complete }),
1374
+ ...(event.bornFrom === undefined ? {} : { bornFrom: event.bornFrom }),
1375
+ }));
1376
+ const activityEvents: PlayRunLedgerEvent[] = (
1377
+ finalUpdate.activityObservations ?? []
1378
+ ).map((observation: PlayActivityObservation) => ({
1379
+ type: 'activity.observed',
1380
+ runId: input.launch.runId,
1381
+ source: 'worker',
1382
+ occurredAt: observation.observedAt,
1383
+ observation,
1384
+ }));
1385
+ return [...progressEvents, ...datasetEvents, ...activityEvents];
1386
+ }