deepline 0.1.199 → 0.1.200

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 (72) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +5 -1
  2. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +51 -17
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +1 -1
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +16 -0
  5. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +40 -9
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +108 -39
  7. package/dist/bundling-sources/sdk/src/http.ts +5 -2
  8. package/dist/bundling-sources/sdk/src/play.ts +1 -1
  9. package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +7 -0
  10. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  11. package/dist/bundling-sources/sdk/src/worker-play-entry.ts +11 -55
  12. package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +355 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +172 -73
  14. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1794 -377
  15. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +3 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +67 -4
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +161 -39
  18. package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +218 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +1 -1
  20. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +115 -25
  21. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +17 -17
  22. package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +22 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +2 -1
  24. package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +0 -1
  25. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +265 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +135 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/play-input.ts +17 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +0 -3
  29. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +9 -15
  31. package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +262 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/receipt-heartbeat-supervisor.ts +64 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +76 -8
  34. package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +59 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +3 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +269 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +2 -3
  38. package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +0 -1
  39. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +433 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +227 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +882 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +573 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +264 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +33 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +35 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +53 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +0 -1
  48. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +459 -103
  49. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/runtime-infra-mode.ts +23 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +23 -2
  52. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +16 -24
  53. package/dist/bundling-sources/shared_libs/play-runtime/serial-task-queue.ts +26 -0
  54. package/dist/bundling-sources/shared_libs/play-runtime/submit-limits.ts +1 -1
  55. package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +81 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +54 -9
  57. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +17 -3
  58. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +0 -20
  59. package/dist/bundling-sources/shared_libs/plays/dataset.ts +105 -4
  60. package/dist/bundling-sources/shared_libs/plays/row-identity.ts +4 -11
  61. package/dist/cli/index.js +167 -54
  62. package/dist/cli/index.mjs +167 -54
  63. package/dist/index.d.mts +1 -1
  64. package/dist/index.d.ts +1 -1
  65. package/dist/index.js +8 -8
  66. package/dist/index.mjs +8 -8
  67. package/dist/plays/bundle-play-file.mjs +15 -20
  68. package/package.json +1 -1
  69. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +0 -321
  70. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +0 -158
  71. package/dist/bundling-sources/shared_libs/temporal/constants.ts +0 -39
  72. package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +0 -150
