deepline 0.3.472 → 0.3.474

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 (184) hide show
  1. package/dist/bundling-sources/sdk/src/client-core.ts +4 -1
  2. package/dist/bundling-sources/sdk/src/play.ts +7 -2
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/integration-contracts/billing-entitlements.ts +3 -1
  5. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +18 -0
  6. package/dist/bundling-sources/shared_libs/observability/scheduled-work-control.ts +17 -0
  7. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +55 -0
  8. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +101 -2
  9. package/dist/bundling-sources/shared_libs/play-runtime/modal-runtime-config.ts +62 -20
  10. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +2 -2
  11. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +24 -8
  12. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +19 -7
  13. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +196 -10
  14. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +40 -17
  15. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +4 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal-resource-usage.ts +222 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +46 -14
  18. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +2 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +9 -5
  20. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +8 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +2 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +2 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +5 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +14 -5
  25. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +36 -0
  26. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +18 -8
  27. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +1 -1
  28. package/dist/bundling-sources/shared_libs/plays/sandbox-runtime-limits.ts +64 -16
  29. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
  30. package/dist/bundling-sources/shared_libs/product-notifications/billing-sales.ts +14 -1
  31. package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +40 -0
  32. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +23 -1
  33. package/dist/bundling-sources/shared_libs/product-notifications/events.ts +18 -0
  34. package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +2 -1
  35. package/dist/cli/auth-O6KAH543.js +41 -0
  36. package/dist/cli/{auth-35DAEPHC.mjs → auth-QMANU5PV.mjs} +5 -5
  37. package/dist/cli/{billing-5ED5X2IO.js → billing-WZ5POBZX.js} +85 -83
  38. package/dist/cli/{billing-CXVBJEC4.mjs → billing-YJAP4VZN.mjs} +12 -10
  39. package/dist/cli/{chunk-CFDQZQE2.mjs → chunk-26CZVDQN.mjs} +2 -2
  40. package/dist/cli/{chunk-UXFUD2MB.mjs → chunk-35SZHTRW.mjs} +5 -5
  41. package/dist/cli/{chunk-R73LE2IM.mjs → chunk-3MZPWIOE.mjs} +1 -1
  42. package/dist/cli/{chunk-HBVT4FW5.js → chunk-3NRZFMX4.js} +6 -6
  43. package/dist/cli/{chunk-SBPI7AEB.js → chunk-3ODBCZLL.js} +272 -258
  44. package/dist/cli/{chunk-ODAARZSK.js → chunk-475V572K.js} +36 -36
  45. package/dist/cli/{chunk-L3AXQXNT.js → chunk-4C34LGI4.js} +13 -13
  46. package/dist/cli/{chunk-V6OMS7E2.js → chunk-4IOY2SCQ.js} +10 -2
  47. package/dist/cli/{chunk-6ZTSHKJF.js → chunk-5QS4T4KR.js} +10 -3
  48. package/dist/cli/{chunk-R6L6JRDR.mjs → chunk-7IYOWIEJ.mjs} +2 -2
  49. package/dist/cli/{chunk-RDOPZK7A.js → chunk-AIPTW3EK.js} +18 -4
  50. package/dist/cli/{chunk-T5GRG37B.js → chunk-AK7UKQ5R.js} +5 -5
  51. package/dist/cli/{chunk-2G4R4APL.js → chunk-AWJKYS25.js} +2 -2
  52. package/dist/cli/{chunk-IPJ27SD2.mjs → chunk-BB4RZMWA.mjs} +5 -5
  53. package/dist/cli/{chunk-GJPRBELO.js → chunk-CXBEZAPU.js} +16 -16
  54. package/dist/cli/{chunk-Z52WEIBR.js → chunk-DKR7QPEO.js} +11 -7
  55. package/dist/cli/{chunk-YK64LQPQ.mjs → chunk-DQZBERDL.mjs} +1 -1
  56. package/dist/cli/{chunk-YUNPYX5T.mjs → chunk-DUGZKPRK.mjs} +10 -3
  57. package/dist/cli/{chunk-2S36YV2K.mjs → chunk-EIKONWW7.mjs} +1 -1
  58. package/dist/cli/{chunk-VDHDTXCK.js → chunk-EK2TAZJZ.js} +2 -2
  59. package/dist/cli/{chunk-2DND7LLL.js → chunk-G3UHAYRX.js} +8 -8
  60. package/dist/cli/{chunk-UPXGN62E.js → chunk-IK3VOGDR.js} +21 -21
  61. package/dist/cli/{chunk-UKKCS6Z4.js → chunk-JCNJQH6X.js} +1 -1
  62. package/dist/cli/{chunk-ZZQIS3TH.mjs → chunk-JEYX3345.mjs} +3 -3
  63. package/dist/cli/{chunk-QSCLOPWR.mjs → chunk-JJVYUKLY.mjs} +16 -2
  64. package/dist/cli/{chunk-XSSIBO74.js → chunk-JSXMJSBE.js} +8 -8
  65. package/dist/cli/{chunk-4NFBYW7X.mjs → chunk-KKWQ24VM.mjs} +2 -2
  66. package/dist/cli/{chunk-3L76SFGN.js → chunk-LHOEWAVD.js} +53 -53
  67. package/dist/cli/{chunk-NZBZFEVA.mjs → chunk-MKSB2FTU.mjs} +4 -4
  68. package/dist/cli/{chunk-MRZ7AO72.mjs → chunk-MNK4ZK2C.mjs} +26 -12
  69. package/dist/cli/{chunk-6PKONIIP.mjs → chunk-MXUJVCKV.mjs} +5 -5
  70. package/dist/cli/{chunk-7KAS4QOW.js → chunk-N62MS3XJ.js} +6 -6
  71. package/dist/cli/{chunk-SYWOSAW7.js → chunk-ORQYKA4I.js} +4 -4
  72. package/dist/cli/{chunk-X3RAGNNZ.mjs → chunk-OWUZKXE7.mjs} +1 -1
  73. package/dist/cli/{chunk-LQUOZGM6.js → chunk-PGKELBIY.js} +51 -51
  74. package/dist/cli/{chunk-52VOORHM.mjs → chunk-RGIQ5ZJX.mjs} +2 -2
  75. package/dist/cli/{chunk-M475BQ77.js → chunk-RLQIGBFK.js} +3 -3
  76. package/dist/cli/{chunk-5FHU67AT.js → chunk-RXITRC37.js} +3 -3
  77. package/dist/cli/{chunk-XSX6TJUA.mjs → chunk-SD7QWZTV.mjs} +10 -2
  78. package/dist/cli/{chunk-H7YFUC3J.js → chunk-STLJYWUC.js} +7 -7
  79. package/dist/cli/{chunk-T5H7ETIM.mjs → chunk-THDXVSNL.mjs} +4 -4
  80. package/dist/cli/{chunk-JRZW4LWK.mjs → chunk-UGYLGT7B.mjs} +1 -1
  81. package/dist/cli/{chunk-YNW47NOJ.mjs → chunk-UKX4FESF.mjs} +2 -2
  82. package/dist/cli/{chunk-RZHUHL53.mjs → chunk-V4ILQ2DS.mjs} +11 -7
  83. package/dist/cli/{chunk-QLVYYDUI.js → chunk-XNP7XTBN.js} +26 -26
  84. package/dist/cli/{chunk-SKVMC6K6.js → chunk-XP7FAYLN.js} +5 -5
  85. package/dist/cli/{chunk-RMQB72WW.js → chunk-YFDTF2XC.js} +9 -9
  86. package/dist/cli/{chunk-KQJHIVCE.mjs → chunk-YK64UZXN.mjs} +1 -1
  87. package/dist/cli/{chunk-IIQ6O5F3.js → chunk-YKNULB7V.js} +5 -5
  88. package/dist/cli/{chunk-YMB2CD55.mjs → chunk-YVJ6IWVD.mjs} +1 -1
  89. package/dist/cli/{chunk-TMMODGPV.mjs → chunk-YX762HFK.mjs} +2 -2
  90. package/dist/cli/{chunk-OMPW5ELS.mjs → chunk-Z27NSLPC.mjs} +1 -1
  91. package/dist/cli/{chunk-FHIO7WI2.mjs → chunk-ZGC2SUAA.mjs} +2 -2
  92. package/dist/cli/{chunk-C5PY2PP6.mjs → chunk-ZRWQKTSX.mjs} +3 -3
  93. package/dist/cli/command-docs.js +2 -2
  94. package/dist/cli/command-docs.mjs +1 -1
  95. package/dist/cli/{csv-SQJQM3B2.js → csv-ENKCFCSM.js} +13 -13
  96. package/dist/cli/{csv-OSDYY3QW.mjs → csv-QCLQOIEA.mjs} +4 -4
  97. package/dist/cli/{db-ATDYXAD5.js → db-5TLBZDT3.js} +27 -27
  98. package/dist/cli/{db-2M6BU6G5.mjs → db-P25LGP33.mjs} +6 -6
  99. package/dist/cli/describe-44L7OBGM.js +48 -0
  100. package/dist/cli/{describe-WIAJCWF3.mjs → describe-W2H5DHR6.mjs} +19 -19
  101. package/dist/cli/describe-core.d.mts +3 -3
  102. package/dist/cli/describe-core.d.ts +3 -3
  103. package/dist/cli/describe-core.js +3 -3
  104. package/dist/cli/describe-core.mjs +2 -2
  105. package/dist/cli/{executions-2J6CCJL2.mjs → executions-VYZCYUBR.mjs} +6 -6
  106. package/dist/cli/{executions-Y32LMYH6.js → executions-YHFAWJOJ.js} +8 -8
  107. package/dist/cli/{feedback-L4AVIMGI.mjs → feedback-K2FWYYLZ.mjs} +4 -4
  108. package/dist/cli/{feedback-RXQLS6QU.js → feedback-RJF3QSBB.js} +8 -8
  109. package/dist/cli/git-F3EI5DY5.js +25 -0
  110. package/dist/cli/{git-V3K77AWH.mjs → git-S3APUPLL.mjs} +5 -5
  111. package/dist/cli/index.js +139 -139
  112. package/dist/cli/index.mjs +41 -41
  113. package/dist/cli/{legacy-noops-B2KJT4CZ.js → legacy-noops-3SWNRE7Z.js} +8 -8
  114. package/dist/cli/{legacy-noops-LOGMYOY5.mjs → legacy-noops-ICYZACTW.mjs} +5 -5
  115. package/dist/cli/{monitors-5ETSB7S7.mjs → monitors-DEZCEQLB.mjs} +7 -7
  116. package/dist/cli/{monitors-A5WYUCGD.js → monitors-VRIWTRPG.js} +91 -91
  117. package/dist/cli/{org-BJOAUSXL.mjs → org-IQW5ZIAO.mjs} +6 -6
  118. package/dist/cli/{org-7EZDWYKK.js → org-NX752DTE.js} +72 -72
  119. package/dist/cli/{pages-3BYGBTVK.mjs → pages-5WQRVVKX.mjs} +6 -6
  120. package/dist/cli/{pages-45VNTEQU.js → pages-YIBWYGVS.js} +17 -17
  121. package/dist/cli/{play-5KER3JFU.js → play-3EXNH3HD.js} +662 -601
  122. package/dist/cli/{play-R7JQV533.mjs → play-WOMJXS4O.mjs} +110 -49
  123. package/dist/cli/{providers-OHMZ6QIF.js → providers-C2WYSR2Y.js} +9 -9
  124. package/dist/cli/{providers-RMFCE6MM.mjs → providers-XDVPYRTS.mjs} +6 -6
  125. package/dist/cli/{quickstart-GNRE4SAT.js → quickstart-7RABZF66.js} +6 -6
  126. package/dist/cli/{quickstart-IHR4VC5K.mjs → quickstart-YMSIL2TK.mjs} +4 -4
  127. package/dist/cli/{run-77QIMUUV.js → run-5QVVWFGL.js} +39 -39
  128. package/dist/cli/{run-Y4MIRFJY.mjs → run-A4JD6TAR.mjs} +24 -24
  129. package/dist/cli/{search-24YKTHMK.mjs → search-2TOSF2G3.mjs} +7 -7
  130. package/dist/cli/search-772R7YHI.js +32 -0
  131. package/dist/cli/{secrets-KHX62AWC.mjs → secrets-BUN4ORE7.mjs} +6 -6
  132. package/dist/cli/{secrets-SSEBT3IS.js → secrets-Q7UVOSNM.js} +15 -15
  133. package/dist/cli/{sessions-H7T25HGE.mjs → sessions-IAOORI6X.mjs} +5 -5
  134. package/dist/cli/{sessions-E6KNWYEH.js → sessions-NXQOQ4HX.js} +6 -6
  135. package/dist/cli/{settings-XSJG6M32.mjs → settings-BMI373AI.mjs} +11 -11
  136. package/dist/cli/{settings-GXUYF5G7.js → settings-WD4KSY5S.js} +78 -78
  137. package/dist/cli/{setup-JP5YPIA4.js → setup-3IA7BVPE.js} +44 -44
  138. package/dist/cli/{setup-GD7IOD4U.mjs → setup-55VIWAWR.mjs} +9 -9
  139. package/dist/cli/{skills-HLHE3ENN.mjs → skills-GKITMBTM.mjs} +7 -7
  140. package/dist/cli/skills-PUUTFMP3.js +31 -0
  141. package/dist/cli/tools-3WX7TUUG.js +55 -0
  142. package/dist/cli/{tools-Q4XIQX7W.mjs → tools-XHCFC7GP.mjs} +17 -17
  143. package/dist/cli/update-PC3CLODV.js +33 -0
  144. package/dist/cli/{update-3KRMR2QJ.mjs → update-SWB24YKE.mjs} +9 -9
  145. package/dist/cli/{workflow-NMUESL3O.js → workflow-5QPMFI3Z.js} +17 -17
  146. package/dist/cli/{workflow-NOZ7WFEY.mjs → workflow-XO4CKUA7.mjs} +7 -7
  147. package/dist/{client-C9bJ0QXu.d.mts → client-Bgl0_zXi.d.mts} +1 -1
  148. package/dist/{client-CuNH54w3.d.ts → client-_ah6IuhK.d.ts} +1 -1
  149. package/dist/{client-core-CSiXbe2n.d.mts → client-core-Bfsedm0_.d.mts} +44 -3
  150. package/dist/{client-core-C8Q6DKga.d.ts → client-core-C-ZF5vh9.d.ts} +44 -3
  151. package/dist/{compiler-manifest-Zwm9yqOA.d.ts → compiler-manifest-CCOqDGjU.d.mts} +12 -10
  152. package/dist/{compiler-manifest-Zwm9yqOA.d.mts → compiler-manifest-CCOqDGjU.d.ts} +12 -10
  153. package/dist/{errors-nEsqNLL8.d.mts → errors-BLccD7tG.d.mts} +1 -1
  154. package/dist/{errors-CsYwlpn4.d.ts → errors-Cy3kPnsi.d.ts} +1 -1
  155. package/dist/{index-CXF2jtza.d.mts → index-4-j44HuT.d.mts} +1 -1
  156. package/dist/{index-9pUZqkPH.d.ts → index-D5e4h1-H.d.ts} +1 -1
  157. package/dist/index.d.mts +14 -9
  158. package/dist/index.d.ts +14 -9
  159. package/dist/index.js +9 -9
  160. package/dist/index.mjs +6 -6
  161. package/dist/install-integrity.json +115 -114
  162. package/dist/plays/bundle-play-file.d.mts +3 -3
  163. package/dist/plays/bundle-play-file.d.ts +3 -3
  164. package/dist/plays/bundle-play-file.mjs +1 -1
  165. package/dist/plays/play-description.d.mts +2 -2
  166. package/dist/plays/play-description.d.ts +2 -2
  167. package/dist/plays/run-request.d.mts +2 -2
  168. package/dist/plays/run-request.d.ts +2 -2
  169. package/dist/release.d.mts +1 -1
  170. package/dist/release.d.ts +1 -1
  171. package/dist/release.js +2 -2
  172. package/dist/release.mjs +1 -1
  173. package/dist/server.d.mts +4 -4
  174. package/dist/server.d.ts +4 -4
  175. package/dist/server.js +5 -5
  176. package/dist/server.mjs +3 -3
  177. package/package.json +1 -1
  178. package/dist/cli/auth-Q5TM66UI.js +0 -41
  179. package/dist/cli/describe-37QXXDCF.js +0 -48
  180. package/dist/cli/git-JGUR335T.js +0 -25
  181. package/dist/cli/search-S3VO2UKV.js +0 -32
  182. package/dist/cli/skills-C4KTW3US.js +0 -31
  183. package/dist/cli/tools-KS7JMRK6.js +0 -55
  184. package/dist/cli/update-GYTQYK2W.js +0 -33
