deepline 0.3.89 → 0.3.90

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 (211) hide show
  1. package/contracts/install-commands.json +29 -0
  2. package/dist/bundling-sources/sdk/src/client.ts +120 -37
  3. package/dist/bundling-sources/sdk/src/index.ts +4 -0
  4. package/dist/bundling-sources/sdk/src/pagination.ts +25 -0
  5. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  6. package/dist/bundling-sources/sdk/src/release.ts +16 -9
  7. package/dist/bundling-sources/sdk/src/types.ts +23 -0
  8. package/dist/bundling-sources/shared_libs/monitors/analytics-public.ts +10 -0
  9. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +3 -0
  10. package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +4 -4
  11. package/dist/bundling-sources/shared_libs/monitors/inventory-reconciliation.ts +59 -0
  12. package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +7 -9
  13. package/dist/bundling-sources/shared_libs/monitors/package.json +7 -0
  14. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +44 -2
  15. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +2 -2
  16. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +2 -2
  17. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +1 -1
  18. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +245 -0
  19. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-message.ts +38 -0
  20. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-ack-probe.ts +94 -0
  21. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue-delivery.ts +60 -0
  22. package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-queue.ts +60 -0
  23. package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +1 -1
  24. package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +2 -2
  25. package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +1 -1
  26. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +1 -1
  27. package/dist/bundling-sources/shared_libs/observability/build-context.ts +13 -0
  28. package/dist/bundling-sources/shared_libs/observability/log-metadata.ts +160 -0
  29. package/dist/bundling-sources/shared_libs/observability/request-context.ts +68 -0
  30. package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +654 -0
  31. package/dist/bundling-sources/shared_libs/observability/scheduled-work-manual.ts +35 -0
  32. package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +5 -0
  33. package/dist/bundling-sources/shared_libs/play-data-plane/neon-constants.ts +9 -0
  34. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +272 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +45 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +66 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/async-play-invocation.ts +0 -1
  38. package/dist/bundling-sources/shared_libs/play-runtime/bounceban-batching.ts +137 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +779 -140
  40. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +46 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/daytona-payload-contract.ts +68 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +103 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +725 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +29 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +23 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-invocation-fence.ts +442 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/execution-protocol.ts +3 -3
  48. package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +18 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +9 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +1 -1
  51. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +1 -1
  52. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +66 -0
  53. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +145 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/ports.ts +21 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/postgres-store.ts +170 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/internal-bootstrap-token.ts +1322 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +2 -2
  58. package/dist/bundling-sources/shared_libs/play-runtime/lane-retirement-gate.ts +1 -1
  59. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +112 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +8 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/perf-trace.ts +127 -0
  62. package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +5 -0
  63. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +24 -1
  64. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/contracts.ts +56 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/index.ts +13 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/job.ts +41 -0
  67. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/ports.ts +17 -0
  68. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/admission/postgres-adapter.ts +54 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/contracts.ts +114 -0
  70. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/engine-suspension.ts +108 -0
  71. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/index.ts +36 -0
  72. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/job.ts +97 -0
  73. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/ports.ts +104 -0
  74. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/postgres-fence-adapter.ts +263 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/attempts/worker-terminal-projection.ts +64 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/contracts.ts +29 -0
  77. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +56 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/index.ts +15 -0
  79. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/job.ts +65 -0
  80. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/ports.ts +17 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +42 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/index.ts +17 -0
  83. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +142 -0
  84. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/ports.ts +42 -0
  85. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/postgres-adapter.ts +102 -0
  86. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +60 -0
  87. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/index.ts +16 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +78 -0
  89. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +54 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +71 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +4 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +2 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +5 -8
  94. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +3 -5
  95. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +168 -0
  96. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +72 -9
  97. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +6 -12
  98. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +19 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +0 -1
  100. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +14 -44
  101. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +57 -205
  102. package/dist/bundling-sources/shared_libs/play-runtime/runtime-authority.ts +201 -0
  103. package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
  104. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +67 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/runtime-release-environment.ts +2 -0
  106. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-contract.ts +68 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/runtime-scheduler-topology.ts +14 -2
  108. package/dist/bundling-sources/shared_libs/play-runtime/runtime-telemetry.ts +108 -0
  109. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +4 -0
  110. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +3 -0
  111. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +7 -0
  112. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-deadline-reconciliation.ts +88 -0
  113. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +124 -0
  114. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-run-task.ts +68 -0
  115. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-pool.ts +193 -0
  116. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-lifecycle.ts +269 -0
  117. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-queue-suffix.ts +73 -0
  118. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +6642 -0
  119. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +897 -0
  120. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-metrics.ts +120 -0
  121. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +76 -0
  122. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +476 -0
  123. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +47 -0
  124. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +147 -0
  125. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/index.ts +27 -0
  126. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/lazy.ts +50 -0
  127. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/play-run-recovery.ts +98 -0
  128. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-budget-state.ts +260 -0
  129. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +1416 -0
  130. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +1255 -0
  131. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +942 -0
  132. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +14636 -0
  133. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +888 -0
  134. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +355 -0
  135. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +46 -0
  136. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runner-push.ts +60 -0
  137. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-api-origin.ts +33 -0
  138. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-gateway-health.ts +64 -0
  139. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +835 -0
  140. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +1218 -0
  141. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-operation.ts +207 -0
  142. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-scheduler-schema.ts +10 -0
  143. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-capacity.ts +123 -0
  144. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/scheduler-launch-read-observability.ts +78 -0
  145. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/submit-admission.ts +169 -0
  146. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/terminal-contract.ts +20 -0
  147. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-queue-metrics.ts +34 -0
  148. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +249 -0
  149. package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +7 -0
  150. package/dist/bundling-sources/shared_libs/play-runtime/step-progress.ts +296 -0
  151. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +3 -3
  152. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +20 -1
  153. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +60 -15
  154. package/dist/bundling-sources/shared_libs/play-runtime/tool-execution-outcome.ts +20 -0
  155. package/dist/bundling-sources/shared_libs/play-runtime/transient-service-error.ts +12 -0
  156. package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +14 -1
  157. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +165 -0
  158. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +15 -23
  159. package/dist/bundling-sources/shared_libs/play-runtime/workflow-executor-auth.ts +169 -0
  160. package/dist/bundling-sources/shared_libs/play-runtime/zerobounce-batching.ts +122 -0
  161. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +2 -0
  162. package/dist/bundling-sources/shared_libs/plays/contracts.ts +2 -0
  163. package/dist/bundling-sources/shared_libs/plays/deployment-status.ts +1 -1
  164. package/dist/bundling-sources/shared_libs/plays/docflow.ts +1 -1
  165. package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +39 -0
  166. package/dist/bundling-sources/shared_libs/plays/share/copy.ts +271 -0
  167. package/dist/bundling-sources/shared_libs/plays/share/metrics.ts +140 -0
  168. package/dist/bundling-sources/shared_libs/plays/share/public-read-model.ts +86 -0
  169. package/dist/bundling-sources/shared_libs/plays/share/seo-quality.ts +181 -0
  170. package/dist/bundling-sources/shared_libs/plays/share/static-pipeline-summary.ts +658 -0
  171. package/dist/bundling-sources/shared_libs/plays/share/types.ts +197 -0
  172. package/dist/bundling-sources/shared_libs/plays/slack-hitl-timeout-error.ts +37 -0
  173. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +6 -1
  174. package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +1 -1
  175. package/dist/bundling-sources/shared_libs/product-notifications/configuration.ts +7 -1
  176. package/dist/bundling-sources/shared_libs/product-notifications/default-policy.ts +3 -2
  177. package/dist/bundling-sources/shared_libs/product-notifications/overflow.ts +18 -0
  178. package/dist/bundling-sources/shared_libs/product-notifications/rolling-limit.ts +24 -0
  179. package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +624 -0
  180. package/dist/bundling-sources/shared_libs/product-notifications/slack-payload.ts +140 -0
  181. package/dist/bundling-sources/shared_libs/product-notifications/slack-transport.ts +75 -0
  182. package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
  183. package/dist/bundling-sources/shared_libs/security/email-blacklist.ts +42 -0
  184. package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +10 -3
  185. package/dist/bundling-sources/shared_libs/security/slack-sanitize.ts +133 -0
  186. package/dist/cli/getting-started.d.mts +52 -0
  187. package/dist/cli/getting-started.d.ts +52 -0
  188. package/dist/cli/getting-started.js +65 -0
  189. package/dist/cli/getting-started.mjs +36 -0
  190. package/dist/cli/index.d.mts +9 -0
  191. package/dist/cli/index.d.ts +9 -0
  192. package/dist/cli/index.js +545 -264
  193. package/dist/cli/index.mjs +543 -271
  194. package/dist/{compiler-manifest-DsLbQaew.d.mts → compiler-manifest-BaJfE-5H.d.mts} +5 -3
  195. package/dist/{compiler-manifest-DsLbQaew.d.ts → compiler-manifest-BaJfE-5H.d.ts} +5 -3
  196. package/dist/index.d.mts +81 -148
  197. package/dist/index.d.ts +81 -148
  198. package/dist/index.js +83 -26
  199. package/dist/index.mjs +83 -26
  200. package/dist/install-integrity.json +130 -2
  201. package/dist/monitor-fleet-contract.d.mts +138 -0
  202. package/dist/monitor-fleet-contract.d.ts +138 -0
  203. package/dist/monitor-fleet-contract.js +404 -0
  204. package/dist/monitor-fleet-contract.mjs +367 -0
  205. package/dist/plays/bundle-play-file.d.mts +2 -2
  206. package/dist/plays/bundle-play-file.d.ts +2 -2
  207. package/dist/release.d.mts +255 -0
  208. package/dist/release.d.ts +255 -0
  209. package/dist/release.js +215 -0
  210. package/dist/release.mjs +188 -0
  211. package/package.json +21 -4
