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.
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +5 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +67 -18
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/README.md +1 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +16 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +40 -9
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +108 -39
- package/dist/bundling-sources/sdk/src/http.ts +5 -2
- package/dist/bundling-sources/sdk/src/play.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +7 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/worker-play-entry.ts +11 -55
- package/dist/bundling-sources/shared_libs/play-runtime/adaptive-tool-dispatcher.ts +355 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +172 -73
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1794 -377
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-contract.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +67 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +161 -39
- package/dist/bundling-sources/shared_libs/play-runtime/governor/adaptive-admission.ts +218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +115 -25
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +17 -17
- package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +2 -1
- package/dist/bundling-sources/shared_libs/play-runtime/live-events.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +265 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +135 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-input.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-latency-trace.ts +0 -3
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +10 -1
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +9 -15
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-completion-sink.ts +262 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-heartbeat-supervisor.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +76 -8
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-state-sink.ts +59 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +269 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +45 -3
- package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +433 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +227 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +882 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +573 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/bundle.ts +264 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/index.ts +33 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runner-events.ts +35 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +53 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +0 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +561 -105
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-infra-mode.ts +23 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +23 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +16 -24
- package/dist/bundling-sources/shared_libs/play-runtime/serial-task-queue.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/submit-limits.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/vercel-protection.ts +81 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +54 -9
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +17 -3
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +0 -20
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +105 -4
- package/dist/bundling-sources/shared_libs/plays/row-identity.ts +4 -11
- package/dist/cli/index.js +167 -54
- package/dist/cli/index.mjs +167 -54
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/index.mjs +8 -8
- package/dist/plays/bundle-play-file.mjs +15 -20
- package/package.json +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-diagnosis.ts +0 -321
- package/dist/bundling-sources/shared_libs/play-runtime/hatchet-cold-execution-target.ts +0 -158
- package/dist/bundling-sources/shared_libs/temporal/constants.ts +0 -39
- 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.
|
|
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.
|
|
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.
|
|
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 (
|
|
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
|
|
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
|
|
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.
|
|
28
|
-
return '
|
|
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',
|
|
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/
|
|
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'>,
|