deepline 0.2.55 → 0.2.57

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 (49) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +14 -0
  2. package/dist/bundling-sources/sdk/src/http.ts +19 -1
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/stream-reconnect.ts +6 -0
  5. package/dist/bundling-sources/sdk/src/types.ts +7 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +146 -12
  7. package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +6 -3
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1631 -748
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +20 -0
  10. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +81 -52
  11. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +146 -6
  12. package/dist/bundling-sources/shared_libs/play-runtime/gateway-auth-session.ts +56 -22
  13. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +14 -11
  14. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +21 -2
  15. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +81 -21
  16. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +7 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +21 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +27 -2
  20. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +49 -10
  21. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +96 -3
  22. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +341 -67
  23. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +10 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +8 -1
  25. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +17 -2
  26. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +4 -1
  27. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +17 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +16 -2
  29. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +25 -0
  30. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +3 -0
  31. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +2 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +80 -1
  33. package/dist/bundling-sources/shared_libs/plays/docflow.ts +113 -14
  34. package/dist/bundling-sources/shared_libs/plays/play-exports.ts +53 -4
  35. package/dist/bundling-sources/shared_libs/security/safe-fetch.ts +9 -0
  36. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +85 -48
  37. package/dist/cli/index.js +429 -54
  38. package/dist/cli/index.mjs +409 -28
  39. package/dist/{compiler-manifest-Bl8kmLx9.d.mts → compiler-manifest-TgaC4DeD.d.mts} +13 -0
  40. package/dist/{compiler-manifest-Bl8kmLx9.d.ts → compiler-manifest-TgaC4DeD.d.ts} +13 -0
  41. package/dist/index.d.mts +21 -3
  42. package/dist/index.d.ts +21 -3
  43. package/dist/index.js +29 -2
  44. package/dist/index.mjs +29 -2
  45. package/dist/install-integrity.json +2 -2
  46. package/dist/plays/bundle-play-file.d.mts +2 -2
  47. package/dist/plays/bundle-play-file.d.ts +2 -2
  48. package/dist/plays/bundle-play-file.mjs +78 -18
  49. package/package.json +1 -1
@@ -275,10 +275,25 @@ type RuntimeSheetPreparedRowDisposition = {
275
275
  disposition: RuntimeSheetPrepareDisposition;
276
276
  };
277
277
 
278
+ // A failed connect retires one cached generation. It cannot end that pool
279
+ // until every request that already borrowed the generation has returned.
280
+ type CachedRuntimePostgresPool = {
281
+ pool: RuntimePool;
282
+ borrowers: number;
283
+ retired: boolean;
284
+ endPromise: Promise<void> | null;
285
+ };
286
+
287
+ type RuntimePostgresPoolLease = {
288
+ pool: RuntimePool;
289
+ cached: CachedRuntimePostgresPool | null;
290
+ release(): Promise<void>;
291
+ };
292
+
278
293
  type RuntimeSheetDatasetMode = 'upsert' | 'net_new';
279
294
  const dbSessionCache = new Map<string, DbSessionCacheEntry>();
280
295
  const dbSessionInFlight = new Map<string, Promise<CreateDbSessionResponse>>();
281
- const postgresPools = new Map<string, RuntimePool>();
296
+ const postgresPools = new Map<string, CachedRuntimePostgresPool>();
282
297
  const runtimePostgresAdmissions = new Map<string, RuntimePostgresAdmission>();
283
298
  const runtimeWorkReceiptEnsureCache = new Map<string, Promise<void>>();
284
299
  const runtimeSheetEnsureCache = new Map<
@@ -331,6 +346,14 @@ const RUNTIME_API_REQUEST_TIMEOUT_MS = 30_000;
331
346
  // writes. Keep the caller alive long enough for the FIFO admission queue to
332
347
  // make progress instead of aborting a healthy queued start after 30 seconds.
333
348
  const RUNTIME_SHEET_ADMISSION_REQUEST_TIMEOUT_MS = 10 * 60_000;
