deepline 0.1.198 → 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.
- 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 +51 -17
- 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 +2 -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 +459 -103
- 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 +441 -101
- package/dist/cli/index.mjs +441 -101
- 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,7 +27,6 @@ export type PlayRunLedgerStepStatus =
|
|
|
27
27
|
|
|
28
28
|
export type PlayRunLedgerEventSource =
|
|
29
29
|
| 'worker'
|
|
30
|
-
| 'temporal'
|
|
31
30
|
| 'convex'
|
|
32
31
|
| 'coordinator'
|
|
33
32
|
| 'system';
|
|
@@ -95,7 +94,7 @@ export type PlayRunLedgerSnapshot = {
|
|
|
95
94
|
result?: unknown;
|
|
96
95
|
/**
|
|
97
96
|
* Source of the event that made this snapshot terminal. Authoritative
|
|
98
|
-
* producers ('worker',
|
|
97
|
+
* producers ('worker', scheduler-outbox 'system') carry the
|
|
99
98
|
* play's final result; a 'coordinator'-sourced terminal is a transport
|
|
100
99
|
* heal whose `result` came from the coordinator's live cache and may be a
|
|
101
100
|
* partial mid-execution snapshot. Read-side canonical checks treat
|
|
@@ -431,7 +430,7 @@ export function normalizePlayRunLedgerSnapshot(
|
|
|
431
430
|
* A transport heal ('coordinator') never overwrites an already-recorded
|
|
432
431
|
* authoritative source: when the producer's own terminal has landed, a late
|
|
433
432
|
* heal replay must not demote the snapshot back to provisional. Any
|
|
434
|
-
* authoritative source (worker/
|
|
433
|
+
* authoritative source (worker/system/convex) always wins, so the
|
|
435
434
|
* worker's post-heal `run.completed` flips a healed snapshot to final.
|
|
436
435
|
*/
|
|
437
436
|
function nextTerminalSource(
|