@@ -1,321 +0,0 @@
1
- import {
2
- HATCHET_COLD_EXECUTION_PHASES,
3
- HATCHET_COLD_EXECUTION_TARGET,
4
- type HatchetColdExecutionPhaseId,
5
- type HatchetColdExecutionPhaseSummary,
6
- } from './hatchet-cold-execution-target';
7
-
8
- export type HatchetColdExecutionPhaseSeverity =
9
- | 'missing'
10
- | 'ok'
11
- | 'near_budget'
12
- | 'over_budget';
13
-
14
- export type HatchetColdExecutionPhaseDiagnosis = {
15
- id: HatchetColdExecutionPhaseId;
16
- phase: string;
17
- budgetMs: number;
18
- p95: number | null;
19
- overBudgetMs: number | null;
20
- severity: HatchetColdExecutionPhaseSeverity;
21
- ownerModule: string;
22
- ownerFiles: readonly string[];
23
- interpretation: string;
24
- nextAction: string;
25
- };
26
-
27
- export type HatchetColdExecutionDiagnosis = {
28
- ok: boolean;
29
- targetName: string;
30
- thresholdMs: number;
31
- overallP95: number | null;
32
- overallOverThresholdMs: number | null;
33
- bottleneck: HatchetColdExecutionPhaseDiagnosis | null;
34
- phases: HatchetColdExecutionPhaseDiagnosis[];
35
- observations: string[];
36
- nextActions: string[];
37
- summary: string;
38
- };
39
-
40
- type DiagnoseInput = {
41
- overallP95: number | null;
42
- thresholdMs?: number;
43
- phaseSummaries: readonly HatchetColdExecutionPhaseSummary[];
44
- };
45
-
46
- type PhaseOwnership = {
47
- ownerModule: string;
48
- ownerFiles: readonly string[];
49
- interpretation: string;
50
- nextAction: string;
51
- };
52
-
53
- const PHASE_OWNERSHIP: Record<HatchetColdExecutionPhaseId, PhaseOwnership> = {
54
- submit: {
55
- ownerModule: 'Play Run Launch Plan',
56
- ownerFiles: [
57
- 'src/lib/plays/start-run.ts',
58
- 'src/lib/plays/launch/',
59
- 'scripts/check-hatchet-cold-execution-latency.ts',
60
- ],
61
- interpretation:
62
- 'Submit time is being spent before the worker can claim the run: app validation, authority minting, start-row persistence, scheduler-row creation, or Hatchet dispatch.',
63
- nextAction:
64
- 'Inspect routeTimings plus schema_ready / scheduler_row_submitted / run_no_wait_done deltas, then move non-launch work out of the hot path or overlap insert and dispatch.',
65
- },
66
- hatchet_dispatch_claim: {
67
- ownerModule: 'Play Scheduler Adapter',
68
- ownerFiles: [
69
- 'src/lib/plays/scheduler-backends/hatchet-dispatch.ts',
70
- 'src/lib/plays/scheduler-backends/hatchet-runtime-worker.ts',
71
- 'deploy/fly/hatchet/',
72
- ],
73
- interpretation:
74
- 'Hatchet assignment or worker claim is slower than budget, so the run is waiting in orchestration before Daytona work begins.',
75
- nextAction:
76
- 'Check Hatchet engine placement, worker replica health, worker slots, and dispatch mode before changing Daytona or payload transport.',
77
- },
78
- daytona_create: {
79
- ownerModule: 'One-Shot Sandbox Runner',
80
- ownerFiles: [
81
- 'apps/temporal-worker/src/runner-backends/backends/daytona.ts',
82
- 'apps/temporal-worker/src/runner-backends/backends/daytona-lifecycle.ts',
83
- ],
84
- interpretation:
85
- 'Fresh Daytona sandbox creation is consuming the cold-execution budget before runner files upload.',
86
- nextAction:
87
- 'Compare default image versus configured snapshot startup, then verify create timing in Daytona lifecycle events before touching artifact upload.',
88
- },
89
- daytona_upload: {
90
- ownerModule: 'Play Artifact Target',
91
- ownerFiles: [
92
- 'apps/temporal-worker/src/runner-backends/backends/daytona-payload-transport.ts',
93
- 'shared_libs/plays/artifact-transport.ts',
94
- 'apps/play-runner/src/entry.ts',
95
- ],
96
- interpretation:
97
- 'Runner bundle, play artifact, config, or materialized-file staging is now the dominant transfer cost into the one-shot sandbox.',
98
- nextAction:
99
- 'Measure uploaded bytes by label, then reduce runner/artifact/config bytes or move immutable runner material into the snapshot without changing the one-shot sandbox Interface.',
100
- },
101
- daytona_execute: {
102
- ownerModule: 'One-Shot Sandbox Runner',
103
- ownerFiles: [
104
- 'apps/temporal-worker/src/runner-backends/backends/daytona.ts',
105
- 'apps/play-runner/src/entry.ts',
106
- 'shared_libs/play-runtime/protocol.ts',
107
- ],
108
- interpretation:
109
- 'The sandbox command is slow after files are present, usually runner boot, artifact load/decode, module startup, or the hello-world execution path.',
110
- nextAction:
111
- 'Inspect the captured runner output and runtimeTiming; shrink startup imports or shift decode/load work into upload/snapshot prep instead of adding pools.',
112
- },
113
- terminal_observation: {
114
- ownerModule: 'Run Snapshot',
115
- ownerFiles: [
116
- 'src/lib/plays/scheduler-backends/hatchet-completion-publisher.ts',
117
- 'src/lib/plays/scheduler-backends/postgres-progress.ts',
118
- 'convex/model/runLogStream.ts',
119
- ],
120
- interpretation:
121
- 'The worker has reached terminal state, but the customer-visible observer is seeing it late.',
122
- nextAction:
123
- 'Check terminal write ordering, NOTIFY/direct observer connection, and polling fallback before changing execution or scheduler code.',
124
- },
125
- };
126
-
127
- export function diagnoseHatchetColdExecution(
128
- input: DiagnoseInput,
129
- ): HatchetColdExecutionDiagnosis {
130
- const thresholdMs =
131
- input.thresholdMs ?? HATCHET_COLD_EXECUTION_TARGET.thresholdMs;
132
- const summariesById = new Map(
133
- input.phaseSummaries.map((summary) => [summary.id, summary]),
134
- );
135
- const phases = HATCHET_COLD_EXECUTION_PHASES.map((definition) => {
136
- const summary = summariesById.get(definition.id);
137
- const p95 = summary?.p95 ?? null;
138
- const overBudgetMs =
139
- p95 === null ? null : Math.max(0, p95 - definition.budgetMs);
140
- const severity = phaseSeverity(p95, definition.budgetMs);
141
- return {
142
- id: definition.id,
143
- phase: definition.phase,
144
- budgetMs: definition.budgetMs,
145
- p95,
146
- overBudgetMs,
147
- severity,
148
- ...PHASE_OWNERSHIP[definition.id],
149
- };
150
- });
151
-
152
- const overallOverThresholdMs =
153
- input.overallP95 === null
154
- ? null
155
- : Math.max(0, input.overallP95 - thresholdMs);
156
- const ok = input.overallP95 !== null && input.overallP95 <= thresholdMs;
157
- const bottleneck = chooseBottleneck(phases);
158
- const observations = buildObservations({
159
- ok,
160
- thresholdMs,
161
- overallP95: input.overallP95,
162
- overallOverThresholdMs,
163
- phases,
164
- bottleneck,
165
- });
166
- const nextActions = buildNextActions(bottleneck, phases);
167
-
168
- return {
169
- ok,
170
- targetName: HATCHET_COLD_EXECUTION_TARGET.name,
171
- thresholdMs,
172
- overallP95: input.overallP95,
173
- overallOverThresholdMs,
174
- bottleneck,
175
- phases,
176
- observations,
177
- nextActions,
178
- summary: buildSummary({ ok, input, thresholdMs, bottleneck }),
179
- };
180
- }
181
-
182
- export function formatHatchetColdExecutionDiagnosis(
183
- diagnosis: HatchetColdExecutionDiagnosis,
184
- ): string[] {
185
- const lines = [
186
- `[hatchet-cold-diagnosis] ${diagnosis.summary}`,
187
- ...diagnosis.observations.map(
188
- (observation) => `[hatchet-cold-diagnosis] ${observation}`,
189
- ),
190
- ];
191
- if (diagnosis.bottleneck) {
192
- lines.push(
193
- `[hatchet-cold-diagnosis] owner=${diagnosis.bottleneck.ownerModule}`,
194
- `[hatchet-cold-diagnosis] next=${diagnosis.bottleneck.nextAction}`,
195
- );
196
- }
197
- return lines;
198
- }
199
-
200
- function phaseSeverity(
201
- p95: number | null,
202
- budgetMs: number,
203
- ): HatchetColdExecutionPhaseSeverity {
204
- if (p95 === null) return 'missing';
205
- if (p95 > budgetMs) return 'over_budget';
206
- if (p95 >= Math.floor(budgetMs * 0.85)) return 'near_budget';
207
- return 'ok';
208
- }
209
-
210
- function chooseBottleneck(
211
- phases: readonly HatchetColdExecutionPhaseDiagnosis[],
212
- ): HatchetColdExecutionPhaseDiagnosis | null {
213
- const overBudget = phases
214
- .filter((phase) => phase.severity === 'over_budget')
215
- .sort(
216
- (left, right) =>
217
- (right.overBudgetMs ?? 0) - (left.overBudgetMs ?? 0) ||
218
- (right.p95 ?? 0) - (left.p95 ?? 0),
219
- );
220
- if (overBudget[0]) return overBudget[0];
221
-
222
- const nearBudget = phases
223
- .filter((phase) => phase.severity === 'near_budget')
224
- .sort((left, right) => (right.p95 ?? 0) - (left.p95 ?? 0));
225
- if (nearBudget[0]) return nearBudget[0];
226
-
227
- const missing = phases.find((phase) => phase.severity === 'missing');
228
- return missing ?? null;
229
- }
230
-
231
- function buildObservations(input: {
232
- ok: boolean;
233
- thresholdMs: number;
234
- overallP95: number | null;
235
- overallOverThresholdMs: number | null;
236
- phases: readonly HatchetColdExecutionPhaseDiagnosis[];
237
- bottleneck: HatchetColdExecutionPhaseDiagnosis | null;
238
- }): string[] {
239
- const observations: string[] = [];
240
- if (input.overallP95 === null) {
241
- observations.push(
242
- 'No overall p95 was observed; latency target cannot be evaluated.',
243
- );
244
- } else if (input.ok) {
245
- observations.push(
246
- `Overall p95=${input.overallP95}ms is within threshold=${input.thresholdMs}ms.`,
247
- );
248
- } else {
249
- observations.push(
250
- `Overall p95=${input.overallP95}ms exceeds threshold=${input.thresholdMs}ms by ${input.overallOverThresholdMs}ms.`,
251
- );
252
- }
253
-
254
- const overBudget = input.phases.filter(
255
- (phase) => phase.severity === 'over_budget',
256
- );
257
- if (overBudget.length > 0) {
258
- observations.push(
259
- `Over-budget phases: ${overBudget
260
- .map(
261
- (phase) =>
262
- `${phase.id} p95=${phase.p95}ms budget=${phase.budgetMs}ms (+${phase.overBudgetMs}ms)`,
263
- )
264
- .join(', ')}.`,
265
- );
266
- }
267
-
268
- const missing = input.phases.filter((phase) => phase.severity === 'missing');
269
- if (missing.length > 0) {
270
- observations.push(
271
- `Missing phase samples: ${missing.map((phase) => phase.id).join(', ')}.`,
272
- );
273
- }
274
-
275
- if (input.bottleneck) {
276
- observations.push(
277
- `Primary bottleneck: ${input.bottleneck.id} owned by ${input.bottleneck.ownerModule}.`,
278
- );
279
- }
280
- return observations;
281
- }
282
-
283
- function buildNextActions(
284
- bottleneck: HatchetColdExecutionPhaseDiagnosis | null,
285
- phases: readonly HatchetColdExecutionPhaseDiagnosis[],
286
- ): string[] {
287
- const actions: string[] = [];
288
- if (bottleneck) {
289
- actions.push(bottleneck.nextAction);
290
- }
291
- const missing = phases.find((phase) => phase.severity === 'missing');
292
- if (missing) {
293
- actions.push(
294
- 'Fix missing runtimeTiming/phase sample emission before trusting the latency diagnosis.',
295
- );
296
- }
297
- if (actions.length === 0) {
298
- actions.push(
299
- 'Keep the current phase budgets and rerun the same gate after the next runtime change.',
300
- );
301
- }
302
- return [...new Set(actions)];
303
- }
304
-
305
- function buildSummary(input: {
306
- ok: boolean;
307
- input: DiagnoseInput;
308
- thresholdMs: number;
309
- bottleneck: HatchetColdExecutionPhaseDiagnosis | null;
310
- }): string {
311
- if (input.input.overallP95 === null) {
312
- return `${HATCHET_COLD_EXECUTION_TARGET.name}: inconclusive; no overall p95 sample.`;
313
- }
314
- if (input.ok) {
315
- return `${HATCHET_COLD_EXECUTION_TARGET.name}: pass at p95=${input.input.overallP95}ms.`;
316
- }
317
- const owner = input.bottleneck
318
- ? `; primary owner ${input.bottleneck.ownerModule}`
319
- : '';
320
- return `${HATCHET_COLD_EXECUTION_TARGET.name}: fail at p95=${input.input.overallP95}ms over threshold=${input.thresholdMs}ms${owner}.`;
321
- }
@@ -1,158 +0,0 @@
1
- export type HatchetColdExecutionPhaseId =
2
- | 'submit'
3
- | 'hatchet_dispatch_claim'
4
- | 'daytona_create'
5
- | 'daytona_upload'
6
- | 'daytona_execute'
7
- | 'terminal_observation';
8
-
9
- export type HatchetColdExecutionPhaseDefinition = {
10
- id: HatchetColdExecutionPhaseId;
11
- header: string;
12
- phase: string;
13
- budgetMs: number;
14
- description: string;
15
- };
16
-
17
- export type HatchetColdExecutionSample = Partial<
18
- Record<HatchetColdExecutionPhaseId, number | null>
19
- >;
20
-
21
- export type HatchetColdExecutionPhaseSummary = {
22
- phase: string;
23
- id: HatchetColdExecutionPhaseId;
24
- budgetMs: number;
25
- p50: number | null;
26
- p95: number | null;
27
- max: number | null;
28
- observed: number;
29
- ok: boolean | null;
30
- };
31
-
32
- export const HATCHET_COLD_EXECUTION_TARGET = {
33
- name: 'hatchet-cold-execution-under-2s',
34
- description:
35
- 'Customer-observed cold execution from submit through terminal observation on the real Hatchet + Daytona path.',
36
- thresholdMs: 2_000,
37
- percentile: 95,
38
- } as const;
39
-
40
- export const HATCHET_COLD_EXECUTION_PHASES = [
41
- {
42
- id: 'submit',
43
- header: 'hc_submit',
44
- phase: 'hatchet_cold.submit',
45
- budgetMs: 300,
46
- description:
47
- 'Customer submit route response, including app validation, authority minting, scheduler row creation, and Hatchet dispatch.',
48
- },
49
- {
50
- id: 'hatchet_dispatch_claim',
51
- header: 'hc_hatchet',
52
- phase: 'hatchet_cold.hatchet_dispatch_claim',
53
- budgetMs: 250,
54
- description:
55
- 'Hatchet dispatch and worker claim time from persisted scheduler row to claimed execution.',
56
- },
57
- {
58
- id: 'daytona_create',
59
- header: 'hc_create',
60
- phase: 'hatchet_cold.daytona_create',
61
- budgetMs: 300,
62
- description: 'Fresh Daytona sandbox creation for the claimed run.',
63
- },
64
- {
65
- id: 'daytona_upload',
66
- header: 'hc_upload',
67
- phase: 'hatchet_cold.daytona_upload',
68
- budgetMs: 350,
69
- description:
70
- 'Runner bundle, play artifact, config, and staged-file upload into the Daytona sandbox.',
71
- },
72
- {
73
- id: 'daytona_execute',
74
- header: 'hc_execute',
75
- phase: 'hatchet_cold.daytona_execute',
76
- budgetMs: 650,
77
- description:
78
- 'Daytona command execution until the play runner returns its terminal result.',
79
- },
80
- {
81
- id: 'terminal_observation',
82
- header: 'hc_observe',
83
- phase: 'hatchet_cold.terminal_observation',
84
- budgetMs: 150,
85
- description:
86
- 'Lag between scheduler terminal persistence and the customer-visible observer noticing terminal state.',
87
- },
88
- ] as const satisfies readonly HatchetColdExecutionPhaseDefinition[];
89
-
90
- export const HATCHET_COLD_EXECUTION_PHASE_DEFINITIONS =
91
- HATCHET_COLD_EXECUTION_PHASES.map((definition) => ({
92
- header: definition.header,
93
- phase: definition.phase,
94
- source: 'hatchet_cold',
95
- description: `${definition.description} Budget: ${definition.budgetMs}ms.`,
96
- }));
97
-
98
- export function validateHatchetColdExecutionTarget(): string[] {
99
- const errors: string[] = [];
100
- const phaseIds = new Set<string>();
101
- const phaseNames = new Set<string>();
102
- let budgetTotal = 0;
103
- for (const phase of HATCHET_COLD_EXECUTION_PHASES) {
104
- if (phaseIds.has(phase.id)) {
105
- errors.push(`Duplicate phase id: ${phase.id}`);
106
- }
107
- phaseIds.add(phase.id);
108
- if (phaseNames.has(phase.phase)) {
109
- errors.push(`Duplicate phase name: ${phase.phase}`);
110
- }
111
- phaseNames.add(phase.phase);
112
- if (!Number.isFinite(phase.budgetMs) || phase.budgetMs <= 0) {
113
- errors.push(`Phase ${phase.id} must have a positive budget.`);
114
- }
115
- budgetTotal += phase.budgetMs;
116
- }
117
- if (budgetTotal !== HATCHET_COLD_EXECUTION_TARGET.thresholdMs) {
118
- errors.push(
119
- `Phase budgets sum to ${budgetTotal}ms, expected ${HATCHET_COLD_EXECUTION_TARGET.thresholdMs}ms.`,
120
- );
121
- }
122
- return errors;
123
- }
124
-
125
- export function summarizeHatchetColdExecutionPhases(
126
- samples: readonly HatchetColdExecutionSample[],
127
- ): HatchetColdExecutionPhaseSummary[] {
128
- return HATCHET_COLD_EXECUTION_PHASES.map((definition) => {
129
- const values = samples
130
- .map((sample) => sample[definition.id])
131
- .filter(
132
- (value): value is number =>
133
- typeof value === 'number' && Number.isFinite(value),
134
- )
135
- .sort((left, right) => left - right);
136
- const p50 = percentile(values, 50);
137
- const p95 = percentile(values, HATCHET_COLD_EXECUTION_TARGET.percentile);
138
- return {
139
- phase: definition.phase,
140
- id: definition.id,
141
- budgetMs: definition.budgetMs,
142
- p50,
143
- p95,
144
- max: values.at(-1) ?? null,
145
- observed: values.length,
146
- ok: p95 === null ? null : p95 <= definition.budgetMs,
147
- };
148
- });
149
- }
150
-
151
- function percentile(sortedValues: readonly number[], p: number): number | null {
152
- if (sortedValues.length === 0) return null;
153
- const index = Math.min(
154
- sortedValues.length - 1,
155
- Math.max(0, Math.ceil((p / 100) * sortedValues.length) - 1),
156
- );
157
- return sortedValues[index] ?? null;
158
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * Shared Temporal execution constants.
3
- *
4
- * Keep values that both the API/auth surface and the worker need here so the
5
- * API never imports from worker-only modules.
6
- */
7
-
8
- /**
9
- * Local Temporal dev defaults.
10
- *
11
- * These match the host ports exposed by docker-compose.yml and the env files
12
- * used by the local dev flows (`.env.local`, `.env.worktree`).
13
- */
14
- export const LOCAL_TEMPORAL_FRONTEND_PORT = 17233;
15
- export const LOCAL_TEMPORAL_UI_PORT = 18233;
16
- export const LOCAL_TEMPORAL_NAMESPACE = 'default';
17
- export const LOCAL_TEMPORAL_ADDRESS = `127.0.0.1:${LOCAL_TEMPORAL_FRONTEND_PORT}`;
18
- export const LOCAL_TEMPORAL_UI_URL = `http://127.0.0.1:${LOCAL_TEMPORAL_UI_PORT}`;
19
-
20
- /** Maximum active user-code runtime for a standard play, in seconds. */
21
- export const STANDARD_PLAY_RUNTIME_LIMIT_SECONDS = 20 * 60; // 20 minutes
22
- export const STANDARD_PLAY_RUNTIME_LIMIT_LABEL = '20 minutes';
23
-
24
- /**
25
- * Activity timeout includes setup, cleanup, and billing headroom after the 20 minute
26
- * user-code runtime cap. Keep this higher than STANDARD_PLAY_RUNTIME_LIMIT_SECONDS.
27
- */
28
- export const PLAY_ACTIVITY_TIMEOUT_SECONDS = 30 * 60; // 30 minutes
29
-
30
- /** Heartbeat cadence for the long-running play execution activity. */
31
- export const PLAY_EXECUTE_ACTIVITY_HEARTBEAT_INTERVAL_SECONDS = 15;
32
-
33
- /**
34
- * TTL for workflow executor tokens, in seconds.
35
- * Matches the activity timeout so tokens expire when the activity would
36
- * time out anyway.
37
- */
38
- export const WORKFLOW_EXECUTOR_TOKEN_TTL_SECONDS =
39
- PLAY_ACTIVITY_TIMEOUT_SECONDS;
@@ -1,150 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import * as path from 'node:path';
3
- import * as dotenv from 'dotenv';
4
- import { LOCAL_TEMPORAL_ADDRESS, LOCAL_TEMPORAL_NAMESPACE } from './constants';
5
-
6
- export const PLAY_TASK_QUEUE = 'deepline-plays';
7
- // Internal process flag set by `bun run dev:v2 --preview`. Do not put this in
8
- // .env.local; the dev command owns the mode selection so local/preview runs are
9
- // obvious at the call site.
10
- export const DEEPLINE_V2_INFRA_MODE_ENV = 'DEEPLINE_V2_INFRA_MODE';
11
- export const PREVIEW_PLAYS_ENV_FILE = '.env.plays.preview';
12
-
13
- type TemporalTlsConfig = {
14
- clientCertPair: {
15
- crt: Uint8Array;
16
- key: Uint8Array;
17
- };
18
- };
19
-
20
- export type DeeplineTemporalConfig = {
21
- address: string;
22
- namespace: string;
23
- taskQueue: string;
24
- tls?: TemporalTlsConfig;
25
- deployment: 'local' | 'preview';
26
- };
27
-
28
- let loadedPreviewEnv = false;
29
-
30
- function decodePemEnv(value: string): Uint8Array {
31
- return Buffer.from(value.replace(/\\n/g, '\n'), 'utf-8');
32
- }
33
-
34
- export function isPreviewPlaysDeployment(): boolean {
35
- const mode = process.env[DEEPLINE_V2_INFRA_MODE_ENV]?.trim().toLowerCase();
36
- return mode === 'external' || mode === 'preview';
37
- }
38
-
39
- export function isCloudflarePlaysDeployment(): boolean {
40
- const mode = process.env[DEEPLINE_V2_INFRA_MODE_ENV]?.trim().toLowerCase();
41
- return mode === 'cloudflare';
42
- }
43
-
44
- /**
45
- * True when the play runtime callback baseUrl must be a public URL — i.e. when
46
- * the plays runner runs outside the local app process and can't reach
47
- * 127.0.0.1. Both `external` (Daytona) and `cloudflare` (Workers/DOs) need
48
- * this; only fully-local mode can keep baseUrl on localhost.
49
- */
50
- export function requiresPublicCallbackBaseUrl(): boolean {
51
- return isPreviewPlaysDeployment() || isCloudflarePlaysDeployment();
52
- }
53
-
54
- export function loadPreviewPlaysEnv(cwd = process.cwd()): void {
55
- if (loadedPreviewEnv || !isPreviewPlaysDeployment()) {
56
- return;
57
- }
58
-
59
- const envPath = path.join(cwd, PREVIEW_PLAYS_ENV_FILE);
60
- if (existsSync(envPath)) {
61
- dotenv.config({ path: envPath, override: true });
62
- }
63
-
64
- loadedPreviewEnv = true;
65
- }
66
-
67
- function temporalTlsFromEnv(): TemporalTlsConfig | undefined {
68
- const cert =
69
- process.env.TEMPORAL_TLS_CERT_DATA ?? process.env.FAS_TEMPORAL_CERT_DATA;
70
- const key =
71
- process.env.TEMPORAL_TLS_KEY_DATA ?? process.env.FAS_TEMPORAL_KEY_DATA;
72
-
73
- if (!cert && !key) {
74
- return undefined;
75
- }
76
- if (!cert || !key) {
77
- throw new Error(
78
- 'Temporal Cloud mTLS requires both TEMPORAL_TLS_CERT_DATA and TEMPORAL_TLS_KEY_DATA.',
79
- );
80
- }
81
-
82
- return {
83
- clientCertPair: {
84
- crt: decodePemEnv(cert),
85
- key: decodePemEnv(key),
86
- },
87
- };
88
- }
89
-
90
- function temporalAddressFromEnv(): string | undefined {
91
- return (
92
- process.env.TEMPORAL_ADDRESS?.trim() ||
93
- process.env.FAS_TEMPORAL_ADDRESS?.trim() ||
94
- undefined
95
- );
96
- }
97
-
98
- function temporalNamespaceFromEnv(address: string): string | undefined {
99
- const previewConfigured = process.env.FAS_TEMPORAL_NAMESPACE?.trim();
100
- if (previewConfigured) {
101
- return previewConfigured;
102
- }
103
-
104
- const host = address.split(':')[0] ?? '';
105
- const temporalCloudNamespace = /^([^.]+\.[^.]+)\.tmprl\.cloud$/i.exec(host);
106
- if (temporalCloudNamespace?.[1]) {
107
- return temporalCloudNamespace[1];
108
- }
109
-
110
- const configured = process.env.TEMPORAL_NAMESPACE?.trim();
111
- return configured || undefined;
112
- }
113
-
114
- export function getDeeplineTemporalConfig(options?: {
115
- loadPreviewEnv?: boolean;
116
- }): DeeplineTemporalConfig {
117
- if (options?.loadPreviewEnv !== false) {
118
- loadPreviewPlaysEnv();
119
- }
120
-
121
- if (isPreviewPlaysDeployment()) {
122
- const address = temporalAddressFromEnv();
123
- const namespace = address ? temporalNamespaceFromEnv(address) : undefined;
124
- if (!address) {
125
- throw new Error(
126
- 'dev:v2 preview mode requires TEMPORAL_ADDRESS or FAS_TEMPORAL_ADDRESS.',
127
- );
128
- }
129
- if (!namespace) {
130
- throw new Error(
131
- 'dev:v2 preview mode requires TEMPORAL_NAMESPACE or FAS_TEMPORAL_NAMESPACE.',
132
- );
133
- }
134
- return {
135
- address,
136
- namespace,
137
- taskQueue: process.env.TEMPORAL_TASK_QUEUE?.trim() || PLAY_TASK_QUEUE,
138
- tls: temporalTlsFromEnv(),
139
- deployment: 'preview',
140
- };
141
- }
142
-
143
- return {
144
- address: process.env.TEMPORAL_ADDRESS ?? LOCAL_TEMPORAL_ADDRESS,
145
- namespace: process.env.TEMPORAL_NAMESPACE ?? LOCAL_TEMPORAL_NAMESPACE,
146
- taskQueue: process.env.TEMPORAL_TASK_QUEUE?.trim() || PLAY_TASK_QUEUE,
147
- tls: temporalTlsFromEnv(),
148
- deployment: 'local',
149
- };
150
- }