deepline 0.3.475 → 0.3.477

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 (162) hide show
  1. package/dist/bundling-sources/sdk/src/play.ts +2 -2
  2. package/dist/bundling-sources/sdk/src/release.ts +3 -3
  3. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +19 -6
  4. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +4 -4
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +33 -0
  6. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +90 -1
  7. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +1 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +3 -0
  9. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +21 -4
  10. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +6 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +13 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +12 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +6 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-scope.ts +8 -6
  15. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +167 -7
  16. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +104 -6
  17. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +42 -0
  18. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +1 -1
  19. package/dist/cli/auth-QITP34OX.js +41 -0
  20. package/dist/cli/{auth-CFDYJ7FZ.mjs → auth-XVXRRWIQ.mjs} +5 -5
  21. package/dist/cli/{billing-XLIL3FLK.js → billing-2H7GFJDT.js} +79 -79
  22. package/dist/cli/{billing-PEXWWLRT.mjs → billing-EOPUJ4JI.mjs} +6 -6
  23. package/dist/cli/{chunk-TLPW2SHS.js → chunk-23EEVP5W.js} +6 -6
  24. package/dist/cli/{chunk-ZQYBKDPA.js → chunk-255O6APS.js} +2 -2
  25. package/dist/cli/{chunk-D6SXN34W.mjs → chunk-2GZFIWSA.mjs} +4 -4
  26. package/dist/cli/{chunk-M7K5BCWP.js → chunk-2QT6KN54.js} +10 -10
  27. package/dist/cli/{chunk-MUDOASYL.mjs → chunk-2YNLW43I.mjs} +2 -2
  28. package/dist/cli/{chunk-BSK2TGB4.js → chunk-347EE4YE.js} +8 -8
  29. package/dist/cli/{chunk-V4ILQ2DS.mjs → chunk-3A7TDUZV.mjs} +1 -1
  30. package/dist/cli/{chunk-G4SGYWKV.js → chunk-3UKKM6OS.js} +6 -6
  31. package/dist/cli/{chunk-NBVYS6VG.js → chunk-3W6E3WVU.js} +9 -9
  32. package/dist/cli/{chunk-F4FNMXI3.js → chunk-4RAENEAX.js} +53 -53
  33. package/dist/cli/{chunk-42F6LTBJ.mjs → chunk-6IJJCRE5.mjs} +3 -3
  34. package/dist/cli/{chunk-CKSRTD45.js → chunk-6KKDIISC.js} +3 -3
  35. package/dist/cli/{chunk-7SWRXZ4A.js → chunk-75NLWBYQ.js} +93 -93
  36. package/dist/cli/{chunk-KTWXY73Q.js → chunk-7CILZKLE.js} +5 -5
  37. package/dist/cli/{chunk-B7NJ5JQ2.js → chunk-ACDXGOXE.js} +4 -4
  38. package/dist/cli/{chunk-FC4C7MZM.mjs → chunk-C2AN2HTB.mjs} +4 -4
  39. package/dist/cli/{chunk-EA45HSEC.mjs → chunk-C2V2HQDW.mjs} +2 -2
  40. package/dist/cli/{chunk-YMS7VYFA.mjs → chunk-C46DWB56.mjs} +1 -1
  41. package/dist/cli/{chunk-3MQJJ6YX.js → chunk-CH4VMDB5.js} +51 -51
  42. package/dist/cli/{chunk-7U3L2S7X.mjs → chunk-DFKHXZQ7.mjs} +10 -10
  43. package/dist/cli/{chunk-VBGPRRJ6.js → chunk-DMWHEYZM.js} +2 -2
  44. package/dist/cli/{chunk-3WVA7E7O.mjs → chunk-DS3DZUES.mjs} +2 -2
  45. package/dist/cli/{chunk-Z5VSBCFV.js → chunk-E2CUSH56.js} +16 -16
  46. package/dist/cli/{chunk-AD7KL6AI.mjs → chunk-ISEUOYCB.mjs} +97 -12
  47. package/dist/cli/{chunk-VFP3MFLD.mjs → chunk-JS4UO6G4.mjs} +1 -1
  48. package/dist/cli/{chunk-HMHH7JQD.mjs → chunk-KMXBZIKO.mjs} +2 -2
  49. package/dist/cli/{chunk-M4QO3GRD.js → chunk-KWQNNPMH.js} +3 -3
  50. package/dist/cli/{chunk-3QSFSHCJ.mjs → chunk-LJYTYNQZ.mjs} +1 -1
  51. package/dist/cli/{chunk-TZRTT7EI.js → chunk-LXKGZKTL.js} +26 -26
  52. package/dist/cli/{chunk-U3F63UWD.js → chunk-M2UL4ZS5.js} +7 -7
  53. package/dist/cli/{chunk-DKR7QPEO.js → chunk-MZSLFWOT.js} +1 -1
  54. package/dist/cli/{chunk-BLW2K6YH.js → chunk-NC52JG66.js} +3 -3
  55. package/dist/cli/{chunk-HALAYT3Y.mjs → chunk-NJMYX2TP.mjs} +3 -3
  56. package/dist/cli/{chunk-NHQP4UPJ.mjs → chunk-NO7ZC2GV.mjs} +5 -5
  57. package/dist/cli/{chunk-YAAQNDP6.mjs → chunk-PQQNMMWM.mjs} +3 -3
  58. package/dist/cli/{chunk-BVAYLXMH.mjs → chunk-QODW5ILA.mjs} +2 -2
  59. package/dist/cli/{chunk-QKMWQTMI.js → chunk-R5FNCFEK.js} +3 -3
  60. package/dist/cli/{chunk-7XTBEYLS.js → chunk-RGNMMDSJ.js} +131 -46
  61. package/dist/cli/{chunk-6PRYHABU.mjs → chunk-RHIFH3GD.mjs} +1 -1
  62. package/dist/cli/{chunk-4QHAP3MR.mjs → chunk-RLCAECZH.mjs} +5 -5
  63. package/dist/cli/{chunk-DIMKSDF6.mjs → chunk-SG2SZQ4K.mjs} +2 -2
  64. package/dist/cli/{chunk-VTCXSICG.mjs → chunk-SVU2VR6Q.mjs} +2 -2
  65. package/dist/cli/{chunk-D3Q4FUFS.js → chunk-THMGJOR4.js} +8 -8
  66. package/dist/cli/{chunk-4QHQ3WYW.js → chunk-UBNS6GSV.js} +21 -21
  67. package/dist/cli/{chunk-DUGZKPRK.mjs → chunk-UYA7LJGC.mjs} +2 -2
  68. package/dist/cli/{chunk-5QS4T4KR.js → chunk-WFVEMDQL.js} +2 -2
  69. package/dist/cli/{chunk-M27SSAQ7.mjs → chunk-WRHLAAGN.mjs} +1 -1
  70. package/dist/cli/{chunk-VD3E3QMI.mjs → chunk-XQLYPHYW.mjs} +1 -1
  71. package/dist/cli/{chunk-NGOKBSBH.mjs → chunk-YZA5UJXM.mjs} +1 -1
  72. package/dist/cli/{chunk-ZANUM4XV.js → chunk-ZDPL5AIK.js} +5 -5
  73. package/dist/cli/command-docs.js +2 -2
  74. package/dist/cli/command-docs.mjs +1 -1
  75. package/dist/cli/{csv-4YHSYV2C.js → csv-ECB7TYR2.js} +13 -13
  76. package/dist/cli/{csv-WC7ILC7P.mjs → csv-IDRPTSSC.mjs} +4 -4
  77. package/dist/cli/{db-I5PV6NTC.mjs → db-FOBZD44N.mjs} +6 -6
  78. package/dist/cli/{db-APZNM2GI.js → db-S74XZZ77.js} +27 -27
  79. package/dist/cli/{describe-XH5F3X5W.mjs → describe-KMTPPI2J.mjs} +17 -17
  80. package/dist/cli/describe-PI7HCT7K.js +48 -0
  81. package/dist/cli/describe-core.d.mts +3 -3
  82. package/dist/cli/describe-core.d.ts +3 -3
  83. package/dist/cli/{executions-PIY4XM7T.mjs → executions-4JAIVE2E.mjs} +6 -6
  84. package/dist/cli/{executions-VIXFKZYU.js → executions-ZE3JMNA4.js} +8 -8
  85. package/dist/cli/{feedback-IGDHBWGT.mjs → feedback-EUX5PDBA.mjs} +4 -4
  86. package/dist/cli/{feedback-WCLIFQTR.js → feedback-W6VA47A3.js} +8 -8
  87. package/dist/cli/git-NDOGCQZY.js +25 -0
  88. package/dist/cli/{git-FRICPPTA.mjs → git-UOVG4PLH.mjs} +5 -5
  89. package/dist/cli/index.js +139 -139
  90. package/dist/cli/index.mjs +41 -41
  91. package/dist/cli/{legacy-noops-DSFOPYDY.js → legacy-noops-3H35EH5Y.js} +8 -8
  92. package/dist/cli/{legacy-noops-ROAXEG6D.mjs → legacy-noops-ZBWUNFWV.mjs} +5 -5
  93. package/dist/cli/{monitors-FZ5PRLW6.mjs → monitors-AYSGSUAK.mjs} +7 -7
  94. package/dist/cli/{monitors-KUCYQLU5.js → monitors-PI3KPLR2.js} +91 -91
  95. package/dist/cli/{org-DRNND5XU.mjs → org-2BIG7U4V.mjs} +6 -6
  96. package/dist/cli/{org-SRHMWKQR.js → org-MVS3Y6BK.js} +72 -72
  97. package/dist/cli/{pages-XNGA356T.mjs → pages-4TRRL24G.mjs} +6 -6
  98. package/dist/cli/{pages-TEJQIHTU.js → pages-VB6FOVDE.js} +17 -17
  99. package/dist/cli/{play-GCGFQSFP.js → play-BLT6IT2O.js} +340 -323
  100. package/dist/cli/{play-NE45ROIZ.mjs → play-JR3VUOZM.mjs} +37 -20
  101. package/dist/cli/{providers-57TJOSMV.mjs → providers-547DPK4S.mjs} +6 -6
  102. package/dist/cli/{providers-VHMUR63R.js → providers-BBXIAGW4.js} +9 -9
  103. package/dist/cli/{quickstart-K3SH4NXP.mjs → quickstart-NTLXD7HS.mjs} +4 -4
  104. package/dist/cli/{quickstart-BPXWO4UD.js → quickstart-RNPI5B6J.js} +6 -6
  105. package/dist/cli/{run-553AAXDU.js → run-BCCEXBOG.js} +36 -36
  106. package/dist/cli/{run-ZRWJUEN2.mjs → run-QZ4SFECD.mjs} +22 -22
  107. package/dist/cli/{search-BW6XRS4I.mjs → search-PYNELH4C.mjs} +7 -7
  108. package/dist/cli/search-YHEICYQL.js +32 -0
  109. package/dist/cli/{secrets-OYYCFGZE.mjs → secrets-O5WGREQ7.mjs} +6 -6
  110. package/dist/cli/{secrets-AMYQYUL5.js → secrets-VD35LMYJ.js} +15 -15
  111. package/dist/cli/{sessions-O4SVVOMO.mjs → sessions-2TL6Z5J5.mjs} +5 -5
  112. package/dist/cli/{sessions-RYCJUMJ2.js → sessions-NGGIBYXF.js} +6 -6
  113. package/dist/cli/{settings-QK7DHDU7.mjs → settings-6KM74AJT.mjs} +11 -11
  114. package/dist/cli/{settings-7R5FQKII.js → settings-ERFNZRHO.js} +78 -78
  115. package/dist/cli/{setup-OTQDQHDN.js → setup-6X7EJ6RG.js} +44 -44
  116. package/dist/cli/{setup-VDAAVCKK.mjs → setup-Y3BN6XRS.mjs} +9 -9
  117. package/dist/cli/skills-F7MPYC4P.js +33 -0
  118. package/dist/cli/{skills-HRK5O5JO.mjs → skills-OUBQASHI.mjs} +9 -7
  119. package/dist/cli/{tools-O7BM62QV.mjs → tools-EELZ5FF5.mjs} +16 -16
  120. package/dist/cli/tools-MCWDAK53.js +55 -0
  121. package/dist/cli/update-G5BRJOVT.js +33 -0
  122. package/dist/cli/{update-UHKHCSTY.mjs → update-TX7SW32Y.mjs} +9 -9
  123. package/dist/cli/{workflow-7Z5NCTGG.js → workflow-5AI2BJA7.js} +17 -17
  124. package/dist/cli/{workflow-T2EDR7L4.mjs → workflow-PUNQG7LG.mjs} +7 -7
  125. package/dist/{client-Bgl0_zXi.d.mts → client-AjmfA5nR.d.mts} +1 -1
  126. package/dist/{client-_ah6IuhK.d.ts → client-DMyY-QL8.d.ts} +1 -1
  127. package/dist/{client-core-C-ZF5vh9.d.ts → client-core-C705vFyV.d.ts} +1 -1
  128. package/dist/{client-core-Bfsedm0_.d.mts → client-core-CpBbvLEX.d.mts} +1 -1
  129. package/dist/{compiler-manifest-CCOqDGjU.d.mts → compiler-manifest-hqIAukR0.d.mts} +1 -1
  130. package/dist/{compiler-manifest-CCOqDGjU.d.ts → compiler-manifest-hqIAukR0.d.ts} +1 -1
  131. package/dist/{errors-Cy3kPnsi.d.ts → errors-CRO2qQ6k.d.ts} +1 -1
  132. package/dist/{errors-BLccD7tG.d.mts → errors-DIHVetNY.d.mts} +1 -1
  133. package/dist/{index-D5e4h1-H.d.ts → index-B8DVoeYt.d.ts} +1 -1
  134. package/dist/{index-4-j44HuT.d.mts → index-DVZ_X4Gb.d.mts} +1 -1
  135. package/dist/index.d.mts +9 -9
  136. package/dist/index.d.ts +9 -9
  137. package/dist/index.js +9 -9
  138. package/dist/index.mjs +6 -6
  139. package/dist/install-integrity.json +110 -110
  140. package/dist/plays/bundle-play-file.d.mts +3 -3
  141. package/dist/plays/bundle-play-file.d.ts +3 -3
  142. package/dist/plays/bundle-play-file.mjs +1 -1
  143. package/dist/plays/play-description.d.mts +2 -2
  144. package/dist/plays/play-description.d.ts +2 -2
  145. package/dist/plays/run-request.d.mts +2 -2
  146. package/dist/plays/run-request.d.ts +2 -2
  147. package/dist/release.d.mts +3 -3
  148. package/dist/release.d.ts +3 -3
  149. package/dist/release.js +2 -2
  150. package/dist/release.mjs +1 -1
  151. package/dist/server.d.mts +4 -4
  152. package/dist/server.d.ts +4 -4
  153. package/dist/server.js +5 -5
  154. package/dist/server.mjs +3 -3
  155. package/package.json +1 -1
  156. package/dist/cli/auth-7GVCW24N.js +0 -41
  157. package/dist/cli/describe-OR7FZBCD.js +0 -48
  158. package/dist/cli/git-CKN2F32Y.js +0 -25
  159. package/dist/cli/search-WF26AUDJ.js +0 -32
  160. package/dist/cli/skills-VNLMEOXW.js +0 -31
  161. package/dist/cli/tools-C3MUHL7G.js +0 -55
  162. package/dist/cli/update-TCKTU6QJ.js +0 -33
