deepline 0.1.119 → 0.1.121

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 (148) hide show
  1. package/README.md +4 -0
  2. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +21 -0
  3. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/batching.ts +185 -0
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-batch.ts +107 -0
  5. package/dist/{repo → bundling-sources}/sdk/src/client.ts +116 -12
  6. package/dist/bundling-sources/sdk/src/compat.ts +191 -0
  7. package/dist/bundling-sources/sdk/src/gtm.ts +146 -0
  8. package/dist/bundling-sources/sdk/src/helpers.ts +12 -0
  9. package/dist/{repo → bundling-sources}/sdk/src/index.ts +2 -1
  10. package/dist/{repo → bundling-sources}/sdk/src/play.ts +3 -1
  11. package/dist/{repo → bundling-sources}/sdk/src/plays/bundle-play-file.ts +17 -5
  12. package/dist/{repo → bundling-sources}/sdk/src/release.ts +2 -2
  13. package/dist/{repo → bundling-sources}/sdk/src/runs/observe-transport.ts +2 -3
  14. package/dist/bundling-sources/shared_libs/play-data-plane/index.ts +3 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +838 -0
  16. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5510 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +261 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +828 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/dataset-id.ts +10 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +50 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +20 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/event-wait-tools.ts +9 -0
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +171 -0
  24. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +321 -0
  25. package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +158 -0
  26. package/dist/bundling-sources/shared_libs/play-runtime/internal-step-ids.ts +34 -0
  27. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +34 -0
  28. package/dist/bundling-sources/shared_libs/play-runtime/live-state-contract.ts +50 -0
  29. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +119 -0
  30. package/dist/{repo → bundling-sources}/shared_libs/play-runtime/map-row-identity.ts +1 -1
  31. package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +636 -0
  32. package/dist/bundling-sources/shared_libs/play-runtime/postgres-json.ts +9 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/progress-emitter.ts +197 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/projection.ts +262 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +143 -0
  36. package/dist/bundling-sources/shared_libs/play-runtime/public-play-contract.ts +42 -0
  37. package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +40 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +178 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +4015 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/runtime-constraints.ts +2 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-neon-serverless.ts +238 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver-pg.ts +53 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/runtime-pg-driver.ts +149 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +68 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/tool-batch-executor.ts +149 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/tool-result-types.ts +159 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/tracing.ts +33 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/waterfall-replay.ts +79 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +139 -0
  50. package/dist/bundling-sources/shared_libs/plays/artifact-transport.ts +14 -0
  51. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +49 -0
  52. package/dist/bundling-sources/shared_libs/plays/compiler-manifest.ts +41 -0
  53. package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +163 -0
  54. package/dist/bundling-sources/shared_libs/plays/definition.ts +267 -0
  55. package/dist/bundling-sources/shared_libs/plays/file-refs.ts +11 -0
  56. package/dist/bundling-sources/shared_libs/plays/input-contract.ts +146 -0
  57. package/dist/bundling-sources/shared_libs/plays/resolve-static-pipeline.ts +190 -0
  58. package/dist/bundling-sources/shared_libs/plays/runtime-validation.ts +417 -0
  59. package/dist/bundling-sources/shared_libs/plays/tool-codegen.ts +142 -0
  60. package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +274 -0
  61. package/dist/bundling-sources/shared_libs/temporal/preview-config.ts +150 -0
  62. package/dist/cli/index.js +811 -2207
  63. package/dist/cli/index.mjs +847 -2258
  64. package/dist/compiler-manifest-BjoRENv9.d.mts +227 -0
  65. package/dist/compiler-manifest-BjoRENv9.d.ts +227 -0
  66. package/dist/index.d.mts +8 -231
  67. package/dist/index.d.ts +8 -231
  68. package/dist/index.js +101 -15
  69. package/dist/index.mjs +101 -15
  70. package/dist/plays/bundle-play-file.d.mts +120 -0
  71. package/dist/plays/bundle-play-file.d.ts +120 -0
  72. package/dist/plays/bundle-play-file.mjs +1830 -0
  73. package/package.json +4 -9
  74. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/child-play-await.ts +0 -0
  75. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/child-play-submit.ts +0 -0
  76. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/coordinator-entry.ts +0 -0
  77. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/dedup-do.ts +0 -0
  78. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/entry.ts +0 -0
  79. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/csv-rows.ts +0 -0
  80. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/dataset-handles.ts +0 -0
  81. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +0 -0
  82. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/live-progress.ts +0 -0
  83. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +0 -0
  84. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/receipts.ts +0 -0
  85. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/row-isolation.ts +0 -0
  86. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/runtime/tool-http-errors.ts +0 -0
  87. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/workflow-instance-create.ts +0 -0
  88. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/workflow-retry-state.ts +0 -0
  89. /package/dist/{repo → bundling-sources}/apps/play-runner-workers/src/workflow-retry.ts +0 -0
  90. /package/dist/{repo → bundling-sources}/sdk/src/agent-runtime.ts +0 -0
  91. /package/dist/{repo → bundling-sources}/sdk/src/config.ts +0 -0
  92. /package/dist/{repo → bundling-sources}/sdk/src/errors.ts +0 -0
  93. /package/dist/{repo → bundling-sources}/sdk/src/http.ts +0 -0
  94. /package/dist/{repo → bundling-sources}/sdk/src/plays/harness-stub.ts +0 -0
  95. /package/dist/{repo → bundling-sources}/sdk/src/plays/local-file-discovery.ts +0 -0
  96. /package/dist/{repo → bundling-sources}/sdk/src/stream-reconnect.ts +0 -0
  97. /package/dist/{repo → bundling-sources}/sdk/src/tool-output.ts +0 -0
  98. /package/dist/{repo → bundling-sources}/sdk/src/types.ts +0 -0
  99. /package/dist/{repo → bundling-sources}/sdk/src/version.ts +0 -0
  100. /package/dist/{repo → bundling-sources}/sdk/src/worker-play-entry.ts +0 -0
  101. /package/dist/{repo → bundling-sources}/shared_libs/play-data-plane/cell-policy.ts +0 -0
  102. /package/dist/{repo → bundling-sources}/shared_libs/play-data-plane/column-names.ts +0 -0
  103. /package/dist/{repo → bundling-sources}/shared_libs/play-data-plane/sheet-contract.ts +0 -0
  104. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/backend.ts +0 -0
  105. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/batch-runtime.ts +0 -0
  106. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/batching-types.ts +0 -0
  107. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/cell-staleness.ts +0 -0
  108. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/coordinator-headers.ts +0 -0
  109. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/csv-rename.ts +0 -0
  110. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/db-session-crypto.ts +0 -0
  111. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/db-session-plan.ts +0 -0
  112. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/db-session.ts +0 -0
  113. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/dedup-backend.ts +0 -0
  114. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/default-batch-strategies.ts +0 -0
  115. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/email-status.ts +0 -0
  116. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/execution-plan.ts +0 -0
  117. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/extractor-targets.ts +0 -0
  118. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/fullenrich-batching.ts +0 -0
  119. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/coordinator-rate-state-backend.ts +0 -0
  120. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/governor.ts +0 -0
  121. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/policy.ts +0 -0
  122. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/governor/rate-state-backend.ts +0 -0
  123. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/live-events.ts +0 -0
  124. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/play-runtime-batching-registry.ts +0 -0
  125. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/profiles.ts +0 -0
  126. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/providers.ts +0 -0
  127. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/run-failure.ts +0 -0
  128. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/run-ledger.ts +0 -0
  129. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/run-snapshot-stream.ts +0 -0
  130. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/scheduler-backend.ts +0 -0
  131. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/secret-capability.ts +0 -0
  132. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/secret-redaction.ts +0 -0
  133. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/step-lifecycle-tracker.ts +0 -0
  134. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/step-program-dataset-builder.ts +0 -0
  135. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/submit-limits.ts +0 -0
  136. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/tool-result.ts +0 -0
  137. /package/dist/{repo → bundling-sources}/shared_libs/play-runtime/work-receipts.ts +0 -0
  138. /package/dist/{repo → bundling-sources}/shared_libs/plays/bootstrap-routes.ts +0 -0
  139. /package/dist/{repo → bundling-sources}/shared_libs/plays/bundling/index.ts +0 -0
  140. /package/dist/{repo → bundling-sources}/shared_libs/plays/bundling/limits.ts +0 -0
  141. /package/dist/{repo → bundling-sources}/shared_libs/plays/contracts.ts +0 -0
  142. /package/dist/{repo → bundling-sources}/shared_libs/plays/dataset.ts +0 -0
  143. /package/dist/{repo → bundling-sources}/shared_libs/plays/row-identity.ts +0 -0
  144. /package/dist/{repo → bundling-sources}/shared_libs/plays/secret-guardrails.ts +0 -0
  145. /package/dist/{repo → bundling-sources}/shared_libs/plays/static-pipeline.ts +0 -0
  146. /package/dist/{repo → bundling-sources}/shared_libs/security/outbound-url-policy.ts +0 -0
  147. /package/dist/{repo → bundling-sources}/shared_libs/security/safe-fetch.ts +0 -0
  148. /package/dist/{repo → bundling-sources}/shared_libs/temporal/constants.ts +0 -0