349
+ const RUNTIME_SHEET_COMPLETION_SERVER_OPERATION_TIMEOUT_MS = 75_000;
350
+ // Server admission is bounded to 30s and the single connection attempt to
351
+ // 10s. Keep the transport outside that envelope so it never retries while the
352
+ // first server-side write can still be active.
353
+ const RUNTIME_SHEET_COMPLETION_REQUEST_TIMEOUT_MS = 150_000;
354
+ const RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS = 180_000;
355
+ const RUNTIME_SHEET_COMPLETION_STATEMENT_TIMEOUT_MS = 60_000;
356
+ const RUNTIME_POSTGRES_SHEET_ADMISSION_TIMEOUT_MS = 30_000;
334
357
  const RUNTIME_POSTGRES_PREWARM_MAX_ATTEMPTS = 4;
335
358
  const RUNTIME_POSTGRES_PREWARM_RETRY_DELAYS_MS = [250, 750, 1_500] as const;
336
359
  const RUNTIME_POSTGRES_CONNECT_MAX_ATTEMPTS = 4;
@@ -624,13 +647,21 @@ async function postRuntimeApi<TResponse>(
624
647
  context.vercelProtectionBypassToken,
625
648
  );
626
649
  const maxAttempts = runtimeApiMaxAttempts(body.action);
650
+ const operationStartedAt = Date.now();
627
651
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
628
652
  let response: Response | null = null;
629
653
  let parsed: Record<string, unknown> | null = null;
630
654
  const abortController = new AbortController();
655
+ const operationRemainingMs = runtimeApiOperationRemainingMs(
656
+ body.action,
657
+ operationStartedAt,
658
+ );
659
+ if (operationRemainingMs <= 0) {
660
+ throw runtimeApiTotalDeadlineError(body.action);
661
+ }
631
662
  const timeout = setTimeout(
632
663
  () => abortController.abort(),
633
- runtimeApiRequestTimeoutMs(body.action),
664
+ Math.min(runtimeApiRequestTimeoutMs(body.action), operationRemainingMs),
634
665
  );
