deepline 0.3.247 → 0.3.249
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/client.ts +177 -13
- package/dist/bundling-sources/sdk/src/index.ts +8 -0
- package/dist/bundling-sources/sdk/src/plays/input-example.ts +40 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/runs/overview.ts +457 -0
- package/dist/bundling-sources/sdk/src/runs/receipts.ts +23 -0
- package/dist/bundling-sources/sdk/src/tool-calls.ts +21 -1
- package/dist/bundling-sources/sdk/src/types.ts +16 -1
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +19 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +17 -3
- package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -8
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +26 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +144 -14
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +2 -1
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-runtime-config.ts +6 -3
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +26 -21
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +23 -21
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/log-context.ts +105 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +8 -1
- 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/protocol.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +40 -4
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +50 -5
- package/dist/bundling-sources/shared_libs/play-runtime/run-lifecycle-policy.ts +3 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +14 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +56 -16
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +327 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +87 -30
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +24 -13
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-start-admission.ts +23 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +76 -33
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +90 -35
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +112 -50
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +447 -77
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +22 -4
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +105 -121
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +1648 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-queue-health.ts +16 -19
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +14 -6
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +40 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +5 -2
- package/dist/bundling-sources/shared_libs/plays/dataset-summary.ts +11 -7
- package/dist/bundling-sources/shared_libs/plays/static-pipeline-storage-codec.ts +5 -2
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +11 -2
- package/dist/cli/index.js +3504 -2368
- package/dist/cli/index.mjs +3188 -2052
- package/dist/{compiler-manifest-n8ZtPv2h.d.mts → compiler-manifest-DavmLa8y.d.mts} +1 -0
- package/dist/{compiler-manifest-n8ZtPv2h.d.ts → compiler-manifest-DavmLa8y.d.ts} +1 -0
- package/dist/index.d.mts +97 -5
- package/dist/index.d.ts +97 -5
- package/dist/index.js +135 -15
- package/dist/index.mjs +135 -15
- package/dist/install-integrity.json +9 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +1 -1
- 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
|
@@ -25,6 +25,7 @@ import type {
|
|
|
25
25
|
PlayRunnerRuntimeTiming,
|
|
26
26
|
} from '../protocol';
|
|
27
27
|
import { PROD_DEFAULT_RUNTIME_SCHEDULER_SCHEMA } from '../runtime-scheduler-topology';
|
|
28
|
+
import { RUNTIME_EVENT_SEARCH_OUTBOX_PROJECTION_MARKER } from './runtime-event-search';
|
|
28
29
|
import type { PlayRunnerRuntimeResource } from '../runner-backends/types';
|
|
29
30
|
import {
|
|
30
31
|
RuntimeSandboxOperationState,
|
|
@@ -76,6 +77,10 @@ import {
|
|
|
76
77
|
type DocflowObservationUpsert,
|
|
77
78
|
} from '../docflow-observation';
|
|
78
79
|
import { normalizePlayDocflowNodeIoState } from '../docflow-node-io';
|
|
80
|
+
import {
|
|
81
|
+
normalizePlayLogContext,
|
|
82
|
+
type PlayLogContext,
|
|
83
|
+
} from '../log-context';
|
|
79
84
|
import {
|
|
80
85
|
workReceiptFailureKindCodeForWrite,
|
|
81
86
|
workReceiptFailureKindFromCode,
|
|
@@ -299,7 +304,11 @@ const POSTGRES_SCHEDULER_SCHEMA_INIT_STATEMENT_TIMEOUT_MS = 60_000;
|
|
|
299
304
|
// v46 adds a bounded provider-scope Retry-After hold with shared adaptive-rate
|
|
300
305
|
// state. The scope owns learning and dispatch across runs and tool rules;
|
|
301
306
|
// existing deadline rows expand additively and initialize on their first seed.
|
|
302
|
-
|
|
307
|
+
// v47 attaches the append-only runtime event projection to durable outbox and
|
|
308
|
+
// receipt transitions; no existing runtime wire field changes.
|
|
309
|
+
// v48 expands optional compact-log columns and readers before writer enablement.
|
|
310
|
+
// v49 installs transactional compact-log writers; only new admissions opt in.
|
|
311
|
+
export const POSTGRES_SCHEDULER_SCHEMA_VERSION = 49;
|
|
303
312
|
const POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE =
|
|
304
313
|
'scheduler_schema_migrations';
|
|
305
314
|
export const POSTGRES_SCHEDULER_OPEN_RECOVERY_INDEX =
|
|
@@ -885,6 +894,8 @@ const OUTBOX_RETRY_MAX_MS = 15 * 60 * 1_000;
|
|
|
885
894
|
// diagnostic window, then delete in small, independently committed batches.
|
|
886
895
|
export const POSTGRES_SCHEDULER_OUTBOX_DELIVERED_RETENTION_MS =
|
|
887
896
|
30 * 24 * 60 * 60 * 1_000;
|
|
897
|
+
export const POSTGRES_SCHEDULER_OUTBOX_LOG_BODY_RETENTION_MS =
|
|
898
|
+
7 * 24 * 60 * 60 * 1_000;
|
|
888
899
|
export const POSTGRES_SCHEDULER_OUTBOX_DELIVERED_RETENTION_BATCH_LIMIT = 1_000;
|
|
889
900
|
const RUN_LEDGER_DISPATCH_TOKEN_TIMEOUT_MS = 15_000;
|
|
890
901
|
const COMPUTE_BILLING_DISPATCH_TOKEN_TIMEOUT_MS = 15_000;
|
|
@@ -928,6 +939,20 @@ function schemaName(
|
|
|
928
939
|
);
|
|
929
940
|
}
|
|
930
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
|
+
|
|
931
956
|
function tableName(
|
|
932
957
|
table: string,
|
|
933
958
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
@@ -1101,6 +1126,8 @@ export type PostgresSchedulerSchemaShapeRequirements = Readonly<{
|
|
|
1101
1126
|
rateRequesterFairShare?: boolean;
|
|
1102
1127
|
/** v45 added durable Retry-After holds shared across rules and runs. */
|
|
1103
1128
|
rateScopeRetryAfter?: boolean;
|
|
1129
|
+
/** v48 expands compact log storage; prior readers ignore these columns. */
|
|
1130
|
+
runtimeLogStorage?: boolean;
|
|
1104
1131
|
}>;
|
|
1105
1132
|
|
|
1106
1133
|
export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
@@ -1718,6 +1745,22 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1718
1745
|
AND index_row.indisvalid
|
|
1719
1746
|
)
|
|
1720
1747
|
)
|
|
1748
|
+
AND (
|
|
1749
|
+
$38::boolean = false
|
|
1750
|
+
OR (
|
|
1751
|
+
EXISTS (
|
|
1752
|
+
SELECT 1 FROM information_schema.columns
|
|
1753
|
+
WHERE table_schema = $1 AND table_name = 'runs'
|
|
1754
|
+
AND column_name = 'run_log_store' AND is_nullable = 'YES'
|
|
1755
|
+
)
|
|
1756
|
+
AND (
|
|
1757
|
+
SELECT count(*) FROM information_schema.columns
|
|
1758
|
+
WHERE table_schema = $1 AND table_name = 'attempts'
|
|
1759
|
+
AND column_name IN ('runner_result_digest', 'runner_log_payload_json')
|
|
1760
|
+
AND is_nullable = 'YES'
|
|
1761
|
+
) = 2
|
|
1762
|
+
)
|
|
1763
|
+
)
|
|
1721
1764
|
AS ready
|
|
1722
1765
|
`,
|
|
1723
1766
|
[
|
|
@@ -1758,6 +1801,7 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1758
1801
|
`${quotedSchema}.${quoteIdent('rate_requesters')}`,
|
|
1759
1802
|
requirements.rateScopeRetryAfter !== false,
|
|
1760
1803
|
`${quotedSchema}.${quoteIdent('rate_scope_holds')}`,
|
|
1804
|
+
requirements.runtimeLogStorage !== false,
|
|
1761
1805
|
],
|
|
1762
1806
|
);
|
|
1763
1807
|
if (currentShape.rows[0]?.ready !== true) return false;
|
|
@@ -1980,6 +2024,19 @@ export async function ensurePostgresSchedulerOnlineAdditiveExpansion(
|
|
|
1980
2024
|
options,
|
|
1981
2025
|
)} (state, updated_at)
|
|
1982
2026
|
`);
|
|
2027
|
+
await ensurePostgresSchedulerRunLogStorageExpansion(client, options);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
/** Deployment-only expansion; absent values keep legacy storage authoritative. */
|
|
2031
|
+
async function ensurePostgresSchedulerRunLogStorageExpansion(
|
|
2032
|
+
client: PostgresSchedulerQueryClient,
|
|
2033
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
2034
|
+
): Promise<void> {
|
|
2035
|
+
await client.query(`ALTER TABLE ${tableName('runs', options)}
|
|
2036
|
+
ADD COLUMN IF NOT EXISTS run_log_store text`);
|
|
2037
|
+
await client.query(`ALTER TABLE ${tableName('attempts', options)}
|
|
2038
|
+
ADD COLUMN IF NOT EXISTS runner_result_digest text,
|
|
2039
|
+
ADD COLUMN IF NOT EXISTS runner_log_payload_json jsonb`);
|
|
1983
2040
|
}
|
|
1984
2041
|
|
|
1985
2042
|
/** Read the highest deployment-owned scheduler schema marker. */
|
|
@@ -2546,6 +2603,7 @@ export async function ensurePostgresSchedulerSchema(
|
|
|
2546
2603
|
ALTER TABLE ${tableName('attempts', options)}
|
|
2547
2604
|
ADD COLUMN IF NOT EXISTS runner_terminal_received_at timestamptz
|
|
2548
2605
|
`);
|
|
2606
|
+
await ensurePostgresSchedulerRunLogStorageExpansion(client, options);
|
|
2549
2607
|
// Authoritative provenance for terminal reporting. Do not infer this from
|
|
2550
2608
|
// the completed/cleanup state shape: direct ingress and compatibility wake
|
|
2551
2609
|
// can both produce those rows.
|
|
@@ -4882,7 +4940,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4882
4940
|
input_json,
|
|
4883
4941
|
status,
|
|
4884
4942
|
absurd_task_id,
|
|
4885
|
-
admission_json
|
|
4943
|
+
admission_json,
|
|
4944
|
+
run_log_store
|
|
4886
4945
|
)
|
|
4887
4946
|
SELECT $1, $2, $3, $4, $5, $6, $7, launch_payload.launch_json, coalesce(nullif(launch_payload.launch_json -> 'input', 'null'::jsonb), '{}'::jsonb), 'queued',
|
|
4888
4947
|
(SELECT task_id FROM task_spawn),
|
|
@@ -4894,7 +4953,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4894
4953
|
'runtimeDeployVersion',
|
|
4895
4954
|
'coordinatorInternalToken',
|
|
4896
4955
|
'vercelProtectionBypassToken'
|
|
4897
|
-
]::text[]
|
|
4956
|
+
]::text[],
|
|
4957
|
+
${newRunLogStoreSql('launch_payload.launch_json', options)}
|
|
4898
4958
|
FROM admission
|
|
4899
4959
|
CROSS JOIN launch_payload
|
|
4900
4960
|
WHERE NOT existing_run
|
|
@@ -4902,7 +4962,7 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4902
4962
|
AND parent_accepting
|
|
4903
4963
|
AND NOT ${existingOnlySql}
|
|
4904
4964
|
ON CONFLICT (run_id) DO NOTHING
|
|
4905
|
-
RETURNING run_id, absurd_task_id
|
|
4965
|
+
RETURNING run_id, absurd_task_id, run_log_store
|
|
4906
4966
|
), run_recovery AS (
|
|
4907
4967
|
UPDATE ${tableName('runs', options)} AS runs
|
|
4908
4968
|
SET absurd_task_id = (SELECT task_id FROM task_spawn),
|
|
@@ -4949,7 +5009,8 @@ async function submitPostgresSchedulerRunTransaction(
|
|
|
4949
5009
|
'graphHash', $5::text,
|
|
4950
5010
|
'runtimeBackend', $6::text,
|
|
4951
5011
|
'schedulerBackend', launch_payload.launch_json ->> 'schedulerBackend',
|
|
4952
|
-
'schedulerSchema', launch_payload.launch_json ->> 'schedulerSchema',
|
|
5012
|
+
'schedulerSchema', COALESCE(launch_payload.launch_json ->> 'schedulerSchema', '${schemaName(options)}'),
|
|
5013
|
+
'runLogStore', run_insert.run_log_store,
|
|
4953
5014
|
'executionProfile', launch_payload.launch_json ->> 'executionProfile',
|
|
4954
5015
|
'runtimeReleaseId', launch_payload.launch_json ->> 'runtimeReleaseId',
|
|
4955
5016
|
'runtimeDeployVersion', launch_payload.launch_json ->> 'runtimeDeployVersion',
|
|
@@ -5951,7 +6012,7 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
5951
6012
|
INSERT INTO ${tableName('runs', options)} (
|
|
5952
6013
|
run_id, org_id, play_id, play_name, graph_hash, runtime_profile,
|
|
5953
6014
|
artifact_storage_key, launch_json, input_json, status,
|
|
5954
|
-
absurd_task_id, admission_json
|
|
6015
|
+
absurd_task_id, admission_json, run_log_store
|
|
5955
6016
|
)
|
|
5956
6017
|
SELECT $5::text,
|
|
5957
6018
|
launch_payload.launch_json ->> 'orgId',
|
|
@@ -5972,12 +6033,13 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
5972
6033
|
'runtimeDeployVersion',
|
|
5973
6034
|
'coordinatorInternalToken',
|
|
5974
6035
|
'vercelProtectionBypassToken'
|
|
5975
|
-
]::text[]
|
|
6036
|
+
]::text[],
|
|
6037
|
+
${newRunLogStoreSql('launch_payload.launch_json', options)}
|
|
5976
6038
|
FROM occurrence
|
|
5977
6039
|
CROSS JOIN launch_payload
|
|
5978
6040
|
WHERE occurrence.schedule_current
|
|
5979
6041
|
AND NOT occurrence.existing_run
|
|
5980
|
-
RETURNING run_id
|
|
6042
|
+
RETURNING run_id, run_log_store
|
|
5981
6043
|
), occurrence_admitted AS MATERIALIZED (
|
|
5982
6044
|
SELECT true AS admitted
|
|
5983
6045
|
FROM occurrence
|
|
@@ -6023,7 +6085,8 @@ export async function firePostgresSchedulerCronSchedule(
|
|
|
6023
6085
|
'graphHash', launch_payload.launch_json ->> 'graphHash',
|
|
6024
6086
|
'runtimeBackend', launch_payload.launch_json ->> 'runtimeBackend',
|
|
6025
6087
|
'schedulerBackend', launch_payload.launch_json ->> 'schedulerBackend',
|
|
6026
|
-
'schedulerSchema', launch_payload.launch_json ->> 'schedulerSchema',
|
|
6088
|
+
'schedulerSchema', COALESCE(launch_payload.launch_json ->> 'schedulerSchema', '${schemaName(options)}'),
|
|
6089
|
+
'runLogStore', run_insert.run_log_store,
|
|
6027
6090
|
'executionProfile', launch_payload.launch_json ->> 'executionProfile',
|
|
6028
6091
|
'runtimeReleaseId', launch_payload.launch_json ->> 'runtimeReleaseId',
|
|
6029
6092
|
'runtimeDeployVersion', launch_payload.launch_json ->> 'runtimeDeployVersion',
|
|
@@ -8472,6 +8535,36 @@ export type PostgresSchedulerAttemptAuthority = Pick<
|
|
|
8472
8535
|
(typeof POSTGRES_SCHEDULER_ATTEMPT_AUTHORITY_FIELDS)[number]
|
|
8473
8536
|
>;
|
|
8474
8537
|
|
|
8538
|
+
type PostgresSchedulerAuthorityRow = PostgresSchedulerAttemptAuthority & {
|
|
8539
|
+
launch_required_fields_valid: boolean;
|
|
8540
|
+
};
|
|
8541
|
+
|
|
8542
|
+
// Keep JSON types intact: text columns would coerce malformed launch scalars.
|
|
8543
|
+
const POSTGRES_SCHEDULER_AUTHORITY_COLUMNS = [
|
|
8544
|
+
...POSTGRES_SCHEDULER_ATTEMPT_AUTHORITY_FIELDS,
|
|
8545
|
+
'playId',
|
|
8546
|
+
'executorToken',
|
|
8547
|
+
]
|
|
8548
|
+
.map((field) => `${quoteIdent(field)} jsonb`)
|
|
8549
|
+
.join(', ');
|
|
8550
|
+
const POSTGRES_SCHEDULER_AUTHORITY_SELECT = `${POSTGRES_SCHEDULER_ATTEMPT_AUTHORITY_FIELDS.map((field) => `launch.${quoteIdent(field)}`).join(', ')},
|
|
8551
|
+
${['runId', 'playId', 'playName', 'executorToken', 'baseUrl']
|
|
8552
|
+
.map((field) => `jsonb_typeof(launch.${quoteIdent(field)}) = 'string'`)
|
|
8553
|
+
.join(' AND ')} AS launch_required_fields_valid`;
|
|
8554
|
+
|
|
8555
|
+
function parsePostgresSchedulerAuthorityRow(
|
|
8556
|
+
row: PostgresSchedulerAuthorityRow | undefined,
|
|
8557
|
+
): PostgresSchedulerAttemptAuthority | null {
|
|
8558
|
+
if (!row) return null;
|
|
8559
|
+
const { launch_required_fields_valid, ...authority } = row;
|
|
8560
|
+
if (launch_required_fields_valid !== true) {
|
|
8561
|
+
throw new Error(
|
|
8562
|
+
'Postgres scheduler launch_json is missing required fields.',
|
|
8563
|
+
);
|
|
8564
|
+
}
|
|
8565
|
+
return authority;
|
|
8566
|
+
}
|
|
8567
|
+
|
|
8475
8568
|
/**
|
|
8476
8569
|
* Prove the same attempt lease as the full execution reader, returning only
|
|
8477
8570
|
* the token issuer's authority. Input, graph, and artifact bytes never cross
|
|
@@ -8484,29 +8577,13 @@ export async function readPostgresSchedulerAttemptAuthorityForLease(
|
|
|
8484
8577
|
input: { runId: string; attempt: number; leaseToken: string },
|
|
8485
8578
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
8486
8579
|
): Promise<PostgresSchedulerAttemptAuthority | null> {
|
|
8487
|
-
const
|
|
8488
|
-
// Keep JSON types intact. A text record column would coerce malformed launch
|
|
8489
|
-
// scalars into strings, bypassing the full reader's required-field checks.
|
|
8490
|
-
const columns = [...fields, 'playId', 'executorToken']
|
|
8491
|
-
.map((field) => `${quoteIdent(field)} jsonb`)
|
|
8492
|
-
.join(', ');
|
|
8493
|
-
const result = await client.query<
|
|
8494
|
-
PostgresSchedulerAttemptAuthority & {
|
|
8495
|
-
launch_required_fields_valid: boolean;
|
|
8496
|
-
}
|
|
8497
|
-
>(
|
|
8580
|
+
const result = await client.query<PostgresSchedulerAuthorityRow>(
|
|
8498
8581
|
`
|
|
8499
|
-
SELECT ${
|
|
8500
|
-
${['runId', 'playId', 'playName', 'executorToken', 'baseUrl']
|
|
8501
|
-
.map(
|
|
8502
|
-
(field) =>
|
|
8503
|
-
`jsonb_typeof(launch.${quoteIdent(field)}) = 'string'`,
|
|
8504
|
-
)
|
|
8505
|
-
.join(' AND ')} AS launch_required_fields_valid
|
|
8582
|
+
SELECT ${POSTGRES_SCHEDULER_AUTHORITY_SELECT}
|
|
8506
8583
|
FROM ${tableName('runs', options)} AS runs
|
|
8507
8584
|
JOIN ${tableName('attempts', options)} AS attempts
|
|
8508
8585
|
ON attempts.run_id = runs.run_id
|
|
8509
|
-
CROSS JOIN LATERAL jsonb_to_record(runs.launch_json) AS launch(${
|
|
8586
|
+
CROSS JOIN LATERAL jsonb_to_record(runs.launch_json) AS launch(${POSTGRES_SCHEDULER_AUTHORITY_COLUMNS})
|
|
8510
8587
|
WHERE runs.run_id = $1
|
|
8511
8588
|
AND runs.status IN ('queued', 'running', 'waiting')
|
|
8512
8589
|
AND attempts.attempt = $2
|
|
@@ -8516,15 +8593,7 @@ export async function readPostgresSchedulerAttemptAuthorityForLease(
|
|
|
8516
8593
|
`,
|
|
8517
8594
|
[input.runId, input.attempt, input.leaseToken],
|
|
8518
8595
|
);
|
|
8519
|
-
|
|
8520
|
-
if (!row) return null;
|
|
8521
|
-
const { launch_required_fields_valid, ...authority } = row;
|
|
8522
|
-
if (launch_required_fields_valid !== true) {
|
|
8523
|
-
throw new Error(
|
|
8524
|
-
'Postgres scheduler launch_json is missing required fields.',
|
|
8525
|
-
);
|
|
8526
|
-
}
|
|
8527
|
-
return authority;
|
|
8596
|
+
return parsePostgresSchedulerAuthorityRow(result.rows[0]);
|
|
8528
8597
|
}
|
|
8529
8598
|
|
|
8530
8599
|
/**
|
|
@@ -9655,16 +9724,29 @@ export async function settlePostgresSchedulerRuntimeResources(
|
|
|
9655
9724
|
*/
|
|
9656
9725
|
export async function recordPostgresSchedulerRunnerTerminal(
|
|
9657
9726
|
client: PostgresSchedulerQueryClient,
|
|
9658
|
-
input: {
|
|
9727
|
+
input: {
|
|
9728
|
+
runId: string;
|
|
9729
|
+
attempt: number;
|
|
9730
|
+
result: unknown;
|
|
9731
|
+
terminalProgressEvents?: readonly unknown[];
|
|
9732
|
+
},
|
|
9659
9733
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
9660
9734
|
): Promise<boolean> {
|
|
9661
9735
|
const recorded = await client.query<{ run_id: string }>(
|
|
9662
9736
|
`
|
|
9663
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.
|
|
9664
9740
|
SELECT clock_timestamp() AS accepted_at
|
|
9665
|
-
|
|
9741
|
+
FROM ${tableName('runs', options)}
|
|
9742
|
+
WHERE run_id = $1
|
|
9743
|
+
)
|
|
9666
9744
|
UPDATE ${tableName('attempts', options)} AS target
|
|
9667
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,
|
|
9668
9750
|
runner_terminal_received_at = COALESCE(
|
|
9669
9751
|
target.runner_terminal_received_at,
|
|
9670
9752
|
accepted.accepted_at
|
|
@@ -9722,7 +9804,12 @@ export async function recordPostgresSchedulerRunnerTerminal(
|
|
|
9722
9804
|
AND target.attempt = $2
|
|
9723
9805
|
RETURNING target.run_id
|
|
9724
9806
|
`,
|
|
9725
|
-
[
|
|
9807
|
+
[
|
|
9808
|
+
input.runId,
|
|
9809
|
+
input.attempt,
|
|
9810
|
+
stringifyPostgresJson(input.result ?? null),
|
|
9811
|
+
stringifyPostgresJson(runnerTerminalLogPayload(input)),
|
|
9812
|
+
],
|
|
9726
9813
|
);
|
|
9727
9814
|
return recorded.rows.length > 0;
|
|
9728
9815
|
}
|
|
@@ -9817,10 +9904,11 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
9817
9904
|
const ledgerTerminalResult = terminalRunResultForLedger(terminalResult, {
|
|
9818
9905
|
content: 'full',
|
|
9819
9906
|
});
|
|
9820
|
-
const
|
|
9907
|
+
const terminalLogProjection = terminalLogProjectionForOutbox(
|
|
9821
9908
|
input.result,
|
|
9822
9909
|
input.terminalLogOutbox,
|
|
9823
9910
|
);
|
|
9911
|
+
const terminalLogLines = terminalLogProjection.lines;
|
|
9824
9912
|
const runtimeTiming = extractRunnerRuntimeTiming(input.result);
|
|
9825
9913
|
const terminalWorkCounterSegment = terminalWorkCounterSegmentFromRunnerResult(
|
|
9826
9914
|
input.result,
|
|
@@ -9855,6 +9943,10 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
9855
9943
|
SET status = 'completed',
|
|
9856
9944
|
terminalization_mode = 'direct',
|
|
9857
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,
|
|
9858
9950
|
runner_terminal_received_at = COALESCE(
|
|
9859
9951
|
target.runner_terminal_received_at,
|
|
9860
9952
|
run.accepted_at
|
|
@@ -9910,7 +10002,13 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
9910
10002
|
AND target.attempt = $2
|
|
9911
10003
|
AND target.status IN ('running', 'waiting')
|
|
9912
10004
|
AND target.lease_token = $11
|
|
9913
|
-
AND (
|
|
10005
|
+
AND (
|
|
10006
|
+
target.runner_result_json IS NULL
|
|
10007
|
+
OR target.runner_result_json = $3::jsonb
|
|
10008
|
+
OR target.runner_result_digest = encode(
|
|
10009
|
+
sha256(convert_to(($3::jsonb)::text, 'UTF8')), 'hex'
|
|
10010
|
+
)
|
|
10011
|
+
)
|
|
9914
10012
|
AND run.run_id = target.run_id
|
|
9915
10013
|
AND NOT EXISTS (
|
|
9916
10014
|
SELECT 1
|
|
@@ -9923,7 +10021,8 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
9923
10021
|
target.run_id,
|
|
9924
10022
|
target.attempt,
|
|
9925
10023
|
target.runner_terminal_received_at,
|
|
9926
|
-
target.resource_json
|
|
10024
|
+
target.resource_json,
|
|
10025
|
+
target.runner_log_payload_json
|
|
9927
10026
|
),
|
|
9928
10027
|
settled_attempt_resources AS (
|
|
9929
10028
|
UPDATE ${tableName('attempts', options)} AS attempt_resources
|
|
@@ -10069,10 +10168,16 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
10069
10168
|
'run.completed',
|
|
10070
10169
|
jsonb_build_object(
|
|
10071
10170
|
'output', $6::jsonb,
|
|
10171
|
+
'producerAttempt', $2::integer,
|
|
10072
10172
|
'terminalLogLines', $7::jsonb,
|
|
10073
|
-
'
|
|
10173
|
+
'terminalLogContexts', $17::jsonb,
|
|
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),
|
|
10074
10179
|
'resultSummary', $16::jsonb,
|
|
10075
|
-
'terminalWorkCounterSegment', $
|
|
10180
|
+
'terminalWorkCounterSegment', $18::jsonb
|
|
10076
10181
|
),
|
|
10077
10182
|
${causalOutboxCreatedAt('run_terminal.run_id', options)}
|
|
10078
10183
|
FROM run_terminal
|
|
@@ -10107,7 +10212,9 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
10107
10212
|
input.engineWake.wakeChannel,
|
|
10108
10213
|
input.allowCappedCompletion === true,
|
|
10109
10214
|
stringifyPostgresJson(resultSummary),
|
|
10215
|
+
JSON.stringify(terminalLogProjection.lineContexts),
|
|
10110
10216
|
stringifyPostgresJson(terminalWorkCounterSegment ?? null),
|
|
10217
|
+
stringifyPostgresJson(runnerTerminalLogPayload(input)),
|
|
10111
10218
|
],
|
|
10112
10219
|
);
|
|
10113
10220
|
if (completed.rows.length > 0) return 'completed';
|
|
@@ -10618,6 +10725,36 @@ export async function claimPostgresSchedulerComputeBillingJobs(
|
|
|
10618
10725
|
}));
|
|
10619
10726
|
}
|
|
10620
10727
|
|
|
10728
|
+
/**
|
|
10729
|
+
* Issue terminal compute-billing authority under the same settlement lease as
|
|
10730
|
+
* the full worker reader, without hydrating execution input or retained graphs.
|
|
10731
|
+
* Historical launches keep their absent-descriptor fallback in the issuer.
|
|
10732
|
+
*/
|
|
10733
|
+
export async function readComputeBillingDispatchAuthorityForLease(
|
|
10734
|
+
client: PostgresSchedulerQueryClient,
|
|
10735
|
+
input: { runId: string; leaseOwner: string; attemptCount?: number },
|
|
10736
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
10737
|
+
): Promise<PostgresSchedulerAttemptAuthority | null> {
|
|
10738
|
+
const result = await client.query<PostgresSchedulerAuthorityRow>(
|
|
10739
|
+
`
|
|
10740
|
+
SELECT ${POSTGRES_SCHEDULER_AUTHORITY_SELECT}
|
|
10741
|
+
FROM ${tableName('compute_billing_jobs_v2', options)} AS job
|
|
10742
|
+
JOIN ${tableName('runs', options)} AS runs ON runs.run_id = job.run_id
|
|
10743
|
+
CROSS JOIN LATERAL jsonb_to_record(runs.launch_json)
|
|
10744
|
+
AS launch(${POSTGRES_SCHEDULER_AUTHORITY_COLUMNS})
|
|
10745
|
+
WHERE job.run_id = $1
|
|
10746
|
+
AND job.status = 'settling'
|
|
10747
|
+
AND job.lease_owner = $2
|
|
10748
|
+
AND job.lease_expires_at > now()
|
|
10749
|
+
AND ($3::integer IS NULL OR job.attempt_count = $3)
|
|
10750
|
+
AND runs.status IN ('completed', 'failed', 'cancelled')
|
|
10751
|
+
LIMIT 1
|
|
10752
|
+
`,
|
|
10753
|
+
[input.runId, input.leaseOwner, input.attemptCount ?? null],
|
|
10754
|
+
);
|
|
10755
|
+
return parsePostgresSchedulerAuthorityRow(result.rows[0]);
|
|
10756
|
+
}
|
|
10757
|
+
|
|
10621
10758
|
export async function readComputeBillingDispatchLaunchForLease(
|
|
10622
10759
|
client: PostgresSchedulerQueryClient,
|
|
10623
10760
|
input: { runId: string; leaseOwner: string; attemptCount?: number },
|
|
@@ -12075,7 +12212,8 @@ export async function isPostgresSchedulerRunnerProgressAttemptCurrent(
|
|
|
12075
12212
|
* Bumps `heartbeat_at` on the runner's attempt row (the same liveness column
|
|
12076
12213
|
* the worker's own state transitions maintain — no new schema) and returns the
|
|
12077
12214
|
* play-level run status so the gateway can put cancellation on the heartbeat
|
|
12078
|
-
* channel. Returns null when the run
|
|
12215
|
+
* channel. Returns null when the run is missing or this attempt is no longer
|
|
12216
|
+
* current while the run remains active.
|
|
12079
12217
|
*/
|
|
12080
12218
|
export async function heartbeatPostgresSchedulerDetachedRunner(
|
|
12081
12219
|
client: PostgresSchedulerQueryClient,
|
|
@@ -12125,7 +12263,26 @@ export async function heartbeatPostgresSchedulerDetachedRunner(
|
|
|
12125
12263
|
`,
|
|
12126
12264
|
[input.runId, input.attempt],
|
|
12127
12265
|
);
|
|
12128
|
-
|
|
12266
|
+
const status = heartbeat.rows[0]?.status;
|
|
12267
|
+
if (status) return status;
|
|
12268
|
+
|
|
12269
|
+
// A terminal writer can commit while the UPDATE waits for its attempt lock.
|
|
12270
|
+
// PostgreSQL then rechecks the changed attempt and skips it, but the UNION
|
|
12271
|
+
// still reads the statement's earlier snapshot. Read terminal state with a
|
|
12272
|
+
// fresh snapshot after that wait; never acknowledge a replacement's active
|
|
12273
|
+
// state without renewing this exact attempt through the fence above.
|
|
12274
|
+
const terminal = await client.query<{
|
|
12275
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
12276
|
+
}>(
|
|
12277
|
+
`
|
|
12278
|
+
SELECT status
|
|
12279
|
+
FROM ${tableName('runs', options)}
|
|
12280
|
+
WHERE run_id = $1
|
|
12281
|
+
AND status IN ('completed', 'failed', 'cancelled')
|
|
12282
|
+
`,
|
|
12283
|
+
[input.runId],
|
|
12284
|
+
);
|
|
12285
|
+
return terminal.rows[0]?.status ?? null;
|
|
12129
12286
|
}
|
|
12130
12287
|
|
|
12131
12288
|
export async function claimPostgresSchedulerSignal(
|
|
@@ -12214,6 +12371,7 @@ export async function completePostgresSchedulerAttempt(
|
|
|
12214
12371
|
rowOutcomes?: unknown;
|
|
12215
12372
|
runtimeTiming?: PlayRunnerRuntimeTiming | null;
|
|
12216
12373
|
terminalLogLines?: readonly string[] | null;
|
|
12374
|
+
terminalLogContexts?: readonly (PlayLogContext | null)[] | null;
|
|
12217
12375
|
terminalWorkCounterSegment?: PlayRunLedgerTerminalWorkCounterSegment;
|
|
12218
12376
|
},
|
|
12219
12377
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
@@ -12223,10 +12381,25 @@ export async function completePostgresSchedulerAttempt(
|
|
|
12223
12381
|
content: 'full',
|
|
12224
12382
|
});
|
|
12225
12383
|
const resultSummary = terminalRowOutcomesSummary(input.rowOutcomes);
|
|
12226
|
-
|
|
12227
|
-
.
|
|
12228
|
-
.
|
|
12384
|
+
if (
|
|
12385
|
+
input.terminalLogContexts != null &&
|
|
12386
|
+
input.terminalLogContexts.length !== (input.terminalLogLines ?? []).length
|
|
12387
|
+
) {
|
|
12388
|
+
throw new TypeError(
|
|
12389
|
+
'Terminal log contexts must align with terminal log lines.',
|
|
12390
|
+
);
|
|
12391
|
+
}
|
|
12392
|
+
const terminalLogEntries = (input.terminalLogLines ?? [])
|
|
12393
|
+
.map((line, index) => ({
|
|
12394
|
+
line: line.trim(),
|
|
12395
|
+
context: input.terminalLogContexts?.[index] ?? null,
|
|
12396
|
+
}))
|
|
12397
|
+
.filter((entry) => entry.line.length > 0)
|
|
12229
12398
|
.slice(-LOG_TAIL_LIMIT);
|
|
12399
|
+
const terminalLogLines = terminalLogEntries.map((entry) => entry.line);
|
|
12400
|
+
const terminalLogContexts = terminalLogEntries.map((entry) =>
|
|
12401
|
+
normalizePlayLogContext(entry.context),
|
|
12402
|
+
);
|
|
12230
12403
|
const completed = await client.query<{ run_id: string }>(
|
|
12231
12404
|
`
|
|
12232
12405
|
WITH completed AS (
|
|
@@ -12401,9 +12574,11 @@ export async function completePostgresSchedulerAttempt(
|
|
|
12401
12574
|
'run.completed',
|
|
12402
12575
|
jsonb_build_object(
|
|
12403
12576
|
'output', $9::jsonb,
|
|
12577
|
+
'producerAttempt', $2::integer,
|
|
12404
12578
|
'terminalLogLines', $8::jsonb,
|
|
12579
|
+
'terminalLogContexts', $11::jsonb,
|
|
12405
12580
|
'resultSummary', $10::jsonb,
|
|
12406
|
-
'terminalWorkCounterSegment', $
|
|
12581
|
+
'terminalWorkCounterSegment', $12::jsonb
|
|
12407
12582
|
),
|
|
12408
12583
|
${causalOutboxCreatedAt('run_terminal.run_id', options)}
|
|
12409
12584
|
FROM run_terminal
|
|
@@ -12425,6 +12600,7 @@ export async function completePostgresSchedulerAttempt(
|
|
|
12425
12600
|
JSON.stringify(terminalLogLines),
|
|
12426
12601
|
stringifyPostgresJson(ledgerTerminalResult ?? null),
|
|
12427
12602
|
stringifyPostgresJson(resultSummary),
|
|
12603
|
+
JSON.stringify(terminalLogContexts),
|
|
12428
12604
|
stringifyPostgresJson(input.terminalWorkCounterSegment ?? null),
|
|
12429
12605
|
],
|
|
12430
12606
|
);
|
|
@@ -12443,6 +12619,7 @@ export async function failPostgresSchedulerAttempt(
|
|
|
12443
12619
|
error: string;
|
|
12444
12620
|
result?: unknown;
|
|
12445
12621
|
terminalLogLines?: readonly string[] | null;
|
|
12622
|
+
terminalLogContexts?: readonly (PlayLogContext | null)[] | null;
|
|
12446
12623
|
/** Refuse to overwrite an authoritative detached runner terminal that won
|
|
12447
12624
|
* after claim. Resumable `suspended` receipts may be superseded by the
|
|
12448
12625
|
* hard ceiling in the same fenced statement. */
|
|
@@ -12478,10 +12655,25 @@ export async function failPostgresSchedulerAttempt(
|
|
|
12478
12655
|
);
|
|
12479
12656
|
// Keep the recovery tail separate from result: large failures are represented
|
|
12480
12657
|
// by a result reference, but their customer guidance must still be logged.
|
|
12481
|
-
|
|
12482
|
-
.
|
|
12483
|
-
.
|
|
12658
|
+
if (
|
|
12659
|
+
input.terminalLogContexts != null &&
|
|
12660
|
+
input.terminalLogContexts.length !== (input.terminalLogLines ?? []).length
|
|
12661
|
+
) {
|
|
12662
|
+
throw new TypeError(
|
|
12663
|
+
'Terminal log contexts must align with terminal log lines.',
|
|
12664
|
+
);
|
|
12665
|
+
}
|
|
12666
|
+
const terminalLogEntries = (input.terminalLogLines ?? [])
|
|
12667
|
+
.map((line, index) => ({
|
|
12668
|
+
line: line.trim(),
|
|
12669
|
+
context: input.terminalLogContexts?.[index] ?? null,
|
|
12670
|
+
}))
|
|
12671
|
+
.filter((entry) => entry.line.length > 0)
|
|
12484
12672
|
.slice(-LOG_TAIL_LIMIT);
|
|
12673
|
+
const terminalLogLines = terminalLogEntries.map((entry) => entry.line);
|
|
12674
|
+
const terminalLogContexts = terminalLogEntries.map((entry) =>
|
|
12675
|
+
normalizePlayLogContext(entry.context),
|
|
12676
|
+
);
|
|
12485
12677
|
const failed = await client.query<{ run_id: string }>(
|
|
12486
12678
|
`
|
|
12487
12679
|
WITH failed AS (
|
|
@@ -12716,9 +12908,11 @@ export async function failPostgresSchedulerAttempt(
|
|
|
12716
12908
|
jsonb_build_object(
|
|
12717
12909
|
'error', $4::jsonb ->> 'message',
|
|
12718
12910
|
'result', $8::jsonb,
|
|
12911
|
+
'producerAttempt', $2::integer,
|
|
12719
12912
|
'terminalLogLines', $12::jsonb,
|
|
12913
|
+
'terminalLogContexts', $14::jsonb,
|
|
12720
12914
|
'resultSummary', $13::jsonb,
|
|
12721
|
-
'terminalWorkCounterSegment', $
|
|
12915
|
+
'terminalWorkCounterSegment', $15::jsonb
|
|
12722
12916
|
),
|
|
12723
12917
|
${causalOutboxCreatedAt('run_terminal.run_id', options)}
|
|
12724
12918
|
FROM run_terminal
|
|
@@ -12743,6 +12937,7 @@ export async function failPostgresSchedulerAttempt(
|
|
|
12743
12937
|
input.preserveRunnerTerminal === true,
|
|
12744
12938
|
JSON.stringify(terminalLogLines),
|
|
12745
12939
|
stringifyPostgresJson(resultSummary),
|
|
12940
|
+
JSON.stringify(terminalLogContexts),
|
|
12746
12941
|
stringifyPostgresJson(terminalWorkCounterSegment ?? null),
|
|
12747
12942
|
],
|
|
12748
12943
|
);
|
|
@@ -13431,37 +13626,93 @@ function boundedRunLogTailMetadata(logs: string[]): {
|
|
|
13431
13626
|
return tail.length > 0 ? { tail, totalLogCount: logs.length } : null;
|
|
13432
13627
|
}
|
|
13433
13628
|
|
|
13434
|
-
|
|
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
|
+
|
|
13660
|
+
function terminalLogProjectionForOutbox(
|
|
13435
13661
|
runnerResult: unknown,
|
|
13436
13662
|
mode: PostgresSchedulerTerminalLogOutbox | undefined,
|
|
13437
|
-
): string[] {
|
|
13663
|
+
): { lines: string[]; lineContexts: Array<PlayLogContext | null> } {
|
|
13438
13664
|
if (
|
|
13439
13665
|
mode !== 'ctx_log_tail' ||
|
|
13440
13666
|
!runnerResult ||
|
|
13441
13667
|
typeof runnerResult !== 'object'
|
|
13442
13668
|
) {
|
|
13443
|
-
return [];
|
|
13669
|
+
return { lines: [], lineContexts: [] };
|
|
13444
13670
|
}
|
|
13445
|
-
const
|
|
13671
|
+
const result = runnerResult as {
|
|
13672
|
+
logs?: unknown;
|
|
13673
|
+
logContexts?: unknown;
|
|
13674
|
+
};
|
|
13675
|
+
const logs = result.logs;
|
|
13446
13676
|
if (!Array.isArray(logs)) {
|
|
13447
|
-
return [];
|
|
13677
|
+
return { lines: [], lineContexts: [] };
|
|
13448
13678
|
}
|
|
13449
|
-
|
|
13450
|
-
.
|
|
13451
|
-
|
|
13679
|
+
if (
|
|
13680
|
+
result.logContexts !== undefined &&
|
|
13681
|
+
(!Array.isArray(result.logContexts) ||
|
|
13682
|
+
result.logContexts.length !== logs.length)
|
|
13683
|
+
) {
|
|
13684
|
+
throw new TypeError('Terminal log contexts must align with runner logs.');
|
|
13685
|
+
}
|
|
13686
|
+
const logContexts = Array.isArray(result.logContexts)
|
|
13687
|
+
? result.logContexts
|
|
13688
|
+
: undefined;
|
|
13689
|
+
const entries = logs
|
|
13690
|
+
.map((line, index) => {
|
|
13691
|
+
if (typeof line !== 'string') return null;
|
|
13452
13692
|
const normalized = readProvenanceTag(line).line.trim();
|
|
13453
13693
|
// Retain bounded terminal measurement summaries alongside timestamped
|
|
13454
13694
|
// customer/recovery lines after a detached runner has exited.
|
|
13455
|
-
|
|
13695
|
+
const retained =
|
|
13456
13696
|
CTX_LOG_TIMESTAMP_PREFIX_RE.test(normalized) ||
|
|
13457
13697
|
normalized.startsWith('[runtime.failure_diagnostic] ') ||
|
|
13458
13698
|
normalized.startsWith('[runtime.phase_summary] ') ||
|
|
13459
13699
|
normalized.startsWith('[runtime.logs] ') ||
|
|
13460
|
-
normalized.startsWith('[runtime.log_transport] ')
|
|
13461
|
-
);
|
|
13700
|
+
normalized.startsWith('[runtime.log_transport] ');
|
|
13701
|
+
if (!retained) return null;
|
|
13702
|
+
return {
|
|
13703
|
+
line: line.trim(),
|
|
13704
|
+
context: normalizePlayLogContext(logContexts?.[index]),
|
|
13705
|
+
};
|
|
13462
13706
|
})
|
|
13463
|
-
.
|
|
13707
|
+
.filter(
|
|
13708
|
+
(entry): entry is { line: string; context: PlayLogContext | null } =>
|
|
13709
|
+
entry !== null,
|
|
13710
|
+
)
|
|
13464
13711
|
.slice(-LOG_TAIL_LIMIT);
|
|
13712
|
+
return {
|
|
13713
|
+
lines: entries.map((entry) => entry.line),
|
|
13714
|
+
lineContexts: entries.map((entry) => entry.context),
|
|
13715
|
+
};
|
|
13465
13716
|
}
|
|
13466
13717
|
|
|
13467
13718
|
export function attachRunnerWorkProgressMetadata(
|
|
@@ -13694,6 +13945,10 @@ export async function executePostgresSchedulerClaim(
|
|
|
13694
13945
|
{ logMetadata: options.completionLogMetadata ?? 'attach' },
|
|
13695
13946
|
)
|
|
13696
13947
|
: output;
|
|
13948
|
+
const terminalLogProjection = terminalLogProjectionForOutbox(
|
|
13949
|
+
output,
|
|
13950
|
+
options.terminalLogOutbox,
|
|
13951
|
+
);
|
|
13697
13952
|
const completed = await completePostgresSchedulerAttempt(
|
|
13698
13953
|
client,
|
|
13699
13954
|
{
|
|
@@ -13703,14 +13958,12 @@ export async function executePostgresSchedulerClaim(
|
|
|
13703
13958
|
output: terminalOutput,
|
|
13704
13959
|
rowOutcomes: (output as { rowOutcomes?: unknown } | null)?.rowOutcomes,
|
|
13705
13960
|
runtimeTiming: extractRunnerRuntimeTiming(output),
|
|
13961
|
+
terminalLogLines: terminalLogProjection.lines,
|
|
13962
|
+
terminalLogContexts: terminalLogProjection.lineContexts,
|
|
13706
13963
|
terminalWorkCounterSegment: terminalWorkCounterSegmentFromRunnerResult(
|
|
13707
13964
|
output,
|
|
13708
13965
|
claim.attempt,
|
|
13709
13966
|
),
|
|
13710
|
-
terminalLogLines: terminalLogLinesForOutbox(
|
|
13711
|
-
output,
|
|
13712
|
-
options.terminalLogOutbox,
|
|
13713
|
-
),
|
|
13714
13967
|
},
|
|
13715
13968
|
options,
|
|
13716
13969
|
);
|
|
@@ -13754,6 +14007,10 @@ export async function executePostgresSchedulerClaim(
|
|
|
13754
14007
|
checkpoint: null,
|
|
13755
14008
|
tableNamespace: null,
|
|
13756
14009
|
};
|
|
14010
|
+
const terminalLogProjection = terminalLogProjectionForOutbox(
|
|
14011
|
+
terminalFailureResult,
|
|
14012
|
+
options.terminalLogOutbox,
|
|
14013
|
+
);
|
|
13757
14014
|
const failed = await failPostgresSchedulerAttempt(
|
|
13758
14015
|
client,
|
|
13759
14016
|
{
|
|
@@ -13762,10 +14019,8 @@ export async function executePostgresSchedulerClaim(
|
|
|
13762
14019
|
leaseToken: claim.leaseToken,
|
|
13763
14020
|
error: message,
|
|
13764
14021
|
result: terminalFailureResult,
|
|
13765
|
-
terminalLogLines:
|
|
13766
|
-
|
|
13767
|
-
options.terminalLogOutbox,
|
|
13768
|
-
),
|
|
14022
|
+
terminalLogLines: terminalLogProjection.lines,
|
|
14023
|
+
terminalLogContexts: terminalLogProjection.lineContexts,
|
|
13769
14024
|
sandboxCrashDiagnostic: options.sandboxCrashDiagnostic,
|
|
13770
14025
|
},
|
|
13771
14026
|
options,
|
|
@@ -17032,9 +17287,101 @@ export async function listPostgresSchedulerDeliveredOutboxRetentionCandidates(
|
|
|
17032
17287
|
*/
|
|
17033
17288
|
export async function purgePostgresSchedulerDeliveredOutboxRetentionBatch(
|
|
17034
17289
|
client: PostgresSchedulerQueryClient,
|
|
17035
|
-
input: {
|
|
17290
|
+
input: {
|
|
17291
|
+
olderThan: Date;
|
|
17292
|
+
logBodiesOlderThan?: Date;
|
|
17293
|
+
limit?: number;
|
|
17294
|
+
},
|
|
17036
17295
|
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
17037
17296
|
): Promise<PostgresSchedulerDeliveredOutboxRow[]> {
|
|
17297
|
+
if (input.logBodiesOlderThan) {
|
|
17298
|
+
const logCutoff = input.logBodiesOlderThan.getTime();
|
|
17299
|
+
if (
|
|
17300
|
+
!Number.isFinite(logCutoff) ||
|
|
17301
|
+
logCutoff > Date.now() - 60 * 60 * 1_000
|
|
17302
|
+
) {
|
|
17303
|
+
throw new TypeError(
|
|
17304
|
+
'Delivered outbox log-body cleanup cutoff must be at least one hour old.',
|
|
17305
|
+
);
|
|
17306
|
+
}
|
|
17307
|
+
const limit = normalizeOutboxRetentionLimit(input.limit);
|
|
17308
|
+
await client.query(
|
|
17309
|
+
`WITH candidates AS (
|
|
17310
|
+
SELECT event_id
|
|
17311
|
+
FROM ${tableName('outbox', options)} AS outbox
|
|
17312
|
+
WHERE delivered_at IS NOT NULL
|
|
17313
|
+
AND failed_at IS NULL
|
|
17314
|
+
AND delivered_at < $1::timestamptz
|
|
17315
|
+
AND (
|
|
17316
|
+
(event_type = 'run.events'
|
|
17317
|
+
AND jsonb_typeof(payload_json->'events') = 'array'
|
|
17318
|
+
AND EXISTS (
|
|
17319
|
+
SELECT 1
|
|
17320
|
+
FROM jsonb_array_elements(
|
|
17321
|
+
CASE WHEN jsonb_typeof(payload_json->'events') = 'array'
|
|
17322
|
+
THEN payload_json->'events' ELSE '[]'::jsonb END
|
|
17323
|
+
) AS item
|
|
17324
|
+
WHERE item->>'type' = 'log.appended'
|
|
17325
|
+
AND (item ? 'lines' OR item ? 'lineContexts')
|
|
17326
|
+
))
|
|
17327
|
+
OR (event_type IN ('run.completed', 'run.failed')
|
|
17328
|
+
AND (
|
|
17329
|
+
payload_json ? 'terminalLogLines'
|
|
17330
|
+
OR (jsonb_typeof(payload_json->'terminalProgressEvents') = 'array'
|
|
17331
|
+
AND EXISTS (
|
|
17332
|
+
SELECT 1
|
|
17333
|
+
FROM jsonb_array_elements(
|
|
17334
|
+
CASE WHEN jsonb_typeof(payload_json->'terminalProgressEvents') = 'array'
|
|
17335
|
+
THEN payload_json->'terminalProgressEvents' ELSE '[]'::jsonb END
|
|
17336
|
+
) AS item
|
|
17337
|
+
WHERE item->>'type' = 'log.appended'
|
|
17338
|
+
AND (item ? 'lines' OR item ? 'lineContexts')
|
|
17339
|
+
))
|
|
17340
|
+
))
|
|
17341
|
+
)
|
|
17342
|
+
ORDER BY delivered_at ASC, event_id ASC
|
|
17343
|
+
LIMIT $2::integer
|
|
17344
|
+
FOR UPDATE SKIP LOCKED
|
|
17345
|
+
)
|
|
17346
|
+
UPDATE ${tableName('outbox', options)} AS outbox
|
|
17347
|
+
SET payload_json = CASE
|
|
17348
|
+
WHEN outbox.event_type = 'run.events' THEN jsonb_set(
|
|
17349
|
+
outbox.payload_json,
|
|
17350
|
+
'{events}',
|
|
17351
|
+
(SELECT jsonb_agg(
|
|
17352
|
+
CASE WHEN item.value->>'type' = 'log.appended'
|
|
17353
|
+
THEN item.value - 'lines' - 'lineContexts'
|
|
17354
|
+
ELSE item.value
|
|
17355
|
+
END ORDER BY item.ordinality
|
|
17356
|
+
)
|
|
17357
|
+
FROM jsonb_array_elements(
|
|
17358
|
+
CASE WHEN jsonb_typeof(outbox.payload_json->'events') = 'array'
|
|
17359
|
+
THEN outbox.payload_json->'events' ELSE '[]'::jsonb END
|
|
17360
|
+
)
|
|
17361
|
+
WITH ORDINALITY AS item(value, ordinality)),
|
|
17362
|
+
false
|
|
17363
|
+
)
|
|
17364
|
+
WHEN jsonb_typeof(outbox.payload_json->'terminalProgressEvents') = 'array'
|
|
17365
|
+
THEN jsonb_set(
|
|
17366
|
+
outbox.payload_json - 'terminalLogLines' - 'terminalLogContexts',
|
|
17367
|
+
'{terminalProgressEvents}',
|
|
17368
|
+
coalesce((SELECT jsonb_agg(
|
|
17369
|
+
CASE WHEN item.value->>'type' = 'log.appended'
|
|
17370
|
+
THEN item.value - 'lines' - 'lineContexts'
|
|
17371
|
+
ELSE item.value
|
|
17372
|
+
END ORDER BY item.ordinality
|
|
17373
|
+
)
|
|
17374
|
+
FROM jsonb_array_elements(outbox.payload_json->'terminalProgressEvents')
|
|
17375
|
+
WITH ORDINALITY AS item(value, ordinality)), '[]'::jsonb),
|
|
17376
|
+
false
|
|
17377
|
+
)
|
|
17378
|
+
ELSE outbox.payload_json - 'terminalLogLines' - 'terminalLogContexts'
|
|
17379
|
+
END
|
|
17380
|
+
FROM candidates
|
|
17381
|
+
WHERE outbox.event_id = candidates.event_id`,
|
|
17382
|
+
[input.logBodiesOlderThan.toISOString(), limit],
|
|
17383
|
+
);
|
|
17384
|
+
}
|
|
17038
17385
|
const result = await client.query<{
|
|
17039
17386
|
event_id: string;
|
|
17040
17387
|
run_id: string;
|
|
@@ -17317,9 +17664,11 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
17317
17664
|
const occurredAt = event.createdAt.getTime();
|
|
17318
17665
|
if (event.eventType === 'run.events') {
|
|
17319
17666
|
const events = payloadField(event.payload, 'events');
|
|
17667
|
+
const logsStored =
|
|
17668
|
+
payloadField(event.payload, 'logBodyStore') === 'planet_scale';
|
|
17320
17669
|
if (
|
|
17321
17670
|
!Array.isArray(events) ||
|
|
17322
|
-
events.length === 0 ||
|
|
17671
|
+
(events.length === 0 && !logsStored) ||
|
|
17323
17672
|
events.some(
|
|
17324
17673
|
(value) =>
|
|
17325
17674
|
!recordPayload(value) ||
|
|
@@ -17364,6 +17713,7 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
17364
17713
|
event.runId,
|
|
17365
17714
|
);
|
|
17366
17715
|
const lines = stringArrayPayloadField(event.payload, 'terminalLogLines');
|
|
17716
|
+
const lineContexts = terminalLogContextsPayloadField(event.payload, lines);
|
|
17367
17717
|
const resultSummary = payloadField(event.payload, 'resultSummary');
|
|
17368
17718
|
return [
|
|
17369
17719
|
...progressEvents.map(normalizeOutboxIngressPreview),
|
|
@@ -17376,6 +17726,7 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
17376
17726
|
occurredAt,
|
|
17377
17727
|
source: 'worker',
|
|
17378
17728
|
lines,
|
|
17729
|
+
...(lineContexts ? { lineContexts } : {}),
|
|
17379
17730
|
} as PlayRunLedgerEvent,
|
|
17380
17731
|
]),
|
|
17381
17732
|
{
|
|
@@ -17404,6 +17755,7 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
17404
17755
|
event.runId,
|
|
17405
17756
|
);
|
|
17406
17757
|
const lines = stringArrayPayloadField(event.payload, 'terminalLogLines');
|
|
17758
|
+
const lineContexts = terminalLogContextsPayloadField(event.payload, lines);
|
|
17407
17759
|
const result = terminalRunResultForLedger(
|
|
17408
17760
|
payloadField(event.payload, 'result'),
|
|
17409
17761
|
);
|
|
@@ -17419,6 +17771,7 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
17419
17771
|
occurredAt,
|
|
17420
17772
|
source: 'worker',
|
|
17421
17773
|
lines,
|
|
17774
|
+
...(lineContexts ? { lineContexts } : {}),
|
|
17422
17775
|
} as PlayRunLedgerEvent,
|
|
17423
17776
|
]),
|
|
17424
17777
|
{
|
|
@@ -18236,6 +18589,9 @@ async function projectPostgresSchedulerCreatedEvent(
|
|
|
18236
18589
|
runtimeBackend: value('runtimeBackend'),
|
|
18237
18590
|
schedulerBackend: value('schedulerBackend'),
|
|
18238
18591
|
schedulerSchema: value('schedulerSchema'),
|
|
18592
|
+
...(value('runLogStore') === 'planet_scale'
|
|
18593
|
+
? { runLogStore: 'planet_scale' as const }
|
|
18594
|
+
: {}),
|
|
18239
18595
|
executionProfile: value('executionProfile'),
|
|
18240
18596
|
runtimeReleaseId: value('runtimeReleaseId'),
|
|
18241
18597
|
runtimeDeployVersion: value('runtimeDeployVersion'),
|
|
@@ -18666,6 +19022,20 @@ function stringArrayPayloadField(payload: unknown, key: string): string[] {
|
|
|
18666
19022
|
: [];
|
|
18667
19023
|
}
|
|
18668
19024
|
|
|
19025
|
+
function terminalLogContextsPayloadField(
|
|
19026
|
+
payload: unknown,
|
|
19027
|
+
lines: readonly string[],
|
|
19028
|
+
): Array<PlayLogContext | null> | null {
|
|
19029
|
+
const value = payloadField(payload, 'terminalLogContexts');
|
|
19030
|
+
if (value === undefined) return null;
|
|
19031
|
+
if (!Array.isArray(value) || value.length !== lines.length) {
|
|
19032
|
+
throw new TypeError(
|
|
19033
|
+
'Terminal log contexts must align with terminal log lines.',
|
|
19034
|
+
);
|
|
19035
|
+
}
|
|
19036
|
+
return value.map(normalizePlayLogContext);
|
|
19037
|
+
}
|
|
19038
|
+
|
|
18669
19039
|
function ledgerEventsPayloadField(
|
|
18670
19040
|
payload: unknown,
|
|
18671
19041
|
key: string,
|