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,35 @@
1
+ /**
2
+ * The Convex cron roots that an authenticated platform operator may run on
3
+ * demand. Keep this list aligned with `packages/convex/crons.ts`; the catalog
4
+ * check makes a newly added Convex cron fail closed until it joins here.
5
+ *
6
+ * Other scheduler types deliberately are not listed. They need their own
7
+ * narrowly-scoped execution authority (rather than letting a Convex deploy
8
+ * key impersonate GitHub, Vercel, or Inngest).
9
+ */
10
+ export const MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS = [
11
+ 'convex.fold-usage-rollups',
12
+ 'convex.record-neon-managed-project-usage',
13
+ 'convex.cleanup-expired-mcp-oauth-codes',
14
+ 'convex.cleanup-expired-analytics-ip2company-cache',
15
+ 'convex.cleanup-expired-domain-health-cache',
16
+ 'convex.salesforce-oauth-refresh-maintenance',
17
+ 'convex.monitor-fleet-convergence-tick',
18
+ 'convex.provider-monitor-global-audit-daily',
19
+ 'convex.recover-notification-delivery-work',
20
+ 'convex.cleanup-expired-notification-audit-records',
21
+ 'convex.fail-stale-play-deployments',
22
+ ] as const;
23
+
24
+ export type ManuallyRunnableConvexCronJobId =
25
+ (typeof MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS)[number];
26
+
27
+ const manuallyRunnableConvexCronJobIds = new Set<string>(
28
+ MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS,
29
+ );
30
+
31
+ export function isManuallyRunnableConvexCronJob(
32
+ jobId: string,
33
+ ): jobId is ManuallyRunnableConvexCronJobId {
34
+ return manuallyRunnableConvexCronJobIds.has(jobId);
35
+ }
@@ -10,6 +10,7 @@ export type ScheduledWorkScheduler =
10
10
  | 'vercel_cron';
11
11
 
12
12
  export type ScheduledWorkOutcome = 'running' | 'succeeded' | 'failed';
13
+ export type ScheduledWorkTrigger = 'scheduled' | 'manual';
13
14
 