635
666
  try {
636
667
  response = await fetch(url, {
@@ -661,8 +692,18 @@ async function postRuntimeApi<TResponse>(
661
692
  if (context.abortSignal?.aborted) {
662
693
  throw context.abortSignal.reason ?? error;
663
694
  }
695
+ if (
696
+ runtimeApiOperationRemainingMs(body.action, operationStartedAt) <= 0
697
+ ) {
698
+ throw runtimeApiTotalDeadlineError(body.action);
699
+ }
664
700
  if (attempt < maxAttempts) {
665
- await sleepRuntimeApiRetry(body.action, attempt);
701
+ await sleepRuntimeApiRetry({
702
+ action: body.action,
703
+ attempt,
704
+ operationStartedAt,
705
+ abortSignal: context.abortSignal ?? undefined,
706
+ });
666
707
  continue;
667
708
  }
668
709
  const message = error instanceof Error ? error.message : String(error);
@@ -686,7 +727,12 @@ async function postRuntimeApi<TResponse>(
686
727
  // every runtime action returns a JSON object and runtime writes are
687
728
  // already retry-safe across the equivalent fetch-failed boundary.
688
729
  if (attempt < maxAttempts) {
689
- await sleepRuntimeApiRetry(body.action, attempt);
730
+ await sleepRuntimeApiRetry({
731
+ action: body.action,
732
+ attempt,
733
+ operationStartedAt,
734
+ abortSignal: context.abortSignal ?? undefined,
735
+ });
690
736
  continue;
691
737
  }
692
738
  const requestId = response.headers.get('x-deepline-request-id');
@@ -740,7 +786,13 @@ async function postRuntimeApi<TResponse>(
740
786
  `${typeof parsed?.error === 'string' ? parsed.error : ''}\n${details ?? ''}`,
741
787
  ));
742
788
  if (shouldRetryRuntimeResponse && attempt < maxAttempts) {
743
- await sleepRuntimeApiRetry(body.action, attempt, retryAfterMs);
789
+ await sleepRuntimeApiRetry({
790
+ action: body.action,
791
+ attempt,
792
+ retryAfterMs,
793
+ operationStartedAt,
794
+ abortSignal: context.abortSignal ?? undefined,
795
+ });
744
796
  continue;
745
797
  }
746
798
 
@@ -786,22 +838,75 @@ function runtimeApiRetryDelayMs(
786
838
  function runtimeApiRequestTimeoutMs(
787
839
  action: RuntimeApiActionRequest['action'],
788
840
  ): number {
789
- return action === 'runtime_sheet_start' ||
790
- action === 'runtime_sheet_complete_map_rows'
841
+ if (action === 'runtime_sheet_complete_map_rows') {
842
+ return RUNTIME_SHEET_COMPLETION_REQUEST_TIMEOUT_MS;
843
+ }
844
+ return action === 'runtime_sheet_start'
791
845
  ? RUNTIME_SHEET_ADMISSION_REQUEST_TIMEOUT_MS
792
846
  : RUNTIME_API_REQUEST_TIMEOUT_MS;
793
847
  }
794
848
 
795
- async function sleepRuntimeApiRetry(
849
+ function runtimeApiOperationRemainingMs(
796
850
  action: RuntimeApiActionRequest['action'],
797
- attempt: number,
798
- retryAfterMs?: number,
799
- ): Promise<void> {
800
- await new Promise((resolve) =>
801
- setTimeout(resolve, runtimeApiRetryDelayMs(action, attempt, retryAfterMs)),
851
+ startedAt: number,
852
+ ): number {
853
+ return action === 'runtime_sheet_complete_map_rows'
854
+ ? Math.max(
855
+ 0,
856
+ RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS - (Date.now() - startedAt),
857
+ )
858
+ : Number.POSITIVE_INFINITY;
859
+ }
860
+
861
+ function runtimeApiTotalDeadlineError(
862
+ action: RuntimeApiActionRequest['action'],
863
+ ): Error {
864
+ return new Error(
865
+ `Runtime API ${action} exceeded its ` +
866
+ `${RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS}ms total deadline.`,
802
867
  );
803
868
  }
804
869
 
870
+ async function sleepRuntimeApiRetry(input: {
871
+ action: RuntimeApiActionRequest['action'];
872
+ attempt: number;
873
+ retryAfterMs?: number;
874
+ operationStartedAt: number;
875
+ abortSignal?: AbortSignal;
876
+ }): Promise<void> {
877
+ const requestedDelayMs = runtimeApiRetryDelayMs(
878
+ input.action,
879
+ input.attempt,
880
+ input.retryAfterMs,
881
+ );
882
+ const operationRemainingMs = runtimeApiOperationRemainingMs(
883
+ input.action,
884
+ input.operationStartedAt,
885
+ );
886
+ if (operationRemainingMs <= 0) {
887
+ throw runtimeApiTotalDeadlineError(input.action);
888
+ }
889
+ const delayMs = Math.min(requestedDelayMs, operationRemainingMs);
890
+ await new Promise<void>((resolve, reject) => {
891
+ const timeout = setTimeout(() => {
892
+ input.abortSignal?.removeEventListener('abort', onAbort);
893
+ resolve();
894
+ }, delayMs);
895
+ const onAbort = () => {
896
+ clearTimeout(timeout);
897
+ reject(input.abortSignal?.reason ?? new Error('Runtime API aborted.'));
898
+ };
899
+ if (input.abortSignal?.aborted) {
900
+ onAbort();
901
+ return;
902
+ }
903
+ input.abortSignal?.addEventListener('abort', onAbort, { once: true });
904
+ });
905
+ if (requestedDelayMs >= operationRemainingMs) {
906
+ throw runtimeApiTotalDeadlineError(input.action);
907
+ }
908
+ }
909
+
805
910
  function runtimeApiRetryDelaysForAction(
806
911
  action: RuntimeApiActionRequest['action'],
807
912
  ): readonly number[] {
@@ -1708,10 +1813,17 @@ async function connectRuntimePostgresPool(
1708
1813
  let released = false;
1709
1814
  return {
1710
1815
  query: (text, params) => client.query(text, params),
1711
- release: () => {
1816
+ release: (destroy = false) => {
1712
1817
  if (released) return;
1713
1818
  released = true;
1714
- client.release();
1819
+ client.release(destroy);
1820
+ releaseAdmission();
1821
+ },
1822
+ destroy: (error) => {
1823
+ if (released) return;
1824
+ released = true;
1825
+ if (client.destroy) client.destroy(error);
1826
+ else client.release(true);
1715
1827
  releaseAdmission();
1716
1828
  },
1717
1829
  };
@@ -1753,15 +1865,13 @@ function getRuntimePostgresAdmission(
1753
1865
  receipts: RUNTIME_POSTGRES_RECEIPT_ADMISSION_MAX_QUEUED,
1754
1866
  sheets: RUNTIME_POSTGRES_SHEET_ADMISSION_MAX_QUEUED,
1755
1867
  },
1756
- // Sheet capacity is backpressure, not a sheet-persistence failure. The
1757
- // surrounding runtime request has its own bounded deadline and the queue
1758
- // stays FIFO; no healthy request is discarded merely because two writes
1759
- // are active for ten seconds. Receipt admission retains its short timeout:
1760
- // a receipt can follow the ordinary retry/recovery path without holding a
1761
- // row's data-plane transaction open.
1868
+ // Capacity is backpressure, not a sheet-persistence failure. Bound both
1869
+ // queues so the gateway can return typed capacity before an HTTP caller
1870
+ // times out. The runner then parks the same frozen write and retries it;
1871
+ // it never leaves an invisible request queued behind a dead client.
1762
1872
  acquireTimeoutMs: {
1763
1873
  receipts: RUNTIME_POSTGRES_RECEIPT_ADMISSION_TIMEOUT_MS,
1764
- sheets: null,
1874
+ sheets: RUNTIME_POSTGRES_SHEET_ADMISSION_TIMEOUT_MS,
1765
1875
  },
1766
1876
  });
1767
1877
  runtimePostgresAdmissions.set(postgresUrl, admission);
@@ -1774,46 +1884,149 @@ export function runtimePostgresAdmissionTelemetry(
1774
1884
  return runtimePostgresAdmissions.get(postgresUrl)?.snapshot() ?? null;
1775
1885
  }
1776
1886
 
1777
- function getPostgresPool(
1887
+ function endRetiredRuntimePostgresPool(
1888
+ cached: CachedRuntimePostgresPool,
1889
+ ): Promise<void> {
1890
+ if (!cached.retired || cached.borrowers > 0) {
1891
+ return Promise.resolve();
1892
+ }
1893
+ cached.endPromise ??= Promise.resolve()
1894
+ .then(() => cached.pool.end())
1895
+ .catch(() => {});
1896
+ return cached.endPromise;
1897
+ }
1898
+
1899
+ async function retireRuntimePostgresPool(
1900
+ poolKey: string,
1901
+ cached: CachedRuntimePostgresPool,
1902
+ ): Promise<void> {
1903
+ if (postgresPools.get(poolKey) === cached) {
1904
+ postgresPools.delete(poolKey);
1905
+ }
1906
+ cached.retired = true;
1907
+ await endRetiredRuntimePostgresPool(cached);
1908
+ }
1909
+
1910
+ function getPostgresPoolLease(
1778
1911
  postgresUrl: string,
1779
1912
  lane: RuntimePostgresLane,
1780
1913
  cachePool = true,
1781
- ): RuntimePool {
1914
+ ): RuntimePostgresPoolLease {
1782
1915
  if (!cachePool) {
1783
- return createRuntimePool({
1784
- connectionString: postgresUrl,
1785
- maxConnections: RUNTIME_POSTGRES_POOL_CONNECTIONS_PER_LANE,
1786
- idleTimeoutMs: 0,
1787
- connectTimeoutMs: RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS,
1788
- });
1916
+ return {
1917
+ pool: createRuntimePool({
1918
+ connectionString: postgresUrl,
1919
+ maxConnections: RUNTIME_POSTGRES_POOL_CONNECTIONS_PER_LANE,
1920
+ idleTimeoutMs: 0,
1921
+ connectTimeoutMs: RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS,
1922
+ }),
1923
+ cached: null,
1924
+ release: async () => {},
1925
+ };
1789
1926
  }
1790
1927
  const poolKey = runtimePostgresPoolKey(postgresUrl, lane);
1791
- const existing = postgresPools.get(poolKey);
1792
- if (existing) {
1793
- return existing;
1794
- }
1795
- const pool = createRuntimePool({
1796
- connectionString: postgresUrl,
1797
- maxConnections: RUNTIME_POSTGRES_POOL_CONNECTIONS_PER_LANE,
1798
- idleTimeoutMs: 15_000,
1799
- connectTimeoutMs: RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS,
1800
- });
1801
- postgresPools.set(poolKey, pool);
1802
- return pool;
1928
+ let cached = postgresPools.get(poolKey);
1929
+ if (!cached) {
1930
+ cached = {
1931
+ pool: createRuntimePool({
1932
+ connectionString: postgresUrl,
1933
+ maxConnections: RUNTIME_POSTGRES_POOL_CONNECTIONS_PER_LANE,
1934
+ idleTimeoutMs: 15_000,
1935
+ connectTimeoutMs: RUNTIME_POSTGRES_CONNECT_TIMEOUT_MS,
1936
+ }),
1937
+ borrowers: 0,
1938
+ retired: false,
1939
+ endPromise: null,
1940
+ };
1941
+ postgresPools.set(poolKey, cached);
1942
+ }
1943
+ cached.borrowers += 1;
1944
+ let released = false;
1945
+ return {
1946
+ pool: cached.pool,
1947
+ cached,
1948
+ release: async () => {
1949
+ if (released) return;
1950
+ released = true;
1951
+ cached.borrowers -= 1;
1952
+ await endRetiredRuntimePostgresPool(cached);
1953
+ },
1954
+ };
1803
1955
  }
1804
1956
 
1805
1957
  async function resetRuntimePostgresPool(postgresUrl: string): Promise<void> {
1806
- const pools = (['receipts', 'sheets'] as const)
1958
+ const retirements = (['receipts', 'sheets'] as const)
1807
1959
  .map((lane) => runtimePostgresPoolKey(postgresUrl, lane))
1808
1960
  .map((key) => {
1809
- const pool = postgresPools.get(key);
1810
- postgresPools.delete(key);
1811
- return pool;
1961
+ const cached = postgresPools.get(key);
1962
+ if (!cached) return null;
1963
+ return retireRuntimePostgresPool(key, cached);
1812
1964
  })
1813
- .filter((pool): pool is RuntimePool => pool !== undefined);
1814
- await Promise.all(
1815
- pools.map((pool) => Promise.resolve(pool.end()).catch(() => {})),
1816
- );
1965
+ .filter((retirement): retirement is Promise<void> => retirement !== null);
1966
+ await Promise.all(retirements);
1967
+ }
1968
+
1969
+ async function awaitRuntimePostgresOperation<T>(input: {
1970
+ client: RuntimePoolClient;
1971
+ deadlineAt: number | null;
1972
+ operation: Promise<T>;
1973
+ signal?: AbortSignal | null;
1974
+ }): Promise<T> {
1975
+ if (input.deadlineAt === null && !input.signal) return await input.operation;
1976
+ const remainingMs =
1977
+ input.deadlineAt === null
1978
+ ? null
1979
+ : Math.max(0, input.deadlineAt - Date.now());
1980
+ if (remainingMs === 0 || input.signal?.aborted) {
1981
+ const error =
1982
+ input.signal?.reason ??
1983
+ new Error('Runtime Postgres operation exceeded its total deadline.');
1984
+ if (input.client.destroy) input.client.destroy(error);
1985
+ else input.client.release(true);
1986
+ throw error;
1987
+ }
1988
+ return await new Promise<T>((resolve, reject) => {
1989
+ let timeout: ReturnType<typeof setTimeout> | null = null;
1990
+ const cleanup = () => {
1991
+ if (timeout) clearTimeout(timeout);
1992
+ input.signal?.removeEventListener('abort', onAbort);
1993
+ };
1994
+ const rejectAndDestroy = (error: unknown) => {
1995
+ const normalizedError =
1996
+ error instanceof Error ? error : new Error(String(error));
1997
+ if (input.client.destroy) input.client.destroy(normalizedError);
1998
+ else input.client.release(true);
1999
+ cleanup();
2000
+ reject(error);
2001
+ };
2002
+ const onAbort = () =>
2003
+ rejectAndDestroy(
2004
+ input.signal?.reason ??
2005
+ new Error('Runtime Postgres operation aborted.'),
2006
+ );
2007
+ if (remainingMs !== null) {
2008
+ timeout = setTimeout(
2009
+ () =>
2010
+ rejectAndDestroy(
2011
+ new Error(
2012
+ `Runtime Postgres operation exceeded its ${remainingMs}ms remaining deadline.`,
2013
+ ),
2014
+ ),
2015
+ remainingMs,
2016
+ );
2017
+ }
2018
+ input.signal?.addEventListener('abort', onAbort, { once: true });
2019
+ input.operation.then(
2020
+ (value) => {
2021
+ cleanup();
2022
+ resolve(value);
2023
+ },
2024
+ (error) => {
2025
+ cleanup();
2026
+ reject(error);
2027
+ },
2028
+ );
2029
+ });
1817
2030
  }
1818
2031
 
1819
2032
  async function withRuntimePostgres<T>(
@@ -1825,12 +2038,15 @@ async function withRuntimePostgres<T>(
1825
2038
  options: {
1826
2039
  cachePool?: boolean;
1827
2040
  maxConnectAttempts?: number;
2041
+ operationTimeoutMs?: number;
1828
2042
  signal?: AbortSignal | null;
1829
2043
  } = {},
1830
2044
  ): Promise<T> {
1831
2045
  let client: RuntimePoolClient | null = null;
1832
2046
  let requestLocalPool: RuntimePool | null = null;
2047
+ let poolLease: RuntimePostgresPoolLease | null = null;
1833
2048
  let roleTransactionStarted = false;
2049
+ let operationDeadlineAt: number | null = null;
1834
2050
  const cachePool =
1835
2051
  (options.cachePool ?? true) && canReuseRuntimePostgresPoolsAcrossRequests();
1836
2052
  const maxConnectAttempts =
@@ -1841,7 +2057,8 @@ async function withRuntimePostgres<T>(
1841
2057
  const admission = getRuntimePostgresAdmission(session.postgresUrl);
1842
2058
  for (let attempt = 1; attempt <= maxConnectAttempts; attempt += 1) {
1843
2059
  try {
1844
- const pool = getPostgresPool(session.postgresUrl, lane, cachePool);
2060
+ poolLease = getPostgresPoolLease(session.postgresUrl, lane, cachePool);
2061
+ const pool = poolLease.pool;
1845
2062
  if (!cachePool) {
1846
2063
  requestLocalPool = pool;
1847
2064
  }
@@ -1852,18 +2069,37 @@ async function withRuntimePostgres<T>(
1852
2069
  session.postgresUrl,
1853
2070
  options.signal,
1854
2071
  );
2072
+ operationDeadlineAt =
2073
+ options.operationTimeoutMs === undefined
2074
+ ? null
2075
+ : Date.now() + Math.max(1, Math.floor(options.operationTimeoutMs));
1855
2076
  if (session.executionRole) {
1856
- await client.query('BEGIN');
2077
+ await awaitRuntimePostgresOperation({
2078
+ client,
2079
+ deadlineAt: operationDeadlineAt,
2080
+ operation: client.query('BEGIN'),
2081
+ signal: options.signal,
2082
+ });
1857
2083
  roleTransactionStarted = true;
1858
- await client.query(
1859
- `SET LOCAL ROLE ${quoteIdentifier(session.executionRole)}`,
1860
- );
2084
+ await awaitRuntimePostgresOperation({
2085
+ client,
2086
+ deadlineAt: operationDeadlineAt,
2087
+ operation: client.query(
2088
+ `SET LOCAL ROLE ${quoteIdentifier(session.executionRole)}`,
2089
+ ),
2090
+ signal: options.signal,
2091
+ });
1861
2092
  }
1862
2093
  break;
1863
2094
  } catch (error) {
1864
2095
  if (client) {
1865
2096
  if (roleTransactionStarted) {
1866
- await client.query('ROLLBACK').catch(() => {});
2097
+ await awaitRuntimePostgresOperation({
2098
+ client,
2099
+ deadlineAt: operationDeadlineAt,
2100
+ operation: client.query('ROLLBACK'),
2101
+ signal: options.signal,
2102
+ }).catch(() => {});
1867
2103
  roleTransactionStarted = false;
1868
2104
  }
1869
2105
  client.release();
@@ -1875,17 +2111,15 @@ async function withRuntimePostgres<T>(
1875
2111
  // the backpressure in the first place.
1876
2112
  const preserveSharedPool =
1877
2113
  cachePool && isRuntimePostgresAdmissionError(error);
1878
- if (cachePool && !preserveSharedPool) {
2114
+ if (cachePool && !preserveSharedPool && poolLease?.cached) {
1879
2115
  const poolKey = runtimePostgresPoolKey(session.postgresUrl, lane);
1880
- const pool = postgresPools.get(poolKey);
1881
- postgresPools.delete(poolKey);
1882
- if (pool) {
1883
- await Promise.resolve(pool.end()).catch(() => {});
1884
- }
2116
+ await retireRuntimePostgresPool(poolKey, poolLease.cached);
1885
2117
  } else if (requestLocalPool) {
1886
2118
  await Promise.resolve(requestLocalPool.end()).catch(() => {});
1887
2119
  requestLocalPool = null;
1888
2120
  }
2121
+ await poolLease?.release();
2122
+ poolLease = null;
1889
2123
  if (
1890
2124
  attempt >= maxConnectAttempts ||
1891
2125
  !isTransientRuntimePostgresConnectionError(error)
@@ -1904,20 +2138,36 @@ async function withRuntimePostgres<T>(
1904
2138
  throw new Error('Runtime Postgres connection was not acquired.');
1905
2139
  }
1906
2140
  try {
1907
- const result = await fn(client, { active: roleTransactionStarted });
2141
+ const result = await awaitRuntimePostgresOperation({
2142
+ client,
2143
+ deadlineAt: operationDeadlineAt,
2144
+ operation: fn(client, { active: roleTransactionStarted }),
2145
+ signal: options.signal,
2146
+ });
1908
2147
  if (roleTransactionStarted) {
1909
- await client.query('COMMIT');
2148
+ await awaitRuntimePostgresOperation({
2149
+ client,
2150
+ deadlineAt: operationDeadlineAt,
2151
+ operation: client.query('COMMIT'),
2152
+ signal: options.signal,
2153
+ });
1910
2154
  roleTransactionStarted = false;
1911
2155
  }
1912
2156
  return result;
1913
2157
  } catch (error) {
1914
2158
  if (roleTransactionStarted) {
1915
- await client.query('ROLLBACK').catch(() => {});
2159
+ await awaitRuntimePostgresOperation({
2160
+ client,
2161
+ deadlineAt: operationDeadlineAt,
2162
+ operation: client.query('ROLLBACK'),
2163
+ signal: options.signal,
2164
+ }).catch(() => {});
1916
2165
  roleTransactionStarted = false;
1917
2166
  }
1918
2167
  throw error;
1919
2168
  } finally {
1920
2169
  client.release();
2170
+ await poolLease?.release();
1921
2171
  if (requestLocalPool) {
1922
2172
  await Promise.resolve(requestLocalPool.end()).catch(() => {});
1923
2173
  }
@@ -2083,6 +2333,7 @@ async function withRuntimeSheetQueryClient<T>(
2083
2333
  tableNamespace: string;
2084
2334
  sheetContract: PlaySheetContract;
2085
2335
  transactional: boolean;
2336
+ statementTimeoutMs?: number;
2086
2337
  timings?: RuntimeSheetTiming[];
2087
2338
  },
2088
2339
  operation: (client: RuntimeQueryClient) => Promise<T>,
@@ -2092,7 +2343,11 @@ async function withRuntimeSheetQueryClient<T>(
2092
2343
  context,
2093
2344
  input,
2094
2345
  async () => {
2095
- if (!input.transactional && isRuntimeOneShotQueryFactoryRegistered()) {
2346
+ if (
2347
+ !input.transactional &&
2348
+ input.statementTimeoutMs === undefined &&
2349
+ isRuntimeOneShotQueryFactoryRegistered()
2350
+ ) {
2096
2351
  const operationStartedAt = Date.now();
2097
2352
  return await withRuntimeOneShotPostgres(session, operation).finally(
2098
2353
  () => {
@@ -2107,9 +2362,16 @@ async function withRuntimeSheetQueryClient<T>(
2107
2362
  return await withRuntimePostgres(
2108
2363
  session,
2109
2364
  async (client, transaction) => {
2110
- const managesTransaction = input.transactional && !transaction.active;
2365
+ const managesTransaction =
2366
+ (input.transactional || input.statementTimeoutMs !== undefined) &&
2367
+ !transaction.active;
2111
2368
  if (managesTransaction) await client.query('BEGIN');
2112
2369
  try {
2370
+ if (input.statementTimeoutMs !== undefined) {
2371
+ await client.query(
2372
+ `SET LOCAL statement_timeout TO ${Math.max(1, Math.floor(input.statementTimeoutMs))}`,
2373
+ );
2374
+ }
2113
2375
  const result = await operation(client);
2114
2376
  if (managesTransaction) await client.query('COMMIT');
2115
2377
  return result;
@@ -2122,6 +2384,12 @@ async function withRuntimeSheetQueryClient<T>(
2122
2384
  },
2123
2385
  {
2124
2386
  cachePool: !context.disablePostgresPoolCache,
2387
+ maxConnectAttempts:
2388
+ input.statementTimeoutMs === undefined ? undefined : 1,
2389
+ operationTimeoutMs:
2390
+ input.statementTimeoutMs === undefined
2391
+ ? undefined
2392
+ : RUNTIME_SHEET_COMPLETION_SERVER_OPERATION_TIMEOUT_MS,
2125
2393
  signal: context.abortSignal,
2126
2394
  },
2127
2395
  );
@@ -6695,6 +6963,8 @@ export type RuntimeMapRowsWriteResult = {
6695
6963
  fencedKeys: string[];
6696
6964
  staleDropped?: number;
6697
6965
  staleDroppedKeys?: string[];
6966
+ /** Synthetic Preview-only proof that the gateway applied the page-tail hold. */
6967
+ runtimeTestPageTailHoldMs?: number;
6698
6968
  };
6699
6969
 
6700
6970
  type RuntimeMapRowsMutationResult = {
@@ -7802,6 +8072,9 @@ export async function completeRuntimeMapRows(
7802
8072
  }),
7803
8073
  // Preserve the gateway's compatibility classification during rollout.
7804
8074
  fencedKeys: result.fencedKeys,
8075
+ ...(typeof result.runtimeTestPageTailHoldMs === 'number'
8076
+ ? { runtimeTestPageTailHoldMs: result.runtimeTestPageTailHoldMs }
8077
+ : {}),
7805
8078
  };
7806
8079
  }
7807
8080
  const sheetContract = augmentSheetContractWithDatasetFields({
@@ -7897,6 +8170,7 @@ export async function completeRuntimeMapRows(
7897
8170
  tableNamespace: input.tableNamespace,
7898
8171
  sheetContract,
7899
8172
  transactional: needsTransaction,
8173
+ statementTimeoutMs: RUNTIME_SHEET_COMPLETION_STATEMENT_TIMEOUT_MS,
7900
8174
  },
7901
8175
  async (client) => {
7902
8176
  const completed =
@@ -23,6 +23,8 @@ export const RUNTIME_CAPACITY_POLICY = {
23
23
  },
24
24
  receiptGateway: {
25
25
  admissionTimeoutMs: 10_000,
26
+ /** End identical receipt-row work before the caller can overlap a retry. */
27
+ claimOperationTimeoutMs: 20_000,
26
28
  requestTimeoutMs: 30_000,
27
29
  },
28
30
  } as const;
@@ -103,6 +105,14 @@ export function assertRuntimeCapacityPolicy(): void {
103
105
  'Receipt gateway request timeout must exceed its admission timeout.',
104
106
  );
105
107
  }
108
+ if (
109
+ RUNTIME_CAPACITY_POLICY.receiptGateway.claimOperationTimeoutMs + 5_000 >
110
+ RUNTIME_CAPACITY_POLICY.receiptGateway.requestTimeoutMs
111
+ ) {
112
+ throw new Error(
113
+ 'Receipt claim operations must leave at least five seconds for cancellation and response delivery.',
114
+ );
115
+ }
106
116
  if (
107
117
  RUNTIME_CAPACITY_POLICY.absurd.perOrgConcurrency >
108
118
  ABSURD_GLOBAL_RUN_CONCURRENCY
@@ -1,4 +1,4 @@
1
- export const PLAY_RUNTIME_CONTRACT = 4;
1
+ export const PLAY_RUNTIME_CONTRACT = 5;
2
2
  export const PLAY_RUNTIME_CONTRACT_MIN_SUPPORTED = 1;
3
3
  export const PLAY_RUNTIME_CONTRACT_HEADER = 'x-deepline-runtime-contract';
4
4
  /** Shared runner/gateway envelope limit for unacknowledged progress recovery. */
@@ -27,6 +27,13 @@ export function resolvePlayRuntimeTransportAttemptId(
27
27
  }
28
28
 
29
29
  export const RUNTIME_CONTRACT_CHANGELOG = [
30
+ {
31
+ contract: 5,
32
+ changed:
33
+ 'Runtime Sheet completion and lazy launch lookup use bounded, cancellation-aware gateway operations.',
34
+ compatOwner: 'runtime',
35
+ compatRemoval: 'contract 4 runtime lane drain',
36
+ },
30
37
  {
31
38
  contract: 4,
32
39
  changed: