deepline 0.1.199 → 0.1.201

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 +67 -18
  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 +45 -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 +561 -105
  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
@@ -8,7 +8,7 @@ export function workReceiptClaimableStatusCodes(input: {
8
8
  }): number[] {
9
9
  if (input.forceRefresh === true) {
10
10
  return [
11
- RECEIPT_STATUS_CODE.pending,
11
+ RECEIPT_STATUS_CODE.queued,
12
12
  RECEIPT_STATUS_CODE.failed,
13
13
  ...(input.reclaimRunning === true ? [RECEIPT_STATUS_CODE.running] : []),
14
14
  RECEIPT_STATUS_CODE.completed,
@@ -17,12 +17,12 @@ export function workReceiptClaimableStatusCodes(input: {
17
17
  }
18
18
  if (input.reclaimRunning === true) {
19
19
  return [
20
- RECEIPT_STATUS_CODE.pending,
20
+ RECEIPT_STATUS_CODE.queued,
21
21
  RECEIPT_STATUS_CODE.running,
22
22
  RECEIPT_STATUS_CODE.failed,
23
23
  ];
24
24
  }
25
- return [RECEIPT_STATUS_CODE.pending, RECEIPT_STATUS_CODE.failed];
25
+ return [RECEIPT_STATUS_CODE.queued, RECEIPT_STATUS_CODE.failed];
26
26
  }
27
27
 
28
28
  export function workReceiptClaimConflictPredicateSql(input: {
@@ -30,11 +30,13 @@ export function workReceiptClaimConflictPredicateSql(input: {
30
30
  claimableStatusesSql: string;
31
31
  claimantRunIdSql: string;
32
32
  claimantRunAttemptSql?: string;
33
+ reclaimRunningSql?: string;
33
34
  forceRefreshSql: string;
34
35
  forceFailedRefreshSql?: string;
35
36
  }): string {
36
37
  const table = input.receiptTable;
37
38
  const claimantRunAttemptSql = input.claimantRunAttemptSql ?? '0';
39
+ const reclaimRunningSql = input.reclaimRunningSql ?? 'false';
38
40
  const forceFailedRefreshSql = input.forceFailedRefreshSql ?? 'false';
39
41
  return `${table}.status = ANY(${input.claimableStatusesSql}::smallint[])
40
42
  AND (
@@ -54,11 +56,45 @@ export function workReceiptClaimConflictPredicateSql(input: {
54
56
  )
55
57
  AND (
56
58
  ${table}.status = ${RECEIPT_STATUS_CODE.failed}::smallint
57
- OR (${table}.lease_id IS NULL AND ${table}.lease_expires_at IS NULL)
59
+ OR (
60
+ ${table}.status = ${RECEIPT_STATUS_CODE.queued}::smallint
61
+ AND ${table}.lease_id IS NULL
62
+ AND ${table}.lease_expires_at IS NULL
63
+ )
64
+ OR (
65
+ ${reclaimRunningSql}::boolean
66
+ AND ${table}.status = ${RECEIPT_STATUS_CODE.running}::smallint
67
+ AND ${table}.lease_id IS NULL
68
+ AND ${table}.lease_expires_at IS NULL
69
+ )
58
70
  OR ${table}.lease_expires_at <= now()
59
71
  OR (
72
+ ${input.forceRefreshSql}::boolean
73
+ AND ${table}.status IN (
74
+ ${RECEIPT_STATUS_CODE.completed}::smallint,
75
+ ${RECEIPT_STATUS_CODE.skipped}::smallint
76
+ )
77
+ AND (
78
+ (
79
+ ${table}.lease_id IS NULL
80
+ AND ${table}.lease_expires_at IS NULL
81
+ )
82
+ OR ${table}.lease_expires_at <= now()
83
+ OR (
84
+ ${table}.lease_expires_at IS NOT NULL
85
+ AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.claimantRunIdSql}
86
+ AND COALESCE(${table}.lease_owner_attempt, 0) < ${claimantRunAttemptSql}::integer
87
+ )
88
+ )
89
+ )
90
+ OR (
91
+ (
92
+ ${table}.status = ${RECEIPT_STATUS_CODE.queued}::smallint
93
+ OR ${reclaimRunningSql}::boolean
94
+ OR ${input.forceRefreshSql}::boolean
95
+ )
60
96
  /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */
61
- ${table}.lease_id IS NOT NULL
97
+ AND ${table}.lease_id IS NOT NULL
62
98
  AND ${table}.lease_expires_at IS NOT NULL
63
99
  AND
64
100
  COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.claimantRunIdSql}
@@ -88,6 +124,30 @@ export function workReceiptRunningOwnerPredicateSql(input: {
88
124
  )`;
89
125
  }
90
126
 
127
+ export function workReceiptActiveOwnerPredicateSql(input: {
128
+ receiptTable: string;
129
+ ownerRunIdSql: string;
130
+ ownerRunAttemptSql?: string;
131
+ leaseIdSql: string;
132
+ }): string {
133
+ const table = input.receiptTable;
134
+ const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0';
135
+ return `${table}.status IN (
136
+ ${RECEIPT_STATUS_CODE.queued}::smallint,
137
+ ${RECEIPT_STATUS_CODE.running}::smallint
138
+ )
139
+ /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */
140
+ AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql}
141
+ AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer
142
+ AND (
143
+ ${table}.lease_id IS NULL
144
+ OR (
145
+ ${input.leaseIdSql} IS NOT NULL
146
+ AND ${table}.lease_id = ${input.leaseIdSql}
147
+ )
148
+ )`;
149
+ }
150
+
91
151
  /**
92
152
  * Fence for a controlled work-receipt lease RELEASE on run-fatal teardown.
93
153
  * Matches running receipts still leased to the releasing run+attempt, regardless
@@ -102,10 +162,15 @@ export function workReceiptReleaseOwnerPredicateSql(input: {
102
162
  }): string {
103
163
  const table = input.receiptTable;
104
164
  const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0';
105
- return `${table}.status = ${RECEIPT_STATUS_CODE.running}::smallint
165
+ return `${table}.status IN (
166
+ ${RECEIPT_STATUS_CODE.queued}::smallint,
167
+ ${RECEIPT_STATUS_CODE.running}::smallint
168
+ )
106
169
  /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */
107
170
  AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql}
108
- AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer`;
171
+ AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer
172
+ AND ${table}.lease_id IS NOT NULL
173
+ AND ${table}.lease_expires_at IS NOT NULL`;
109
174
  }
110
175
 
111
176
  export function workReceiptHeartbeatPredicateSql(input: {
@@ -116,7 +181,10 @@ export function workReceiptHeartbeatPredicateSql(input: {
116
181
  }): string {
117
182
  const table = input.receiptTable;
118
183
  const ownerRunAttemptSql = input.ownerRunAttemptSql ?? '0';
119
- return `${table}.status = ${RECEIPT_STATUS_CODE.running}::smallint
184
+ return `${table}.status IN (
185
+ ${RECEIPT_STATUS_CODE.queued}::smallint,
186
+ ${RECEIPT_STATUS_CODE.running}::smallint
187
+ )
120
188
  /* owner: runtime; remove lease-owner fallback compat after ledger cutover M1 */
121
189
  AND COALESCE(${table}.lease_owner_run_id, ${table}.run_id) = ${input.ownerRunIdSql}
122
190
  AND COALESCE(${table}.lease_owner_attempt, 0) = ${ownerRunAttemptSql}::integer
@@ -0,0 +1,59 @@
1
+ import { RuntimeReceiptCompletionSink } from './receipt-completion-sink';
2
+
3
+ export interface RuntimeReceiptStateSinkOptions<TInput, TOutput> {
4
+ maxBatchSize?: number;
5
+ maxFlushMs?: number;
6
+ estimateBytes?: (input: TInput) => number;
7
+ applyMany: (inputs: TInput[]) => Promise<TOutput[]>;
8
+ onFlushStart?: (event: { count: number; bytes: number }) => void;
9
+ onFlushDone?: (event: {
10
+ count: number;
11
+ bytes: number;
12
+ elapsedMs?: number;
13
+ }) => void;
14
+ onFlushError?: (event: {
15
+ count: number;
16
+ bytes: number;
17
+ elapsedMs?: number;
18
+ error: unknown;
19
+ }) => void;
20
+ }
21
+
22
+ /**
23
+ * Small coalescing sink for receipt state transitions where callers need the
24
+ * resulting receipt before continuing, but sibling row/tool promises arrive in
25
+ * the same event-loop turn. It is intentionally thin over the proven completion
26
+ * sink so mark-running, mark-queued, and future state changes share one
27
+ * batching shape.
28
+ */
29
+ export class RuntimeReceiptStateSink<TInput, TOutput> {
30
+ #sink: RuntimeReceiptCompletionSink<TInput, TOutput>;
31
+
32
+ constructor(options: RuntimeReceiptStateSinkOptions<TInput, TOutput>) {
33
+ this.#sink = new RuntimeReceiptCompletionSink<TInput, TOutput>({
34
+ maxBatchSize: options.maxBatchSize,
35
+ maxFlushMs: options.maxFlushMs,
36
+ estimateBytes: options.estimateBytes,
37
+ completeMany: options.applyMany,
38
+ onFlushStart: options.onFlushStart,
39
+ onFlushDone: options.onFlushDone,
40
+ onFlushError: options.onFlushError,
41
+ });
42
+ }
43
+
44
+ add(input: TInput): Promise<TOutput> {
45
+ return this.#sink.addComplete(input);
46
+ }
47
+
48
+ addMany(inputs: readonly TInput[]): Promise<TOutput[]> {
49
+ return this.#sink.addMany(inputs);
50
+ }
51
+
52
+ async flush(): Promise<void> {
53
+ await this.#sink.flush();
54
+ }
55
+
56
+ async close(): Promise<void> {
57
+ await this.#sink.close();
58
+ }
59
+ }
@@ -1,6 +1,7 @@
1
1
  import type { WorkReceiptStatus } from './work-receipts';
2
2
 
3
3
  export const RECEIPT_STATUS_CODE = {
4
+ queued: 0,
4
5
  pending: 0,
5
6
  running: 1,
6
7
  completed: 2,
@@ -24,8 +25,8 @@ export function receiptStatusFromCode(value: unknown): WorkReceiptStatus {
24
25
  return 'failed';
25
26
  case RECEIPT_STATUS_CODE.skipped:
26
27
  return 'skipped';
27
- case RECEIPT_STATUS_CODE.pending:
28
- return 'pending';
28
+ case RECEIPT_STATUS_CODE.queued:
29
+ return 'queued';
29
30
  default:
30
31
  throw new Error(`Unknown runtime work receipt status code: ${value}`);
31
32
  }
@@ -0,0 +1,269 @@
1
+ import type { PlayExecutionGovernor, WorkLease } from './governor/governor';
2
+
3
+ export type RuntimeResourceLease = WorkLease;
4
+
5
+ export type RuntimeResourceObservation = {
6
+ providerResourceKey?: string | null;
7
+ rowEstimatedBytes?: number | null;
8
+ rowAdmissionWaitMs?: number | null;
9
+ toolAdmissionWaitMs?: number | null;
10
+ providerLatencyMs?: number | null;
11
+ providerSuccess?: boolean;
12
+ provider429?: boolean;
13
+ retryAfterMs?: number | null;
14
+ receiptLatencyMs?: number | null;
15
+ sheetFlushLatencyMs?: number | null;
16
+ sheetFlushBytes?: number | null;
17
+ memoryBytes?: number | null;
18
+ };
19
+
20
+ export type RuntimeResourceSnapshot = {
21
+ rows: {
22
+ acquired: number;
23
+ estimatedBytes: number;
24
+ admissionWaitMsEwma: number | null;
25
+ };
26
+ tools: {
27
+ acquired: number;
28
+ admissionWaitMsEwma: number | null;
29
+ };
30
+ sheetFlush: {
31
+ acquired: number;
32
+ bytes: number;
33
+ latencyMsEwma: number | null;
34
+ };
35
+ providers: Record<
36
+ string,
37
+ {
38
+ latencyMsEwma: number | null;
39
+ retryAfterMsEwma: number | null;
40
+ provider429Count: number;
41
+ }
42
+ >;
43
+ };
44
+
45
+ export interface RuntimeResourceGovernor {
46
+ acquireRow(input?: {
47
+ estimatedBytes?: number | null;
48
+ signal?: AbortSignal;
49
+ }): Promise<RuntimeResourceLease>;
50
+ acquireTool(input: {
51
+ orgId?: string | null;
52
+ providerResourceKey?: string | null;
53
+ toolId: string;
54
+ estimatedCost?: number | null;
55
+ signal?: AbortSignal;
56
+ }): Promise<RuntimeResourceLease>;
57
+ acquireSheetFlush(input?: {
58
+ estimatedBytes?: number | null;
59
+ rowCount?: number | null;
60
+ signal?: AbortSignal;
61
+ }): Promise<RuntimeResourceLease>;
62
+ suggestedToolParallelism(toolId: string, fallback: number): Promise<number>;
63
+ resolveRowConcurrency(requested?: number): number;
64
+ reportProviderBackpressure(input: {
65
+ provider: string;
66
+ retryAfterMs: number;
67
+ }): void;
68
+ observe(input: RuntimeResourceObservation): void;
69
+ snapshot(): RuntimeResourceSnapshot;
70
+ }
71
+
72
+ type Ewma = {
73
+ value: number | null;
74
+ observe(value: number | null | undefined): void;
75
+ };
76
+
77
+ function createEwma(alpha = 0.2): Ewma {
78
+ let value: number | null = null;
79
+ return {
80
+ get value() {
81
+ return value;
82
+ },
83
+ observe(next) {
84
+ if (next == null || !Number.isFinite(next)) return;
85
+ const normalized = Math.max(0, next);
86
+ value =
87
+ value == null ? normalized : value * (1 - alpha) + normalized * alpha;
88
+ },
89
+ };
90
+ }
91
+
92
+ function noOpLease(): RuntimeResourceLease {
93
+ return { release() {} };
94
+ }
95
+
96
+ function elapsedSince(startedAt: number): number {
97
+ return Math.max(0, Date.now() - startedAt);
98
+ }
99
+
100
+ function resourceKey(input?: string | null): string {
101
+ const trimmed = input?.trim();
102
+ return trimmed || 'unknown';
103
+ }
104
+
105
+ export function createRuntimeResourceGovernor(input: {
106
+ executionGovernor: PlayExecutionGovernor;
107
+ }): RuntimeResourceGovernor {
108
+ const rowAdmissionWait = createEwma();
109
+ const toolAdmissionWait = createEwma();
110
+ const sheetFlushLatency = createEwma();
111
+ const providers = new Map<
112
+ string,
113
+ {
114
+ latency: Ewma;
115
+ retryAfter: Ewma;
116
+ provider429Count: number;
117
+ }
118
+ >();
119
+ const rowCounters = {
120
+ acquired: 0,
121
+ estimatedBytes: 0,
122
+ };
123
+ const toolCounters = {
124
+ acquired: 0,
125
+ };
126
+ const sheetFlushCounters = {
127
+ acquired: 0,
128
+ bytes: 0,
129
+ };
130
+
131
+ const providerStats = (key: string) => {
132
+ const normalized = resourceKey(key);
133
+ let stats = providers.get(normalized);
134
+ if (!stats) {
135
+ stats = {
136
+ latency: createEwma(),
137
+ retryAfter: createEwma(),
138
+ provider429Count: 0,
139
+ };
140
+ providers.set(normalized, stats);
141
+ }
142
+ return stats;
143
+ };
144
+
145
+ const observe = (observation: RuntimeResourceObservation): void => {
146
+ rowAdmissionWait.observe(observation.rowAdmissionWaitMs);
147
+ toolAdmissionWait.observe(observation.toolAdmissionWaitMs);
148
+ sheetFlushLatency.observe(observation.sheetFlushLatencyMs);
149
+ if (observation.rowEstimatedBytes != null) {
150
+ rowCounters.estimatedBytes += Math.max(
151
+ 0,
152
+ Math.floor(observation.rowEstimatedBytes),
153
+ );
154
+ }
155
+ if (observation.sheetFlushBytes != null) {
156
+ sheetFlushCounters.bytes += Math.max(
157
+ 0,
158
+ Math.floor(observation.sheetFlushBytes),
159
+ );
160
+ }
161
+ if (
162
+ observation.providerResourceKey ||
163
+ observation.providerLatencyMs != null ||
164
+ observation.provider429 ||
165
+ observation.retryAfterMs != null
166
+ ) {
167
+ const stats = providerStats(observation.providerResourceKey ?? 'unknown');
168
+ stats.latency.observe(observation.providerLatencyMs);
169
+ stats.retryAfter.observe(observation.retryAfterMs);
170
+ if (observation.provider429) {
171
+ stats.provider429Count += 1;
172
+ }
173
+ if (
174
+ observation.providerSuccess === true &&
175
+ observation.providerResourceKey
176
+ ) {
177
+ input.executionGovernor.observeProviderSuccess({
178
+ provider: observation.providerResourceKey,
179
+ latencyMs: observation.providerLatencyMs,
180
+ });
181
+ }
182
+ }
183
+ };
184
+
185
+ return {
186
+ async acquireRow(rowInput) {
187
+ const startedAt = Date.now();
188
+ const lease = await input.executionGovernor.acquireRowSlot({
189
+ signal: rowInput?.signal,
190
+ });
191
+ rowCounters.acquired += 1;
192
+ observe({
193
+ rowAdmissionWaitMs: elapsedSince(startedAt),
194
+ rowEstimatedBytes: rowInput?.estimatedBytes ?? null,
195
+ });
196
+ return lease;
197
+ },
198
+
199
+ async acquireTool(toolInput) {
200
+ const startedAt = Date.now();
201
+ const lease = await input.executionGovernor.acquireToolSlot(
202
+ toolInput.toolId,
203
+ { signal: toolInput.signal },
204
+ );
205
+ toolCounters.acquired += 1;
206
+ observe({
207
+ providerResourceKey: toolInput.providerResourceKey ?? toolInput.toolId,
208
+ toolAdmissionWaitMs: elapsedSince(startedAt),
209
+ });
210
+ return lease;
211
+ },
212
+
213
+ async acquireSheetFlush(sheetInput) {
214
+ sheetFlushCounters.acquired += 1;
215
+ observe({
216
+ sheetFlushBytes: sheetInput?.estimatedBytes ?? null,
217
+ });
218
+ return noOpLease();
219
+ },
220
+
221
+ suggestedToolParallelism(toolId, fallback) {
222
+ return input.executionGovernor.suggestedParallelism(toolId, fallback);
223
+ },
224
+
225
+ resolveRowConcurrency(requested) {
226
+ return input.executionGovernor.resolveRowConcurrency(requested);
227
+ },
228
+
229
+ reportProviderBackpressure(backpressure) {
230
+ input.executionGovernor.reportProviderBackpressure(backpressure);
231
+ observe({
232
+ providerResourceKey: backpressure.provider,
233
+ provider429: true,
234
+ retryAfterMs: backpressure.retryAfterMs,
235
+ });
236
+ },
237
+
238
+ observe,
239
+
240
+ snapshot() {
241
+ return {
242
+ rows: {
243
+ acquired: rowCounters.acquired,
244
+ estimatedBytes: rowCounters.estimatedBytes,
245
+ admissionWaitMsEwma: rowAdmissionWait.value,
246
+ },
247
+ tools: {
248
+ acquired: toolCounters.acquired,
249
+ admissionWaitMsEwma: toolAdmissionWait.value,
250
+ },
251
+ sheetFlush: {
252
+ acquired: sheetFlushCounters.acquired,
253
+ bytes: sheetFlushCounters.bytes,
254
+ latencyMsEwma: sheetFlushLatency.value,
255
+ },
256
+ providers: Object.fromEntries(
257
+ [...providers.entries()].map(([key, stats]) => [
258
+ key,
259
+ {
260
+ latencyMsEwma: stats.latency.value,
261
+ retryAfterMsEwma: stats.retryAfter.value,
262
+ provider429Count: stats.provider429Count,
263
+ },
264
+ ]),
265
+ ),
266
+ };
267
+ },
268
+ };
269
+ }
@@ -27,11 +27,36 @@ export type PlayRunLedgerStepStatus =
27
27
 
28
28
  export type PlayRunLedgerEventSource =
29
29
  | 'worker'
30
- | 'temporal'
31
30
  | 'convex'
32
31
  | 'coordinator'
33
32
  | 'system';
34
33
 
34
+ /**
35
+ * Live progress for a single step/node.
36
+ *
37
+ * Verified producer semantics (workers_edge map executor —
38
+ * `apps/play-runner-workers/src/entry.ts`):
39
+ *
40
+ * - `completed` counts SUCCESSFULLY written rows ONLY, never failures/skips.
41
+ * In-flight it is `min(total, totalRowsWritten + completedExecutedRows)`
42
+ * where `completedExecutedRows` increments on success only (entry.ts:5635);
43
+ * failures increment the separate `failedExecutedRows` (entry.ts:5684).
44
+ * At map finalize it is set to `finalizedRowsWritten` (successes).
45
+ * - `failed` counts settled-but-failed rows (row-failure isolation persists
46
+ * these; they re-execute on the next run).
47
+ * - `total` is the settled-row denominator. In-flight it is the row-count
48
+ * estimate (`rowCountHint`); at map finalize it is reconciled to
49
+ * `completed + failed` and CAN SHRINK from the initial estimate when rows are
50
+ * filtered/skipped. So `total` is monotonic-up during flight but is corrected
51
+ * down to the settled count at terminal.
52
+ *
53
+ * Terminal invariant: once the step is settled, `completed + failed === total`.
54
+ * Because `completed` is successes-only (NOT all-settled), readers must NOT
55
+ * assume `completed === total` at terminal — a truthful terminal render is
56
+ * `{total} rows` when `failed === 0`, else a `completed`/`failed`/`total`
57
+ * breakdown. See `settleRunningStepsOnTerminal` for why the reducer does not
58
+ * force `completed = total`.
59
+ */
35
60
  export type PlayRunLedgerStepProgress = {
36
61
  completed?: number;
37
62
  total?: number;
@@ -95,7 +120,7 @@ export type PlayRunLedgerSnapshot = {
95
120
  result?: unknown;
96
121
  /**
97
122
  * Source of the event that made this snapshot terminal. Authoritative
98
- * producers ('worker', 'temporal', scheduler-outbox 'system') carry the
123
+ * producers ('worker', scheduler-outbox 'system') carry the
99
124
  * play's final result; a 'coordinator'-sourced terminal is a transport
100
125
  * heal whose `result` came from the coordinator's live cache and may be a
101
126
  * partial mid-execution snapshot. Read-side canonical checks treat
@@ -431,7 +456,7 @@ export function normalizePlayRunLedgerSnapshot(
431
456
  * A transport heal ('coordinator') never overwrites an already-recorded
432
457
  * authoritative source: when the producer's own terminal has landed, a late
433
458
  * heal replay must not demote the snapshot back to provisional. Any
434
- * authoritative source (worker/temporal/system/convex) always wins, so the
459
+ * authoritative source (worker/system/convex) always wins, so the
435
460
  * worker's post-heal `run.completed` flips a healed snapshot to final.
436
461
  */
437
462
  function nextTerminalSource(
@@ -776,6 +801,23 @@ function retryablePlatformDeployFailureSnapshot(
776
801
  );
777
802
  }
778
803
 
804
+ // Force-settles steps still marked `running` when the RUN reaches a terminal
805
+ // status (the fallback for steps that never emitted their own step.completed,
806
+ // e.g. a crash between the last progress bump and the terminal event). This
807
+ // only flips status and stamps `completedAt`; it deliberately does NOT
808
+ // reconcile `progress.completed` to `progress.total`.
809
+ //
810
+ // Why not reconcile: per PlayRunLedgerStepProgress semantics, `completed`
811
+ // counts SUCCESSES ONLY, so `completed === total` is NOT a valid invariant at
812
+ // terminal — a run can legitimately settle with failed/skipped rows (row-
813
+ // failure isolation). Forcing `completed = total` here would fabricate
814
+ // successes that never happened and falsify the persisted counts. The truthful
815
+ // terminal counts are carried by the producer's final reconciled bump, which
816
+ // the workers_edge finalize path now drains BEFORE emitting step.completed
817
+ // (entry.ts finalize ordering invariant). This settle path is the last-resort
818
+ // heal for the missing-final-bump case, where the honest thing is "we know it
819
+ // stopped, we do not know it fully succeeded" — leave the last observed
820
+ // counts, just mark it settled so the UI stops rendering a live ticker.
779
821
  function settleRunningStepsOnTerminal(
780
822
  snapshot: PlayRunLedgerSnapshot,
781
823
  status: Extract<PlayRunLedgerStepStatus, 'completed' | 'failed'>,
@@ -2,7 +2,6 @@ import type { PlayRunLedgerEventSource } from './run-ledger';
2
2
 
3
3
  const PLAY_RUN_LEDGER_EVENT_SOURCES: readonly PlayRunLedgerEventSource[] = [
4
4
  'worker',
5
- 'temporal',
6
5
  'convex',
7
6
  'coordinator',
8
7
  'system',