14
15
  export type ScheduledWorkVolume = {
15
16
  unit: string;
@@ -34,6 +35,7 @@ export type ScheduledWorkObservation = {
34
35
  occurredAt: number;
35
36
  outcome: ScheduledWorkOutcome;
36
37
  expectedWithinMs: number;
38
+ trigger?: ScheduledWorkTrigger;
37
39
  durationMs?: number;
38
40
  errorSummary?: string;
39
41
  runUrl?: string;
@@ -53,6 +55,9 @@ export function scheduledWorkTelemetryFields(
53
55
  scheduledWorkOutcome: observation.outcome,
54
56
  scheduledWorkInvocationId: observation.invocationId,
55
57
  scheduledWorkExpectedWithinMs: observation.expectedWithinMs,
58
+ ...(observation.trigger === undefined
59
+ ? {}
60
+ : { scheduledWorkTrigger: observation.trigger }),
56
61
  ...(observation.durationMs === undefined
57
62
  ? {}
58
63
  : { scheduledWorkDurationMs: observation.durationMs }),
@@ -0,0 +1,9 @@
1
+ /** Shared tenant data-plane topology and database role names. */
2
+ export const TENANT_DATA_PLANE_TOPOLOGY = 'project_per_org';
3
+
4
+ export const TENANT_OWNER_ROLE_NAME = 'dl_platform_owner';
5
+ export const TENANT_RUNTIME_ROLE_NAME = 'dl_platform_runtime';
6
+ export const TENANT_READ_ROLE_NAME = 'dl_tenant_read';
7
+ export const TENANT_OVERRIDE_ROLE_NAME = 'dl_tenant_override';
8
+
9
+ export const TENANT_BASELINE_SCHEMA_VERSION = 1;
@@ -0,0 +1,272 @@
1
+ import {
2
+ S3Client,
3
+ PutObjectCommand,
4
+ GetObjectCommand,
5
+ HeadObjectCommand,
6
+ } from '@aws-sdk/client-s3';
7
+ import { NodeHttpHandler } from '@smithy/node-http-handler';
8
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
9
+ import { Agent as HttpsAgent } from 'node:https';
10
+ import { isBuildContext } from '../observability/build-context';
11
+
12
+ type R2PresignerClient = Parameters<typeof getSignedUrl>[0];
13
+ type R2PresignerCommand = Parameters<typeof getSignedUrl>[1];
14
+
15
+ export type R2BucketKind = 'default' | 'playArtifacts';
16
+
17
+ const PLAY_ARTIFACT_BUCKETS = {
18
+ dev: 'plays-artifacts-v1-dev',
19
+ prod: 'plays-artifacts-v1-prod',
20
+ } as const;
21
+
22
+ function requireEnv(name: string): string {
23
+ const value = process.env[name]?.trim();
24
+ if (!value) {
25
+ throw new Error(`Missing ${name}`);
26
+ }
27
+ return value;
28
+ }
29
+
30
+ function getRuntimeEnv(): keyof typeof PLAY_ARTIFACT_BUCKETS {
31
+ const candidates = [
32
+ process.env.DEEPLINE_ENV,
33
+ process.env.VERCEL_ENV,
34
+ process.env.NODE_ENV,
35
+ ]
36
+ .filter(Boolean)
37
+ .map((value) => String(value).trim().toLowerCase());
38
+
39
+ for (const value of candidates) {
40
+ if (value === 'production' || value === 'prod' || value === 'live') {
41
+ return 'prod';
42
+ }
43
+ if (
44
+ value === 'development' ||
45
+ value === 'dev' ||
46
+ value === 'preview' ||
47
+ value === 'staging' ||
48
+ value === 'stage' ||
49
+ value === 'test'
50
+ ) {
51
+ return 'dev';
52
+ }
53
+ }
54
+
55
+ return 'dev';
56
+ }
57
+
58
+ function resolveBucket(kind: R2BucketKind): string {
59
+ if (kind === 'playArtifacts') {
60
+ return PLAY_ARTIFACT_BUCKETS[getRuntimeEnv()];
61
+ }
62
+ return requireEnv('CLOUDFLARE_R2_BUCKET_NAME');
63
+ }
64
+
65
+ export function getR2Config(kind: R2BucketKind = 'default') {
66
+ return {
67
+ endpoint: requireEnv('CLOUDFLARE_R2_ENDPOINT'),
68
+ accessKeyId: requireEnv('CLOUDFLARE_R2_ACCESS_KEY_ID'),
69
+ secretAccessKey: requireEnv('CLOUDFLARE_R2_SECRET_ACCESS_KEY'),
70
+ bucket: resolveBucket(kind),
71
+ publicUrl: requireEnv('CLOUDFLARE_R2_PUBLIC_URL'),
72
+ };
73
+ }
74
+
75
+ export function createR2Client(
76
+ config: ReturnType<typeof getR2Config>,
77
+ ): S3Client {
78
+ return new S3Client({
79
+ region: 'auto',
80
+ endpoint: config.endpoint,
81
+ credentials: {
82
+ accessKeyId: config.accessKeyId,
83
+ secretAccessKey: config.secretAccessKey,
84
+ },
85
+ requestHandler: new NodeHttpHandler({
86
+ httpsAgent: new HttpsAgent({
87
+ keepAlive: true,
88
+ maxSockets: 64,
89
+ }),
90
+ }),
91
+ });
92
+ }
93
+
94
+ const r2ClientMemo = new Map<string, S3Client>();
95
+
96
+ function getR2Client(config: ReturnType<typeof getR2Config>): S3Client {
97
+ const key = `${config.endpoint}|${config.bucket}|${config.accessKeyId}`;
98
+ const existing = r2ClientMemo.get(key);
99
+ if (existing) {
100
+ return existing;
101
+ }
102
+ const client = createR2Client(config);
103
+ r2ClientMemo.set(key, client);
104
+ return client;
105
+ }
106
+
107
+ function logR2Perf(
108
+ operation: string,
109
+ key: string,
110
+ startedAt: number,
111
+ extra: Record<string, unknown> = {},
112
+ ) {
113
+ // `[perf][r2]` fires on every object op; during the prebuilt-artifact build it
114
+ // floods the log. Keep it at runtime (Axiom) but silence it in the build.
115
+ if (isBuildContext()) return;
116
+ console.info('[perf][r2]', {
117
+ operation,
118
+ key,
119
+ ms: Date.now() - startedAt,
120
+ ...extra,
121
+ });
122
+ }
123
+
124
+ export async function r2ObjectExists(
125
+ key: string,
126
+ kind: R2BucketKind = 'default',
127
+ ): Promise<boolean> {
128
+ const startedAt = Date.now();
129
+ const config = getR2Config(kind);
130
+ const client = getR2Client(config);
131
+ try {
132
+ await client.send(
133
+ new HeadObjectCommand({
134
+ Bucket: config.bucket,
135
+ Key: key,
136
+ }),
137
+ );
138
+ logR2Perf('head', key, startedAt, { kind, exists: true });
139
+ return true;
140
+ } catch {
141
+ logR2Perf('head', key, startedAt, { kind, exists: false });
142
+ return false;
143
+ }
144
+ }
145
+
146
+ export async function uploadBufferToR2(input: {
147
+ key: string;
148
+ body: Buffer | Uint8Array;
149
+ contentType: string;
150
+ bucketKind?: R2BucketKind;
151
+ }): Promise<string> {
152
+ const startedAt = Date.now();
153
+ const bucketKind = input.bucketKind ?? 'default';
154
+ const configStartedAt = Date.now();
155
+ const config = getR2Config(bucketKind);
156
+ logR2Perf('config', input.key, configStartedAt, { kind: bucketKind });
157
+ const clientStartedAt = Date.now();
158
+ const client = getR2Client(config);
159
+ logR2Perf('client', input.key, clientStartedAt, { kind: bucketKind });
160
+
161
+ const sendStartedAt = Date.now();
162
+ await client.send(
163
+ new PutObjectCommand({
164
+ Bucket: config.bucket,
165
+ Key: input.key,
166
+ Body:
167
+ input.body instanceof Buffer ? new Uint8Array(input.body) : input.body,
168
+ ContentType: input.contentType,
169
+ }),
170
+ );
171
+ logR2Perf('put-send', input.key, sendStartedAt, {
172
+ kind: bucketKind,
173
+ bytes: input.body.byteLength,
174
+ });
175
+ logR2Perf('put', input.key, startedAt, {
176
+ kind: bucketKind,
177
+ bytes: input.body.byteLength,
178
+ });
179
+
180
+ if (bucketKind === 'playArtifacts') {
181
+ return `r2://${config.bucket}/${input.key}`;
182
+ }
183
+ return `${config.publicUrl.replace(/\/+$/, '')}/${input.key}`;
184
+ }
185
+
186
+ export async function getBufferFromR2(
187
+ key: string,
188
+ kind: R2BucketKind = 'default',
189
+ ): Promise<Buffer> {
190
+ const startedAt = Date.now();
191
+ const config = getR2Config(kind);
192
+ const client = getR2Client(config);
193
+
194
+ const response = await client.send(
195
+ new GetObjectCommand({
196
+ Bucket: config.bucket,
197
+ Key: key,
198
+ }),
199
+ );
200
+
201
+ const bytes = await response.Body!.transformToByteArray();
202
+ logR2Perf('get', key, startedAt, { kind, bytes: bytes.byteLength });
203
+ return Buffer.from(bytes);
204
+ }
205
+
206
+ export async function createSignedR2ReadUrl(input: {
207
+ key: string;
208
+ kind?: R2BucketKind;
209
+ expiresInSeconds: number;
210
+ }): Promise<{ url: string; expiresAt: string }> {
211
+ const startedAt = Date.now();
212
+ const kind = input.kind ?? 'default';
213
+ const config = getR2Config(kind);
214
+ const client = createR2Client(config);
215
+ const expiresInSeconds = Math.max(1, Math.floor(input.expiresInSeconds));
216
+ const command = new GetObjectCommand({
217
+ Bucket: config.bucket,
218
+ Key: input.key,
219
+ });
220
+ const url = await getSignedUrl(
221
+ client as R2PresignerClient,
222
+ command as R2PresignerCommand,
223
+ { expiresIn: expiresInSeconds },
224
+ );
225
+ logR2Perf('sign-get', input.key, startedAt, {
226
+ kind,
227
+ expiresInSeconds,
228
+ });
229
+ return {
230
+ url,
231
+ expiresAt: new Date(Date.now() + expiresInSeconds * 1000).toISOString(),
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Mint a short-lived presigned PUT URL so a client can upload an object body
237
+ * straight to R2, bypassing the serverless request-body ceiling that a proxied
238
+ * upload through the Next.js route would hit.
239
+ *
240
+ * The URL signs `ContentType`, so the caller MUST send a matching
241
+ * `Content-Type` header on the PUT for the signature to validate.
242
+ */
243
+ export async function createSignedR2WriteUrl(input: {
244
+ key: string;
245
+ kind?: R2BucketKind;
246
+ contentType: string;
247
+ expiresInSeconds: number;
248
+ }): Promise<{ url: string; expiresAt: string }> {
249
+ const startedAt = Date.now();
250
+ const kind = input.kind ?? 'default';
251
+ const config = getR2Config(kind);
252
+ const client = createR2Client(config);
253
+ const expiresInSeconds = Math.max(1, Math.floor(input.expiresInSeconds));
254
+ const command = new PutObjectCommand({
255
+ Bucket: config.bucket,
256
+ Key: input.key,
257
+ ContentType: input.contentType,
258
+ });
259
+ const url = await getSignedUrl(
260
+ client as R2PresignerClient,
261
+ command as R2PresignerCommand,
262
+ { expiresIn: expiresInSeconds },
263
+ );
264
+ logR2Perf('sign-put', input.key, startedAt, {
265
+ kind,
266
+ expiresInSeconds,
267
+ });
268
+ return {
269
+ url,
270
+ expiresAt: new Date(Date.now() + expiresInSeconds * 1000).toISOString(),
271
+ };
272
+ }
@@ -198,6 +198,13 @@ type RuntimeApiRequest =
198
198
  workflowId: string;
199
199
  runId: string;
200
200
  workflowFamilyKey: string;
201
+ /** Optional for root and older durable admission envelopes. */
202
+ parentRunId?: string | null;
203
+ rootRunId?: string | null;
204
+ asyncLaunchKey?: string | null;
205
+ asyncInvocationKey?: string | null;
206
+ asyncRequestFingerprint?: string | null;
207
+ asyncAncestryPlayIds?: string[] | null;
201
208
  definitionScope?: 'org' | 'system';
202
209
  revisionId?: string | null;
203
210
  artifactStorageKey?: string | null;
@@ -592,6 +599,17 @@ function isRetryableAppRuntimeResponse(input: {
592
599
  ) {
593
600
  return true;
594
601
  }
602
+ // Convex can deploy the projector before the Vercel app knows this action.
603
+ // That old app returns this exact unstructured 403. The projection is
604
+ // idempotent and the outbox keeps its delivery receipt, so retrying bridges
605
+ // the release skew without classifying real executor authorization failures
606
+ // as transient.
607
+ if (
608
+ input.action === 'project_run_created' &&
609
+ isLegacyProjectRunCreatedUnsupportedActionResponse(input)
610
+ ) {
611
+ return true;
612
+ }
595
613
  // The app runtime may explicitly classify an otherwise-client-error status
596
614
  // as transient. Keep the structured delivery contract authoritative rather
597
615
  // than reducing every 4xx to permanent at the worker boundary.
@@ -610,6 +628,32 @@ function isRetryableAppRuntimeResponse(input: {
610
628
  return isRetryableAppRuntimeResponseStatus(input.status);
611
629
  }
612
630
 
631
+ /**
632
+ * The pre-reader app returns this one precise response for an action it does
633
+ * not yet know. Do not use a substring match here: an authorization failure
634
+ * that merely repeats the phrase must remain terminal and reach an operator.
635
+ */
636
+ function isLegacyProjectRunCreatedUnsupportedActionResponse(input: {
637
+ status: number;
638
+ body: string;
639
+ }): boolean {
640
+ if (input.status !== 403) return false;
641
+ const expected = 'Unsupported runtime action capability scope.';
642
+ try {
643
+ const parsed = JSON.parse(input.body) as unknown;
644
+ return (
645
+ !!parsed &&
646
+ typeof parsed === 'object' &&
647
+ !Array.isArray(parsed) &&
648
+ (parsed as { error?: unknown }).error === expected
649
+ );
650
+ } catch {
651
+ // The oldest route can return the bare legacy message. Retain that bridge
652
+ // while keeping it exact rather than accepting an arbitrary 403 body.
653
+ return input.body.trim() === expected;
654
+ }
655
+ }
656
+
613
657
  function appRuntimeExplicitRetryable(body: string): boolean | null {
614
658
  try {
615
659
  const parsed = JSON.parse(body) as unknown;
@@ -762,6 +806,7 @@ function isRetryableAppRuntimeAction(
762
806
  action === 'rate_state_release' ||
763
807
  action === 'rate_state_penalize' ||
764
808
  action === 'record_first_claim_latency' ||
809
+ action === 'project_run_created' ||
765
810
  action === 'release_runtime_step_receipt' ||
766
811
  action === 'save_results' ||
767
812
  action === 'skip_runtime_step_receipt' ||
@@ -95,11 +95,26 @@ export type AsyncOperationContractWire = {
95
95
  * status. This rule applies only to that launch response; unknown poll
96
96
  * responses still use terminal.defaultOutcome.
97
97
  */
98
- launch?: { unclassifiedJobOutcome: 'running' };
98
+ launch?: {
99
+ unclassifiedJobOutcome: 'running';
100
+ /** Ask this start action for its handle instead of its legacy sync result. */
101
+ preferAsync?: boolean;
102
+ };
99
103
  job: { idPaths: readonly string[] };
100
104
  polling: {
101
105
  initialDelayMs: number;
102
106
  intervalMs: number;
107
+ /**
108
+ * The longest one provider status/result read may wait when the caller
109
+ * owns the lifecycle. This is deliberately separate from the complete
110
+ * job timeout: a Play fan-out must release its transport slot promptly
111
+ * and let the next invocation perform the next check.
112
+ *
113
+ * Provider adapters translate this generic budget into their native
114
+ * long-poll parameter when they have one. Omit it when a poll endpoint
115
+ * already returns immediately.
116
+ */
117
+ checkTimeoutMs?: number;
103
118
  /** Optional public start-input field that overrides intervalMs for this call. */
104
119
  intervalInputField?: string;
105
120
  backoffMultiplier?: number;
@@ -128,6 +143,14 @@ export type AsyncOperationContractWire = {
128
143
  };
129
144
 
130
145
  export type AsyncOperationContract = AsyncOperationContractWire;
146
+ /**
147
+ * Runtime projection for one lifecycle companion action. This is the single
148
+ * cadence fact a provider adapter receives; it does not expose a provider
149
+ * handle or make a result receipt into provider-job state.
150
+ */
151
+ export type AsyncOperationPollExecution = {
152
+ checkTimeoutMs: number;
153
+ };
131
154
  export type AsyncActionFlowCompatibilityProjection = {
132
155
  pollActions: readonly string[];
133
156
  finishAction?: string;
@@ -261,6 +284,12 @@ export function defineAsyncOperation(
261
284
  nonNegativeInteger(contract.polling.initialDelayMs, 'polling.initialDelayMs');
262
285
  positiveInteger(contract.polling.intervalMs, 'polling.intervalMs');
263
286
  positiveInteger(contract.polling.timeoutMs, 'polling.timeoutMs');
287
+ if (contract.polling.checkTimeoutMs !== undefined) {
288
+ positiveInteger(
289
+ contract.polling.checkTimeoutMs,
290
+ 'polling.checkTimeoutMs',
291
+ );
292
+ }
264
293
  if (contract.polling.intervalInputField !== undefined) {
265
294
  required(contract.polling.intervalInputField, 'polling.intervalInputField');
266
295
  }
@@ -406,6 +435,17 @@ export function defineCallerManagedAsyncOperation(input: {
406
435
  pollActions: readonly string[];
407
436
  finishAction?: string;
408
437
  jobIdPaths?: readonly string[];
438
+ /**
439
+ * The provider has an explicit asynchronous launch path. A Play will receive
440
+ * its job handle immediately and own subsequent polling.
441
+ */
442
+ preferAsyncStart?: boolean;
443
+ /**
444
+ * Bound one provider-side status/result check for Plays. The default keeps
445
+ * large fan-outs nonblocking while direct SDK and CLI calls retain their
446
+ * provider-specific behavior.
447
+ */
448
+ checkTimeoutMs?: number;
409
449
  /**
410
450
  * Preserve an existing public asyncGetAction, or pass null when the legacy
411
451
  * action exposed only asyncFlow metadata.
@@ -431,6 +471,14 @@ export function defineCallerManagedAsyncOperation(input: {
431
471
  ? { finishAction: companion(input.finishAction) }
432
472
  : {}),
433
473
  },
474
+ ...(input.preferAsyncStart
475
+ ? {
476
+ launch: {
477
+ unclassifiedJobOutcome: 'running' as const,
478
+ preferAsync: true,
479
+ },
480
+ }
481
+ : {}),
434
482
  job: {
435
483
  idPaths: input.jobIdPaths ?? [
436
484
  'job_id',
@@ -446,6 +494,7 @@ export function defineCallerManagedAsyncOperation(input: {
446
494
  polling: {
447
495
  initialDelayMs: 0,
448
496
  intervalMs: 1_000,
497
+ checkTimeoutMs: input.checkTimeoutMs ?? 1_000,
449
498
  timeoutMs: 15 * 60_000,
450
499
  },
451
500
  terminal: {
@@ -462,6 +511,22 @@ export function defineCallerManagedAsyncOperation(input: {
462
511
  });
463
512
  }
464
513
 
514
+ /**
515
+ * Project the generic caller-managed cadence onto each polling companion.
516
+ * `always` and `input_flag` operations execute their complete lifecycle at
517
+ * the API boundary, so exposing a bounded companion policy for them would be
518
+ * misleading.
519
+ */
520
+ export function projectAsyncOperationPollExecution(
521
+ contract: AsyncOperationContractWire,
522
+ ): AsyncOperationPollExecution | null {
523
+ if (contract.wait.kind !== 'caller_managed') return null;
524
+ return {
525
+ checkTimeoutMs:
526
+ contract.polling.checkTimeoutMs ?? contract.polling.intervalMs,
527
+ };
528
+ }
529
+
465
530
  export function asyncOperationCompanionActions(
466
531
  contract: AsyncOperationContract,
467
532
  ): string[] {
@@ -42,7 +42,6 @@ export function assertAsyncPlayInvocation(input: {
42
42
  `${CTX_RUN_PLAY_ASYNC_DATASET_UNSUPPORTED}: ctx.runPlayAsync("${childPlayName}") launches one independent scalar or HITL Play Run, but the target owns ctx.dataset(), ctx.csv(), or a Runtime Sheet. Keep batch work in its top-level Play or extract a scalar per-record child.`,
43
43
  );
44
44
  }
45
-
46
45
  }
47
46
 
48
47
  /** Admit a child against the persisted root-to-parent Play lineage. */
@@ -0,0 +1,137 @@
1
+ import {
2
+ defineBatchStrategyMap,
3
+ type BatchOperationStrategy,
4
+ } from './batching-types';
5
+
6
+ type Single = {
7
+ email: string;
8
+ mode?: 'regular' | 'deepverify';
9
+ disable_catchall_verify?: '0' | '1';
10
+ url?: string;
11
+ };
12
+ type Bulk = Omit<Single, 'email'> & { emails: string[] };
13
+ type Verdict = Record<string, unknown> & { email: string; status?: string };
14
+ type Results = { items?: Verdict[]; data?: { items?: Verdict[] } };
15
+
16
+ function options(payload: Single) {
17
+ return {
18
+ mode: payload.mode ?? 'regular',
19
+ disable_catchall_verify: payload.disable_catchall_verify ?? '0',
20
+ };
21
+ }
22
+ function bucket(payload: Single) {
23
+ // Single and bulk webhook payloads differ. Do not batch webhook callers.
24
+ return JSON.stringify([options(payload), payload.url ?? null]);
25
+ }
26
+ function emailKey(email: string) {
27
+ return email.trim().toLowerCase();
28
+ }
29
+
30
+ export const bouncebanVerifyBatchStrategy: BatchOperationStrategy<
31
+ Single,
32
+ Bulk | Single,
33
+ Results,
34
+ { data: Verdict }
35
+ > = {
36
+ sourceOperation: 'bounceban_verify_single',
37
+ batchOperation: 'bounceban_verify_bulk',
38
+ kind: 'async_dataset_job',
39
+ // The async finish action retrieves at most 100 emails per request.
40
+ maxBatchSize: 100,
41
+ bucketKeyPayloadFields: ['mode', 'disable_catchall_verify', 'url'],
42
+ canBatchWith(left, right) {
43
+ return !left.url && !right.url && bucket(left) === bucket(right);
44
+ },
45
+ toBucketKey: bucket,
46
+ toItemKey: (payload) => emailKey(payload.email),
47
+ compile(payloads) {
48
+ const first = payloads[0];
49
+ if (payloads.length === 1 && first) {
50
+ return {
51
+ batchOperation: 'bounceban_verify_single',
52
+ batchPayload: first,
53
+ items: [{ itemKey: emailKey(first.email), payload: first }],
54
+ };
55
+ }
56
+ if (
57
+ !first ||
58
+ payloads.length > 100 ||
59
+ payloads.some((p) => p.url || bucket(p) !== bucket(first))
60
+ ) {
61
+ throw new Error(
62
+ 'BounceBan batch requires 1–100 emails with matching verification options and no webhooks.',
63
+ );
64
+ }
65
+ return {
66
+ batchOperation: 'bounceban_verify_bulk',
67
+ batchPayload: {
68
+ emails: [...new Set(payloads.map((p) => emailKey(p.email)))],
69
+ ...options(first),
70
+ },
71
+ items: payloads.map((payload) => ({
72
+ itemKey: emailKey(payload.email),
73
+ payload,
74
+ })),
75
+ };
76
+ },
77
+ splitResult(fullResult, compiled) {
78
+ if (compiled.batchOperation === 'bounceban_verify_single') {
79
+ const row = (fullResult.data ?? fullResult) as Verdict;
80
+ if (
81
+ row.status !== 'success' ||
82
+ typeof row.email !== 'string' ||
83
+ emailKey(row.email) !== compiled.items[0]?.itemKey ||
84
+ !['deliverable', 'undeliverable', 'risky', 'unknown'].includes(
85
+ String(row.result),
86
+ )
87
+ ) {
88
+ throw new Error(
89
+ 'BounceBan single verification returned an unfinished or malformed verdict.',
90
+ );
91
+ }
92
+ return [
93
+ {
94
+ itemKey: compiled.items[0]!.itemKey,
95
+ result: { data: row },
96
+ rawResult: row,
97
+ },
98
+ ];
99
+ }
100
+ const rows = fullResult.items ?? fullResult.data?.items;
101
+ if (!Array.isArray(rows))
102
+ throw new Error('BounceBan bulk verification returned no result list.');
103
+ const byEmail = new Map<string, Verdict>();
104
+ for (const row of rows) {
105
+ if (
106
+ !row ||
107
+ typeof row.email !== 'string' ||
108
+ (row.status !== undefined && row.status !== 'success') ||
109
+ !['deliverable', 'undeliverable', 'risky', 'unknown'].includes(
110
+ String(row.result),
111
+ )
112
+ ) {
113
+ throw new Error(
114
+ 'BounceBan bulk verification returned an unfinished or malformed verdict.',
115
+ );
116
+ }
117
+ const key = emailKey(row.email);
118
+ if (byEmail.has(key))
119
+ throw new Error(
120
+ 'BounceBan bulk verification returned duplicate verdicts.',
121
+ );
122
+ byEmail.set(key, row);
123
+ }
124
+ return compiled.items.map((item) => {
125
+ const row = byEmail.get(item.itemKey);
126
+ if (!row)
127
+ throw new Error(
128
+ `BounceBan bulk verification omitted a requested email result.`,
129
+ );
130
+ return { itemKey: item.itemKey, result: { data: row }, rawResult: row };
131
+ });
132
+ },
133
+ };
134
+
135
+ export const bouncebanBatchStrategies = defineBatchStrategyMap({
136
+ bounceban_verify_single: bouncebanVerifyBatchStrategy,
137
+ });