@@ -18,10 +18,11 @@ import {
18
18
  PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
19
19
  } from '../../runtime-constants';
20
20
  import {
21
- MODAL_SANDBOX_CPU_CORES,
22
- MODAL_SANDBOX_MEMORY_MIB,
21
+ modalSandboxResourcesForPlay,
23
22
  loadModalClientConfig,
24
23
  loadModalRequiredConfig,
24
+ resolveModalSandboxMemoryPolicy,
25
+ isModalPreviewEvaluation,
25
26
  withModalCallSignal,
26
27
  } from '../../modal-runtime-config';
27
28
  import type { PlaySandboxRuntimeLimits } from '../../../plays/sandbox-runtime-limits';
@@ -371,10 +372,33 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
371
372
  runtimeSchedulerSchema: config.context.runtimeSchedulerSchema,
372
373
  limits: config.context.sandboxRuntimeLimits,
373
374
  });
375
+ const resources = modalSandboxResourcesForPlay(modalConfig.limits);
376
+ const memoryPolicy = resolveModalSandboxMemoryPolicy({
377
+ limits: modalConfig.limits,
378
+ runtimeSchedulerSchema: config.context.runtimeSchedulerSchema,
379
+ });
374
380
  const app = await modalConfig.client.apps.fromName(modalConfig.appName, {
375
381
  createIfMissing: true,
376
382
  });