@@ -0,0 +1,897 @@
1
+ /**
2
+ * Shared constants and pure helpers for the `absurd` play-runtime scheduler
3
+ * profile — imported by both the scheduler backend (`absurd.ts`, submit side)
4
+ * and the durable task handler (`absurd-runtime-worker.ts`, worker side).
5
+ *
6
+ * This module is greenfield and does not import legacy scheduler modules. The
7
+ * battle-tested Postgres run-state store
8
+ * (`postgres.ts`) remains the source of truth for launch rows, attempts,
9
+ * receipts, signals, and terminal state; `absurd-shared` only adds the
10
+ * absurd-specific wiring (queue, event names, checkpoint boundary shaping).
11
+ */
12
+ import { createHash } from 'node:crypto';
13
+ import type {
14
+ PlayCheckpoint,
15
+ PlayExecutionEvent,
16
+ PlayRowUpdate,
17
+ } from '../ctx-types';
18
+ import type { PlayExecutionSuspension } from '../suspension';
19
+ import type { PlayRunLedgerEvent } from '../run-ledger';
20
+ import type { PlaySchedulerSubmitInput } from '../scheduler-backend';
21
+ import type { WorkerRuntimeApiContext } from '../app-runtime-api';
22
+ import { RUNTIME_CAPACITY_POLICY } from '../runtime-capacity-policy';
23
+ import { assertKnownResolvedBoundary } from '../checkpoint-boundaries';
24
+ import type { Absurd, Log, RetryStrategy } from '@deepline/absurd/sdk';
25
+ import { isIsolatedRuntimeSchedulerSchema } from '../runtime-scheduler-topology';
26
+ import {
27
+ ensureAbsurdSchema,
28
+ failPostgresSchedulerScheduledChildWaits,
29
+ type PostgresSchedulerWorkerOnceResult,
30
+ } from './postgres';
31
+ import { createAbsurdForRuntimeScheduler } from './absurd-pool';
32
+ import {
33
+ RUNTIME_SCHEDULER_CONTROL_PLANE_MAX_CLIENTS,
34
+ resolveWorkflowRuntimePostgresUrl,
35
+ withRuntimeSchedulerClient,
36
+ type RuntimeSchedulerPoolOptions,
37
+ } from './postgres-pool';
38
+ import { runtimeApiBaseUrlFromLaunch } from './runtime-api-origin';
39
+ import {
40
+ ABSURD_DEV_RELEASE_PREFIX,
41
+ ABSURD_PROD_DEFAULT_SCHEMA,
42
+ ABSURD_QUEUE_SUFFIX_ENV,
43
+ ABSURD_SCHEDULER_SCHEMA_ENV,
44
+ absurdDevReleaseIdForQueueSuffix,
45
+ resolveAbsurdQueueSuffix,
46
+ resolveAbsurdQueueSuffixForSchema,
47
+ } from './absurd-queue-suffix';
48
+
49
+ export {
50
+ ABSURD_PROD_DEFAULT_SCHEMA,
51
+ ABSURD_QUEUE_SUFFIX_ENV,
52
+ ABSURD_SCHEDULER_SCHEMA_ENV,
53
+ resolveAbsurdQueueSuffix,
54
+ resolveAbsurdQueueSuffixForSchema,
55
+ } from './absurd-queue-suffix';
56
+
57
+ type EnvMap = Record<string, string | undefined>;
58
+
59
+ /**
60
+ * Per-worktree ENGINE-table isolation — option (a): queue-NAME scoping.
61
+ *
62
+ * The vendored Absurd SQL hardcodes the schema literal `absurd`, and dev:v2
63
+ * points every worktree at the SAME docker Postgres (`deepline_dev`), so all
64
+ * worktrees write into one shared `absurd` schema. Absurd partitions its state
65
+ * BY QUEUE: each queue owns its own `t_/r_/c_/e_/w_` tables
66
+ * (`absurd.t_<queue>`, `absurd.r_<queue>`, ...). Two worktrees with DISTINCT
67
+ * queue names therefore touch DISJOINT tables inside the shared schema. We scope
68
+ * the queue NAMES by a per-worktree suffix instead of rewriting the schema
69
+ * literal (option (b)), which would mean patching the whole SQL blob and every
70
+ * cross-queue `absurd.` function reference — far riskier for zero extra benefit.
71
+ *
72
+ * The suffix comes from a DEDICATED env var `DEEPLINE_ABSURD_QUEUE_SUFFIX` (set
73
+ * per worktree by dev-v2-runtime.sh), NOT from DEEPLINE_RUNTIME_SCHEDULER_SCHEMA.
74
+ * Keeping it separate means unit tests — which never set it, and which mutate
75
+ * the scheduler-schema env — keep the canonical base name `runs`
76
+ * and stay hermetic. Unset (tests, prod default) => base names, so nothing
77
+ * changes anywhere the suffix is absent.
78
+ *
79
+ * Agreement across processes: the backend (submit/cancel/signal) and the worker
80
+ * (task registration + poll loop) both import THESE constants, so within a
81
+ * process they are identical, and across the app + worker processes they agree
82
+ * because dev:v2 exports the same suffix into both environments. Queue metrics
83
+ * read the run-state store's `attempts` table, isolated by
84
+ * DEEPLINE_RUNTIME_SCHEDULER_SCHEMA, so they never touch these queue-name tables.
85
+ */
86
+ /**
87
+ * Worker release identity env var. Baked into the Fly image at build time as an
88
+ * ARG/ENV (see the Fly Dockerfile), so a given machine's process knows exactly
89
+ * which release lane it belongs to. Absent in dev/unit tests => the dev-collapse
90
+ * rule applies and lane checks pass trivially.
91
+ */
92
+ export const ABSURD_RELEASE_ID_ENV = 'DEEPLINE_RUNTIME_DEPLOY_VERSION';
93
+
94
+ /**
95
+ * Sentinel release id used everywhere no real deploy version exists (unit tests,
96
+ * plain local `dev:v2` with no worktree suffix, prod default). Maps to the plain
97
+ * base `runs` queue — byte-identical to today.
98
+ */
99
+ export const ABSURD_DEV_RELEASE_ID = 'dev';
100
+
101
+ /**
102
+ * Prefix stamped on the dev-collapse release id when a worktree/preview queue
103
+ * suffix is active. `dev-<suffix>` deterministically maps back to today's
104
+ * `runs_<suffix>` queue via `absurdQueueNameForRelease`, so local dev is
105
+ * unchanged while the release id still travels on the run for the lane guard.
106
+ */
107
+ const ABSURD_RUNS_QUEUE_BASE = 'runs';
108
+
109
+ /** True for any release lane derived from the durable Absurd runs queue. */
110
+ export function isAbsurdRunsQueue(queueName: string): boolean {
111
+ return (
112
+ queueName === ABSURD_RUNS_QUEUE_BASE ||
113
+ queueName.startsWith(`${ABSURD_RUNS_QUEUE_BASE}_`)
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Longest queue-name suffix we allow before hashing. Absurd derives engine
119
+ * tables `t_<queue>` / `r_<queue>` (2-char prefixes) from the queue name, and
120
+ * Postgres caps identifiers at 63 bytes, so `runs_` (5) + prefix (2) + suffix
121
+ * must stay under 63. We cap the release-derived suffix well below that and hash
122
+ * any overflow deterministically so two long release ids never collide onto one
123
+ * lane.
124
+ */
125
+ const ABSURD_RELEASE_QUEUE_SUFFIX_MAX = 40;
126
+
127
+ /**
128
+ * A worker whose scheduler schema is anything OTHER than the prod default is
129
+ * running an isolated lane topology — a per-PR preview fleet or a per-worktree
130
+ * dev fleet — where the release lane (and therefore the queue it must poll) is
131
+ * NOT the base `runs` queue but `runs_<suffix>`. In that topology the suffix is
132
+ * the SINGLE provisioned lane identity: the submitting app derives it from the
133
+ * per-request schema and the polling worker reads it from its own env, and both
134
+ * MUST agree or every run enqueues onto a queue no worker drains and hangs.
135
+ *
136
+ * This returns true exactly when the worker's schema demands a provisioned
137
+ * suffix. The boot guard uses it to refuse to start (loudly, naming the missing
138
+ * variable) rather than silently fall back to the base `runs` queue — the
139
+ * dev-collapse fallback is safe ONLY when the topology can guarantee all parties
140
+ * the same inputs, which an isolated-schema worker with no suffix cannot.
141
+ * Delegates to the shared topology predicate so the Daytona egress guard and
142
+ * this lane guard can never disagree about what "isolated" means.
143
+ */
144
+ export function absurdWorkerSchemaRequiresQueueSuffix(
145
+ schedulerSchema: string | null | undefined,
146
+ ): boolean {
147
+ return isIsolatedRuntimeSchedulerSchema(schedulerSchema);
148
+ }
149
+
150
+ /**
151
+ * Sanitize an arbitrary release id into a queue-name segment: lowercase, collapse
152
+ * non-`[a-z0-9_]` to `_`, trim leading/trailing `_`. Overflow past
153
+ * `ABSURD_RELEASE_QUEUE_SUFFIX_MAX` is truncated AND fingerprinted with a short
154
+ * sha256 tail so two long-but-distinct release ids can never alias onto the same
155
+ * lane. Returns `''` for an empty/garbage input so the caller can fail loudly.
156
+ */
157
+ function sanitizeReleaseQueueSegment(releaseId: string): string {
158
+ const normalized = releaseId
159
+ .trim()
160
+ .toLowerCase()
161
+ .replace(/[^a-z0-9_]+/g, '_')
162
+ .replace(/^_+|_+$/g, '');
163
+ if (!normalized) return '';
164
+ if (normalized.length <= ABSURD_RELEASE_QUEUE_SUFFIX_MAX) return normalized;
165
+ // Deterministic overflow: keep a readable head, append a hash tail so the full
166
+ // (untruncated) release id disambiguates collisions.
167
+ const hashTail = createHash('sha256')
168
+ .update(normalized)
169
+ .digest('hex')
170
+ .slice(0, 8);
171
+ const headBudget = ABSURD_RELEASE_QUEUE_SUFFIX_MAX - hashTail.length - 1;
172
+ return `${normalized.slice(0, headBudget).replace(/_+$/g, '')}_${hashTail}`;
173
+ }
174
+
175
+ /**
176
+ * Resolve THIS worker process's release lane id (the "current" lane). Precedence,
177
+ * matching the dev-collapse rule:
178
+ * 1. queue suffix set (worktree/local dev/preview CI) => `dev-<suffix>`, which
179
+ * maps back to today's `runs_<suffix>` queue — local dev is unchanged.
180
+ * 2. deploy version set (Fly release machine) => sanitized deploy version.
181
+ * 3. neither (unit tests / prod default) => `dev` => plain `runs` queue.
182
+ */
183
+ export function currentAbsurdReleaseId(env: EnvMap = process.env): string {
184
+ const suffix = resolveAbsurdQueueSuffixFromEnv(env);
185
+ if (suffix) return `${ABSURD_DEV_RELEASE_PREFIX}${suffix}`;
186
+ const deployVersion = env[ABSURD_RELEASE_ID_ENV]?.trim();
187
+ if (deployVersion) {
188
+ const sanitized = sanitizeReleaseQueueSegment(deployVersion);
189
+ if (!sanitized) {
190
+ throw new Error(
191
+ `absurd release id: ${ABSURD_RELEASE_ID_ENV}="${deployVersion}" ` +
192
+ `sanitizes to empty. A deploy version must contain at least one ` +
193
+ `[a-z0-9] character.`,
194
+ );
195
+ }
196
+ return sanitized;
197
+ }
198
+ return ABSURD_DEV_RELEASE_ID;
199
+ }
200
+
201
+ /**
202
+ * Release id as persisted in `runs.launch_json`. Production launches retain the
203
+ * raw registry id, while the worker lane id above is sanitized/hashed to fit an
204
+ * Absurd queue name. Preview suffixes still use the dev-collapse id because the
205
+ * suffix outranks a baked deploy version in both the app and worker topology.
206
+ */
207
+ export function currentAbsurdLaunchReleaseId(
208
+ env: EnvMap = process.env,
209
+ ): string {
210
+ const suffix = resolveAbsurdQueueSuffixFromEnv(env);
211
+ if (suffix) return `${ABSURD_DEV_RELEASE_PREFIX}${suffix}`;
212
+ return env[ABSURD_RELEASE_ID_ENV]?.trim() || ABSURD_DEV_RELEASE_ID;
213
+ }
214
+
215
+ /**
216
+ * The release lane queue for a given release id. This is the ONE queue a run and
217
+ * all of its descendants/wakeups ride for its whole life.
218
+ *
219
+ * Dev-collapse mapping (byte-identical to today's suffix behavior):
220
+ * - `dev` => `runs` (plain base queue)
221
+ * - `dev-<suffix>` => `runs_<suffix>` (existing worktree/preview queue)
222
+ * - `<release>` => `runs_<sanitized-release>` (a real Fly release lane)
223
+ *
224
+ * Loud failure: an empty/whitespace release id is a programming error (the
225
+ * resolver never returns one), so we throw rather than silently landing work on
226
+ * the base queue where no lane worker may be draining.
227
+ */
228
+ export function absurdQueueNameForRelease(releaseId: string): string {
229
+ const trimmed = releaseId?.trim();
230
+ if (!trimmed) {
231
+ throw new Error(
232
+ 'absurdQueueNameForRelease requires a non-empty release id. ' +
233
+ 'Resolve it via currentAbsurdReleaseId() or the launch row.',
234
+ );
235
+ }
236
+ if (trimmed === ABSURD_DEV_RELEASE_ID) {
237
+ return absurdQueueName(ABSURD_RUNS_QUEUE_BASE, '');
238
+ }
239
+ if (trimmed.startsWith(ABSURD_DEV_RELEASE_PREFIX)) {
240
+ const suffix = normalizeQueueSuffixValue(
241
+ trimmed.slice(ABSURD_DEV_RELEASE_PREFIX.length),
242
+ );
243
+ return absurdQueueName(ABSURD_RUNS_QUEUE_BASE, suffix);
244
+ }
245
+ const segment = sanitizeReleaseQueueSegment(trimmed);
246
+ if (!segment) {
247
+ throw new Error(
248
+ `absurdQueueNameForRelease: release id "${trimmed}" sanitizes to empty.`,
249
+ );
250
+ }
251
+ return absurdQueueName(ABSURD_RUNS_QUEUE_BASE, segment);
252
+ }
253
+
254
+ /**
255
+ * Loud lane guard used belt-and-suspenders when a worker executes a claimed run.
256
+ * The monogamous poll loop makes a mismatch structurally unreachable; this exists
257
+ * to catch operator error (e.g. a lane machine pointed at the wrong queue). The
258
+ * message is operator-actionable: it names both ids and the remediation.
259
+ */
260
+ export function assertAbsurdReleaseLaneMatches(input: {
261
+ runId: string;
262
+ runReleaseId: string;
263
+ workerReleaseId: string;
264
+ }): void {
265
+ if (input.runReleaseId === input.workerReleaseId) return;
266
+ // A release id reaches this guard in one of two forms: the raw deploy slug
267
+ // stamped onto a launch from the release registry (`main-<sha>`), or the
268
+ // sanitized lane id a worker reports via `currentAbsurdReleaseId()`
269
+ // (`main_<sha>_<hash>`). Both map to the SAME lane queue, which is the real
270
+ // claim boundary — a run lands on, and is claimed off, its queue regardless
271
+ // of which spelling named it. Compare derived queues so the two spellings of
272
+ // one lane match. Without this, a production-length id (whose sanitized form
273
+ // diverges from the raw slug once it overflows the queue-segment budget)
274
+ // fails this guard on EVERY attempt even though the run correctly landed on
275
+ // this worker's queue; short dev/preview ids never overflow, so raw ==
276
+ // sanitized and they slipped past this guard in CI.
277
+ if (
278
+ absurdQueueNameForRelease(input.runReleaseId) ===
279
+ absurdQueueNameForRelease(input.workerReleaseId)
280
+ ) {
281
+ return;
282
+ }
283
+ throw new Error(
284
+ `absurd release lane mismatch for run ${input.runId}: the run was launched ` +
285
+ `on release "${input.runReleaseId}" but this worker serves release ` +
286
+ `"${input.workerReleaseId}". A run must finish on the release that ` +
287
+ `launched it. Remediation: start a lane machine for "${input.runReleaseId}" ` +
288
+ `(queue ${absurdQueueNameForRelease(input.runReleaseId)}).`,
289
+ );
290
+ }
291
+
292
+ /**
293
+ * Seconds a foreign, non-adoptable task is parked for before the owning lane
294
+ * gets another chance at it. Matches the historical release-guard deferral so
295
+ * the observable retry cadence is unchanged.
296
+ */
297
+ export const ABSURD_LANE_ADOPTION_DEFER_SECONDS = 30;
298
+
299
+ /** Absurd worker-id suffix that opts a poll loop into cross-lane claims. */
300
+ export const ABSURD_CONTINUATION_WORKER_SUFFIX = ':continuations';
301
+
302
+ /**
303
+ * Worker id for an adoption poll loop. The `:continuations` suffix is load
304
+ * bearing: vendored `claim_task` patch (g) admits such a worker only for runs
305
+ * still `sleeping` or carrying a non-null event-wake payload, so a fresh or
306
+ * ordinary retry-pending task on the adopted queue is never handed to it.
307
+ */
308
+ export function absurdContinuationWorkerId(input: {
309
+ workerBase: string;
310
+ queue: string;
311
+ }): string {
312
+ return `${input.workerBase}:${input.queue}${ABSURD_CONTINUATION_WORKER_SUFFIX}`;
313
+ }
314
+
315
+ export type AbsurdReleaseLaneDisposition =
316
+ /** The run belongs to this worker's own lane. */
317
+ | { kind: 'own' }
318
+ /** A retired lane's parked durable wait resumes on this newer release. */
319
+ | { kind: 'adopt'; adoptedReleaseId: string }
320
+ /** Foreign, not adoptable: park it without spending a task attempt. */
321
+ | { kind: 'defer'; reason: string; delaySeconds: number }
322
+ /** Foreign lane that still claims — operator error, fail loudly. */
323
+ | { kind: 'refuse' };
324
+
325
+ /**
326
+ * Decide what this worker may do with a claimed run, given the durable lane
327
+ * facts read from the scheduler Postgres.
328
+ *
329
+ * This is the adapter half of the adoption rule (lane handoff). The engine's
330
+ * `:continuations` fence already stops most cross-lane claims; this decides the
331
+ * cases the engine cannot see, because `sleeping` in the engine covers durable
332
+ * suspensions, retry backoff, and capacity deferrals alike.
333
+ *
334
+ * `laneClaiming` and `parkedInAdoptableWait` come from
335
+ * `readPostgresSchedulerRunLaneState`, never from a timer or a machine count.
336
+ */
337
+ export function resolveAbsurdReleaseLaneDisposition(input: {
338
+ runId: string;
339
+ runReleaseId: string;
340
+ workerReleaseId: string;
341
+ /** Does the run's OWN lane still claim work off its queue? */
342
+ laneClaiming: boolean;
343
+ /** Is the run parked in a sleep/integration-event wait right now? */
344
+ parkedInAdoptableWait: boolean;
345
+ }): AbsurdReleaseLaneDisposition {
346
+ if (
347
+ input.runReleaseId === input.workerReleaseId ||
348
+ absurdQueueNameForRelease(input.runReleaseId) ===
349
+ absurdQueueNameForRelease(input.workerReleaseId)
350
+ ) {
351
+ return { kind: 'own' };
352
+ }
353
+ // A lane that still claims owns every one of its rows. Reaching this branch
354
+ // means a machine is polling a queue it does not serve; stay loud.
355
+ if (input.laneClaiming) return { kind: 'refuse' };
356
+ if (!input.parkedInAdoptableWait) {
357
+ return {
358
+ kind: 'defer',
359
+ reason:
360
+ `Run ${input.runId} rides retired release lane "${input.runReleaseId}" ` +
361
+ `but is not parked in an adoptable durable wait. Ordinary queued and ` +
362
+ `running work is never adopted; leaving it for its owning lane.`,
363
+ delaySeconds: ABSURD_LANE_ADOPTION_DEFER_SECONDS,
364
+ };
365
+ }
366
+ return { kind: 'adopt', adoptedReleaseId: input.runReleaseId };
367
+ }
368
+
369
+ /**
370
+ * Adoption target queues for a worker, given the release ids the scheduler
371
+ * reported as adoptable. Two release-id spellings of one lane collapse to one
372
+ * queue, and this worker's own queue is never attached twice — which is why a
373
+ * dev-collapse topology (every run stamped `dev`) attaches nothing at all.
374
+ */
375
+ export function absurdAdoptionQueuesForReleases(input: {
376
+ workerReleaseId: string;
377
+ adoptableReleaseIds: readonly string[];
378
+ }): string[] {
379
+ const ownQueue = absurdQueueNameForRelease(input.workerReleaseId);
380
+ const queues = new Set<string>();
381
+ for (const releaseId of input.adoptableReleaseIds) {
382
+ const trimmed = releaseId?.trim();
383
+ if (!trimmed) continue;
384
+ const queue = absurdQueueNameForRelease(trimmed);
385
+ if (queue === ownQueue) continue;
386
+ queues.add(queue);
387
+ }
388
+ return [...queues].sort();
389
+ }
390
+
391
+ /**
392
+ * Env-driven queue suffix read (worker/dev:v2). Extracted so
393
+ * `currentAbsurdReleaseId` can take an explicit env for hermetic tests while
394
+ * `resolveAbsurdQueueSuffix` keeps its process.env signature.
395
+ */
396
+ function resolveAbsurdQueueSuffixFromEnv(env: EnvMap): string {
397
+ return resolveAbsurdQueueSuffix(env);
398
+ }
399
+
400
+ function normalizeQueueSuffixValue(raw: string): string {
401
+ return raw
402
+ .toLowerCase()
403
+ .replace(/[^a-z0-9_]+/g, '_')
404
+ .replace(/^_+|_+$/g, '')
405
+ .slice(0, 32);
406
+ }
407
+
408
+ function absurdQueueName(base: string, suffix: string): string {
409
+ return suffix ? `${base}_${suffix}` : base;
410
+ }
411
+
412
+ /**
413
+ * Resolve the per-worktree/per-PR queue suffix for a submit/signal/cancel that
414
+ * carries an EXPLICIT per-request scheduler schema (threaded from the CLI via
415
+ * the runtime-scheduler-schema header → `createAbsurdSchedulerBackend({schema})`).
416
+ *
417
+ * Why this exists: the module-level `ABSURD_RUNS_QUEUE` constants read the
418
+ * `DEEPLINE_ABSURD_QUEUE_SUFFIX` env var ONCE at import. That works for the Fly
419
+ * worker (env set as a secret) and dev:v2 (env exported into every process),
420
+ * but NOT for the shared hosted preview app, which learns the per-PR schema
421
+ * only per-request and has no such env — so its enqueue would land on the base
422
+ * `runs` queue while the worker drains `runs_<suffix>`, and every run hangs.
423
+ *
424
+ * Precedence: the env suffix applies when the explicit schema is native to the
425
+ * process. A different explicit schema identifies a remote runtime, so derive
426
+ * the SAME suffix its worker uses — sha256(schema)[:12]. Prod-default or an
427
+ * unset schema retains the process suffix when present.
428
+ */
429
+ /**
430
+ * Normalize an explicit queue suffix (from the request layer via
431
+ * `resolveAbsurdQueueSuffixForSchema`). An empty/undefined value falls back to
432
+ * the process env suffix — so the Fly worker and dev:v2 (env set) keep their
433
+ * suffixed queues, direct backend construction in unit tests (no suffix, env
434
+ * cleared) stays on base names, and the shared hosted app (env absent) uses the
435
+ * explicit per-request suffix threaded down from `lazy.ts`.
436
+ */
437
+ function effectiveAbsurdQueueSuffix(explicit?: string | null): string {
438
+ const normalized = explicit?.trim();
439
+ if (normalized) return normalizeQueueSuffixValue(normalized);
440
+ return resolveAbsurdQueueSuffix();
441
+ }
442
+
443
+ /**
444
+ * Map an effective queue suffix to a release id under the dev-collapse rule.
445
+ * Empty suffix => the process default release (`dev`, or the baked deploy
446
+ * version on a Fly lane machine); a non-empty suffix => `dev-<suffix>`, which
447
+ * `absurdQueueNameForRelease` maps back to today's `runs_<suffix>` queue. This
448
+ * is how the shared hosted preview app (which threads an explicit per-PR suffix
449
+ * derived from the schema) stamps the SAME release id the Fly preview worker
450
+ * serves, without touching local-dev behavior.
451
+ */
452
+ export function absurdReleaseIdForSuffix(
453
+ explicitSuffix?: string | null,
454
+ env: EnvMap = process.env,
455
+ ): string {
456
+ const suffix = effectiveAbsurdQueueSuffix(explicitSuffix);
457
+ if (suffix) return absurdDevReleaseIdForQueueSuffix(suffix);
458
+ return currentAbsurdReleaseId(env);
459
+ }
460
+
461
+ /**
462
+ * Resolve the release lane a launch row belongs to. Enforce-when-exists /
463
+ * allow-when-absent: a run stamped with `absurdReleaseId` rides its lane; a
464
+ * pre-release launch row (no stamp — the only compat allowance in this PR) falls
465
+ * back to THIS process's dev-collapse id so preview/burn-in continuity holds.
466
+ */
467
+ export function absurdReleaseIdForLaunch(
468
+ launch: { absurdReleaseId?: string | null },
469
+ env: EnvMap = process.env,
470
+ ): string {
471
+ const stamped = launch.absurdReleaseId?.trim();
472
+ if (stamped) return stamped;
473
+ return currentAbsurdReleaseId(env);
474
+ }
475
+
476
+ /** The release lane queue a launch row rides for its whole lifecycle. */
477
+ export function absurdQueueNameForLaunch(
478
+ launch: { absurdReleaseId?: string | null },
479
+ env: EnvMap = process.env,
480
+ ): string {
481
+ return absurdQueueNameForRelease(absurdReleaseIdForLaunch(launch, env));
482
+ }
483
+
484
+ /** Shared run queue for top-level plays and their parked resumptions. */
485
+ export function absurdRunsQueueForSuffix(suffix?: string | null): string {
486
+ return absurdQueueName(
487
+ ABSURD_RUNS_QUEUE_BASE,
488
+ effectiveAbsurdQueueSuffix(suffix),
489
+ );
490
+ }
491
+
492
+ /** Compatibility shape for admin callers that enumerate scheduler queues. */
493
+ export function absurdAllQueuesForSuffix(suffix?: string | null): string[] {
494
+ return [absurdRunsQueueForSuffix(suffix)];
495
+ }
496
+
497
+ export const ABSURD_RUNS_QUEUE = absurdRunsQueueForSuffix();
498
+
499
+ /** Durable task for one top-level play run. */
500
+ export {
501
+ ABSURD_PLAY_RUN_MAX_ATTEMPTS,
502
+ ABSURD_PLAY_RUN_RETRY_STRATEGY,
503
+ ABSURD_PLAY_RUN_TASK,
504
+ type AbsurdPlayRunTaskParams,
505
+ } from './absurd-play-run-task';
506
+ /** Worker-owned cleanup for a Daytona sandbox in this release lane. */
507
+ export const ABSURD_DAYTONA_SANDBOX_CLEANUP_TASK = 'daytona-sandbox-cleanup';
508
+
509
+ /** Terminal engine rows are diagnostic scratch state, not the durable run
510
+ * ledger. Keep a six-hour incident window, then let the scaler's bounded
511
+ * maintenance pass remove them so claims do not rescan an ever-growing table. */
512
+ export const ABSURD_RUNS_CLEANUP_TTL = '6 hours';
513
+ export const ABSURD_RUNS_CLEANUP_LIMIT = 1_000;
514
+
515
+ export type AbsurdDaytonaSandboxCleanupTaskParams = {
516
+ runId: string;
517
+ sandboxId: string;
518
+ };
519
+
520
+ /** Schema is `string | undefined` (never null) so it is directly assignable to
521
+ * the Postgres store's `Pick<PostgresSchedulerOptions, 'schema'>`. */
522
+ export type AbsurdSchedulerOptions = {
523
+ url?: string | null;
524
+ schema?: string;
525
+ };
526
+
527
+ export type AbsurdRuntimeResult = {
528
+ ok: true;
529
+ runId: string;
530
+ schedulerStatus: 'completed' | 'failed' | 'idle';
531
+ output?: unknown;
532
+ error?: string;
533
+ };
534
+
535
+ // All queues in one worker share this single process pool. Scheduler queries
536
+ // are short and batched; execution concurrency belongs to Daytona/Fly slots,
537
+ // not to a growing set of database clients. Keeping this at two makes a
538
+ // horizontally scaled fleet predictable against the shared PgBouncer budget.
539
+ export const ABSURD_RUNTIME_POOL_MAX_CLIENTS =
540
+ RUNTIME_SCHEDULER_CONTROL_PLANE_MAX_CLIENTS;
541
+
542
+ /**
543
+ * A Vercel invocation has its own module cache and pool. Letting every
544
+ * short-lived launch hold the worker-sized cap multiplies the shared
545
+ * PgBouncer budget under a burst of webhook deliveries. A launch submits one
546
+ * durable transaction at a time, so one checkout preserves correctness while
547
+ * the long-lived Fly worker keeps its two-client control-plane budget.
548
+ */
549
+ export const ABSURD_SERVERLESS_POOL_MAX_CLIENTS = 1;
550
+
551
+ /**
552
+ * Resolve the runtime scheduler Postgres pool options for the absurd control
553
+ * plane. Loud failure over silent fallback: the absurd worker and scheduler
554
+ * only run under Node where `WORKFLOW_RUNTIME_POSTGRES` is configured.
555
+ */
556
+ export function resolveAbsurdPoolOptions(
557
+ explicitUrl?: string | null,
558
+ ): RuntimeSchedulerPoolOptions {
559
+ const url = explicitUrl?.trim() || resolveWorkflowRuntimePostgresUrl();
560
+ if (!url) {
561
+ throw new Error(
562
+ 'absurd scheduler backend requires WORKFLOW_RUNTIME_POSTGRES (runtime ' +
563
+ 'scheduler Postgres URL). Refusing to fall back to local execution.',
564
+ );
565
+ }
566
+ return {
567
+ url,
568
+ applicationName: 'deepline-absurd-runtime',
569
+ maxClients: process.env.VERCEL?.trim()
570
+ ? ABSURD_SERVERLESS_POOL_MAX_CLIENTS
571
+ : ABSURD_RUNTIME_POOL_MAX_CLIENTS,
572
+ };
573
+ }
574
+
575
+ export function normalizeAbsurdSchedulerOptions(
576
+ options: { url?: string | null; schema?: string | null } = {},
577
+ ): AbsurdSchedulerOptions {
578
+ const url = options.url?.trim() || null;
579
+ const schema = options.schema?.trim();
580
+ return {
581
+ ...(url ? { url } : {}),
582
+ ...(schema ? { schema } : {}),
583
+ };
584
+ }
585
+
586
+ const absurdLog: Log = {
587
+ log: (...args) => console.log('[absurd-runtime]', ...args),
588
+ info: (...args) => console.info('[absurd-runtime]', ...args),
589
+ warn: (...args) => console.warn('[absurd-runtime]', ...args),
590
+ error: (...args) => console.error('[absurd-runtime]', ...args),
591
+ };
592
+
593
+ /**
594
+ * Construct an Absurd client on the hardened runtime scheduler pool. The
595
+ * client's `queueName` is the queue its `startWorker` poll loop drains and the
596
+ * default target for `emitEvent`/`spawn` without an explicit queue; defaults to
597
+ * the root `runs` queue. Task registrations are attached by the worker.
598
+ */
599
+ export function createAbsurdRuntimeClient(
600
+ poolOptions: RuntimeSchedulerPoolOptions,
601
+ queueName: string = ABSURD_RUNS_QUEUE,
602
+ ): Absurd {
603
+ return createAbsurdForRuntimeScheduler(poolOptions, {
604
+ queueName,
605
+ log: absurdLog,
606
+ });
607
+ }
608
+
609
+ const absurdReadyPromises = new Map<string, Promise<void>>();
610
+
611
+ /**
612
+ * Ensure, once per process (keyed by pool url + schema): the vendored absurd
613
+ * schema and the shared queue with its
614
+ * policies. Idempotent and safe under concurrent submits — the memoized promise
615
+ * is shared; a failure evicts the cache so the next caller retries loudly.
616
+ */
617
+ export function ensureAbsurdRuntimeReady(
618
+ poolOptions: RuntimeSchedulerPoolOptions,
619
+ schedulerOptions: AbsurdSchedulerOptions = {},
620
+ queueSuffix?: string | null,
621
+ releaseId?: string | null,
622
+ ): Promise<void> {
623
+ const schema = normalizeAbsurdSchedulerOptions(schedulerOptions).schema ?? '';
624
+ // Queue name derivation: an explicit release id (from the launch envelope or
625
+ // the worker's own `currentAbsurdReleaseId()`) wins — that is the ONE lane the
626
+ // run rides. Otherwise fall back to the explicit per-request suffix (threaded
627
+ // from `lazy.ts`) so the shared hosted app still creates the SAME suffixed
628
+ // queue the Fly worker drains, and worker/dev:v2 with an env suffix are
629
+ // unchanged. Under dev-collapse the release id `dev-<suffix>` maps back to
630
+ // exactly `runs_<suffix>`, so this is byte-identical to today for local dev.
631
+ const runsQueue = releaseId?.trim()
632
+ ? absurdQueueNameForRelease(releaseId)
633
+ : absurdRunsQueueForSuffix(queueSuffix);
634
+ const key = `${poolOptions.url}::${schema}::${runsQueue}`;
635
+ const existing = absurdReadyPromises.get(key);
636
+ if (existing) return existing;
637
+ const promise = (async () => {
638
+ await withRuntimeSchedulerClient(poolOptions, (client) =>
639
+ ensureAbsurdSchema(client),
640
+ );
641
+ // Hard-cutover cleanup: an old worker may have persisted a run waiting
642
+ // on a scheduled ctx.runPlay child. This runtime intentionally has no such
643
+ // scheduler anymore, so make the affected run visibly failed and emit
644
+ // its terminal outbox fact rather than leaving it parked forever.
645
+ await withRuntimeSchedulerClient(poolOptions, (client) =>
646
+ failPostgresSchedulerScheduledChildWaits(client, schedulerOptions),
647
+ );
648
+ const absurd = createAbsurdRuntimeClient(poolOptions, runsQueue);
649
+ const existingQueues = new Set(await absurd.listQueues());
650
+ if (!existingQueues.has(runsQueue)) {
651
+ await absurd.createQueue(runsQueue);
652
+ }
653
+ // Idempotent upsert: parked runs hold no slot; fair claim ordering chooses
654
+ // the next runnable work without imposing an account-level claim cap.
655
+ await absurd.setQueuePolicy(runsQueue, {
656
+ cleanupTtl: ABSURD_RUNS_CLEANUP_TTL,
657
+ cleanupLimit: ABSURD_RUNS_CLEANUP_LIMIT,
658
+ });
659
+ })().catch((error) => {
660
+ absurdReadyPromises.delete(key);
661
+ throw error;
662
+ });
663
+ absurdReadyPromises.set(key, promise);
664
+ return promise;
665
+ }
666
+
667
+ /**
668
+ * Absurd wakeup event name for an integration-event suspension. Scoped by
669
+ * runId so a broadcast emit only wakes the intended run (absurd events are
670
+ * queue-scoped and immutable/first-emit-wins, so an UNSCOPED name would both
671
+ * cross-wake unrelated runs and permanently pin one payload). The authoritative
672
+ * payload is NEVER read from this event — the worker claims the durable signal
673
+ * row (`claimPostgresSchedulerSignal`) for the real payload.
674
+ */
675
+ export function absurdIntegrationEventName(
676
+ runId: string,
677
+ eventKey: string,
678
+ ): string {
679
+ return `evt:${runId}:${eventKey}`;
680
+ }
681
+
682
+ /**
683
+ * Wake an attempt parked on a detached push-execution runner (B2-final). The
684
+ * name is ATTEMPT-scoped on purpose: absurd events are queue-scoped and
685
+ * first-emit-wins, so a stale attempt's late terminal emit names a key nobody
686
+ * is parked on and can never wake (or clobber) a newer generation. The terminal
687
+ * payload is read from scheduler Postgres (`runner_result_json` on the attempt
688
+ * row); the Absurd event is only a durable wakeup.
689
+ */
690
+ export function absurdRunnerTerminalEventName(
691
+ runId: string,
692
+ runnerAttempt: number,
693
+ ): string {
694
+ return `runner-terminal:${runId}:${runnerAttempt}`;
695
+ }
696
+
697
+ /** Build the worker→app runtime API context (ledger + billing callbacks). */
698
+ export function absurdRuntimeApiFromLaunch(
699
+ launch: PlaySchedulerSubmitInput,
700
+ ): WorkerRuntimeApiContext {
701
+ return {
702
+ baseUrl: runtimeApiBaseUrlFromLaunch(launch),
703
+ executorToken: launch.executorToken,
704
+ integrationMode: launch.integrationMode ?? null,
705
+ vercelProtectionBypassToken: launch.vercelProtectionBypassToken ?? null,
706
+ runtimeTestFaultHeader: launch.runtimeTestFaultHeader ?? null,
707
+ };
708
+ }
709
+
710
+ /** Terminal run-ledger event for a settled attempt (source=worker). */
711
+ export function absurdTerminalLedgerEvent(
712
+ result: PostgresSchedulerWorkerOnceResult,
713
+ ): PlayRunLedgerEvent | null {
714
+ const occurredAt = Date.now();
715
+ if (result.status === 'completed') {
716
+ return {
717
+ type: 'run.completed',
718
+ runId: result.runId,
719
+ occurredAt,
720
+ source: 'worker',
721
+ result: result.output,
722
+ };
723
+ }
724
+ if (result.status === 'failed') {
725
+ return {
726
+ type: 'run.failed',
727
+ runId: result.runId,
728
+ occurredAt,
729
+ source: 'worker',
730
+ error: result.error,
731
+ };
732
+ }
733
+ return null;
734
+ }
735
+
736
+ function isRecord(value: unknown): value is Record<string, unknown> {
737
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
738
+ }
739
+
740
+ /**
741
+ * Normalize an arbitrary runner checkpoint into the PlayCheckpoint envelope.
742
+ *
743
+ * Compatibility contract (`docs/runtime-compatibility-contract.md`): unknown
744
+ * TOP-LEVEL fields are PASSED THROUGH. This normalizer's result is written back
745
+ * to the durable checkpoint on every park/resume, so rebuilding the envelope
746
+ * from only the keys this build knows would ERASE state a newer build wrote —
747
+ * silently, and permanently, on the first wait a run takes. The known keys keep
748
+ * exactly the validation they had: they always override the passthrough.
749
+ */
750
+ export function absurdCheckpointBase(checkpoint: unknown): PlayCheckpoint {
751
+ const base = isRecord(checkpoint) ? checkpoint : {};
752
+ const durableCallCacheEpochMs = base.durableCallCacheEpochMs;
753
+ const normalized = {
754
+ ...base,
755
+ completedBatches:
756
+ (base.completedBatches as PlayCheckpoint['completedBatches']) ?? {},
757
+ completedToolBatches:
758
+ (base.completedToolBatches as PlayCheckpoint['completedToolBatches']) ??
759
+ {},
760
+ resolvedWaterfalls:
761
+ (base.resolvedWaterfalls as PlayCheckpoint['resolvedWaterfalls']) ?? {},
762
+ resolvedBoundaries:
763
+ (base.resolvedBoundaries as PlayCheckpoint['resolvedBoundaries']) ?? {},
764
+ } as PlayCheckpoint;
765
+ // Same strictness as before the passthrough: a non-numeric/negative epoch or a
766
+ // non-record mapFrames is DROPPED rather than carried forward by the spread.
767
+ if (
768
+ typeof durableCallCacheEpochMs === 'number' &&
769
+ Number.isFinite(durableCallCacheEpochMs) &&
770
+ durableCallCacheEpochMs >= 0
771
+ ) {
772
+ normalized.durableCallCacheEpochMs = durableCallCacheEpochMs;
773
+ } else {
774
+ delete normalized.durableCallCacheEpochMs;
775
+ }
776
+ if (isRecord(base.mapFrames)) {
777
+ normalized.mapFrames = base.mapFrames as PlayCheckpoint['mapFrames'];
778
+ } else {
779
+ delete normalized.mapFrames;
780
+ }
781
+ return normalized;
782
+ }
783
+
784
+ function resolvedBoundaries(
785
+ checkpoint: PlayCheckpoint,
786
+ ): NonNullable<PlayCheckpoint['resolvedBoundaries']> {
787
+ return checkpoint.resolvedBoundaries ?? {};
788
+ }
789
+
790
+ /** Boundary output object the play runtime reads when an integration event
791
+ * resolves (or times out). Mirrors the belt-and-suspenders envelope carried by
792
+ * the durable signal store today. */
793
+ export function absurdIntegrationEventBoundaryOutput(input: {
794
+ eventKey: string;
795
+ signalData: Record<string, unknown> | null;
796
+ messageRef?: { channel: string; ts: string } | null;
797
+ timedOut: boolean;
798
+ }): Record<string, unknown> {
799
+ if (input.timedOut) {
800
+ return {
801
+ resumed: false,
802
+ timed_out: true,
803
+ signal: input.eventKey,
804
+ event_key: input.eventKey,
805
+ message_ref: input.messageRef ?? null,
806
+ event_payload: null,
807
+ response: null,
808
+ };
809
+ }
810
+ const data = input.signalData ?? {};
811
+ return {
812
+ ...data,
813
+ resumed: true,
814
+ timed_out: false,
815
+ signal: input.eventKey,
816
+ event_key: input.eventKey,
817
+ message_ref: input.messageRef ?? null,
818
+ event_payload: input.signalData ?? null,
819
+ response: { eventKey: input.eventKey, data: input.signalData ?? null },
820
+ };
821
+ }
822
+
823
+ /** Return a checkpoint with the sleep boundary marked complete. */
824
+ export function absurdCheckpointWithSleep(
825
+ checkpoint: unknown,
826
+ boundaryId: string,
827
+ delayMs: number,
828
+ ): PlayCheckpoint {
829
+ const base = absurdCheckpointBase(checkpoint);
830
+ return {
831
+ ...base,
832
+ resolvedBoundaries: {
833
+ ...resolvedBoundaries(base),
834
+ [boundaryId]: { kind: 'sleep', delayMs, completedAt: Date.now() },
835
+ },
836
+ };
837
+ }
838
+
839
+ /** Return a checkpoint with the integration-event boundary resolved. */
840
+ export function absurdCheckpointWithIntegrationEvent(input: {
841
+ checkpoint: unknown;
842
+ boundaryId: string;
843
+ eventKey: string;
844
+ timeoutMs: number;
845
+ messageRef?: { channel: string; ts: string };
846
+ output: Record<string, unknown>;
847
+ }): PlayCheckpoint {
848
+ const base = absurdCheckpointBase(input.checkpoint);
849
+ const existingBoundary = resolvedBoundaries(base)[input.boundaryId];
850
+ // Resolving a wait replaces its output, not the semantics captured when it
851
+ // was armed. In particular, an optional timeout policy must survive through
852
+ // the timeout wake so a later deployment cannot reinterpret the result.
853
+ const existingIntegrationBoundary =
854
+ existingBoundary?.kind === 'integration_event' ? existingBoundary : {};
855
+ return {
856
+ ...base,
857
+ resolvedBoundaries: {
858
+ ...resolvedBoundaries(base),
859
+ [input.boundaryId]: {
860
+ ...existingIntegrationBoundary,
861
+ kind: 'integration_event',
862
+ eventKey: input.eventKey,
863
+ timeoutMs: input.timeoutMs,
864
+ ...(input.messageRef ? { messageRef: input.messageRef } : {}),
865
+ output: input.output,
866
+ completedAt: Date.now(),
867
+ },
868
+ },
869
+ };
870
+ }
871
+
872
+ /** True when a boundary id is already resolved in the checkpoint (idempotent
873
+ * replay guard).
874
+ *
875
+ * An entry whose `kind` this build does not know THROWS rather than reporting
876
+ * "resolved" for a boundary it cannot actually replay — see
877
+ * `assertKnownResolvedBoundary`. */
878
+ export function absurdBoundaryResolved(
879
+ checkpoint: unknown,
880
+ boundaryId: string,
881
+ ): boolean {
882
+ const base = absurdCheckpointBase(checkpoint);
883
+ return Boolean(
884
+ assertKnownResolvedBoundary(
885
+ boundaryId,
886
+ resolvedBoundaries(base)[boundaryId],
887
+ ),
888
+ );
889
+ }
890
+
891
+ /** Runner progress callbacks are a subset of the runner-backend contract; kept
892
+ * here so both the worker and its tests share one shape. */
893
+ export type AbsurdRunnerProgressCallbacks = {
894
+ onLog?: (event: unknown) => void;
895
+ onRowUpdate?: (update: PlayRowUpdate) => void;
896
+ onExecutionEvent?: (event: PlayExecutionEvent) => void;
897
+ };