@@ -0,0 +1,10 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ export function createRuntimeDatasetId(
4
+ playName: string,
5
+ tableNamespace: string,
6
+ ): string {
7
+ return createHash('sha1')
8
+ .update(`${playName}:${tableNamespace}`)
9
+ .digest('hex');
10
+ }
@@ -0,0 +1,50 @@
1
+ import type { Daytona } from '@daytonaio/sdk';
2
+
3
+ export const DAYTONA_DEFAULT_WORKDIR = '/home/daytona';
4
+ export const DAYTONA_BACKGROUND_CLEANUP_ENV = 'DAYTONA_BACKGROUND_CLEANUP';
5
+
6
+ export type DaytonaClientOptions = NonNullable<
7
+ ConstructorParameters<typeof Daytona>[0]
8
+ >;
9
+
10
+ export type DaytonaRequiredConfig = {
11
+ apiKey: string;
12
+ clientOptions: DaytonaClientOptions;
13
+ };
14
+
15
+ export type DaytonaRunnerPathsConfig = {
16
+ workdir: string;
17
+ };
18
+
19
+ export function daytonaClientOptions(apiKey: string): DaytonaClientOptions {
20
+ return {
21
+ apiKey,
22
+ ...(process.env.DAYTONA_API_URL?.trim()
23
+ ? { apiUrl: process.env.DAYTONA_API_URL.trim() }
24
+ : {}),
25
+ ...(process.env.DAYTONA_TARGET?.trim()
26
+ ? { target: process.env.DAYTONA_TARGET.trim() }
27
+ : {}),
28
+ };
29
+ }
30
+
31
+ export function shouldCleanupSandboxInBackground(): boolean {
32
+ return process.env[DAYTONA_BACKGROUND_CLEANUP_ENV]?.trim() === '1';
33
+ }
34
+
35
+ export function loadDaytonaRequiredConfig(): DaytonaRequiredConfig {
36
+ const apiKey = process.env.DAYTONA_API_KEY?.trim();
37
+ if (!apiKey) {
38
+ throw new Error('Missing required Daytona configuration: DAYTONA_API_KEY.');
39
+ }
40
+ return {
41
+ apiKey,
42
+ clientOptions: daytonaClientOptions(apiKey),
43
+ };
44
+ }
45
+
46
+ export function loadDaytonaRunnerPathsConfig(): DaytonaRunnerPathsConfig {
47
+ return {
48
+ workdir: process.env.DAYTONA_WORKDIR?.trim() || DAYTONA_DEFAULT_WORKDIR,
49
+ };
50
+ }
@@ -0,0 +1,20 @@
1
+ export type MapRowOutcomeStatus = 'completed' | 'failed';
2
+
3
+ export type MapRowOutcome = {
4
+ key: string;
5
+ /**
6
+ * Original row position within this dataset invocation. The deterministic
7
+ * key is the primary identity; inputIndex is a same-run repair identity for
8
+ * completion writes after the start phase has already stamped `_run_id`.
9
+ */
10
+ inputIndex?: number | null;
11
+ data: Record<string, unknown>;
12
+ cellMetaPatch?: Record<string, unknown>;
13
+ /**
14
+ * Terminal row status for this write. Defaults to completed. Failed rows keep
15
+ * partial data, persist `_error`, and are returned as pending on the next run.
16
+ */
17
+ status?: MapRowOutcomeStatus;
18
+ /** Row-level error message persisted when `status` is failed. */
19
+ error?: string | null;
20
+ };
@@ -0,0 +1,9 @@
1
+ export const EVENT_WAIT_TOOL_IDS: ReadonlySet<string> = new Set([
2
+ 'slack_message_with_hitl',
3
+ 'slack_send_message_with_hitl',
4
+ 'test_wait_for_event',
5
+ ]);
6
+
7
+ export function isIntegrationEventWaitToolId(toolId: string): boolean {
8
+ return EVENT_WAIT_TOOL_IDS.has(toolId.trim());
9
+ }
@@ -0,0 +1,171 @@
1
+ import type {
2
+ PacingPermit,
3
+ PacingRule,
4
+ RateStateBackend,
5
+ } from './rate-state-backend';
6
+
7
+ /**
8
+ * In-memory Rate State Backend for the single-process `cjs_node20` runner.
9
+ *
10
+ * In one Node process the local buckets ARE the global state, so this is the
11
+ * correct, zero-round-trip backend. The window/concurrency math is lifted from
12
+ * the previous `PlayRateLimitScheduler` (sliding window + per-rule concurrency +
13
+ * serialized acquire), plus a `penalize` cooldown the scheduler lacked so the
14
+ * in-process runner also honors a server-observed Retry-After.
15
+ */
16
+
17
+ const MIN_CONCURRENCY_WAIT_MS = 10;
18
+
19
+ interface RuleState {
20
+ windowStartedAt: number;
21
+ startedInWindow: number;
22
+ activeCount: number;
23
+ }
24
+
25
+ interface Options {
26
+ now?: () => number;
27
+ sleep?: (ms: number) => Promise<void>;
28
+ }
29
+
30
+ export class InMemoryRateStateBackend implements RateStateBackend {
31
+ private readonly ruleStates = new Map<string, RuleState>();
32
+ private readonly cooldownUntilByBucket = new Map<string, number>();
33
+ private lock: Promise<void> = Promise.resolve();
34
+ private readonly now: () => number;
35
+ private readonly sleep: (ms: number) => Promise<void>;
36
+
37
+ constructor(options: Options = {}) {
38
+ this.now = options.now ?? (() => Date.now());
39
+ this.sleep =
40
+ options.sleep ??
41
+ ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));
42
+ }
43
+
44
+ async acquire(input: {
45
+ bucketId: string;
46
+ rules: readonly PacingRule[];
47
+ signal?: AbortSignal;
48
+ }): Promise<PacingPermit> {
49
+ const { bucketId, rules, signal } = input;
50
+ if (rules.length === 0) {
51
+ return { release() {} };
52
+ }
53
+ // Stable order to avoid deadlock between concurrent acquirers on shared rules.
54
+ const ordered = [...rules].sort((a, b) => a.ruleId.localeCompare(b.ruleId));
55
+
56
+ while (true) {
57
+ if (signal?.aborted) {
58
+ throw signal.reason instanceof Error
59
+ ? signal.reason
60
+ : new Error('Rate-state acquire aborted.');
61
+ }
62
+ const decision = await this.withLock(() => {
63
+ const now = this.now();
64
+ let waitMs = 0;
65
+
66
+ const cooldownUntil = this.cooldownUntilByBucket.get(bucketId) ?? 0;
67
+ if (cooldownUntil > now) waitMs = Math.max(waitMs, cooldownUntil - now);
68
+
69
+ for (const rule of ordered) {
70
+ const state = this.getRuleState(bucketId, rule, now);
71
+ this.resetExpiredWindow(state, rule.windowMs, now);
72
+ if (state.startedInWindow >= rule.requestsPerWindow) {
73
+ waitMs = Math.max(waitMs, state.windowStartedAt + rule.windowMs - now);
74
+ }
75
+ if (rule.maxConcurrency != null && state.activeCount >= rule.maxConcurrency) {
76
+ waitMs = Math.max(waitMs, MIN_CONCURRENCY_WAIT_MS);
77
+ }
78
+ }
79
+
80
+ if (waitMs > 0) return { acquired: false, waitMs: Math.max(1, waitMs) } as const;
81
+
82
+ for (const rule of ordered) {
83
+ const state = this.getRuleState(bucketId, rule, now);
84
+ state.startedInWindow += 1;
85
+ if (rule.maxConcurrency != null) state.activeCount += 1;
86
+ }
87
+ return { acquired: true, waitMs: 0 } as const;
88
+ });
89
+
90
+ if (decision.acquired) {
91
+ let released = false;
92
+ return {
93
+ // The concurrency decrement is intentionally not awaitable (the permit
94
+ // signature is sync `release(): void`). It still runs serialized under
95
+ // the lock; do not "fix" this by trying to await a void.
96
+ release: () => {
97
+ if (released) return;
98
+ released = true;
99
+ void this.withLock(() => {
100
+ for (const rule of ordered) {
101
+ if (rule.maxConcurrency == null) continue;
102
+ const state = this.ruleStates.get(this.key(bucketId, rule.ruleId));
103
+ if (state && state.activeCount > 0) state.activeCount -= 1;
104
+ }
105
+ });
106
+ },
107
+ };
108
+ }
109
+ await this.sleep(decision.waitMs);
110
+ }
111
+ }
112
+
113
+ penalize(input: { bucketId: string; cooldownMs: number }): void {
114
+ if (input.cooldownMs <= 0) return;
115
+ const until = this.now() + input.cooldownMs;
116
+ const existing = this.cooldownUntilByBucket.get(input.bucketId) ?? 0;
117
+ this.cooldownUntilByBucket.set(input.bucketId, Math.max(existing, until));
118
+ }
119
+
120
+ private key(bucketId: string, ruleId: string): string {
121
+ return `${bucketId}::${ruleId}`;
122
+ }
123
+
124
+ private getRuleState(
125
+ bucketId: string,
126
+ rule: PacingRule,
127
+ now: number,
128
+ ): RuleState {
129
+ const key = this.key(bucketId, rule.ruleId);
130
+ const existing = this.ruleStates.get(key);
131
+ if (existing) return existing;
132
+ const created: RuleState = {
133
+ windowStartedAt: now,
134
+ startedInWindow: 0,
135
+ activeCount: 0,
136
+ };
137
+ this.ruleStates.set(key, created);
138
+ return created;
139
+ }
140
+
141
+ private resetExpiredWindow(
142
+ state: RuleState,
143
+ windowMs: number,
144
+ now: number,
145
+ ): void {
146
+ if (windowMs <= 0) {
147
+ state.windowStartedAt = now;
148
+ state.startedInWindow = 0;
149
+ return;
150
+ }
151
+ if (now - state.windowStartedAt < windowMs) return;
152
+ const elapsed = Math.floor((now - state.windowStartedAt) / windowMs);
153
+ state.windowStartedAt += elapsed * windowMs;
154
+ state.startedInWindow = 0;
155
+ if (now - state.windowStartedAt >= windowMs) state.windowStartedAt = now;
156
+ }
157
+
158
+ private async withLock<T>(fn: () => T | Promise<T>): Promise<T> {
159
+ const previous = this.lock;
160
+ let releaseLock!: () => void;
161
+ this.lock = new Promise<void>((resolve) => {
162
+ releaseLock = resolve;
163
+ });
164
+ await previous;
165
+ try {
166
+ return await fn();
167
+ } finally {
168
+ releaseLock();
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,321 @@
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
+ }