377
- const image = modalConfig.client.images.fromRegistry(modalConfig.image);
383
+ const baseImage = modalConfig.client.images.fromRegistry(
384
+ modalConfig.image,
385
+ );
386
+ // Modal caches this immutable layer by its build commands. Build before
387
+ // sandbox admission so a cold Python image owns no sandbox-capacity lease
388
+ // or ambiguous SandboxCreate intent. Production keeps its existing image.
389
+ const image = isModalPreviewEvaluation({
390
+ runtimeSchedulerSchema: config.context.runtimeSchedulerSchema,
391
+ })
392
+ ? await withModalCallSignal(
393
+ AbortSignal.timeout(MODAL_SANDBOX_CREATE_TIMEOUT_MS),
394
+ () =>
395
+ baseImage
396
+ .dockerfileCommands([
397
+ 'RUN apt-get update && apt-get install -y --no-install-recommends python3 python-is-python3 && rm -rf /var/lib/apt/lists/*',
398
+ ])
399
+ .build(app),
400
+ )
401
+ : baseImage;
378
402
  const capacityReservation =
379
403
  await callbacks?.reserveSandboxCapacity?.('modal');
380
404
  const createTags = {
@@ -428,10 +452,10 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
428
452
  createSent = true;
429
453
  sandbox = await awaitModalSandboxCreate({
430
454
  create: modalConfig.client.sandboxes.create(app, image, {
431
- cpu: MODAL_SANDBOX_CPU_CORES,
432
- cpuLimit: MODAL_SANDBOX_CPU_CORES,
433
- memoryMiB: MODAL_SANDBOX_MEMORY_MIB,
434
- memoryLimitMiB: MODAL_SANDBOX_MEMORY_MIB,
455
+ cpu: resources.physicalCpuCores,
456
+ cpuLimit: resources.physicalCpuCores,
457
+ memoryMiB: memoryPolicy.reservationMemoryMiB,
458
+ memoryLimitMiB: memoryPolicy.memoryLimitMiB,
435
459
  timeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
436
460
  idleTimeoutMs: modalSandboxLifetimeMs(modalConfig.limits),
437
461
  workdir: modalConfig.workdir,
@@ -473,8 +497,12 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
473
497
  maxBillingDurationSeconds:
474
498
  modalConfig.limits.timeoutSeconds +
475
499
  PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
476
- cpu: MODAL_SANDBOX_CPU_CORES,
477
- memoryGiB: MODAL_SANDBOX_MEMORY_MIB / 1024,
500
+ cpu: resources.physicalCpuCores,
501
+ memoryGiB: modalConfig.limits.memoryGiB,
502
+ ...(memoryPolicy.memoryLimitMiB >
503
+ memoryPolicy.reservationMemoryMiB
504
+ ? { memoryLimitGiB: memoryPolicy.memoryLimitMiB / 1024 }
505
+ : {}),
478
506
  diskGiB: 0,
479
507
  ...(capacityReservation
480
508
  ? { sandboxCapacityLeaseId: capacityReservation.leaseId }
@@ -587,8 +615,11 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
587
615
  maxBillingDurationSeconds:
588
616
  modalConfig.limits.timeoutSeconds +
589
617
  PLAY_RUNNER_TERMINAL_GRACE_SECONDS,
590
- cpu: MODAL_SANDBOX_CPU_CORES,
591
- memoryGiB: MODAL_SANDBOX_MEMORY_MIB / 1024,
618
+ cpu: resources.physicalCpuCores,
619
+ memoryGiB: modalConfig.limits.memoryGiB,
620
+ ...(memoryPolicy.memoryLimitMiB > memoryPolicy.reservationMemoryMiB
621
+ ? { memoryLimitGiB: memoryPolicy.memoryLimitMiB / 1024 }
622
+ : {}),
592
623
  diskGiB: 0,
593
624
  ...(capacityReservation
594
625
  ? { sandboxCapacityLeaseId: capacityReservation.leaseId }
@@ -654,6 +685,9 @@ export const modalPlayRunnerBackend: PlayRunnerBackend = {
654
685
  },
655
686
  bundlePromise: buildPlayRunnerBundle(),
656
687
  config: withRunnerSandboxProvider(config, 'modal'),
688
+ ...(memoryPolicy.memoryLimitMiB > memoryPolicy.reservationMemoryMiB
689
+ ? { memoryLimitMiB: memoryPolicy.memoryLimitMiB }
690
+ : {}),
657
691
  workDir: modalConfig.workdir,
658
692
  startedAt,
659
693
  emitStage: (stage, extra) =>
@@ -972,9 +1006,7 @@ export async function inspectDetachedModalRunner(input: {
972
1006
  const deadline = new AbortController();
973
1007
  const timer = setTimeout(() => {
974
1008
  deadline.abort(
975
- new Error(
976
- `Modal detached-runner inspection exceeded ${timeoutMs}ms.`,
977
- ),
1009
+ new Error(`Modal detached-runner inspection exceeded ${timeoutMs}ms.`),
978
1010
  );
979
1011
  }, timeoutMs);
980
1012
  try {
@@ -41,6 +41,8 @@ export type PlayRunnerRuntimeResource = {
41
41
  terminalAt?: number | null;
42
42
  cpu?: number | null;
43
43
  memoryGiB?: number | null;
44
+ /** Optional Modal burst ceiling; absence retains the fixed reservation. */
45
+ memoryLimitGiB?: number | null;
44
46
  diskGiB?: number | null;
45
47
  /** Scheduler-only soft-capacity reservation made before provider create. */
46
48
  sandboxCapacityLeaseId?: string;
@@ -362,16 +362,20 @@ export const RUNTIME_INCIDENT_DRILL_CATALOG: readonly RuntimeIncidentDrill[] = [
362
362
  topologies: LOCAL_AND_PREVIEW_TOPOLOGIES,
363
363
  control: {
364
364
  kind: 'runtime_test_fault',
365
- // The 120-second lease plus 80 seconds of confirmation puts the first
366
- // provider read at about 200 seconds. The rest absorbs late wakes, so
367
- // that read lands inside the silence and not after it.
368
- defaultValue: 300_000,
365
+ // The 120-second lease plus 80 seconds of on-time confirmation is the
366
+ // earliest provider read. Preview woke late enough to exhaust 300
367
+ // seconds of silence before that read. Keep the synthetic runner quiet
368
+ // for seven minutes without changing the worker's liveness policy.
369
+ defaultValue: 420_000,
369
370
  valueMeaning: 'milliseconds',
370
371
  },
371
372
  expected: {
372
373
  outcome:
373
374
  'The worker asks the provider once the lease and confirmation pass, the sandbox is not proven gone, and the same attempt finishes after heartbeats return.',
374
- safetyFacts: ['same_logical_run', 'no_loss_verdict_without_provider_proof'],
375
+ safetyFacts: [
376
+ 'same_logical_run',
377
+ 'no_loss_verdict_without_provider_proof',
378
+ ],
375
379
  },
376
380
  verification: {
377
381
  kind: 'preview_black_box',
@@ -23,6 +23,7 @@ export function resolveRuntimeSandboxComputeItems(input: {
23
23
  endedAt: number | null;
24
24
  physicalCpuCores: number | null;
25
25
  memoryGiB: number | null;
26
+ memoryLimitGiB: number | null;
26
27
  maxBillingDurationSeconds: number | null;
27
28
  }
28
29
  >();
@@ -52,6 +53,10 @@ export function resolveRuntimeSandboxComputeItems(input: {
52
53
  finiteNonNegative(value.cpu) ?? existing?.physicalCpuCores ?? null,
53
54
  memoryGiB:
54
55
  finiteNonNegative(value.memoryGiB) ?? existing?.memoryGiB ?? null,
56
+ memoryLimitGiB:
57
+ finiteNonNegative(value.memoryLimitGiB) ??
58
+ existing?.memoryLimitGiB ??
59
+ null,
55
60
  maxBillingDurationSeconds:
56
61
  finiteNonNegative(value.maxBillingDurationSeconds) ??
57
62
  existing?.maxBillingDurationSeconds ??
@@ -77,6 +82,9 @@ export function resolveRuntimeSandboxComputeItems(input: {
77
82
  wallTimeSeconds: Math.max(0, endedAt - resource.startedAt) / 1_000,
78
83
  physicalCpuCores: resource.physicalCpuCores ?? 0.5,
79
84
  memoryGiB: resource.memoryGiB ?? 1,
85
+ ...(resource.memoryLimitGiB === null
86
+ ? {}
87
+ : { memoryLimitGiB: resource.memoryLimitGiB }),
80
88
  sandboxId,
81
89
  startedAt: resource.startedAt,
82
90
  endedAt,
@@ -204,6 +204,8 @@ export type SandboxExecutionResource = SandboxRunResource & {
204
204
  billingEndedAtMs?: number | null;
205
205
  cpu?: number | null;
206
206
  memoryGiB?: number | null;
207
+ /** Optional provider burst ceiling, distinct from the reserved memory. */
208
+ memoryLimitGiB?: number | null;
207
209
  diskGiB?: number | null;
208
210
  /** A create settled after the launching attempt had already timed out. */
209
211
  lateAcquired?: boolean;
@@ -254,6 +254,7 @@ function toSandboxResource(
254
254
  maxBillingDurationSeconds: resource.maxBillingDurationSeconds ?? null,
255
255
  cpu: resource.cpu ?? null,
256
256
  memoryGiB: resource.memoryGiB ?? null,
257
+ memoryLimitGiB: resource.memoryLimitGiB ?? null,
257
258
  diskGiB: resource.diskGiB ?? null,
258
259
  // Historical Daytona facts use `daytonaEnvironment`; normalize it at the
259
260
  // portable boundary so the cleanup job stays in the resource's actual
@@ -295,6 +296,7 @@ export function playRunnerRuntimeResourceFromSandboxExecutionResource(
295
296
  maxBillingDurationSeconds: resource.maxBillingDurationSeconds ?? null,
296
297
  cpu: resource.cpu ?? null,
297
298
  memoryGiB: resource.memoryGiB ?? null,
299
+ memoryLimitGiB: resource.memoryLimitGiB ?? null,
298
300
  diskGiB: resource.diskGiB ?? null,
299
301
  ...(resource.capacityLeaseId
300
302
  ? { sandboxCapacityLeaseId: resource.capacityLeaseId }
@@ -24428,7 +24428,11 @@ async function acknowledgeRunLedgerPrefixWithContinuation(
24428
24428
  waitForRunLock: true,
24429
24429
  },
24430
24430
  options,
24431
- { eventIds, targetEventId },
24431
+ {
24432
+ eventIds,
24433
+ targetEventId,
24434
+ createdProjectionWake: input.createdProjectionWake,
24435
+ },
24432
24436
  );
24433
24437
  if (fast !== null) {
24434
24438
  let continuation: RunLedgerOutboxDrainScope | null = null;
@@ -938,6 +938,12 @@ export function startRunLedgerOutboxRecovery(
938
938
  * together for projection.
939
939
  */
940
940
  export const RUN_CREATED_INLINE_DELIVERY_DEADLINE_MS = 1_500;
941
+ /**
942
+ * A response-owned background attempt can finish a cold projection without
943
+ * holding admission open. Keep its deadline plus release grace below the
944
+ * existing five-second delivery lease; foreground callers retain 1.5 seconds.
945
+ */
946
+ export const RUN_CREATED_BACKGROUND_DELIVERY_DEADLINE_MS = 4_000;
941
947
  /**
942
948
  * How long an abandoned inline attempt may keep its caller while it gives the
943
949
  * lease back. The caller is the only owner of that release: a serverless
@@ -1016,7 +1022,8 @@ function inlineAttemptErrorClass(error: unknown): string {
1016
1022
  * Never throws and never exceeds `deadlineMs` plus the release grace. A
1017
1023
  * deadline abandons the append and releases its lease without charging a
1018
1024
  * retry, and that release completes before this returns whenever it fits the
1019
- * grace: nothing else keeps the admitting invocation alive for it.
1025
+ * grace. Background callers own the returned promise through their response
1026
+ * lifecycle; foreground callers retain the shorter inline deadline.
1020
1027
  */
1021
1028
  export async function deliverRunCreatedOutboxEventInline(input: {
1022
1029
  runId: string;
@@ -1024,6 +1031,8 @@ export async function deliverRunCreatedOutboxEventInline(input: {
1024
1031
  schedulerOptions?: Pick<PostgresSchedulerOptions, 'schema'>;
1025
1032
  tokenAuthority: RunLedgerOutboxTokenAuthority;
1026
1033
  appRuntimeFetch?: typeof fetch;
1034
+ /** The caller owns this attempt through its after-response lifecycle. */
1035
+ background?: boolean;
1027
1036
  deadlineMs?: number;
1028
1037
  /** Test seam; clamped to `RUN_CREATED_INLINE_RELEASE_GRACE_MS`. */
1029
1038
  releaseGraceMs?: number;
@@ -1032,12 +1041,12 @@ export async function deliverRunCreatedOutboxEventInline(input: {
1032
1041
  const now = input.now ?? Date.now;
1033
1042
  const startedAt = now();
1034
1043
  const elapsed = () => Math.max(0, now() - startedAt);
1044
+ const maximumDeadlineMs = input.background
1045
+ ? RUN_CREATED_BACKGROUND_DELIVERY_DEADLINE_MS
1046
+ : RUN_CREATED_INLINE_DELIVERY_DEADLINE_MS;
1035
1047
  const deadlineMs = Math.max(
1036
1048
  1,
1037
- Math.min(
1038
- RUN_CREATED_INLINE_DELIVERY_DEADLINE_MS,
1039
- input.deadlineMs ?? RUN_CREATED_INLINE_DELIVERY_DEADLINE_MS,
1040
- ),
1049
+ Math.min(maximumDeadlineMs, input.deadlineMs ?? maximumDeadlineMs),
1041
1050
  );
1042
1051
  const releaseGraceMs = Math.max(
1043
1052
  1,
@@ -1,6 +1,7 @@
1
1
  import type { PlayDocflowNodeIoState } from './docflow-node-io';
2
2
  import type { PlayMapRowActivitySource } from './run-ledger';
3
3
  import type { PlayConditionCounts } from './cell-provenance';
4
+ import { PLAY_SANDBOX_SIZE_LIMITS } from '../plays/sandbox-runtime-limits';
4
5
 
5
6
  export type PlayStagedFileRef = {
6
7
  storageKind: 'r2';
@@ -69,6 +70,17 @@ export const MODAL_SANDBOX_COMPUTE_PRICING_USD = {
69
70
  // Convex billing can use it without importing Modal's Node runtime config.
70
71
  export const MODAL_SANDBOX_CPU_CORES = 0.5;
71
72
 
73
+ /** Scale Modal's physical-core request from Deepline's standard one-vCPU unit. */
74
+ export function modalSandboxResourcesForPlay(input: {
75
+ cpu: number;
76
+ memoryGiB: number;
77
+ }) {
78
+ return {
79
+ physicalCpuCores: MODAL_SANDBOX_CPU_CORES * input.cpu,
80
+ memoryMiB: input.memoryGiB * 1024,
81
+ };
82
+ }
83
+
72
84
  const NEON_COMPUTE_PRICING_USD = {
73
85
  cuHour: 0.106,
74
86
  defaultCu: 1,
@@ -78,6 +90,14 @@ function roundUsd(amount: number): number {
78
90
  return Number(Math.max(0, amount).toFixed(12));
79
91
  }
80
92
 
93
+ /** Portable billing identity derived from the resources actually acquired. */
94
+ function sandboxSizeMetadata(cpu: number, memoryGiB: number) {
95
+ const entry = Object.entries(PLAY_SANDBOX_SIZE_LIMITS).find(
96
+ ([, limits]) => limits.cpu === cpu && limits.memoryGiB === memoryGiB,
97
+ );
98
+ return entry ? { sandboxSize: entry[0] } : {};
99
+ }
100
+
81
101
  export function resolveDaytonaSandboxComputeItem(input: {
82
102
  itemId: string;
83
103
  source?: string;
@@ -110,6 +130,7 @@ export function resolveDaytonaSandboxComputeItem(input: {
110
130
  units: billableSeconds,
111
131
  providerCostUsd,
112
132
  metadata: {
133
+ ...sandboxSizeMetadata(cpu, memoryGiB),
113
134
  sandboxId: input.sandboxId ?? null,
114
135
  cpu,
115
136
  memoryGiB,
@@ -126,6 +147,8 @@ export function resolveModalSandboxComputeItem(input: {
126
147
  wallTimeSeconds: number;
127
148
  physicalCpuCores: number;
128
149
  memoryGiB: number;
150
+ /** Bounded burst allocation; absent on fixed-resource historical items. */
151
+ memoryLimitGiB?: number;
129
152
  sandboxId?: string | null;
130
153
  startedAt?: number;
131
154
  endedAt?: number;
@@ -145,9 +168,22 @@ export function resolveModalSandboxComputeItem(input: {
145
168
  units: billableSeconds,
146
169
  providerCostUsd,
147
170
  metadata: {
171
+ ...sandboxSizeMetadata(
172
+ physicalCpuCores / MODAL_SANDBOX_CPU_CORES,
173
+ memoryGiB,
174
+ ),
148
175
  sandboxId: input.sandboxId ?? null,
149
176
  physicalCpuCores,
150
177
  memoryGiB,
178
+ ...(input.memoryLimitGiB === undefined
179
+ ? {}
180
+ : {
181
+ memoryLimitGiB: input.memoryLimitGiB,
182
+ // The Preview evaluation preserves the existing compute price.
183
+ // Sampled burst cost is diagnostic until provider billing can be
184
+ // reconciled; a hard ceiling is never presented as actual usage.
185
+ costBasis: 'reserved_resources',
186
+ }),
151
187
  startedAt: input.startedAt,
152
188
  endedAt: input.endedAt,
153
189
  },
@@ -3,7 +3,10 @@ export type { BillingEntitlements } from '../integration-contracts/billing-entit
3
3
  import { Type, type Static, type TSchema } from '@sinclair/typebox';
4
4
  import { Value } from '@sinclair/typebox/value';
5
5
  import type { PreviousCell } from './cell-staleness';
6
- import type { PlaySandboxRuntimeLimits } from './sandbox-runtime-limits';
6
+ import type {
7
+ PlaySandboxRuntimeLimits,
8
+ PlaySandboxSize,
9
+ } from './sandbox-runtime-limits';
7
10
  import type {
8
11
  BasicToolExecuteResult,
9
12
  ToolExecuteResult,
@@ -207,6 +210,7 @@ export const PLAY_AUTHORING_CONTRACT_ISSUE_CODES = [
207
210
  'play_authoring_row_sleep_unsupported',
208
211
  'play_authoring_hitl_retry_loop_unsafe',
209
212
  'play_authoring_binding_invalid',
213
+ 'play_runtime_size_preview_only',
210
214
  'play_authoring_input_schema_unresolved',
211
215
  'tool_response_raw_access',
212
216
  'check_error',
@@ -863,7 +867,7 @@ export type PlayAuthoringBindings<TInput = Record<string, unknown>> = {
863
867
  */
864
868
  runtime?: {
865
869
  timeout?: string;
866
- size?: 'standard';
870
+ size?: PlaySandboxSize;
867
871
  };
868
872
  /**
869
873
  * Start at most one run of this Play per window, whatever started it.
@@ -2967,20 +2971,26 @@ export const PLAY_AUTHORING_FIELD_REGISTRY = {
2967
2971
  'rateLimit.every must be a static duration from "10s" to "24h", such as "5m".',
2968
2972
  },
2969
2973
  'runtime.size': {
2970
- schema: Type.Literal('standard'),
2974
+ schema: Type.Union([
2975
+ Type.Literal('standard'),
2976
+ Type.Literal('large'),
2977
+ Type.Literal('xlarge'),
2978
+ ]),
2971
2979
  fixtures: {
2972
- valid: 'standard',
2973
- invalid: 'large',
2980
+ valid: 'large',
2981
+ invalid: 'xxlarge',
2974
2982
  absent: undefined,
2975
2983
  unresolved: { expression: 'size' },
2976
2984
  edition1: 'standard',
2977
2985
  },
2978
- referenceType: "'standard'",
2986
+ referenceType: "'standard' | 'large' | 'xlarge'",
2979
2987
  required: false,
2980
2988
  resolution: 'static-required',
2981
2989
  issueCode: 'play_authoring_binding_invalid',
2982
- description: 'Deepline-managed sandbox size. Only standard is supported.',
2983
- errorMessage: 'runtime.size must be the static literal "standard".',
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.',
2992
+ errorMessage:
2993
+ 'runtime.size must be the static literal "standard", "large", or "xlarge".',
2984
2994
  },
2985
2995
  'ctx.customerDb.query.statement': {
2986
2996
  schema: Type.Union([
@@ -1125,7 +1125,7 @@ function sandboxRuntimeDeclarationFromOptions(
1125
1125
  if (unsupportedProperties.length > 0) {
1126
1126
  throw new Error(
1127
1127
  `Unsupported runtime sandbox option${unsupportedProperties.length === 1 ? '' : 's'} "${unsupportedProperties.join('", "')}". ` +
1128
- 'Use runtime.size with a Deepline prebuilt sandbox. Supported sizes: "standard".',
1128
+ 'Use runtime.size with a Deepline prebuilt sandbox. Supported sizes: "standard", "large", or "xlarge".',
1129
1129
  );
1130
1130
  }
1131
1131
  const timeout = resolveStaticProperty(runtime.value, 'timeout', context);
@@ -5,7 +5,7 @@ export type PlaySandboxRuntimeLimits = {
5
5
  diskGiB: number;
6
6
  };
7
7
 
8
- export type PlaySandboxSize = 'standard';
8
+ export type PlaySandboxSize = 'standard' | 'large' | 'xlarge';
9
9
 
10
10
  export const PLAY_SANDBOX_SIZE_LIMITS = {
11
11
  standard: {
@@ -13,6 +13,16 @@ export const PLAY_SANDBOX_SIZE_LIMITS = {
13
13
  cpu: 1,
14
14
  diskGiB: 3,
15
15
  },
16
+ large: {
17
+ memoryGiB: 4,
18
+ cpu: 2,
19
+ diskGiB: 8,
20
+ },
21
+ xlarge: {
22
+ memoryGiB: 8,
23
+ cpu: 4,
24
+ diskGiB: 10,
25
+ },
16
26
  } as const satisfies Record<
17
27
  PlaySandboxSize,
18
28
  Omit<PlaySandboxRuntimeLimits, 'timeoutSeconds'>
@@ -27,11 +37,50 @@ export const STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS: PlaySandboxRuntimeLimits = {
27
37
  // customer-authored Play can never ask Daytona for an unbounded resource.
28
38
  export const MAX_PLAY_SANDBOX_RUNTIME_LIMITS: PlaySandboxRuntimeLimits = {
29
39
  timeoutSeconds: 4 * 60 * 60,
30
- memoryGiB: STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS.memoryGiB,
31
- cpu: STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS.cpu,
32
- diskGiB: STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS.diskGiB,
40
+ ...PLAY_SANDBOX_SIZE_LIMITS.xlarge,
33
41
  };
34
42
 
43
+ /** Bounded Modal burst headroom; resource reservation remains the chosen profile. */
44
+ export function modalSandboxMemoryLimitGiBForPlay(
45
+ input: Pick<PlaySandboxRuntimeLimits, 'memoryGiB'>,
46
+ ): number {
47
+ if (
48
+ !Object.values(PLAY_SANDBOX_SIZE_LIMITS).some(
49
+ (profile) => profile.memoryGiB === input.memoryGiB,
50
+ )
51
+ ) {
52
+ throw new Error(
53
+ `Unsupported Modal memory reservation: ${input.memoryGiB} GiB.`,
54
+ );
55
+ }
56
+ return Math.min(
57
+ Math.max(4, input.memoryGiB * 2),
58
+ MAX_PLAY_SANDBOX_RUNTIME_LIMITS.memoryGiB,
59
+ );
60
+ }
61
+
62
+ export function playSandboxSizeForLimits(
63
+ limits: Pick<PlaySandboxRuntimeLimits, 'memoryGiB' | 'cpu' | 'diskGiB'>,
64
+ ): PlaySandboxSize | null {
65
+ return (
66
+ (
67
+ Object.entries(PLAY_SANDBOX_SIZE_LIMITS) as [
68
+ PlaySandboxSize,
69
+ (typeof PLAY_SANDBOX_SIZE_LIMITS)[PlaySandboxSize],
70
+ ][]
71
+ ).find(
72
+ ([, profile]) =>
73
+ profile.memoryGiB === limits.memoryGiB &&
74
+ profile.cpu === limits.cpu &&
75
+ profile.diskGiB === limits.diskGiB,
76
+ )?.[0] ?? null
77
+ );
78
+ }
79
+
80
+ const supportedPlaySandboxSizes = Object.keys(PLAY_SANDBOX_SIZE_LIMITS).join(
81
+ ', ',
82
+ );
83
+
35
84
  export type PlaySandboxRuntimeDeclaration = {
36
85
  timeout?: string;
37
86
  size?: PlaySandboxSize;
@@ -48,15 +97,11 @@ function parseTimeout(value: string): number | null {
48
97
  export function validatePlaySandboxRuntimeLimits(
49
98
  value: PlaySandboxRuntimeLimits,
50
99
  ): PlaySandboxRuntimeLimits {
51
- const base = STANDARD_PLAY_SANDBOX_RUNTIME_LIMITS;
52
- for (const key of ['memoryGiB', 'cpu', 'diskGiB'] as const) {
53
- if (value[key] !== base[key]) {
54
- throw new Error(
55
- `Unsupported runtime sandbox resources: ${key}=${value[key]}. ` +
56
- `Use runtime.size with a Deepline prebuilt sandbox. Supported sizes: "standard" ` +
57
- `(${base.cpu} CPU, ${base.memoryGiB}GiB memory, ${base.diskGiB}GiB disk).`,
58
- );
59
- }
100
+ if (!playSandboxSizeForLimits(value)) {
101
+ throw new Error(
102
+ `Unsupported runtime sandbox resources: cpu=${value.cpu} memoryGiB=${value.memoryGiB} diskGiB=${value.diskGiB}. ` +
103
+ `Use runtime.size with a Deepline prebuilt sandbox. Supported sizes: ${supportedPlaySandboxSizes}.`,
104
+ );
60
105
  }
61
106
  if (
62
107
  !Number.isSafeInteger(value.timeoutSeconds) ||
@@ -85,12 +130,15 @@ export function resolvePlaySandboxRuntimeLimits(
85
130
  if (unsupportedProperties.length > 0) {
86
131
  throw new Error(
87
132
  `Unsupported runtime sandbox option${unsupportedProperties.length === 1 ? '' : 's'}: ${unsupportedProperties.join(', ')}. ` +
88
- 'Use runtime.size with a Deepline prebuilt sandbox. Supported sizes: "standard".',
133
+ `Use runtime.size with a Deepline prebuilt sandbox. Supported sizes: ${supportedPlaySandboxSizes}.`,
89
134
  );
90
135
  }
91
- if (declaration.size !== undefined && declaration.size !== 'standard') {
136
+ if (
137
+ declaration.size !== undefined &&
138
+ !Object.hasOwn(PLAY_SANDBOX_SIZE_LIMITS, declaration.size)
139
+ ) {
92
140
  throw new Error(
93
- `Unsupported runtime sandbox size "${String(declaration.size)}". Supported sizes: "standard".`,
141
+ `Unsupported runtime sandbox size "${String(declaration.size)}". Supported sizes: ${supportedPlaySandboxSizes}.`,
94
142
  );
95
143
  }
96
144
  const timeoutSeconds = declaration.timeout
@@ -413,7 +413,7 @@ export type ToolExecuteResultAccessors<
413
413
  * incomplete lists throw: use readList(result, path) for explicit dataset access.
414
414
  * Provider pagination requires a separate request; getters never fetch it.
415
415
  * Historical editions 1–9 retain PlayDataset getters and async materialize/peek;
416
- * missing getters never mean empty results. Inspect rawV2 for debugging: parsed,
416
+ * missing getters never mean empty results. Inspect toolResponse.rawV2 for debugging: parsed,
417
417
  * scrubbed envelopes, pagination and siblings, not HTTP bytes or headers.
418
418
  *
419
419
  * @sdkReference runtime 200
@@ -21,6 +21,14 @@ export function isBillingSalesGroupKey(key: unknown): boolean {
21
21
  );
22
22
  }
23
23
 
24
+ export const BILLING_ALERTS_SLACK_CHANNEL_ID = 'C0CEBRS6PHS';
25
+ export const BILLING_USAGE_GROUP_KEY = 'shadow.billing-usage';
26
+ export const BILLING_USAGE_DESTINATION_KEY = 'shadow.billing-usage.slack';
27
+ export function isPlatformBillingDestination(key: unknown): boolean {
28
+ return (
29
+ key === BILLING_USAGE_DESTINATION_KEY || isBillingSalesDestinationKey(key)
30
+ );
31
+ }
24
32
  export const BILLING_SALES_GROUP_KEY = 'shadow.billing-sales';
25
33
  export const BILLING_SALES_DESTINATION_KEY = 'shadow.billing-sales.slack';
26
34
  export const BILLING_SALES_SLACK_CHANNEL_ID = 'C0AGWVD2TNK';
@@ -33,13 +41,18 @@ export const BILLING_DELIVERY_LATE_RULE_KEYS: readonly string[] = [
33
41
  BILLING_RENEWAL_LATE_RULE_KEY,
34
42
  ];
35
43
  export const BILLING_SALES_RULE_KEYS: readonly string[] = [
44
+ 'internal.billing.usage_charge',
36
45
  'internal.billing.credit_purchase',
37
46
  'internal.billing.paid_plan',
38
47
  ...BILLING_DELIVERY_LATE_RULE_KEYS,
39
48
  ];
40
49
 
41
50
  export function isPlatformNotificationGroup(groupKey: string): boolean {
42
- return groupKey === 'shadow' || isBillingSalesGroupKey(groupKey);
51
+ return (
52
+ groupKey === 'shadow' ||
53
+ isBillingSalesGroupKey(groupKey) ||
54
+ groupKey === BILLING_USAGE_GROUP_KEY
55
+ );
43
56
  }
44
57
 
45
58
  /** Permit only the code-owned purchase-rule move while old deliveries drain. */
@@ -20,6 +20,7 @@ import {
20
20
  BILLING_SALES_CREDIT_PURCHASE_MINIMUM_CENTS,
21
21
  BILLING_CREDIT_PURCHASE_GROUP_KEY,
22
22
  BILLING_SALES_GROUP_KEY,
23
+ BILLING_USAGE_GROUP_KEY,
23
24
  } from './billing-sales';
24
25
 
25
26
  export const DEFAULT_PLAY_FAILURE_SETTINGS = {
@@ -995,6 +996,45 @@ const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
995
996
  }),
996
997
  },
997
998
  },
999
+ {
1000
+ id: 'internal.billing.usage_charge',
1001
+ eventName: ProductNotificationEvent.BillingUsageChargeObserved,
1002
+ requiredFilter: {},
1003
+ allowedTriggers: ['each_match'],
1004
+ catalog: undefined,
1005
+ shadow: true,
1006
+ rule: {
1007
+ default: {
1008
+ ruleKey: 'internal.billing.usage_charge',
1009
+ groupKey: BILLING_USAGE_GROUP_KEY,
1010
+ },
1011
+ create: (_input, base) => ({
1012
+ ...base,
1013
+ repeatMode: 'every_match',
1014
+ trigger: { kind: 'each_match' },
1015
+ }),
1016
+ present: ({ payload }) => ({
1017
+ title: payload.paging
1018
+ ? 'Large individual usage charge'
1019
+ : 'Individual usage charge over $10',
1020
+ summary: [
1021
+ { workspace: true },
1022
+ ` charged ${payload.amount} for ${payload.tool}.`,
1023
+ ],
1024
+ details: [
1025
+ `Tool: ${payload.tool}`,
1026
+ `Charge record time (UTC): ${payload.chargeTime}`,
1027
+ `Charge: ${payload.chargeId}`,
1028
+ `Paging threshold: ${payload.threshold}`,
1029
+ 'Investigate settlement and performed work. For verified legitimate large batches, raise monitoring.largeUsageChargeThresholds for this exact tool in its provider registry and publish the catalog. Keep over-$10 messages enabled. Runbook: https://github.com/getaero-io/deepline-api/blob/main/docs/billing-observability.md#individual-charge-slack-messages-and-response',
1030
+ ],
1031
+ targetUrl: String(payload.targetUrl),
1032
+ subjectKind: 'usage_charge',
1033
+ subjectId: String(payload.chargeId),
1034
+ ...(payload.paging ? { severity: 'error' as const } : {}),
1035
+ }),
1036
+ },
1037
+ },
998
1038
  lateCreditDeliveryRegistration(
999
1039
  BILLING_PURCHASE_LATE_RULE_KEY,
1000
1040
  ProductNotificationEvent.BillingPurchaseLate,