@@ -38,6 +38,26 @@ const RATE_SCOPE_HOLD_TABLE = 'rate_scope_holds';
38
38
  const PHYSICAL_DISPATCH_RULE_ID = 'runtime:physical-dispatch';
39
39
 
40
40
  const MIN_CONCURRENCY_WAIT_MS = 10;
41
+ /**
42
+ * Longest re-ask when every concurrency slot is taken. A full scope re-asks
43
+ * about when its next slot should free: the average age of its live leases
44
+ * divided by their count. A fast provider stays at the 10 ms floor; one whose
45
+ * calls hold a slot for minutes no longer re-locks its scope row every 10 ms.
46
+ * On 2026-11-06 five such runs re-asked about 160 times a second for hours.
47
+ */
48
+ const MAX_CONCURRENCY_WAIT_MS = 1_000;
49
+ /**
50
+ * A healthy scope lock is held for milliseconds. An acquire that cannot get it
51
+ * within this budget stops waiting on the server instead of leaving a backend
52
+ * queued behind a stuck holder after its caller gave up. On 2026-11-06 one
53
+ * stuck holder queued such abandoned waits for 16 minutes until they exhausted
54
+ * the shared scheduler pool.
55
+ */
56
+ const RATE_STATE_LOCK_TIMEOUT_MS = 3_000;
57
+ /** Ends a bounded acquire statement before the gateway's 30 s caller budget. */
58
+ const RATE_STATE_STATEMENT_TIMEOUT_MS = 15_000;
59
+ /** Re-ask after a lock timeout, as for any other busy scope. */
60
+ const RATE_STATE_LOCK_BUSY_WAIT_MS = 1_000;
41
61
  /**
42
62
  * Longest a requester held back by the fair-share cap sleeps before it re-asks.
43
63
  * The natural wait is the rest of the current fair-share window (a sibling's
@@ -771,6 +791,32 @@ async function seedBucketRows(
771
791
  },
772
792
  seeds: RateBucketSeed[],
773
793
  nowMs: number | null,
794
+ ): Promise<void> {
795
+ // Cold path only, so its own bounded transaction costs two round trips once
796
+ // per bucket per process. Its insert waits on the same scope row as a grant.
797
+ await client.query('BEGIN');
798
+ try {
799
+ await boundRateStateTransaction(client);
800
+ await seedBucketRowsStatement(client, input, seeds, nowMs);
801
+ await client.query('COMMIT');
802
+ } catch (error) {
803
+ try {
804
+ await client.query('ROLLBACK');
805
+ } catch {
806
+ // Preserve the original seed/storage error.
807
+ }
808
+ throw rateStateLockBusyOr(error);
809
+ }
810
+ }
811
+
812
+ async function seedBucketRowsStatement(
813
+ client: PostgresSchedulerQueryClient,
814
+ input: { bucketId: string } & {
815
+ schema?: string | null;
816
+ schedulerSchema?: string | null;
817
+ },
818
+ seeds: RateBucketSeed[],
819
+ nowMs: number | null,
774
820
  ): Promise<void> {
775
821
  await client.query(
776
822
  `
@@ -1797,6 +1843,7 @@ async function acquireWithConcurrency(
1797
1843
  while (true) {
1798
1844
  await client.query('BEGIN ISOLATION LEVEL READ COMMITTED');
1799
1845
  try {
1846
+ await boundRateStateTransaction(client);
1800
1847
  await client.query(
1801
1848
  `SELECT bucket_id FROM ${scopeHoldTableName(input)}
1802
1849
  WHERE bucket_id = $1 FOR UPDATE`,
@@ -1824,11 +1871,53 @@ async function acquireWithConcurrency(
1824
1871
  probeBaselineSupported = false;
1825
1872
  continue;
1826
1873
  }
1827
- throw error;
1874
+ throw rateStateLockBusyOr(error);
1828
1875
  }
1829
1876
  }
1830
1877
  }
1831
1878
 
1879
+ /**
1880
+ * Bound the current rate-state transaction. `SET LOCAL` keeps the limits inside
1881
+ * this transaction: the scheduler pool runs behind PgBouncer transaction
1882
+ * pooling, where a session setting would leak to another caller.
1883
+ */
1884
+ async function boundRateStateTransaction(
1885
+ client: PostgresSchedulerQueryClient,
1886
+ ): Promise<void> {
1887
+ await client.query(
1888
+ `SELECT set_config('lock_timeout', $1, true),
1889
+ set_config('statement_timeout', $2, true)`,
1890
+ [`${RATE_STATE_LOCK_TIMEOUT_MS}ms`, `${RATE_STATE_STATEMENT_TIMEOUT_MS}ms`],
1891
+ );
1892
+ }
1893
+
1894
+ /** A scope row another transaction still holds after the lock budget. */
1895
+ class RateStateLockBusyError extends Error {
1896
+ constructor(cause: unknown) {
1897
+ super('Rate-state scope lock was not available within its budget.', {
1898
+ cause,
1899
+ });
1900
+ this.name = 'RateStateLockBusyError';
1901
+ }
1902
+ }
1903
+
1904
+ /**
1905
+ * A bounded rate-state transaction that ran out of its lock or statement
1906
+ * budget (57014 is statement_timeout).
1907
+ */
1908
+ function isRateStateBusy(error: unknown): boolean {
1909
+ return (
1910
+ error instanceof RateStateLockBusyError ||
1911
+ (error as { code?: unknown } | null)?.code === '57014'
1912
+ );
1913
+ }
1914
+
1915
+ function rateStateLockBusyOr(error: unknown): unknown {
1916
+ return (error as { code?: unknown } | null)?.code === '55P03'
1917
+ ? new RateStateLockBusyError(error)
1918
+ : error;
1919
+ }
1920
+
1832
1921
  async function grantWithConcurrency(
1833
1922
  client: PostgresSchedulerQueryClient,
1834
1923
  input: RateStateAcquireInput,
@@ -2315,7 +2404,29 @@ async function grantWithConcurrency(
2315
2404
  (SELECT cool_until_ms FROM cooling),
2316
2405
  (SELECT retry_at_ms FROM cooling)
2317
2406
  ) - (SELECT now_ms FROM refreshed LIMIT 1)${fair?.waitBranch ?? ''}
2318
- WHEN (SELECT free FROM free_slots) <= 0 THEN $7::bigint
2407
+ WHEN (SELECT free FROM free_slots) <= 0 THEN (
2408
+ -- Re-ask about when the next slot should free. Leases are never
2409
+ -- renewed, so a lease's age is its TTL minus its remaining life.
2410
+ SELECT LEAST(
2411
+ ${MAX_CONCURRENCY_WAIT_MS}::bigint,
2412
+ GREATEST(
2413
+ $7::bigint,
2414
+ CEIL(
2415
+ COALESCE(
2416
+ AVG(
2417
+ (SELECT now_ms FROM now_value)
2418
+ - (cl.expires_at_ms - $8::bigint)
2419
+ ),
2420
+ 0
2421
+ ) / GREATEST((SELECT active_count FROM concurrency_context), 1)
2422
+ )::bigint
2423
+ )
2424
+ )
2425
+ FROM ${concurrencyLeaseTableName(input)} cl
2426
+ WHERE cl.bucket_id = $1
2427
+ AND cl.rule_id = ANY($5::text[])
2428
+ AND cl.expires_at_ms > (SELECT now_ms FROM now_value)
2429
+ )
2319
2430
  ELSE LEAST((SELECT pending_recovery_wait_ms FROM provider_hold), (
2320
2431
  SELECT CEIL(
2321
2432
  MAX(
@@ -2417,9 +2528,24 @@ export async function acquirePostgresRateState(
2417
2528
  client: PostgresSchedulerQueryClient,
2418
2529
  input: RateStateAcquireInput,
2419
2530
  ): Promise<PlayRunnerRateStateAcquireResult> {
2420
- return await withRateStateAvailabilityGuard(input, () =>
2421
- acquireRateStateStatements(client, input),
2422
- );
2531
+ return await withRateStateAvailabilityGuard(input, async () => {
2532
+ try {
2533
+ return await acquireRateStateStatements(client, input);
2534
+ } catch (error) {
2535
+ // A store error spends the runner's fail-closed retry ladder, so a stuck
2536
+ // holder elsewhere would fail this run. A busy scope is not a broken
2537
+ // store: answer as for any other wait. Feedback rolled back with a busy
2538
+ // feedback step is not re-sent, because the runner acknowledges any
2539
+ // answer. A lost success sample only slows the next ramp, and its batch
2540
+ // identity still prevents a double apply.
2541
+ if (!(error instanceof RateStateLockBusyError)) throw error;
2542
+ return {
2543
+ granted: 0,
2544
+ waitMs: RATE_STATE_LOCK_BUSY_WAIT_MS,
2545
+ leaseIds: [],
2546
+ };
2547
+ }
2548
+ });
2423
2549
  }
2424
2550
 
2425
2551
  /**
@@ -2838,6 +2964,8 @@ async function applyConcurrencyObservations(
2838
2964
  rules: readonly AcquirePacingRule[],
2839
2965
  observations: readonly ConcurrencyObservation[],
2840
2966
  batch: ConcurrencyObservationBatch | null,
2967
+ /** Acquire bounds its lock waits; release and penalty keep their behaviour. */
2968
+ options: { bounded?: boolean } = {},
2841
2969
  ): Promise<{
2842
2970
  disposition: 'unbatched' | 'applied' | 'already_applied';
2843
2971
  ruleCount: number;
@@ -2895,6 +3023,7 @@ async function applyConcurrencyObservations(
2895
3023
 
2896
3024
  await client.query('BEGIN');
2897
3025
  try {
3026
+ if (options.bounded) await boundRateStateTransaction(client);
2898
3027
  const accepted = await client.query<{ last_sequence: number }>(
2899
3028
  `
2900
3029
  WITH now_value AS (
@@ -3103,7 +3232,7 @@ async function applyConcurrencyObservations(
3103
3232
  } catch {
3104
3233
  // Preserve the original controller/storage error.
3105
3234
  }
3106
- throw error;
3235
+ throw options.bounded ? rateStateLockBusyOr(error) : error;
3107
3236
  }
3108
3237
  }
3109
3238
 
@@ -3143,6 +3272,7 @@ async function acquireRateStateStatements(
3143
3272
  rules,
3144
3273
  concurrencyObservationsFrom(input.concurrencyObservations),
3145
3274
  concurrencyObservationBatchFrom(input.concurrencyObservationBatch),
3275
+ { bounded: true },
3146
3276
  );
3147
3277
  // The buffered success count and RTT observations share one identity. If a
3148
3278
  // runner lost the prior acquire response, its retry still needs a fresh
@@ -3179,7 +3309,16 @@ async function acquireRateStateStatements(
3179
3309
  // so a row widened out of band is repaired here instead of waiting for a
3180
3310
  // process restart. Repairing after the grant keeps the acquire itself to a
3181
3311
  // single granting statement, so no permit is ever issued twice.
3182
- await seedBucketRows(client, input, seeds, nowMs);
3312
+ try {
3313
+ await seedBucketRows(client, input, seeds, nowMs);
3314
+ } catch (error) {
3315
+ // The grant above already committed. A busy scope must not turn its
3316
+ // leases into a zero answer the runner never releases. Leave the bucket
3317
+ // cold so the next acquire repairs before it grants.
3318
+ if (!isRateStateBusy(error)) throw error;
3319
+ warmRateBuckets.delete(warmKey);
3320
+ return acquireResultOf(outcome);
3321
+ }
3183
3322
  }
3184
3323
  markRateBucketWarm(warmKey);
3185
3324
  return acquireResultOf(outcome);
@@ -3199,6 +3338,27 @@ async function releaseRateStateStatements(
3199
3338
  input: PlayRunnerRateStateReleaseInput & { nowMs?: number },
3200
3339
  ): Promise<void> {
3201
3340
  const allRules = normalizeRules(input.rules as AcquirePacingRule[]);
3341
+ if (
3342
+ input.observedSuccesses !== undefined ||
3343
+ input.concurrencyObservations !== undefined ||
3344
+ input.concurrencyObservationBatch !== undefined
3345
+ ) {
3346
+ const batch = concurrencyObservationBatchFrom(
3347
+ input.concurrencyObservationBatch,
3348
+ );
3349
+ if (!batch) {
3350
+ throw new Error(
3351
+ 'Rate-state success feedback requires a replay-safe observation batch.',
3352
+ );
3353
+ }
3354
+ await applyConcurrencyObservations(
3355
+ client,
3356
+ input,
3357
+ allRules,
3358
+ concurrencyObservationsFrom(input.concurrencyObservations),
3359
+ batch,
3360
+ );
3361
+ }
3202
3362
  if (allRules.length === 0) return;
3203
3363
  const refundReservationIds = [
3204
3364
  ...new Set(
@@ -12736,9 +12736,17 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
12736
12736
  if (providers?.length === 0) return [];
12737
12737
  // The v12 table retains deleted history. Avoid planning the lineage repair
12738
12738
  // when neither a legacy row nor a v2 NULL-domain duplicate needs adoption.
12739
- // A row arriving after this read is picked up by the next claim poll; the
12740
- // provider-qualified v2 claim below still runs on every call.
12741
- const adoptionWork = await client.query<{ pending: boolean }>(`
12739
+ // The same read proves whether any v2 row could be claimed: the claim below
12740
+ // is ~15 KB of SQL whose planning costs milliseconds while every idle
12741
+ // worker polls it each second, so it is skipped when nothing is claimable.
12742
+ // `claimable` repeats the claim's own row predicates without the detached
12743
+ // sandbox fence, so it is a superset of what the claim could select. A row
12744
+ // arriving after this read is picked up by the next claim poll.
12745
+ const adoptionWork = await client.query<{
12746
+ pending: boolean;
12747
+ claimable: boolean;
12748
+ }>(
12749
+ `
12742
12750
  SELECT EXISTS (
12743
12751
  SELECT 1 FROM ${tableName('sandbox_cleanup_jobs_v2', options)}
12744
12752
  WHERE provider = 'daytona' AND routing_domain IS NULL
@@ -12746,9 +12754,29 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
12746
12754
  ) OR EXISTS (
12747
12755
  SELECT 1 FROM ${tableName('sandbox_cleanup_jobs', options)}
12748
12756
  WHERE status <> 'deleted'
12749
- ) AS pending
12750
- `);
12751
- if (typeof adoptionWork.rows[0]?.pending !== 'boolean') {
12757
+ ) AS pending,
12758
+ EXISTS (
12759
+ SELECT 1 FROM ${tableName('sandbox_cleanup_jobs_v2', options)} AS cleanup
12760
+ WHERE cleanup.status <> 'deleted' AND cleanup.blocked_at IS NULL
12761
+ AND (cleanup.available_at <= now() OR cleanup.diagnostic_json IS NOT NULL)
12762
+ AND (cleanup.lease_expires_at IS NULL OR cleanup.lease_expires_at <= now())
12763
+ AND ($1::text IS NULL OR cleanup.environment = $1)
12764
+ AND ($2::text[] IS NULL OR cleanup.provider = ANY($2::text[]))
12765
+ AND ($3::text IS NULL OR cleanup.run_id = $3)
12766
+ AND ($4::integer IS NULL OR cleanup.attempt <= $4)
12767
+ ) AS claimable
12768
+ `,
12769
+ [
12770
+ input.environment?.trim() || null,
12771
+ providers,
12772
+ input.runId ?? null,
12773
+ input.throughAttempt ?? null,
12774
+ ],
12775
+ );
12776
+ if (
12777
+ typeof adoptionWork.rows[0]?.pending !== 'boolean' ||
12778
+ typeof adoptionWork.rows[0]?.claimable !== 'boolean'
12779
+ ) {
12752
12780
  throw new Error(
12753
12781
  'Sandbox cleanup adoption preflight returned no pending proof.',
12754
12782
  );
@@ -12944,6 +12972,10 @@ export async function claimPostgresSchedulerSandboxCleanupJobs(
12944
12972
  ON CONFLICT (provider, sandbox_id, routing_domain_key) DO NOTHING
12945
12973
  `,
12946
12974
  );
12975
+ // Adoption may have just created claimable v2 rows, so only an idle read
12976
+ // that proved neither kind of work skips the claim.
12977
+ if (!adoptionWork.rows[0].pending && !adoptionWork.rows[0].claimable)
12978
+ return [];
12947
12979
  const claimed = await client.query<{
12948
12980
  provider: 'daytona' | 'modal';
12949
12981
  sandbox_id: string;
@@ -19774,6 +19806,40 @@ function runLedgerOrderSql(alias: string): string {
19774
19806
  return `CASE ${alias}.event_type WHEN ${RUN_EVENT_KIND_SQL_LITERAL.runCreated} THEN 0 WHEN ${RUN_EVENT_KIND_SQL_LITERAL.runStarted} THEN 1 ELSE 2 END, ${alias}.created_at, ${alias}.event_id`;
19775
19807
  }
19776
19808
 
19809
+ /**
19810
+ * Whether a lane (or, without `searchOnly`, either lane) holds any
19811
+ * undelivered fact. Global discovery and due-clock statements are kilobytes
19812
+ * of SQL planned on every call, and an idle Preview projector issues them
19813
+ * twice a second. Every such statement starts from these same undelivered
19814
+ * rows, so an empty probe proves it would return nothing. The probe is the
19815
+ * first seek of `runLedgerPendingRunsSql`: an ordered LIMIT keeps the lane's
19816
+ * pending partial index even when statistics expect the other lane's backlog
19817
+ * to match, where EXISTS (which discards ORDER BY and LIMIT) would scan the
19818
+ * retained outbox instead.
19819
+ */
19820
+ async function runLedgerOutboxLaneHasPending(
19821
+ client: PostgresSchedulerQueryClient,
19822
+ options: Pick<PostgresSchedulerOptions, 'schema'> | undefined,
19823
+ searchOnly?: boolean,
19824
+ ): Promise<boolean> {
19825
+ const lane =
19826
+ searchOnly === undefined
19827
+ ? ''
19828
+ : `AND ${searchLaneSql('pending')} = ${String(searchOnly)}`;
19829
+ const result = await client.query<{ pending: boolean }>(
19830
+ `SELECT (
19831
+ SELECT pending.run_id FROM ${tableName('outbox', options)} AS pending
19832
+ WHERE pending.delivered_at IS NULL ${lane}
19833
+ ORDER BY pending.run_id LIMIT 1
19834
+ ) IS NOT NULL AS pending`,
19835
+ );
19836
+ const pending = result.rows[0]?.pending;
19837
+ if (typeof pending !== 'boolean') {
19838
+ throw new Error('Run Ledger outbox pending probe returned no proof.');
19839
+ }
19840
+ return pending;
19841
+ }
19842
+
19777
19843
  /** Enumerate pending runs by index seeks, skipping each run's entire fact tail. */
19778
19844
  function runLedgerPendingRunsSql(
19779
19845
  options?: Pick<PostgresSchedulerOptions, 'schema'>,
@@ -21106,6 +21172,16 @@ export async function readPostgresSchedulerOutboxNextDueAt(
21106
21172
  ? [true]
21107
21173
  : [false, true];
21108
21174
  const runId = options?.runId?.trim() || null;
21175
+ // Every due time below belongs to an undelivered fact.
21176
+ if (
21177
+ !runId &&
21178
+ !(await runLedgerOutboxLaneHasPending(
21179
+ client,
21180
+ options,
21181
+ options?.lane === undefined ? undefined : lanes[0],
21182
+ ))
21183
+ )
21184
+ return null;
21109
21185
  const result = await client.query<{ due_at: Date | string | null }>(
21110
21186
  `
21111
21187
  -- Seek distinct run identities without reading their pending fact tails.
@@ -23132,6 +23208,28 @@ async function dispatchRunLedgerOutboxBatchWithScope(
23132
23208
  arrayInputStarted: scope?.nextArrayInputStarted,
23133
23209
  }
23134
23210
  : await withClient(async (client) => {
23211
+ // Global discovery of an empty lane would plan its multi-kilobyte
23212
+ // claim statements only to select nothing.
23213
+ if (
23214
+ !scope &&
23215
+ !input.runId?.trim() &&
23216
+ !input.headEventId?.trim() &&
23217
+ !(await runLedgerOutboxLaneHasPending(
23218
+ client,
23219
+ options,
23220
+ input.searchOnly === true,
23221
+ ))
23222
+ ) {
23223
+ return {
23224
+ events: [] as PostgresSchedulerOutboxEvent[],
23225
+ launchJson: null,
23226
+ ingressPrefixClaimed: false,
23227
+ freshClaimLease: undefined,
23228
+ transportDelivered: undefined,
23229
+ transportOwnershipUnconfirmed: undefined,
23230
+ arrayInputStarted: undefined,
23231
+ };
23232
+ }
23135
23233
  const fast = await claimFastEvents(client);
23136
23234
  let result: Awaited<ReturnType<typeof claimEvents>>;
23137
23235
  if (fast !== null) {
@@ -98,6 +98,32 @@ function sandboxSizeMetadata(cpu: number, memoryGiB: number) {
98
98
  return entry ? { sandboxSize: entry[0] } : {};
99
99
  }
100
100
 
101
+ /**
102
+ * Lets settlement tell a run that used its declared runtime from a duration no
103
+ * runtime limit explains. Billed time stops at the ceiling, so the unclamped
104
+ * duration travels beside it: without it an overrun looks like a run that
105
+ * stayed inside its limit. Items without a ceiling keep their historical shape.
106
+ */
107
+ function runtimeLimitMetadata(
108
+ maxBillingDurationSeconds: number | null | undefined,
109
+ observedWallTimeSeconds: number | undefined,
110
+ ) {
111
+ if (
112
+ typeof maxBillingDurationSeconds !== 'number' ||
113
+ !Number.isFinite(maxBillingDurationSeconds) ||
114
+ maxBillingDurationSeconds <= 0
115
+ ) {
116
+ return {};
117
+ }
118
+ return {
119
+ maxBillingDurationSeconds,
120
+ ...(typeof observedWallTimeSeconds === 'number' &&
121
+ Number.isFinite(observedWallTimeSeconds)
122
+ ? { observedWallTimeSeconds: Math.max(0, observedWallTimeSeconds) }
123
+ : {}),
124
+ };
125
+ }
126
+
101
127
  export function resolveDaytonaSandboxComputeItem(input: {
102
128
  itemId: string;
103
129
  source?: string;
@@ -108,6 +134,10 @@ export function resolveDaytonaSandboxComputeItem(input: {
108
134
  sandboxId?: string | null;
109
135
  startedAt?: number;
110
136
  endedAt?: number;
137
+ /** The resource's own billing ceiling: its timeout plus terminal grace. */
138
+ maxBillingDurationSeconds?: number | null;
139
+ /** Wall time before the ceiling clamped it; recorded only with a ceiling. */
140
+ observedWallTimeSeconds?: number;
111
141
  }): ComputeBillingItem {
112
142
  const billableSeconds = Math.max(1, Math.ceil(input.wallTimeSeconds));
113
143
  const cpu = Math.max(0, input.cpu);
@@ -138,6 +168,10 @@ export function resolveDaytonaSandboxComputeItem(input: {
138
168
  billableDiskGiB,
139
169
  startedAt: input.startedAt,
140
170
  endedAt: input.endedAt,
171
+ ...runtimeLimitMetadata(
172
+ input.maxBillingDurationSeconds,
173
+ input.observedWallTimeSeconds,
174
+ ),
141
175
  },
142
176
  };
143
177
  }
@@ -152,6 +186,10 @@ export function resolveModalSandboxComputeItem(input: {
152
186
  sandboxId?: string | null;
153
187
  startedAt?: number;
154
188
  endedAt?: number;
189
+ /** The resource's own billing ceiling: its timeout plus terminal grace. */
190
+ maxBillingDurationSeconds?: number | null;
191
+ /** Wall time before the ceiling clamped it; recorded only with a ceiling. */
192
+ observedWallTimeSeconds?: number;
155
193
  }): ComputeBillingItem {
156
194
  const billableSeconds = Math.max(1, Math.ceil(input.wallTimeSeconds));
157
195
  const physicalCpuCores = Math.max(0, input.physicalCpuCores);
@@ -186,6 +224,10 @@ export function resolveModalSandboxComputeItem(input: {
186
224
  }),
187
225
  startedAt: input.startedAt,
188
226
  endedAt: input.endedAt,
227
+ ...runtimeLimitMetadata(
228
+ input.maxBillingDurationSeconds,
229
+ input.observedWallTimeSeconds,
230
+ ),
189
231
  },
190
232
  };
191
233
  }
@@ -2988,7 +2988,7 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2988
2988
  resolution: 'static-required',
2989
2989
  issueCode: 'play_authoring_binding_invalid',
2990
2990
  description:
2991
- 'Deepline-managed sandbox size. Standard is 1 GiB memory. Large (4 GiB) and xlarge (8 GiB) are available only on branch Preview deployments. Production supports standard.',
2991
+ 'Deepline-managed sandbox size: standard (1 GiB memory, 1 CPU), large (4 GiB, 2 CPU), or xlarge (8 GiB, 4 CPU). Standard is the default. Compute pricing follows the workspace plan and selected size.',
2992
2992
  errorMessage:
2993
2993
  'runtime.size must be the static literal "standard", "large", or "xlarge".',
2994
2994
  },
@@ -0,0 +1,41 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ var _chunkCH4VMDB5js = require('./chunk-CH4VMDB5.js');
17
+ require('./chunk-M2UL4ZS5.js');
18
+ require('./chunk-NC52JG66.js');
19
+ require('./chunk-ACDXGOXE.js');
20
+ require('./chunk-ALJJRZNJ.js');
21
+ require('./chunk-XNYIZMNQ.js');
22
+ require('./chunk-DIAWYWT5.js');
23
+ require('./chunk-6KKDIISC.js');
24
+ require('./chunk-OG64RBAC.js');
25
+ require('./chunk-COGTRVWW.js');
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+
37
+
38
+
39
+
40
+
41
+ exports.EXIT_WORKSPACE_MISMATCH = _chunkCH4VMDB5js.EXIT_WORKSPACE_MISMATCH; exports.authStatusLine = _chunkCH4VMDB5js.authStatusLine; exports.clearPendingClaim = _chunkCH4VMDB5js.clearPendingClaim; exports.clearSatisfiedPendingClaim = _chunkCH4VMDB5js.clearSatisfiedPendingClaim; exports.handleRegister = _chunkCH4VMDB5js.handleRegister; exports.handleStatus = _chunkCH4VMDB5js.handleStatus; exports.handleWait = _chunkCH4VMDB5js.handleWait; exports.httpJson = _chunkCH4VMDB5js.httpJson; exports.readPendingAuthClaim = _chunkCH4VMDB5js.readPendingAuthClaim; exports.readStatusBanner = _chunkCH4VMDB5js.readStatusBanner; exports.registerAuthCommands = _chunkCH4VMDB5js.registerAuthCommands; exports.reportSetupCompleted = _chunkCH4VMDB5js.reportSetupCompleted; exports.resolvePendingAuthClaimForResume = _chunkCH4VMDB5js.resolvePendingAuthClaimForResume; exports.statusBannerLine = _chunkCH4VMDB5js.statusBannerLine;
@@ -13,14 +13,14 @@ import {
13
13
  reportSetupCompleted,
14
14
  resolvePendingAuthClaimForResume,
15
15
  statusBannerLine
16
- } from "./chunk-42F6LTBJ.mjs";
17
- import "./chunk-BVAYLXMH.mjs";
18
- import "./chunk-NGOKBSBH.mjs";
19
- import "./chunk-3QSFSHCJ.mjs";
16
+ } from "./chunk-6IJJCRE5.mjs";
17
+ import "./chunk-QODW5ILA.mjs";
18
+ import "./chunk-YZA5UJXM.mjs";
19
+ import "./chunk-LJYTYNQZ.mjs";
20
20
  import "./chunk-4QF2K7TM.mjs";
21
21
  import "./chunk-DLIRSHV6.mjs";
22
22
  import "./chunk-HBL4PM5Q.mjs";
23
- import "./chunk-YAAQNDP6.mjs";
23
+ import "./chunk-PQQNMMWM.mjs";
24
24
  import "./chunk-WZYKRD7T.mjs";
25
25
  import "./chunk-KRD6H754.mjs";
26
26
  export {