deepline 0.3.248 → 0.3.250
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/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +62 -2
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +49 -3
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +50 -29
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +94 -26
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +394 -93
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +4 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/{compiler-manifest-DavmLa8y.d.mts → compiler-manifest-C4mdRJOP.d.mts} +4 -0
- package/dist/{compiler-manifest-DavmLa8y.d.ts → compiler-manifest-C4mdRJOP.d.ts} +4 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/install-integrity.json +2 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
|
|
|
200
200
|
// getters keep their established compatibility behavior.
|
|
201
201
|
// 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
|
|
202
202
|
// 0.3.90 is the first deliberately versioned SDK release for API v3.
|
|
203
|
-
version: '0.3.
|
|
203
|
+
version: '0.3.250',
|
|
204
204
|
updateSummary:
|
|
205
205
|
'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
|
|
206
206
|
packageCapabilities: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
flattenStaticPipeline,
|
|
3
|
+
getCompiledPipelineSubsteps,
|
|
4
|
+
type PlayStaticSubstep,
|
|
3
5
|
type PlayStaticPipeline,
|
|
4
6
|
} from '../plays/static-pipeline';
|
|
5
7
|
import type { PlayCallExecution } from './play-call-execution';
|
|
@@ -21,6 +23,8 @@ export type CtxRunPlayInlineOnlyReason =
|
|
|
21
23
|
| 'dataset_child'
|
|
22
24
|
| 'event_wait_child'
|
|
23
25
|
| 'suspending_child'
|
|
26
|
+
| 'row_sleep'
|
|
27
|
+
| 'concurrent_sleep'
|
|
24
28
|
| 'explicit_timeout'
|
|
25
29
|
| 'explicit_child_workflow';
|
|
26
30
|
|
|
@@ -46,7 +50,9 @@ export function ctxRunPlayInlineOnlyMessage(
|
|
|
46
50
|
missing_static_contract: 'its static contract could not be resolved',
|
|
47
51
|
dataset_child: 'it uses ctx.dataset(), ctx.csv(), or a Runtime Sheet',
|
|
48
52
|
event_wait_child: 'it waits for an event',
|
|
49
|
-
suspending_child: 'it attempted
|
|
53
|
+
suspending_child: 'it attempted an unsupported parent suspension',
|
|
54
|
+
row_sleep: 'it calls ctx.sleep from a Dataset column resolver',
|
|
55
|
+
concurrent_sleep: 'it calls ctx.sleep while sibling inline calls overlap',
|
|
50
56
|
explicit_timeout: 'it sets an explicit timeout',
|
|
51
57
|
explicit_child_workflow: 'it requests execution: "child-workflow"',
|
|
52
58
|
};
|
|
@@ -57,6 +63,10 @@ export function ctxRunPlayInlineOnlyMessage(
|
|
|
57
63
|
'Trigger it as a top-level play. Event waits need their own run lifecycle and cannot be awaited through ctx.runPlay().',
|
|
58
64
|
suspending_child:
|
|
59
65
|
'Trigger it as a top-level play. Work that can suspend needs its own run lifecycle and cannot be awaited through ctx.runPlay().',
|
|
66
|
+
row_sleep:
|
|
67
|
+
'Sleep before or after Dataset.run(), or launch a scalar waiting Play with rowCtx.runPlayAsync and put its follow-up work there. runPlayAsync returns a Run reference, not the child result.',
|
|
68
|
+
concurrent_sleep:
|
|
69
|
+
'Await sleeping inline children sequentially outside Dataset callbacks, or use ctx.runPlayAsync for independent waits. A sequential sleep parks and replays the parent Run.',
|
|
60
70
|
missing_static_contract:
|
|
61
71
|
'Use a published play with a resolvable static contract, or move lifecycle-owning work into a top-level play.',
|
|
62
72
|
explicit_timeout:
|
|
@@ -72,7 +82,8 @@ export function ctxRunPlayInlineOnlyMessage(
|
|
|
72
82
|
|
|
73
83
|
/**
|
|
74
84
|
* Resolve an authored child contract. Any shape that would need a second run,
|
|
75
|
-
*
|
|
85
|
+
* independent scheduler state is rejected before invocation. Sequential authored
|
|
86
|
+
* sleep may suspend the existing parent Run.
|
|
76
87
|
*/
|
|
77
88
|
export function resolveChildExecutionStrategy(input: {
|
|
78
89
|
pipeline: PlayStaticPipeline | null | undefined;
|
|
@@ -142,3 +153,52 @@ export function resolveChildExecutionStrategy(input: {
|
|
|
142
153
|
reason: input.execution === 'inline' ? 'explicit_inline' : 'scalar_child',
|
|
143
154
|
};
|
|
144
155
|
}
|
|
156
|
+
|
|
157
|
+
/** Authored sleep may park one sequential inline chain, never a row or sibling fanout. */
|
|
158
|
+
export function assertInlineChildSleepPlacement(input: {
|
|
159
|
+
childPlayName: string;
|
|
160
|
+
rowScoped: boolean;
|
|
161
|
+
concurrent: boolean;
|
|
162
|
+
}): void {
|
|
163
|
+
if (input.rowScoped || input.concurrent) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
ctxRunPlayInlineOnlyMessage(
|
|
166
|
+
input.childPlayName,
|
|
167
|
+
input.rowScoped ? 'row_sleep' : 'concurrent_sleep',
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Transitive authored sleep facts; independent launches do not suspend their caller. */
|
|
174
|
+
export function findInlineChildSleep(
|
|
175
|
+
pipeline: PlayStaticPipeline | null | undefined,
|
|
176
|
+
seen = new Set<PlayStaticPipeline>(),
|
|
177
|
+
): {
|
|
178
|
+
path: string[];
|
|
179
|
+
source: NonNullable<PlayStaticPipeline['authoredSleep']>;
|
|
180
|
+
} | null {
|
|
181
|
+
if (!pipeline || seen.has(pipeline)) return null;
|
|
182
|
+
seen.add(pipeline);
|
|
183
|
+
if (pipeline.authoredSleep)
|
|
184
|
+
return { path: [], source: pipeline.authoredSleep };
|
|
185
|
+
function visit(
|
|
186
|
+
steps: PlayStaticSubstep[],
|
|
187
|
+
): ReturnType<typeof findInlineChildSleep> {
|
|
188
|
+
for (const step of steps) {
|
|
189
|
+
if (step.type === 'play_call') {
|
|
190
|
+
const found = findInlineChildSleep(step.pipeline, seen);
|
|
191
|
+
if (found) return { ...found, path: [step.playId, ...found.path] };
|
|
192
|
+
} else if (
|
|
193
|
+
step.type === 'dataset' ||
|
|
194
|
+
step.type === 'step_suite' ||
|
|
195
|
+
step.type === 'control_flow'
|
|
196
|
+
) {
|
|
197
|
+
const found = visit(step.steps ?? []);
|
|
198
|
+
if (found) return found;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return visit(getCompiledPipelineSubsteps(pipeline));
|
|
204
|
+
}
|
|
@@ -481,6 +481,7 @@ import { executionGatewayDecodedBodyBytes } from './execution-gateway-response-s
|
|
|
481
481
|
import {
|
|
482
482
|
ctxRunPlayInlineOnlyMessage,
|
|
483
483
|
resolveChildExecutionStrategy,
|
|
484
|
+
assertInlineChildSleepPlacement,
|
|
484
485
|
} from './child-execution-strategy';
|
|
485
486
|
|
|
486
487
|
type ResolvedPlayExecutor = (
|
|
@@ -531,6 +532,7 @@ type RowResolverGapTracker = {
|
|
|
531
532
|
openedAtMs: number | null;
|
|
532
533
|
};
|
|
533
534
|
type InlineCompositionStore = {
|
|
535
|
+
invocationPath: readonly symbol[];
|
|
534
536
|
context: PlayContextImpl;
|
|
535
537
|
executionScope: RunExecutionScope;
|
|
536
538
|
governor: PlayExecutionGovernor;
|
|
@@ -2758,6 +2760,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
2758
2760
|
*/
|
|
2759
2761
|
private sleepBoundaryIndexByScope = new Map<string, number>();
|
|
2760
2762
|
private sleepBoundaryIndex = 0;
|
|
2763
|
+
private readonly activeInlineInvocations = new Set<symbol>();
|
|
2761
2764
|
private nextDocflowInvocationSequence = 0;
|
|
2762
2765
|
/**
|
|
2763
2766
|
* Whether this run captures the docflow trace at all (ADR 0019), decided at
|
|
@@ -14105,8 +14108,6 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14105
14108
|
input: Record<string, unknown>,
|
|
14106
14109
|
options?: PlayCallOptions,
|
|
14107
14110
|
): Promise<TOutput> {
|
|
14108
|
-
if (!rowContext.getStore()) this.assertRowAdmissionOpen();
|
|
14109
|
-
this.assertNoPendingDurableSuspension();
|
|
14110
14111
|
if (
|
|
14111
14112
|
arguments.length === 3 &&
|
|
14112
14113
|
typeof playRef === 'object' &&
|
|
@@ -14116,6 +14117,36 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14116
14117
|
'ctx.runPlay(...) signature is ctx.runPlay(key, playRef, input, { description }). Add a stable call key as the first argument.',
|
|
14117
14118
|
);
|
|
14118
14119
|
}
|
|
14120
|
+
const invocation = Symbol('inline-play');
|
|
14121
|
+
const invocationPath = [
|
|
14122
|
+
...(this.activeInlineComposition?.invocationPath ?? []),
|
|
14123
|
+
invocation,
|
|
14124
|
+
];
|
|
14125
|
+
// Register before resolver/governor awaits so Promise.all siblings cannot
|
|
14126
|
+
// slip through while one sibling is still preparing its artifact.
|
|
14127
|
+
this.activeInlineInvocations.add(invocation);
|
|
14128
|
+
try {
|
|
14129
|
+
return await this.runInlinePlay<TOutput>(
|
|
14130
|
+
key,
|
|
14131
|
+
playRef,
|
|
14132
|
+
input,
|
|
14133
|
+
options,
|
|
14134
|
+
invocationPath,
|
|
14135
|
+
);
|
|
14136
|
+
} finally {
|
|
14137
|
+
this.activeInlineInvocations.delete(invocation);
|
|
14138
|
+
}
|
|
14139
|
+
}
|
|
14140
|
+
|
|
14141
|
+
private async runInlinePlay<TOutput>(
|
|
14142
|
+
key: string,
|
|
14143
|
+
playRef: string | { playName?: string; name?: string },
|
|
14144
|
+
input: Record<string, unknown>,
|
|
14145
|
+
options: PlayCallOptions | undefined,
|
|
14146
|
+
invocationPath: readonly symbol[],
|
|
14147
|
+
): Promise<TOutput> {
|
|
14148
|
+
if (!rowContext.getStore()) this.assertRowAdmissionOpen();
|
|
14149
|
+
this.assertNoPendingDurableSuspension();
|
|
14119
14150
|
const resolvedName = resolvePlayReferenceName(playRef);
|
|
14120
14151
|
|
|
14121
14152
|
if (!resolvedName.trim()) {
|
|
@@ -14230,6 +14261,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14230
14261
|
const result = await inlineCompositionContext.run(
|
|
14231
14262
|
{
|
|
14232
14263
|
context: this,
|
|
14264
|
+
invocationPath,
|
|
14233
14265
|
executionScope: childExecutionScope,
|
|
14234
14266
|
governor: inlineChildGovernor,
|
|
14235
14267
|
playName: resolvedName,
|
|
@@ -14749,7 +14781,19 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
14749
14781
|
async sleep(ms: number): Promise<void> {
|
|
14750
14782
|
if (!rowContext.getStore()) this.assertRowAdmissionOpen();
|
|
14751
14783
|
this.assertNoPendingDurableSuspension();
|
|
14752
|
-
this.
|
|
14784
|
+
const composition = this.activeInlineComposition;
|
|
14785
|
+
if (composition) {
|
|
14786
|
+
assertInlineChildSleepPlacement({
|
|
14787
|
+
childPlayName: composition.playName,
|
|
14788
|
+
rowScoped: Boolean(rowContext.getStore()),
|
|
14789
|
+
concurrent:
|
|
14790
|
+
this.pureMapExecutionActive ||
|
|
14791
|
+
this.activeMapCellMeta !== null ||
|
|
14792
|
+
[...this.activeInlineInvocations].some(
|
|
14793
|
+
(invocation) => !composition.invocationPath.includes(invocation),
|
|
14794
|
+
),
|
|
14795
|
+
});
|
|
14796
|
+
}
|
|
14753
14797
|
const delayMs =
|
|
14754
14798
|
this.currentRuntimeSemantics.validation === 'strict'
|
|
14755
14799
|
? (validatePlayAuthoringField('ctx.sleep.ms', ms), ms)
|
|
@@ -19188,6 +19232,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
19188
19232
|
if (
|
|
19189
19233
|
transportAttempt < TOOL_EXECUTE_TRANSPORT_MAX_ATTEMPTS &&
|
|
19190
19234
|
(input.knownNotDispatched === true ||
|
|
19235
|
+
usesDurableInvocationFence ||
|
|
19191
19236
|
ambiguousTransportReplayAllowed ||
|
|
19192
19237
|
fixtureReplaySafe)
|
|
19193
19238
|
) {
|
|
@@ -20126,6 +20171,7 @@ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
|
|
|
20126
20171
|
// this exact body deadline caused the abort.
|
|
20127
20172
|
const transportReplaySafe =
|
|
20128
20173
|
!ambiguousDispatchedFailure ||
|
|
20174
|
+
usesDurableInvocationFence ||
|
|
20129
20175
|
ambiguousTransportReplayAllowed ||
|
|
20130
20176
|
fixtureReplaySafe;
|
|
20131
20177
|
if (!transportReplaySafe) {
|
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts
CHANGED
|
@@ -236,7 +236,8 @@ export type PostgresSchedulerProgressReporter = {
|
|
|
236
236
|
};
|
|
237
237
|
|
|
238
238
|
export type PostgresSchedulerDurableLogMode =
|
|
239
|
-
|
|
239
|
+
| 'progress_reporter'
|
|
240
|
+
| 'direct_worker';
|
|
240
241
|
|
|
241
242
|
function shouldPublishDurableLogEvent(
|
|
242
243
|
mode: PostgresSchedulerDurableLogMode,
|
|
@@ -418,7 +419,8 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
418
419
|
let workCounters: RuntimeStatusUpdate['workCounters'];
|
|
419
420
|
let workCountersDirty = false;
|
|
420
421
|
let lastFlushAt = 0;
|
|
421
|
-
let
|
|
422
|
+
let lastProgressFlushAt = 0;
|
|
423
|
+
let deferredFlushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
422
424
|
let totalRows = 0;
|
|
423
425
|
let activeMapNodeId: string | null = null;
|
|
424
426
|
let pendingDatasetLifecycleEvents: Array<
|
|
@@ -431,6 +433,7 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
431
433
|
|
|
432
434
|
type ProgressFlushRequest = {
|
|
433
435
|
status: 'running' | 'completed' | 'failed';
|
|
436
|
+
force: boolean;
|
|
434
437
|
required: boolean;
|
|
435
438
|
terminalLogReplay: TerminalLogReplayPayload | null;
|
|
436
439
|
waiters: Array<{
|
|
@@ -451,6 +454,7 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
451
454
|
next.status !== 'running'
|
|
452
455
|
? next.status
|
|
453
456
|
: (current?.status ?? next.status),
|
|
457
|
+
force: next.force || current?.force === true,
|
|
454
458
|
required: next.required || current?.required === true,
|
|
455
459
|
terminalLogReplay:
|
|
456
460
|
next.terminalLogReplay ?? current?.terminalLogReplay ?? null,
|
|
@@ -487,18 +491,32 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
487
491
|
const request = pendingProgressUpload;
|
|
488
492
|
pendingProgressUpload = null;
|
|
489
493
|
|
|
490
|
-
|
|
494
|
+
// Customer log latency is independent of row-count progress sampling.
|
|
495
|
+
const includeProgress =
|
|
496
|
+
request.force ||
|
|
497
|
+
request.required ||
|
|
498
|
+
request.status !== 'running' ||
|
|
499
|
+
Date.now() - lastProgressFlushAt >=
|
|
500
|
+
getAdaptiveRunEventIntervalMs(totalRows);
|
|
501
|
+
const nextProgress = includeProgress
|
|
502
|
+
? { ...liveNodeProgress }
|
|
503
|
+
: undefined;
|
|
491
504
|
const nextLogs = liveLogs.slice();
|
|
492
505
|
const nextLogContexts = liveLogContexts.slice();
|
|
493
506
|
const nextLogTotalCount = liveLogsTotalCount;
|
|
494
|
-
const nextDatasetLifecycleEvents =
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
507
|
+
const nextDatasetLifecycleEvents = includeProgress
|
|
508
|
+
? pendingDatasetLifecycleEvents
|
|
509
|
+
: [];
|
|
510
|
+
const nextActivityObservations = includeProgress
|
|
511
|
+
? [...pendingActivityObservations.values()]
|
|
512
|
+
: [];
|
|
499
513
|
const nextWorkCounters = workCountersDirty ? workCounters : undefined;
|
|
500
|
-
|
|
501
|
-
|
|
514
|
+
if (includeProgress) {
|
|
515
|
+
pendingDatasetLifecycleEvents = [];
|
|
516
|
+
pendingActivityObservations.clear();
|
|
517
|
+
liveNodeProgressDirty = false;
|
|
518
|
+
lastProgressFlushAt = Date.now();
|
|
519
|
+
}
|
|
502
520
|
liveLogsDirty = false;
|
|
503
521
|
workCountersDirty = false;
|
|
504
522
|
|
|
@@ -561,30 +579,31 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
561
579
|
pendingDatasetLifecycleEvents.length === 0 &&
|
|
562
580
|
pendingActivityObservations.size === 0
|
|
563
581
|
) {
|
|
564
|
-
if (
|
|
565
|
-
clearTimeout(
|
|
566
|
-
|
|
582
|
+
if (deferredFlushTimer) {
|
|
583
|
+
clearTimeout(deferredFlushTimer);
|
|
584
|
+
deferredFlushTimer = null;
|
|
567
585
|
}
|
|
568
586
|
return activeProgressUpload ?? Promise.resolve();
|
|
569
587
|
}
|
|
570
588
|
const now = Date.now();
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
589
|
+
const interval = liveLogsDirty
|
|
590
|
+
? 1_500
|
|
591
|
+
: getAdaptiveRunEventIntervalMs(totalRows);
|
|
592
|
+
const remaining = interval - (now - lastFlushAt);
|
|
593
|
+
if (!force && remaining > 0) {
|
|
594
|
+
// Quiet runs must flush too: waiting for another event can retain one
|
|
595
|
+
// customer log until a long-running operation finally completes.
|
|
596
|
+
if (deferredFlushTimer !== null) clearTimeout(deferredFlushTimer);
|
|
597
|
+
deferredFlushTimer = setTimeout(() => {
|
|
598
|
+
deferredFlushTimer = null;
|
|
599
|
+
void flush().catch(() => undefined);
|
|
600
|
+
}, remaining);
|
|
601
|
+
deferredFlushTimer.unref?.();
|
|
583
602
|
return activeProgressUpload ?? Promise.resolve();
|
|
584
603
|
}
|
|
585
|
-
if (
|
|
586
|
-
clearTimeout(
|
|
587
|
-
|
|
604
|
+
if (deferredFlushTimer !== null) {
|
|
605
|
+
clearTimeout(deferredFlushTimer);
|
|
606
|
+
deferredFlushTimer = null;
|
|
588
607
|
}
|
|
589
608
|
lastFlushAt = now;
|
|
590
609
|
const requested = new Promise<void>((resolve, reject) => {
|
|
@@ -592,6 +611,7 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
592
611
|
pendingProgressUpload,
|
|
593
612
|
{
|
|
594
613
|
status,
|
|
614
|
+
force,
|
|
595
615
|
required,
|
|
596
616
|
terminalLogReplay: input?.terminalLogReplay ?? null,
|
|
597
617
|
},
|
|
@@ -1411,7 +1431,8 @@ export function createPostgresSchedulerProgressReporter(input: {
|
|
|
1411
1431
|
): Promise<void> => {
|
|
1412
1432
|
const terminalCounters = runtimeWorkCountersFromStats(result?.stats);
|
|
1413
1433
|
const terminalSegment = result?.stats?.runtimeWorkCounterSegment as
|
|
1414
|
-
{ invocationSeq?: unknown; segmentId?: unknown }
|
|
1434
|
+
| { invocationSeq?: unknown; segmentId?: unknown }
|
|
1435
|
+
| undefined;
|
|
1415
1436
|
if (
|
|
1416
1437
|
terminalCounters &&
|
|
1417
1438
|
terminalCounters.provider429s !== undefined &&
|
|
@@ -307,7 +307,8 @@ const POSTGRES_SCHEDULER_SCHEMA_INIT_STATEMENT_TIMEOUT_MS = 60_000;
|
|
|
307
307
|
// v47 attaches the append-only runtime event projection to durable outbox and
|
|
308
308
|
// receipt transitions; no existing runtime wire field changes.
|
|
309
309
|
// v48 expands optional compact-log columns and readers before writer enablement.
|
|
310
|
-
|
|
310
|
+
// v49 installs transactional compact-log writers; only new admissions opt in.
|
|
311
|
+
export const POSTGRES_SCHEDULER_SCHEMA_VERSION = 49;
|
|
311
312
|
const POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE =
|
|
312
313
|
'scheduler_schema_migrations';
|
|
313
314
|
export const POSTGRES_SCHEDULER_OPEN_RECOVERY_INDEX =
|
|
@@ -938,6 +939,20 @@ function schemaName(
|
|
|
938
939
|
);
|
|
939
940
|
}
|
|
940
941
|
|
|
942
|
+
/** Only new immutable admissions opt into the fully installed recorder. */
|
|
943
|
+
function newRunLogStoreSql(
|
|
944
|
+
launchSql: 'launch_payload.launch_json',
|
|
945
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
946
|
+
): string {
|
|
947
|
+
return `CASE WHEN ${launchSql} ? 'playReference' AND EXISTS (
|
|
948
|
+
SELECT 1 FROM pg_proc AS proc
|
|
949
|
+
JOIN pg_namespace AS namespace ON namespace.oid = proc.pronamespace
|
|
950
|
+
WHERE namespace.nspname = '${schemaName(options).replace(/'/g, "''")}'
|
|
951
|
+
AND proc.proname = 'search_outbox_event'
|
|
952
|
+
AND obj_description(proc.oid, 'pg_proc') = '${RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER}'
|
|
953
|
+
) THEN 'planet_scale' END`;
|
|
954
|
+
}
|
|
955
|
+
|
|
941
956
|
function tableName(
|
|
942
957
|
table: string,
|
|
943
958
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
@@ -4925,7 +4940,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4925
4940
|
input_json,
|
|
4926
4941
|
status,
|
|
4927
4942
|
absurd_task_id,
|
|
4928
|
-
admission_json
|
|
4943
|
+
admission_json,
|
|
4944
|
+
run_log_store
|
|
4929
4945
|
)
|
|
4930
4946
|
SELECT $1, $2, $3, $4, $5, $6, $7, launch_payload.launch_json, coalesce(nullif(launch_payload.launch_json -> 'input', 'null'::jsonb), '{}'::jsonb), 'queued',
|
|
4931
4947
|
(SELECT task_id FROM task_spawn),
|
|
@@ -4937,7 +4953,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4937
4953
|
'runtimeDeployVersion',
|
|
4938
4954
|
'coordinatorInternalToken',
|
|
4939
4955
|
'vercelProtectionBypassToken'
|
|
4940
|
-
]::text[]
|
|
4956
|
+
]::text[],
|
|
4957
|
+
${newRunLogStoreSql('launch_payload.launch_json', options)}
|
|
4941
4958
|
FROM admission
|
|
4942
4959
|
CROSS JOIN launch_payload
|
|
4943
4960
|
WHERE NOT existing_run
|
|
@@ -4945,7 +4962,7 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4945
4962
|
AND parent_accepting
|
|
4946
4963
|
AND NOT ${existingOnlySql}
|
|
4947
4964
|
ON CONFLICT (run_id) DO NOTHING
|
|
4948
|
-
RETURNING run_id, absurd_task_id
|
|
4965
|
+
RETURNING run_id, absurd_task_id, run_log_store
|
|
4949
4966
|
), run_recovery AS (
|
|
4950
4967
|
UPDATE ${tableName('runs', options)} AS runs
|
|
4951
4968
|
SET absurd_task_id = (SELECT task_id FROM task_spawn),
|
|
@@ -4993,13 +5010,7 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4993
5010
|
'runtimeBackend', $6::text,
|
|
4994
5011
|
'schedulerBackend', launch_payload.launch_json ->> 'schedulerBackend',
|
|
4995
5012
|
'schedulerSchema', COALESCE(launch_payload.launch_json ->> 'schedulerSchema', '${schemaName(options)}'),
|
|
4996
|
-
'runLogStore',
|
|
4997
|
-
SELECT 1 FROM pg_proc AS proc
|
|
4998
|
-
JOIN pg_namespace AS namespace ON namespace.oid = proc.pronamespace
|
|
4999
|
-
WHERE namespace.nspname = '${schemaName(options)}'
|
|
5000
|
-
AND proc.proname = 'search_outbox_event'
|
|
5001
|
-
AND obj_description(proc.oid, 'pg_proc') = '${RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER}'
|
|
5002
|
-
) THEN 'planet_scale' END,
|
|
5013
|
+
'runLogStore', run_insert.run_log_store,
|
|
5003
5014
|
'executionProfile', launch_payload.launch_json ->> 'executionProfile',
|
|
5004
5015
|
'runtimeReleaseId', launch_payload.launch_json ->> 'runtimeReleaseId',
|
|
5005
5016
|
'runtimeDeployVersion', launch_payload.launch_json ->> 'runtimeDeployVersion',
|
|
@@ -6001,7 +6012,7 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6001
6012
|
INSERT INTO ${tableName('runs', options)} (
|
|
6002
6013
|
run_id, org_id, play_id, play_name, graph_hash, runtime_profile,
|
|
6003
6014
|
artifact_storage_key, launch_json, input_json, status,
|
|
6004
|
-
absurd_task_id, admission_json
|
|
6015
|
+
absurd_task_id, admission_json, run_log_store
|
|
6005
6016
|
)
|
|
6006
6017
|
SELECT $5::text,
|
|
6007
6018
|
launch_payload.launch_json ->> 'orgId',
|
|
@@ -6022,12 +6033,13 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6022
6033
|
'runtimeDeployVersion',
|
|
6023
6034
|
'coordinatorInternalToken',
|
|
6024
6035
|
'vercelProtectionBypassToken'
|
|
6025
|
-
]::text[]
|
|
6036
|
+
]::text[],
|
|
6037
|
+
${newRunLogStoreSql('launch_payload.launch_json', options)}
|
|
6026
6038
|
FROM occurrence
|
|
6027
6039
|
CROSS JOIN launch_payload
|
|
6028
6040
|
WHERE occurrence.schedule_current
|
|
6029
6041
|
AND NOT occurrence.existing_run
|
|
6030
|
-
RETURNING run_id
|
|
6042
|
+
RETURNING run_id, run_log_store
|
|
6031
6043
|
), occurrence_admitted AS MATERIALIZED (
|
|
6032
6044
|
SELECT true AS admitted
|
|
6033
6045
|
FROM occurrence
|
|
@@ -6074,13 +6086,7 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6074
6086
|
'runtimeBackend', launch_payload.launch_json ->> 'runtimeBackend',
|
|
6075
6087
|
'schedulerBackend', launch_payload.launch_json ->> 'schedulerBackend',
|
|
6076
6088
|
'schedulerSchema', COALESCE(launch_payload.launch_json ->> 'schedulerSchema', '${schemaName(options)}'),
|
|
6077
|
-
'runLogStore',
|
|
6078
|
-
SELECT 1 FROM pg_proc AS proc
|
|
6079
|
-
JOIN pg_namespace AS namespace ON namespace.oid = proc.pronamespace
|
|
6080
|
-
WHERE namespace.nspname = '${schemaName(options)}'
|
|
6081
|
-
AND proc.proname = 'search_outbox_event'
|
|
6082
|
-
AND obj_description(proc.oid, 'pg_proc') = '${RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER}'
|
|
6083
|
-
) THEN 'planet_scale' END,
|
|
6089
|
+
'runLogStore', run_insert.run_log_store,
|
|
6084
6090
|
'executionProfile', launch_payload.launch_json ->> 'executionProfile',
|
|
6085
6091
|
'runtimeReleaseId', launch_payload.launch_json ->> 'runtimeReleaseId',
|
|
6086
6092
|
'runtimeDeployVersion', launch_payload.launch_json ->> 'runtimeDeployVersion',
|
|
@@ -9718,16 +9724,29 @@ export async function settlePostgresSchedulerRuntimeResources(
|
|
|
9718
9724
|
*/
|
|
9719
9725
|
export async function recordPostgresSchedulerRunnerTerminal(
|
|
9720
9726
|
client: PostgresSchedulerQueryClient,
|
|
9721
|
-
input: {
|
|
9727
|
+
input: {
|
|
9728
|
+
runId: string;
|
|
9729
|
+
attempt: number;
|
|
9730
|
+
result: unknown;
|
|
9731
|
+
terminalProgressEvents?: readonly unknown[];
|
|
9732
|
+
},
|
|
9722
9733
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
9723
9734
|
): Promise<boolean> {
|
|
9724
9735
|
const recorded = await client.query<{ run_id: string }>(
|
|
9725
9736
|
`
|
|
9726
9737
|
WITH accepted AS MATERIALIZED (
|
|
9738
|
+
-- Match direct completion's Run -> attempt lock order. The log
|
|
9739
|
+
-- acceptance trigger also locks the Run for sequence assignment.
|
|
9727
9740
|
SELECT clock_timestamp() AS accepted_at
|
|
9728
|
-
|
|
9741
|
+
FROM ${tableName('runs', options)}
|
|
9742
|
+
WHERE run_id = $1
|
|
9743
|
+
)
|
|
9729
9744
|
UPDATE ${tableName('attempts', options)} AS target
|
|
9730
9745
|
SET runner_result_json = COALESCE(target.runner_result_json, $3::jsonb),
|
|
9746
|
+
runner_log_payload_json = CASE WHEN EXISTS (
|
|
9747
|
+
SELECT 1 FROM ${tableName('runs', options)} AS log_run
|
|
9748
|
+
WHERE log_run.run_id = target.run_id AND log_run.run_log_store = 'planet_scale'
|
|
9749
|
+
) THEN $4::jsonb ELSE NULL END,
|
|
9731
9750
|
runner_terminal_received_at = COALESCE(
|
|
9732
9751
|
target.runner_terminal_received_at,
|
|
9733
9752
|
accepted.accepted_at
|
|
@@ -9785,7 +9804,12 @@ export async function recordPostgresSchedulerRunnerTerminal(
|
|
|
9785
9804
|
AND target.attempt = $2
|
|
9786
9805
|
RETURNING target.run_id
|
|
9787
9806
|
`,
|
|
9788
|
-
[
|
|
9807
|
+
[
|
|
9808
|
+
input.runId,
|
|
9809
|
+
input.attempt,
|
|
9810
|
+
stringifyPostgresJson(input.result ?? null),
|
|
9811
|
+
stringifyPostgresJson(runnerTerminalLogPayload(input)),
|
|
9812
|
+
],
|
|
9789
9813
|
);
|
|
9790
9814
|
return recorded.rows.length > 0;
|
|
9791
9815
|
}
|
|
@@ -9919,6 +9943,10 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
9919
9943
|
SET status = 'completed',
|
|
9920
9944
|
terminalization_mode = 'direct',
|
|
9921
9945
|
runner_result_json = COALESCE(target.runner_result_json, $3::jsonb),
|
|
9946
|
+
runner_log_payload_json = CASE WHEN EXISTS (
|
|
9947
|
+
SELECT 1 FROM ${tableName('runs', options)} AS log_run
|
|
9948
|
+
WHERE log_run.run_id = target.run_id AND log_run.run_log_store = 'planet_scale'
|
|
9949
|
+
) THEN $19::jsonb ELSE NULL END,
|
|
9922
9950
|
runner_terminal_received_at = COALESCE(
|
|
9923
9951
|
target.runner_terminal_received_at,
|
|
9924
9952
|
run.accepted_at
|
|
@@ -9993,7 +10021,8 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
9993
10021
|
target.run_id,
|
|
9994
10022
|
target.attempt,
|
|
9995
10023
|
target.runner_terminal_received_at,
|
|
9996
|
-
target.resource_json
|
|
10024
|
+
target.resource_json,
|
|
10025
|
+
target.runner_log_payload_json
|
|
9997
10026
|
),
|
|
9998
10027
|
settled_attempt_resources AS (
|
|
9999
10028
|
UPDATE ${tableName('attempts', options)} AS attempt_resources
|
|
@@ -10139,9 +10168,14 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
10139
10168
|
'run.completed',
|
|
10140
10169
|
jsonb_build_object(
|
|
10141
10170
|
'output', $6::jsonb,
|
|
10171
|
+
'producerAttempt', $2::integer,
|
|
10142
10172
|
'terminalLogLines', $7::jsonb,
|
|
10143
10173
|
'terminalLogContexts', $17::jsonb,
|
|
10144
|
-
'terminalProgressEvents',
|
|
10174
|
+
'terminalProgressEvents', coalesce((
|
|
10175
|
+
SELECT accepted.runner_log_payload_json->'terminalProgressEvents'
|
|
10176
|
+
FROM completed_attempt AS accepted
|
|
10177
|
+
WHERE accepted.run_id = run_terminal.run_id
|
|
10178
|
+
), $8::jsonb),
|
|
10145
10179
|
'resultSummary', $16::jsonb,
|
|
10146
10180
|
'terminalWorkCounterSegment', $18::jsonb
|
|
10147
10181
|
),
|
|
@@ -10180,6 +10214,7 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
10180
10214
|
stringifyPostgresJson(resultSummary),
|
|
10181
10215
|
JSON.stringify(terminalLogProjection.lineContexts),
|
|
10182
10216
|
stringifyPostgresJson(terminalWorkCounterSegment ?? null),
|
|
10217
|
+
stringifyPostgresJson(runnerTerminalLogPayload(input)),
|
|
10183
10218
|
],
|
|
10184
10219
|
);
|
|
10185
10220
|
if (completed.rows.length > 0) return 'completed';
|
|
@@ -12539,6 +12574,7 @@ export async function completePostgresSchedulerAttempt(
|
|
|
12539
12574
|
'run.completed',
|
|
12540
12575
|
jsonb_build_object(
|
|
12541
12576
|
'output', $9::jsonb,
|
|
12577
|
+
'producerAttempt', $2::integer,
|
|
12542
12578
|
'terminalLogLines', $8::jsonb,
|
|
12543
12579
|
'terminalLogContexts', $11::jsonb,
|
|
12544
12580
|
'resultSummary', $10::jsonb,
|
|
@@ -12872,6 +12908,7 @@ export async function failPostgresSchedulerAttempt(
|
|
|
12872
12908
|
jsonb_build_object(
|
|
12873
12909
|
'error', $4::jsonb ->> 'message',
|
|
12874
12910
|
'result', $8::jsonb,
|
|
12911
|
+
'producerAttempt', $2::integer,
|
|
12875
12912
|
'terminalLogLines', $12::jsonb,
|
|
12876
12913
|
'terminalLogContexts', $14::jsonb,
|
|
12877
12914
|
'resultSummary', $13::jsonb,
|
|
@@ -13589,6 +13626,37 @@ function boundedRunLogTailMetadata(logs: string[]): {
|
|
|
13589
13626
|
return tail.length > 0 ? { tail, totalLogCount: logs.length } : null;
|
|
13590
13627
|
}
|
|
13591
13628
|
|
|
13629
|
+
/** Full carrier exists only in the statement that consumes its observations. */
|
|
13630
|
+
function runnerTerminalLogPayload(input: {
|
|
13631
|
+
attempt: number;
|
|
13632
|
+
result: unknown;
|
|
13633
|
+
terminalProgressEvents?: readonly unknown[];
|
|
13634
|
+
}): Record<string, unknown> {
|
|
13635
|
+
const projection = terminalLogProjectionForOutbox(
|
|
13636
|
+
input.result,
|
|
13637
|
+
'ctx_log_tail',
|
|
13638
|
+
);
|
|
13639
|
+
return {
|
|
13640
|
+
producerAttempt: input.attempt,
|
|
13641
|
+
terminalLogLines: projection.lines,
|
|
13642
|
+
terminalLogContexts: projection.lineContexts,
|
|
13643
|
+
terminalProgressEvents: input.terminalProgressEvents ?? [],
|
|
13644
|
+
// Older runner envelopes conveyed these diagnostics only in tagged log
|
|
13645
|
+
// text. Preserve their small structured facts before removing that text.
|
|
13646
|
+
recoveredRuntimeDiagnostics:
|
|
13647
|
+
input.result &&
|
|
13648
|
+
typeof input.result === 'object' &&
|
|
13649
|
+
'logs' in input.result &&
|
|
13650
|
+
Array.isArray(input.result.logs)
|
|
13651
|
+
? input.result.logs
|
|
13652
|
+
.filter((line): line is string => typeof line === 'string')
|
|
13653
|
+
.map(parseCtxFetchHttpFailureDiagnostic)
|
|
13654
|
+
.filter((diagnostic) => diagnostic !== null)
|
|
13655
|
+
.slice(0, 16)
|
|
13656
|
+
: [],
|
|
13657
|
+
};
|
|
13658
|
+
}
|
|
13659
|
+
|
|
13592
13660
|
function terminalLogProjectionForOutbox(
|
|
13593
13661
|
runnerResult: unknown,
|
|
13594
13662
|
mode: PostgresSchedulerTerminalLogOutbox | undefined,
|