deepline 0.3.128 → 0.3.130
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 +34 -9
- package/dist/bundling-sources/sdk/src/http.ts +22 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
- package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
- package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
- 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/play-runs/terminal/postgres-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +114 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +28 -8
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +146 -15
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +904 -77
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
- package/dist/cli/index.js +250 -38
- package/dist/cli/index.mjs +250 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -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 +65 -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
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
acquirePgNotifyHub,
|
|
7
7
|
resolveWorkflowRuntimePostgresUrl,
|
|
8
8
|
withRuntimeSchedulerClient,
|
|
9
|
+
withRuntimeSchedulerTransaction,
|
|
10
|
+
RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS,
|
|
11
|
+
RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS,
|
|
9
12
|
type PgNotifyHub,
|
|
10
13
|
type PgNotifyHubState,
|
|
11
14
|
type RuntimeSchedulerPoolOptions,
|
|
@@ -20,6 +23,7 @@ import { PROD_DEFAULT_RUNTIME_SCHEDULER_SCHEMA } from '../runtime-scheduler-topo
|
|
|
20
23
|
import type { PlayRunnerRuntimeResource } from '../runner-backends/types';
|
|
21
24
|
import {
|
|
22
25
|
RuntimeSandboxOperationState,
|
|
26
|
+
type RuntimeSandboxProvider,
|
|
23
27
|
runtimeSandboxProviderForResourceKind,
|
|
24
28
|
} from './runtime-sandbox-operation';
|
|
25
29
|
import {
|
|
@@ -53,6 +57,10 @@ import type {
|
|
|
53
57
|
RuntimeStepReceipt,
|
|
54
58
|
SkipRuntimeStepReceiptInput,
|
|
55
59
|
} from '../ctx-types';
|
|
60
|
+
import {
|
|
61
|
+
isRuntimeReceiptValueReference,
|
|
62
|
+
type RuntimeReceiptValueReference,
|
|
63
|
+
} from '../runtime-receipt-value';
|
|
56
64
|
import { PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS } from '../lease-policy';
|
|
57
65
|
import { RUNTIME_RELIABILITY_POLICY } from '../runtime-reliability-policy';
|
|
58
66
|
import { COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID } from '../durable-receipt-execution';
|
|
@@ -217,12 +225,46 @@ const POSTGRES_SCHEDULER_SCHEMA_INIT_STATEMENT_TIMEOUT_MS = 60_000;
|
|
|
217
225
|
* interrupted deployment cannot claim a newer version without its required
|
|
218
226
|
* objects.
|
|
219
227
|
*/
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
-
|
|
228
|
+
// v34 adds the delivered-outbox retention index. It is additive: old outbox
|
|
229
|
+
// rows and their durable delivery identities remain readable during the
|
|
230
|
+
// rolling window.
|
|
231
|
+
// v35 adds the admission-priority outbox index and wake trigger. They remain
|
|
232
|
+
// additive: the projector still leases every event from durable storage.
|
|
233
|
+
// v36 adds durable rate-permit reservations, so provider cancellation can
|
|
234
|
+
// distinguish an unused permit from one that may have reached the provider.
|
|
235
|
+
// Additive: a v34 reader ignores the table.
|
|
236
|
+
// v37 reserves immutable Integration Operation bodies before object upload.
|
|
237
|
+
// An old gateway ignores the nullable column; a current writer refuses to
|
|
238
|
+
// publish a reference without its fenced pending owner.
|
|
239
|
+
// v38 adds the durable Gradient2 provider-concurrency checkpoint. Older
|
|
240
|
+
// runners keep their declared cap; current runners only require the additive
|
|
241
|
+
// fields after the deploy-owned schema marker has advanced.
|
|
242
|
+
// v39 adds durable deduplication for buffered observations and durable
|
|
243
|
+
// server-authored 429 attribution. A lost acquire response cannot replay
|
|
244
|
+
// learned capacity, and a runner cannot forge occupancy/RTT feedback.
|
|
245
|
+
// v40 combines Foundation's additive objects with main's blocked-outbox
|
|
246
|
+
// oldest-first health index. Deployment must apply both branches' objects.
|
|
247
|
+
// v41 adds the runtime-owned cron schedule cursor and its due-time index from
|
|
248
|
+
// main's durable trigger admission (#5779). That branch numbered the same
|
|
249
|
+
// objects v37, which this lineage had already spent on immutable Integration
|
|
250
|
+
// Operation bodies, so the merged schema carries them here instead. The table
|
|
251
|
+
// is additive on both sides: v40 admission and outbox projection stay valid
|
|
252
|
+
// while a deploy expands the shared schema, and the marker then records the
|
|
253
|
+
// completed runtime-cron capability for deployment tooling.
|
|
254
|
+
// v42 absorbs main's bounded health indexes for the runtime-owned outbox,
|
|
255
|
+
// sandbox cleanup, and compute-billing queues (#6086 lineage). That branch
|
|
256
|
+
// numbered them v38, which this lineage had already spent, so the objects are
|
|
257
|
+
// carried here instead; all nine are `CREATE INDEX IF NOT EXISTS` and additive,
|
|
258
|
+
// so v41 admission and outbox projection stay valid while a deploy expands the
|
|
259
|
+
// shared schema, and the marker then records the completed health capability.
|
|
260
|
+
// v43 adds per-run fair-share tracking for provider rate buckets: the
|
|
261
|
+
// `rate_requesters` table and a nullable `requester_id` on concurrency leases.
|
|
262
|
+
// Both are additive; only an acquire that carries a run identity reads them, so
|
|
263
|
+
// v42 runtime paths stay valid while a deploy expands the shared schema.
|
|
264
|
+
// v44 moves the v39-v43 shared-schema expansion onto the online, statement-
|
|
265
|
+
// scoped migration path. The reader contract is unchanged; the marker ensures
|
|
266
|
+
// deployment retries do not re-enter the old cross-table DDL transaction.
|
|
267
|
+
export const POSTGRES_SCHEDULER_SCHEMA_VERSION = 44;
|
|
226
268
|
const POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE =
|
|
227
269
|
'scheduler_schema_migrations';
|
|
228
270
|
export const POSTGRES_SCHEDULER_OPEN_RECOVERY_INDEX =
|
|
@@ -514,6 +556,8 @@ type ReceiptRow = {
|
|
|
514
556
|
k: string;
|
|
515
557
|
status: string;
|
|
516
558
|
output_json: unknown | null;
|
|
559
|
+
/** Nullable during the inline/reference reader expansion. */
|
|
560
|
+
output_ref?: unknown | null;
|
|
517
561
|
error: string | null;
|
|
518
562
|
error_payload: unknown | null;
|
|
519
563
|
failure_kind: number | string | null;
|
|
@@ -1004,8 +1048,18 @@ export type PostgresSchedulerSchemaShapeRequirements = Readonly<{
|
|
|
1004
1048
|
blockedOutboxIndex?: boolean;
|
|
1005
1049
|
admissionPriorityIndex?: boolean;
|
|
1006
1050
|
terminalComputeBillingOutboxTrigger?: boolean;
|
|
1051
|
+
/** v35 added the table; a v34 reader does not require it. */
|
|
1052
|
+
rateReservations?: boolean;
|
|
1053
|
+
/** v36 added the pending immutable-value owner; a v35 reader ignores it. */
|
|
1054
|
+
integrationOperationValueReservation?: boolean;
|
|
1055
|
+
/** v37 added the durable Gradient2 checkpoint; a v36 reader ignores it. */
|
|
1056
|
+
gradient2ConcurrencyCheckpoint?: boolean;
|
|
1057
|
+
/** v38 makes buffered Gradient2 observations idempotent; v37 ignores it. */
|
|
1058
|
+
gradient2ObservationDeduplication?: boolean;
|
|
1007
1059
|
outboxWakeTrigger?: boolean;
|
|
1008
1060
|
cronScheduleSchema?: boolean;
|
|
1061
|
+
/** v43 added per-run fair-share tracking for rate buckets; v42 ignores it. */
|
|
1062
|
+
rateRequesterFairShare?: boolean;
|
|
1009
1063
|
}>;
|
|
1010
1064
|
|
|
1011
1065
|
export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
@@ -1077,7 +1131,51 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1077
1131
|
AND column_default IS NOT NULL
|
|
1078
1132
|
)
|
|
1079
1133
|
AND (
|
|
1080
|
-
$
|
|
1134
|
+
$27::boolean = false
|
|
1135
|
+
OR (
|
|
1136
|
+
SELECT count(DISTINCT column_name)
|
|
1137
|
+
FROM information_schema.columns
|
|
1138
|
+
WHERE table_schema = $1
|
|
1139
|
+
AND table_name = 'rate_bucket'
|
|
1140
|
+
AND column_name IN (
|
|
1141
|
+
'learned_concurrency_limit',
|
|
1142
|
+
'concurrency_short_rtt_ms',
|
|
1143
|
+
'concurrency_long_rtt_ms',
|
|
1144
|
+
'concurrency_minimum_rtt_ms',
|
|
1145
|
+
'concurrency_successful_samples',
|
|
1146
|
+
'concurrency_window_limit',
|
|
1147
|
+
'concurrency_has_observed_throttle'
|
|
1148
|
+
)
|
|
1149
|
+
) = 7
|
|
1150
|
+
)
|
|
1151
|
+
AND (
|
|
1152
|
+
$28::boolean = false
|
|
1153
|
+
OR (
|
|
1154
|
+
to_regclass($29) IS NOT NULL
|
|
1155
|
+
AND EXISTS (
|
|
1156
|
+
SELECT 1
|
|
1157
|
+
FROM information_schema.columns
|
|
1158
|
+
WHERE table_schema = $1
|
|
1159
|
+
AND table_name = 'rate_concurrency_observers'
|
|
1160
|
+
AND column_name = 'expires_at_ms'
|
|
1161
|
+
AND data_type = 'bigint'
|
|
1162
|
+
AND is_nullable = 'NO'
|
|
1163
|
+
)
|
|
1164
|
+
AND (
|
|
1165
|
+
SELECT count(DISTINCT column_name)
|
|
1166
|
+
FROM information_schema.columns
|
|
1167
|
+
WHERE table_schema = $1
|
|
1168
|
+
AND table_name = 'rate_concurrency_leases'
|
|
1169
|
+
AND column_name IN (
|
|
1170
|
+
'in_flight_at_start',
|
|
1171
|
+
'target_requests_per_second',
|
|
1172
|
+
'minimum_rtt_ms'
|
|
1173
|
+
)
|
|
1174
|
+
) = 3
|
|
1175
|
+
)
|
|
1176
|
+
)
|
|
1177
|
+
AND (
|
|
1178
|
+
$31::boolean = false
|
|
1081
1179
|
OR EXISTS (
|
|
1082
1180
|
SELECT 1
|
|
1083
1181
|
FROM pg_index AS index_row
|
|
@@ -1088,8 +1186,8 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1088
1186
|
WHERE index_namespace.nspname = $1
|
|
1089
1187
|
AND index_class.relname = 'runtime_scheduler_outbox_admission_pending_idx'
|
|
1090
1188
|
AND index_row.indrelid = to_regclass($8)
|
|
1091
|
-
|
|
1092
|
-
|
|
1189
|
+
AND index_row.indisready
|
|
1190
|
+
AND index_row.indisvalid
|
|
1093
1191
|
)
|
|
1094
1192
|
)
|
|
1095
1193
|
AND (
|
|
@@ -1103,7 +1201,7 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1103
1201
|
)
|
|
1104
1202
|
)
|
|
1105
1203
|
AND (
|
|
1106
|
-
$
|
|
1204
|
+
$30::boolean = false
|
|
1107
1205
|
OR EXISTS (
|
|
1108
1206
|
SELECT 1
|
|
1109
1207
|
FROM pg_trigger
|
|
@@ -1254,10 +1352,24 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1254
1352
|
AND to_regclass($20) IS NOT NULL
|
|
1255
1353
|
AND to_regclass($21) IS NOT NULL
|
|
1256
1354
|
AND to_regclass($22) IS NOT NULL
|
|
1355
|
+
AND ($24::boolean = false OR to_regclass($23) IS NOT NULL)
|
|
1356
|
+
AND (
|
|
1357
|
+
$34::boolean = false
|
|
1358
|
+
OR (
|
|
1359
|
+
to_regclass($35) IS NOT NULL
|
|
1360
|
+
AND EXISTS (
|
|
1361
|
+
SELECT 1
|
|
1362
|
+
FROM information_schema.columns
|
|
1363
|
+
WHERE table_schema = $1
|
|
1364
|
+
AND table_name = 'rate_concurrency_leases'
|
|
1365
|
+
AND column_name = 'requester_id'
|
|
1366
|
+
)
|
|
1367
|
+
)
|
|
1368
|
+
)
|
|
1257
1369
|
AND (
|
|
1258
|
-
$
|
|
1370
|
+
$32::boolean = false
|
|
1259
1371
|
OR (
|
|
1260
|
-
to_regclass($
|
|
1372
|
+
to_regclass($33) IS NOT NULL
|
|
1261
1373
|
AND (
|
|
1262
1374
|
SELECT count(DISTINCT column_name)
|
|
1263
1375
|
FROM information_schema.columns
|
|
@@ -1291,7 +1403,7 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1291
1403
|
ON index_namespace.oid = index_class.relnamespace
|
|
1292
1404
|
WHERE index_namespace.nspname = $1
|
|
1293
1405
|
AND index_class.relname = 'runtime_scheduler_cron_schedules_due_idx'
|
|
1294
|
-
AND index_row.indrelid = to_regclass($
|
|
1406
|
+
AND index_row.indrelid = to_regclass($33)
|
|
1295
1407
|
AND index_row.indisready
|
|
1296
1408
|
AND index_row.indisvalid
|
|
1297
1409
|
)
|
|
@@ -1315,6 +1427,7 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1315
1427
|
AND constraint_row.contype = 'p'
|
|
1316
1428
|
) = 'operation_key'
|
|
1317
1429
|
AND (
|
|
1430
|
+
$26::boolean = false OR (
|
|
1318
1431
|
SELECT count(DISTINCT column_name)
|
|
1319
1432
|
FROM information_schema.columns
|
|
1320
1433
|
WHERE table_schema = $1
|
|
@@ -1323,9 +1436,35 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1323
1436
|
'operation_request_digest',
|
|
1324
1437
|
'legacy_request_fingerprint',
|
|
1325
1438
|
'canonical_outcome_version',
|
|
1326
|
-
'canonical_outcome_json'
|
|
1439
|
+
'canonical_outcome_json',
|
|
1440
|
+
'canonical_outcome_ref',
|
|
1441
|
+
'canonical_outcome_pending_ref'
|
|
1327
1442
|
)
|
|
1328
|
-
|
|
1443
|
+
) = 6
|
|
1444
|
+
)
|
|
1445
|
+
AND (
|
|
1446
|
+
$26::boolean = false OR (
|
|
1447
|
+
to_regclass($25) IS NOT NULL
|
|
1448
|
+
AND (
|
|
1449
|
+
SELECT count(DISTINCT column_name)
|
|
1450
|
+
FROM information_schema.columns
|
|
1451
|
+
WHERE table_schema = $1
|
|
1452
|
+
AND table_name = 'runtime_integration_operation_value_retirements'
|
|
1453
|
+
AND column_name IN (
|
|
1454
|
+
'org_id',
|
|
1455
|
+
'object_key',
|
|
1456
|
+
'object_digest',
|
|
1457
|
+
'operation_key',
|
|
1458
|
+
'reference_json',
|
|
1459
|
+
'state',
|
|
1460
|
+
'claim_token',
|
|
1461
|
+
'created_at',
|
|
1462
|
+
'updated_at',
|
|
1463
|
+
'deletion_confirmed_at'
|
|
1464
|
+
)
|
|
1465
|
+
) = 10
|
|
1466
|
+
)
|
|
1467
|
+
)
|
|
1329
1468
|
AND (
|
|
1330
1469
|
SELECT count(DISTINCT column_name)
|
|
1331
1470
|
FROM information_schema.columns
|
|
@@ -1538,10 +1677,19 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1538
1677
|
`${quotedSchema}.${quoteIdent('runtime_gateway_reader_fences')}`,
|
|
1539
1678
|
`${quotedSchema}.${quoteIdent('runtime_sandbox_operations')}`,
|
|
1540
1679
|
`${quotedSchema}.${quoteIdent('outbox_repair_operations')}`,
|
|
1680
|
+
`${quotedSchema}.${quoteIdent('rate_reservations')}`,
|
|
1681
|
+
requirements.rateReservations !== false,
|
|
1682
|
+
`${quotedSchema}.${quoteIdent('runtime_integration_operation_value_retirements')}`,
|
|
1683
|
+
requirements.integrationOperationValueReservation !== false,
|
|
1684
|
+
requirements.gradient2ConcurrencyCheckpoint !== false,
|
|
1685
|
+
requirements.gradient2ObservationDeduplication !== false,
|
|
1686
|
+
`${quotedSchema}.${quoteIdent('rate_concurrency_observers')}`,
|
|
1541
1687
|
requirements.outboxWakeTrigger !== false,
|
|
1542
1688
|
requirements.admissionPriorityIndex !== false,
|
|
1543
1689
|
requirements.cronScheduleSchema !== false,
|
|
1544
1690
|
`${quotedSchema}.${quoteIdent(POSTGRES_SCHEDULER_CRON_SCHEDULES_TABLE)}`,
|
|
1691
|
+
requirements.rateRequesterFairShare !== false,
|
|
1692
|
+
`${quotedSchema}.${quoteIdent('rate_requesters')}`,
|
|
1545
1693
|
],
|
|
1546
1694
|
);
|
|
1547
1695
|
if (currentShape.rows[0]?.ready !== true) return false;
|
|
@@ -1697,6 +1845,89 @@ export async function hasCurrentPostgresSchedulerSchemaShape(
|
|
|
1697
1845
|
return runtimeAdminIndexes.rows[0]?.ready === true;
|
|
1698
1846
|
}
|
|
1699
1847
|
|
|
1848
|
+
/**
|
|
1849
|
+
* Expand the shared scheduler schema without retaining locks across tables.
|
|
1850
|
+
*
|
|
1851
|
+
* The deployment caller gives every statement its own short transaction with
|
|
1852
|
+
* transaction-local lock and statement timeouts. Never wrap the whole helper
|
|
1853
|
+
* in one transaction: old workers acquire the same live-table locks in a
|
|
1854
|
+
* different order and can deadlock the release. The helper does not write the
|
|
1855
|
+
* schema marker; callers record it only after the complete expansion and all
|
|
1856
|
+
* concurrent indexes are verified.
|
|
1857
|
+
*/
|
|
1858
|
+
export async function ensurePostgresSchedulerOnlineAdditiveExpansion(
|
|
1859
|
+
client: PostgresSchedulerQueryClient,
|
|
1860
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
1861
|
+
): Promise<void> {
|
|
1862
|
+
const schema = schemaName(options);
|
|
1863
|
+
await client.query(`
|
|
1864
|
+
ALTER TABLE ${tableName('receipts', options)}
|
|
1865
|
+
ADD COLUMN IF NOT EXISTS output_ref jsonb
|
|
1866
|
+
`);
|
|
1867
|
+
await client.query(`
|
|
1868
|
+
ALTER TABLE ${tableName('integration_invocation_receipts', options)}
|
|
1869
|
+
ADD COLUMN IF NOT EXISTS canonical_outcome_ref jsonb,
|
|
1870
|
+
ADD COLUMN IF NOT EXISTS canonical_outcome_pending_ref jsonb
|
|
1871
|
+
`);
|
|
1872
|
+
await client.query(`
|
|
1873
|
+
CREATE TABLE IF NOT EXISTS ${tableName(
|
|
1874
|
+
'runtime_integration_operation_value_retirements',
|
|
1875
|
+
options,
|
|
1876
|
+
)} (
|
|
1877
|
+
org_id text NOT NULL,
|
|
1878
|
+
object_key text NOT NULL,
|
|
1879
|
+
object_digest text NOT NULL,
|
|
1880
|
+
operation_key text NOT NULL,
|
|
1881
|
+
reference_json jsonb NOT NULL,
|
|
1882
|
+
state text NOT NULL,
|
|
1883
|
+
claim_token text,
|
|
1884
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
1885
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
1886
|
+
deletion_confirmed_at timestamptz,
|
|
1887
|
+
PRIMARY KEY (org_id, object_key, object_digest),
|
|
1888
|
+
CHECK (state IN ('pending', 'deleting', 'deleted', 'cancelled')),
|
|
1889
|
+
CHECK ((state = 'deleting') = (claim_token IS NOT NULL))
|
|
1890
|
+
)
|
|
1891
|
+
`);
|
|
1892
|
+
await client.query(`
|
|
1893
|
+
CREATE INDEX IF NOT EXISTS ${quoteIdent(
|
|
1894
|
+
`${schema}_integration_operation_value_retirements_pending_idx`,
|
|
1895
|
+
)}
|
|
1896
|
+
ON ${tableName(
|
|
1897
|
+
'runtime_integration_operation_value_retirements',
|
|
1898
|
+
options,
|
|
1899
|
+
)} (state, updated_at)
|
|
1900
|
+
`);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/** Read the highest deployment-owned scheduler schema marker. */
|
|
1904
|
+
export async function readPostgresSchedulerSchemaVersion(
|
|
1905
|
+
client: PostgresSchedulerQueryClient,
|
|
1906
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
1907
|
+
): Promise<number> {
|
|
1908
|
+
const result = await client.query<{ version: number | null }>(
|
|
1909
|
+
`SELECT max(version) AS version FROM ${tableName(
|
|
1910
|
+
POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE,
|
|
1911
|
+
options,
|
|
1912
|
+
)}`,
|
|
1913
|
+
);
|
|
1914
|
+
return Number(result.rows[0]?.version ?? 0);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
/** Record the deployment-owned marker after an online expansion is complete. */
|
|
1918
|
+
export async function recordPostgresSchedulerSchemaVersion(
|
|
1919
|
+
client: PostgresSchedulerQueryClient,
|
|
1920
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
1921
|
+
): Promise<void> {
|
|
1922
|
+
await client.query(
|
|
1923
|
+
`INSERT INTO ${tableName(
|
|
1924
|
+
POSTGRES_SCHEDULER_SCHEMA_MIGRATIONS_TABLE,
|
|
1925
|
+
options,
|
|
1926
|
+
)} (version) VALUES ($1) ON CONFLICT (version) DO NOTHING`,
|
|
1927
|
+
[POSTGRES_SCHEDULER_SCHEMA_VERSION],
|
|
1928
|
+
);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1700
1931
|
/**
|
|
1701
1932
|
* Deployment completion check. Migration bookkeeping and the full target
|
|
1702
1933
|
* shape belong to deployment tooling, never request or worker admission.
|
|
@@ -2622,6 +2853,13 @@ export async function ensurePostgresSchedulerSchema(
|
|
|
2622
2853
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
2623
2854
|
PRIMARY KEY (org_id, k)
|
|
2624
2855
|
)
|
|
2856
|
+
`);
|
|
2857
|
+
// Readers first: older rows keep `output_json`; a referenced body is
|
|
2858
|
+
// published only after it was acknowledged by object storage. The nullable
|
|
2859
|
+
// expansion keeps mixed scheduler-schema lanes readable during rollout.
|
|
2860
|
+
await client.query(`
|
|
2861
|
+
ALTER TABLE ${tableName('receipts', options)}
|
|
2862
|
+
ADD COLUMN IF NOT EXISTS output_ref jsonb
|
|
2625
2863
|
`);
|
|
2626
2864
|
await client.query(`
|
|
2627
2865
|
CREATE TABLE IF NOT EXISTS ${tableName(
|
|
@@ -2653,11 +2891,45 @@ export async function ensurePostgresSchedulerSchema(
|
|
|
2653
2891
|
ADD COLUMN IF NOT EXISTS operation_request_digest text,
|
|
2654
2892
|
ADD COLUMN IF NOT EXISTS legacy_request_fingerprint text,
|
|
2655
2893
|
ADD COLUMN IF NOT EXISTS canonical_outcome_version smallint,
|
|
2656
|
-
ADD COLUMN IF NOT EXISTS canonical_outcome_json jsonb
|
|
2894
|
+
ADD COLUMN IF NOT EXISTS canonical_outcome_json jsonb,
|
|
2895
|
+
ADD COLUMN IF NOT EXISTS canonical_outcome_ref jsonb,
|
|
2896
|
+
ADD COLUMN IF NOT EXISTS canonical_outcome_pending_ref jsonb
|
|
2657
2897
|
`);
|
|
2658
2898
|
await client.query(`
|
|
2659
2899
|
ALTER TABLE ${tableName('integration_invocation_receipts', options)}
|
|
2660
2900
|
ADD COLUMN IF NOT EXISTS parent_run_id text NOT NULL DEFAULT ''
|
|
2901
|
+
`);
|
|
2902
|
+
// A reference stays live while it is attached to an operation row. A
|
|
2903
|
+
// later recovery detaches failed or incomplete output into this small,
|
|
2904
|
+
// fenced journal; the gateway can then delete the immutable object and
|
|
2905
|
+
// confirm absence before the journal says it is gone. This is deliberately
|
|
2906
|
+
// a journal rather than an object TTL: completed operation outcomes remain
|
|
2907
|
+
// the replay authority until the operation itself is explicitly replaced.
|
|
2908
|
+
await client.query(`
|
|
2909
|
+
CREATE TABLE IF NOT EXISTS ${tableName(
|
|
2910
|
+
'runtime_integration_operation_value_retirements',
|
|
2911
|
+
options,
|
|
2912
|
+
)} (
|
|
2913
|
+
org_id text NOT NULL,
|
|
2914
|
+
object_key text NOT NULL,
|
|
2915
|
+
object_digest text NOT NULL,
|
|
2916
|
+
operation_key text NOT NULL,
|
|
2917
|
+
reference_json jsonb NOT NULL,
|
|
2918
|
+
state text NOT NULL CHECK (state IN ('pending', 'deleting', 'deleted', 'cancelled')),
|
|
2919
|
+
claim_token text,
|
|
2920
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
2921
|
+
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
2922
|
+
deletion_confirmed_at timestamptz,
|
|
2923
|
+
PRIMARY KEY (org_id, object_key, object_digest),
|
|
2924
|
+
CHECK ((state = 'deleting') = (claim_token IS NOT NULL))
|
|
2925
|
+
)
|
|
2926
|
+
`);
|
|
2927
|
+
await client.query(`
|
|
2928
|
+
CREATE INDEX IF NOT EXISTS ${quoteIdent(
|
|
2929
|
+
`${schema}_integration_operation_value_retirements_pending_idx`,
|
|
2930
|
+
)}
|
|
2931
|
+
ON ${tableName('runtime_integration_operation_value_retirements', options)}
|
|
2932
|
+
(state, updated_at)
|
|
2661
2933
|
`);
|
|
2662
2934
|
// Gateway reader capability is a shared-storage compatibility fact. It is
|
|
2663
2935
|
// intentionally separate from runtime release admission: worker lanes do
|
|
@@ -3961,6 +4233,72 @@ export async function ensureAbsurdSchema(
|
|
|
3961
4233
|
}
|
|
3962
4234
|
}
|
|
3963
4235
|
|
|
4236
|
+
/**
|
|
4237
|
+
* The runtime is allowed to use the shared Absurd schema only after the
|
|
4238
|
+
* deployment migration has installed its exact engine and Deepline
|
|
4239
|
+
* compatibility contract. This is deliberately read-only: workers and
|
|
4240
|
+
* serverless submitters must never take the global Absurd bootstrap lock or
|
|
4241
|
+
* run DDL while serving a customer request.
|
|
4242
|
+
*/
|
|
4243
|
+
export class AbsurdSchemaNotReadyError extends Error {
|
|
4244
|
+
constructor(message: string) {
|
|
4245
|
+
super(message);
|
|
4246
|
+
this.name = 'AbsurdSchemaNotReadyError';
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
export async function assertAbsurdSchemaReady(
|
|
4251
|
+
client: PostgresSchedulerQueryClient,
|
|
4252
|
+
): Promise<void> {
|
|
4253
|
+
let result: {
|
|
4254
|
+
rows: Array<{
|
|
4255
|
+
version: string | null;
|
|
4256
|
+
compatibilityVersion: string | null;
|
|
4257
|
+
}>;
|
|
4258
|
+
};
|
|
4259
|
+
try {
|
|
4260
|
+
result = await client.query<{
|
|
4261
|
+
version: string | null;
|
|
4262
|
+
compatibilityVersion: string | null;
|
|
4263
|
+
}>(`
|
|
4264
|
+
SELECT
|
|
4265
|
+
absurd.get_schema_version() AS "version",
|
|
4266
|
+
absurd.deepline_no_scheduler_org_cap_compatibility_version()
|
|
4267
|
+
AS "compatibilityVersion"
|
|
4268
|
+
`);
|
|
4269
|
+
} catch (error) {
|
|
4270
|
+
const code =
|
|
4271
|
+
typeof error === 'object' && error !== null && 'code' in error
|
|
4272
|
+
? (error as { code?: unknown }).code
|
|
4273
|
+
: undefined;
|
|
4274
|
+
if (
|
|
4275
|
+
code === '42883' ||
|
|
4276
|
+
(error instanceof Error &&
|
|
4277
|
+
/does not exist|undefined function/i.test(error.message))
|
|
4278
|
+
) {
|
|
4279
|
+
throw new AbsurdSchemaNotReadyError(
|
|
4280
|
+
'Absurd scheduler schema is absent or missing the Deepline compatibility marker; run the runtime scheduler migration before activating a lane.',
|
|
4281
|
+
);
|
|
4282
|
+
}
|
|
4283
|
+
throw error;
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
const state = result.rows[0];
|
|
4287
|
+
if (
|
|
4288
|
+
state?.version !== ABSURD_SCHEMA_VERSION ||
|
|
4289
|
+
state.compatibilityVersion !==
|
|
4290
|
+
ABSURD_NO_SCHEDULER_ORG_CAP_COMPATIBILITY_VERSION
|
|
4291
|
+
) {
|
|
4292
|
+
throw new AbsurdSchemaNotReadyError(
|
|
4293
|
+
'Absurd scheduler schema is incompatible: ' +
|
|
4294
|
+
`required_version=${ABSURD_SCHEMA_VERSION} observed_version=${state?.version ?? 'missing'} ` +
|
|
4295
|
+
`required_compatibility=${ABSURD_NO_SCHEDULER_ORG_CAP_COMPATIBILITY_VERSION} ` +
|
|
4296
|
+
`observed_compatibility=${state?.compatibilityVersion ?? 'missing'}. ` +
|
|
4297
|
+
'Run the runtime scheduler migration before activating a lane.',
|
|
4298
|
+
);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
|
|
3964
4302
|
export async function submitPostgresSchedulerRun(
|
|
3965
4303
|
client: PostgresSchedulerQueryClient,
|
|
3966
4304
|
input: PlaySchedulerSubmitInput,
|
|
@@ -7834,6 +8172,65 @@ export async function recordPostgresSchedulerManagedSandboxOperationLive(
|
|
|
7834
8172
|
return updated.rows.length === 1;
|
|
7835
8173
|
}
|
|
7836
8174
|
|
|
8175
|
+
/**
|
|
8176
|
+
* Answers one question a rejected promotion cannot answer on its own: did this
|
|
8177
|
+
* attempt lose its fence, or did it merely re-drive a promotion it already
|
|
8178
|
+
* completed?
|
|
8179
|
+
*
|
|
8180
|
+
* `recordPostgresSchedulerManagedSandboxOperationLive` is a one-shot
|
|
8181
|
+
* `reserved -> live` compare-and-set, so it returns false both when the fence
|
|
8182
|
+
* is gone and when this same fenced attempt already promoted this same sandbox.
|
|
8183
|
+
* A provider retry inside one attempt legitimately re-reports an earlier
|
|
8184
|
+
* sandbox (for example to stamp its billing end when it is retired), and
|
|
8185
|
+
* reading that rejection as a lost fence terminates a run whose attempt is
|
|
8186
|
+
* still fully owned.
|
|
8187
|
+
*
|
|
8188
|
+
* This proves the benign case from durable state rather than assuming it: it
|
|
8189
|
+
* returns true only when the attempt row still carries this lease token in a
|
|
8190
|
+
* live status AND the operation is already live for this exact sandbox. Any
|
|
8191
|
+
* other shape leaves the caller's fence loss loud and unchanged.
|
|
8192
|
+
*/
|
|
8193
|
+
export async function readPostgresSchedulerManagedSandboxOperationAlreadyLive(
|
|
8194
|
+
client: PostgresSchedulerQueryClient,
|
|
8195
|
+
input: {
|
|
8196
|
+
runId: string;
|
|
8197
|
+
attempt: number;
|
|
8198
|
+
leaseToken: string;
|
|
8199
|
+
operationKey: string;
|
|
8200
|
+
provider: RuntimeSandboxProvider;
|
|
8201
|
+
sandboxId: string;
|
|
8202
|
+
},
|
|
8203
|
+
options?: Pick<PostgresSchedulerOptions, 'schema'>,
|
|
8204
|
+
): Promise<boolean> {
|
|
8205
|
+
const found = await client.query<{ operation_key: string }>(
|
|
8206
|
+
`
|
|
8207
|
+
SELECT operation.operation_key
|
|
8208
|
+
FROM ${tableName('runtime_sandbox_operations', options)} AS operation
|
|
8209
|
+
JOIN ${tableName('attempts', options)} AS attempt
|
|
8210
|
+
ON attempt.run_id = operation.run_id
|
|
8211
|
+
AND attempt.attempt = operation.attempt
|
|
8212
|
+
WHERE operation.operation_key = $4
|
|
8213
|
+
AND operation.run_id = $1
|
|
8214
|
+
AND operation.attempt = $2
|
|
8215
|
+
AND operation.provider = $5
|
|
8216
|
+
AND operation.state = $6
|
|
8217
|
+
AND operation.sandbox_id = $7
|
|
8218
|
+
AND attempt.lease_token = $3
|
|
8219
|
+
AND attempt.status IN ('claimed', 'running', 'waiting')
|
|
8220
|
+
`,
|
|
8221
|
+
[
|
|
8222
|
+
input.runId,
|
|
8223
|
+
input.attempt,
|
|
8224
|
+
input.leaseToken,
|
|
8225
|
+
input.operationKey,
|
|
8226
|
+
input.provider,
|
|
8227
|
+
RuntimeSandboxOperationState.Live,
|
|
8228
|
+
input.sandboxId,
|
|
8229
|
+
],
|
|
8230
|
+
);
|
|
8231
|
+
return found.rows.length === 1;
|
|
8232
|
+
}
|
|
8233
|
+
|
|
7837
8234
|
/**
|
|
7838
8235
|
* A create call that timed out may settle only after its caller has marked the
|
|
7839
8236
|
* attempt terminal. Preserve the provider fact under that terminal attempt,
|
|
@@ -8661,6 +9058,10 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
8661
9058
|
attempt: number;
|
|
8662
9059
|
leaseToken: string;
|
|
8663
9060
|
result: unknown;
|
|
9061
|
+
/** Progress facts folded before direct completion. They travel with the
|
|
9062
|
+
* terminal outbox so the ledger sees progress before terminal state in one
|
|
9063
|
+
* ordered projection. */
|
|
9064
|
+
terminalProgressEvents?: readonly unknown[];
|
|
8664
9065
|
engineWake: {
|
|
8665
9066
|
queueName: string;
|
|
8666
9067
|
eventName: string;
|
|
@@ -8715,7 +9116,7 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
8715
9116
|
OR run.status = 'running'
|
|
8716
9117
|
)
|
|
8717
9118
|
AND (
|
|
8718
|
-
$
|
|
9119
|
+
$15::boolean
|
|
8719
9120
|
OR run.launch_json #>> '{contractSnapshot,billingLimit,maxCreditsPerRun}' IS NULL
|
|
8720
9121
|
)
|
|
8721
9122
|
FOR UPDATE
|
|
@@ -8779,7 +9180,7 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
8779
9180
|
WHERE target.run_id = $1
|
|
8780
9181
|
AND target.attempt = $2
|
|
8781
9182
|
AND target.status IN ('running', 'waiting')
|
|
8782
|
-
AND target.lease_token = $
|
|
9183
|
+
AND target.lease_token = $11
|
|
8783
9184
|
AND (target.runner_result_json IS NULL OR target.runner_result_json = $3::jsonb)
|
|
8784
9185
|
AND run.run_id = target.run_id
|
|
8785
9186
|
AND NOT EXISTS (
|
|
@@ -8939,21 +9340,22 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
8939
9340
|
'run.completed',
|
|
8940
9341
|
jsonb_build_object(
|
|
8941
9342
|
'output', $6::jsonb,
|
|
8942
|
-
'terminalLogLines', $7::jsonb
|
|
9343
|
+
'terminalLogLines', $7::jsonb,
|
|
9344
|
+
'terminalProgressEvents', $8::jsonb
|
|
8943
9345
|
),
|
|
8944
9346
|
${causalOutboxCreatedAt('run_terminal.run_id', options)}
|
|
8945
9347
|
FROM run_terminal
|
|
8946
9348
|
ON CONFLICT (event_id) DO NOTHING
|
|
8947
9349
|
),
|
|
8948
9350
|
notify_terminal AS (
|
|
8949
|
-
SELECT run_id, pg_notify($
|
|
9351
|
+
SELECT run_id, pg_notify($9, $10) FROM run_terminal
|
|
8950
9352
|
),
|
|
8951
9353
|
engine_wake AS (
|
|
8952
|
-
SELECT run_id, absurd.emit_event($
|
|
9354
|
+
SELECT run_id, absurd.emit_event($12, $13, 'null'::jsonb)
|
|
8953
9355
|
FROM notify_terminal
|
|
8954
9356
|
),
|
|
8955
9357
|
notify_engine AS (
|
|
8956
|
-
SELECT run_id, pg_notify($
|
|
9358
|
+
SELECT run_id, pg_notify($14, $12) FROM engine_wake
|
|
8957
9359
|
)
|
|
8958
9360
|
SELECT run_id FROM notify_engine
|
|
8959
9361
|
`,
|
|
@@ -8965,6 +9367,7 @@ export async function directCompletePostgresSchedulerRunnerTerminal(
|
|
|
8965
9367
|
stringifyPostgresJson(runtimeTiming ?? null),
|
|
8966
9368
|
stringifyPostgresJson(ledgerTerminalResult ?? null),
|
|
8967
9369
|
JSON.stringify(terminalLogLines),
|
|
9370
|
+
JSON.stringify(input.terminalProgressEvents ?? []),
|
|
8968
9371
|
postgresSchedulerNotifyChannel('terminal', options),
|
|
8969
9372
|
JSON.stringify({ runId: input.runId, status: 'completed' }),
|
|
8970
9373
|
input.leaseToken,
|
|
@@ -11081,6 +11484,7 @@ export async function failPostgresSchedulerAttempt(
|
|
|
11081
11484
|
leaseToken: string;
|
|
11082
11485
|
error: string;
|
|
11083
11486
|
result?: unknown;
|
|
11487
|
+
terminalLogLines?: readonly string[] | null;
|
|
11084
11488
|
/** Refuse to overwrite a detached runner terminal that won after claim. */
|
|
11085
11489
|
preserveRunnerTerminal?: boolean;
|
|
11086
11490
|
sandboxCrashDiagnostic?: PostgresSchedulerSandboxCrashDiagnosticRequest;
|
|
@@ -11105,6 +11509,12 @@ export async function failPostgresSchedulerAttempt(
|
|
|
11105
11509
|
terminalResult === undefined ? (input.result ?? null) : terminalResult,
|
|
11106
11510
|
terminalResult === undefined ? {} : { content: 'full' },
|
|
11107
11511
|
);
|
|
11512
|
+
// Keep the recovery tail separate from result: large failures are represented
|
|
11513
|
+
// by a result reference, but their customer guidance must still be logged.
|
|
11514
|
+
const terminalLogLines = (input.terminalLogLines ?? [])
|
|
11515
|
+
.map((line) => line.trim())
|
|
11516
|
+
.filter(Boolean)
|
|
11517
|
+
.slice(-LOG_TAIL_LIMIT);
|
|
11108
11518
|
const failed = await client.query<{ run_id: string }>(
|
|
11109
11519
|
`
|
|
11110
11520
|
WITH failed AS (
|
|
@@ -11337,7 +11747,8 @@ export async function failPostgresSchedulerAttempt(
|
|
|
11337
11747
|
'run.failed',
|
|
11338
11748
|
jsonb_build_object(
|
|
11339
11749
|
'error', $4::jsonb ->> 'message',
|
|
11340
|
-
'result', $8::jsonb
|
|
11750
|
+
'result', $8::jsonb,
|
|
11751
|
+
'terminalLogLines', $12::jsonb
|
|
11341
11752
|
),
|
|
11342
11753
|
${causalOutboxCreatedAt('run_terminal.run_id', options)}
|
|
11343
11754
|
FROM run_terminal
|
|
@@ -11360,6 +11771,7 @@ export async function failPostgresSchedulerAttempt(
|
|
|
11360
11771
|
runtimeTerminalReasonFromError(input.error),
|
|
11361
11772
|
stringifyPostgresJson(input.sandboxCrashDiagnostic ?? null),
|
|
11362
11773
|
input.preserveRunnerTerminal === true,
|
|
11774
|
+
JSON.stringify(terminalLogLines),
|
|
11363
11775
|
],
|
|
11364
11776
|
);
|
|
11365
11777
|
return failed.rows.length > 0;
|
|
@@ -11424,7 +11836,21 @@ export async function cancelPostgresSchedulerRun(
|
|
|
11424
11836
|
WHERE target.run_id = $1
|
|
11425
11837
|
AND target.status IN ('queued', 'running', 'waiting')
|
|
11426
11838
|
${strandedEligibility}
|
|
11427
|
-
RETURNING target.run_id, target.launch_json
|
|
11839
|
+
RETURNING target.run_id, target.org_id, target.launch_json
|
|
11840
|
+
),
|
|
11841
|
+
released_receipts AS (
|
|
11842
|
+
-- Cancellation is a terminal, and a terminal releases everything the
|
|
11843
|
+
-- run claimed. A receipt left 'running' behind a dead lease matches no
|
|
11844
|
+
-- non-forced claim status set, so a successor would park on it for the
|
|
11845
|
+
-- whole receipt wait budget instead of executing the call itself.
|
|
11846
|
+
UPDATE ${tableName('receipts', options)} AS receipt
|
|
11847
|
+
SET ${RELEASE_RECEIPT_TO_PENDING_SET_CLAUSE}
|
|
11848
|
+
FROM cancelled_run
|
|
11849
|
+
WHERE receipt.org_id = cancelled_run.org_id
|
|
11850
|
+
AND receipt.status IN ('queued', 'running')
|
|
11851
|
+
AND COALESCE(receipt.lease_owner_run_id, receipt.run_id)
|
|
11852
|
+
= cancelled_run.run_id
|
|
11853
|
+
RETURNING receipt.k
|
|
11428
11854
|
),
|
|
11429
11855
|
cancelled_attempts AS (
|
|
11430
11856
|
UPDATE ${tableName('attempts', options)}
|
|
@@ -11878,6 +12304,33 @@ function extractRunnerRuntimeTiming(
|
|
|
11878
12304
|
|
|
11879
12305
|
const CTX_LOG_TIMESTAMP_PREFIX_RE = /^\[\d{4}-\d{2}-\d{2}T[\d:.]+Z\]\s+/;
|
|
11880
12306
|
const RUN_LOG_TAIL_METADATA_MAX_BYTES = 128 * 1024;
|
|
12307
|
+
// The runner's terminal resource snapshot is a small aggregate (phase timings,
|
|
12308
|
+
// process CPU/RSS, per-map admission state). It rides in result metadata so
|
|
12309
|
+
// terminal diagnostics never depend on a bounded log tail.
|
|
12310
|
+
const RUNTIME_RESOURCES_METADATA_MAX_BYTES = 32 * 1024;
|
|
12311
|
+
|
|
12312
|
+
function boundedRuntimeResourcesMetadata(
|
|
12313
|
+
runnerResult: unknown,
|
|
12314
|
+
): Record<string, unknown> | null {
|
|
12315
|
+
const stats = (runnerResult as { stats?: unknown }).stats;
|
|
12316
|
+
if (!stats || typeof stats !== 'object' || Array.isArray(stats)) return null;
|
|
12317
|
+
const runtimeResources = (stats as { runtimeResources?: unknown })
|
|
12318
|
+
.runtimeResources;
|
|
12319
|
+
if (
|
|
12320
|
+
!runtimeResources ||
|
|
12321
|
+
typeof runtimeResources !== 'object' ||
|
|
12322
|
+
Array.isArray(runtimeResources)
|
|
12323
|
+
) {
|
|
12324
|
+
return null;
|
|
12325
|
+
}
|
|
12326
|
+
if (
|
|
12327
|
+
jsonByteLengthUpTo(runtimeResources, RUNTIME_RESOURCES_METADATA_MAX_BYTES)
|
|
12328
|
+
.exceeded
|
|
12329
|
+
) {
|
|
12330
|
+
return null;
|
|
12331
|
+
}
|
|
12332
|
+
return runtimeResources as Record<string, unknown>;
|
|
12333
|
+
}
|
|
11881
12334
|
|
|
11882
12335
|
function boundedRunLogTailMetadata(logs: string[]): {
|
|
11883
12336
|
tail: string[];
|
|
@@ -11915,11 +12368,18 @@ function terminalLogLinesForOutbox(
|
|
|
11915
12368
|
return [];
|
|
11916
12369
|
}
|
|
11917
12370
|
return logs
|
|
11918
|
-
.filter(
|
|
11919
|
-
(
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
|
|
12371
|
+
.filter((line): line is string => {
|
|
12372
|
+
if (typeof line !== 'string') return false;
|
|
12373
|
+
const normalized = readProvenanceTag(line).line.trim();
|
|
12374
|
+
// Retain bounded terminal measurement summaries alongside timestamped
|
|
12375
|
+
// customer/recovery lines after a detached runner has exited.
|
|
12376
|
+
return (
|
|
12377
|
+
CTX_LOG_TIMESTAMP_PREFIX_RE.test(normalized) ||
|
|
12378
|
+
normalized.startsWith('[runtime.phase_summary] ') ||
|
|
12379
|
+
normalized.startsWith('[runtime.logs] ') ||
|
|
12380
|
+
normalized.startsWith('[runtime.log_transport] ')
|
|
12381
|
+
);
|
|
12382
|
+
})
|
|
11923
12383
|
.map((line) => line.trim())
|
|
11924
12384
|
.slice(-LOG_TAIL_LIMIT);
|
|
11925
12385
|
}
|
|
@@ -11961,6 +12421,9 @@ function attachRunnerWorkProgressMetadata(
|
|
|
11961
12421
|
options.logMetadata === 'omit' || metadata?.runLogTail || logs.length === 0
|
|
11962
12422
|
? null
|
|
11963
12423
|
: boundedRunLogTailMetadata(logs);
|
|
12424
|
+
const runtimeResources = metadata?.runtimeResources
|
|
12425
|
+
? null
|
|
12426
|
+
: boundedRuntimeResourcesMetadata(runnerResult);
|
|
11964
12427
|
const outputWarnings = Array.isArray(
|
|
11965
12428
|
(runnerResult as { outputWarnings?: unknown }).outputWarnings,
|
|
11966
12429
|
)
|
|
@@ -12002,6 +12465,7 @@ function attachRunnerWorkProgressMetadata(
|
|
|
12002
12465
|
if (
|
|
12003
12466
|
(total === null || skipped === null || metadata?.workProgress) &&
|
|
12004
12467
|
!runLogTail &&
|
|
12468
|
+
!runtimeResources &&
|
|
12005
12469
|
mergedOutputWarnings.length === 0
|
|
12006
12470
|
) {
|
|
12007
12471
|
return output;
|
|
@@ -12034,6 +12498,7 @@ function attachRunnerWorkProgressMetadata(
|
|
|
12034
12498
|
...(metadata ?? {}),
|
|
12035
12499
|
...(workProgress ? { workProgress } : {}),
|
|
12036
12500
|
...(runLogTail ? { runLogTail } : {}),
|
|
12501
|
+
...(runtimeResources ? { runtimeResources } : {}),
|
|
12037
12502
|
...(mergedOutputWarnings.length > 0
|
|
12038
12503
|
? { outputWarnings: mergedOutputWarnings }
|
|
12039
12504
|
: {}),
|
|
@@ -12185,6 +12650,10 @@ export async function executePostgresSchedulerClaim(
|
|
|
12185
12650
|
leaseToken: claim.leaseToken,
|
|
12186
12651
|
error: message,
|
|
12187
12652
|
result: terminalFailureResult,
|
|
12653
|
+
terminalLogLines: terminalLogLinesForOutbox(
|
|
12654
|
+
terminalFailureResult,
|
|
12655
|
+
options.terminalLogOutbox,
|
|
12656
|
+
),
|
|
12188
12657
|
sandboxCrashDiagnostic: options.sandboxCrashDiagnostic,
|
|
12189
12658
|
},
|
|
12190
12659
|
options,
|
|
@@ -12495,10 +12964,21 @@ function postgresSchedulerReceiptLeaseId(input: {
|
|
|
12495
12964
|
|
|
12496
12965
|
function mapReceiptRow(row: ReceiptRow): RuntimeStepReceipt {
|
|
12497
12966
|
const leaseExpiresAt = row.lease_expires_at;
|
|
12967
|
+
const outputRef = isRuntimeReceiptValueReference(row.output_ref)
|
|
12968
|
+
? row.output_ref
|
|
12969
|
+
: null;
|
|
12498
12970
|
return {
|
|
12499
12971
|
key: row.k,
|
|
12500
12972
|
status: row.status as RuntimeStepReceipt['status'],
|
|
12501
12973
|
...(row.output_json === null ? {} : { output: row.output_json }),
|
|
12974
|
+
...(outputRef
|
|
12975
|
+
? {
|
|
12976
|
+
outputRef,
|
|
12977
|
+
...(outputRef.inlineFields
|
|
12978
|
+
? { inlineFields: outputRef.inlineFields }
|
|
12979
|
+
: {}),
|
|
12980
|
+
}
|
|
12981
|
+
: {}),
|
|
12502
12982
|
...(row.error ? { error: row.error } : {}),
|
|
12503
12983
|
...(row.error_payload &&
|
|
12504
12984
|
typeof row.error_payload === 'object' &&
|
|
@@ -12529,7 +13009,7 @@ export async function getPostgresSchedulerReceipt(
|
|
|
12529
13009
|
): Promise<RuntimeStepReceipt | null> {
|
|
12530
13010
|
const result = await client.query<ReceiptRow>(
|
|
12531
13011
|
`
|
|
12532
|
-
SELECT k, status, output_json, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13012
|
+
SELECT k, status, output_json, output_ref, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
12533
13013
|
FROM ${tableName('receipts', options)}
|
|
12534
13014
|
WHERE org_id = $1
|
|
12535
13015
|
AND k = $2
|
|
@@ -12555,6 +13035,7 @@ export async function getPostgresSchedulerReceipts(
|
|
|
12555
13035
|
SELECT receipt.k,
|
|
12556
13036
|
receipt.status,
|
|
12557
13037
|
receipt.output_json,
|
|
13038
|
+
receipt.output_ref,
|
|
12558
13039
|
receipt.error,
|
|
12559
13040
|
receipt.error_payload,
|
|
12560
13041
|
receipt.failure_kind,
|
|
@@ -12778,7 +13259,7 @@ export async function claimPostgresSchedulerReceipt(
|
|
|
12778
13259
|
-- Completed/skipped receipts are immutable cache hits unless a caller
|
|
12779
13260
|
-- explicitly forces refresh. Read them before the UPSERT so popular
|
|
12780
13261
|
-- prebuilt keys do not take a conflicting-row lock or create WAL.
|
|
12781
|
-
SELECT k, status, output_json, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13262
|
+
SELECT k, status, output_json, output_ref, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
12782
13263
|
FROM ${tableName('receipts', options)}
|
|
12783
13264
|
WHERE org_id = $9
|
|
12784
13265
|
AND k = $1
|
|
@@ -12822,6 +13303,7 @@ export async function claimPostgresSchedulerReceipt(
|
|
|
12822
13303
|
ELSE now() + ($6::double precision * interval '1 millisecond')
|
|
12823
13304
|
END,
|
|
12824
13305
|
output_json = NULL,
|
|
13306
|
+
output_ref = NULL,
|
|
12825
13307
|
error = NULL,
|
|
12826
13308
|
error_payload = NULL,
|
|
12827
13309
|
failure_kind = 0,
|
|
@@ -12855,13 +13337,13 @@ export async function claimPostgresSchedulerReceipt(
|
|
|
12855
13337
|
AND COALESCE(${tableName('receipts', options)}.lease_owner_attempt, 0) < $5::integer
|
|
12856
13338
|
)
|
|
12857
13339
|
)
|
|
12858
|
-
RETURNING k, status, output_json, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13340
|
+
RETURNING k, status, output_json, output_ref, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
12859
13341
|
)
|
|
12860
|
-
SELECT k, status, output_json, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at,
|
|
13342
|
+
SELECT k, status, output_json, output_ref, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at,
|
|
12861
13343
|
'existing'::text AS claim_state
|
|
12862
13344
|
FROM terminal
|
|
12863
13345
|
UNION ALL
|
|
12864
|
-
SELECT k, status, output_json, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at,
|
|
13346
|
+
SELECT k, status, output_json, output_ref, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at,
|
|
12865
13347
|
'claimed'::text AS claim_state
|
|
12866
13348
|
FROM claimed
|
|
12867
13349
|
`,
|
|
@@ -12939,6 +13421,7 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
12939
13421
|
SELECT receipt.k,
|
|
12940
13422
|
receipt.status,
|
|
12941
13423
|
receipt.output_json,
|
|
13424
|
+
receipt.output_ref,
|
|
12942
13425
|
receipt.error,
|
|
12943
13426
|
receipt.error_payload,
|
|
12944
13427
|
receipt.failure_kind,
|
|
@@ -13004,6 +13487,7 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13004
13487
|
ELSE now() + ($6::double precision * interval '1 millisecond')
|
|
13005
13488
|
END,
|
|
13006
13489
|
output_json = NULL,
|
|
13490
|
+
output_ref = NULL,
|
|
13007
13491
|
error = NULL,
|
|
13008
13492
|
error_payload = NULL,
|
|
13009
13493
|
failure_kind = 0,
|
|
@@ -13022,12 +13506,13 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13022
13506
|
AND ${tableName('receipts', options)}.lease_expires_at IS NULL
|
|
13023
13507
|
OR ${tableName('receipts', options)}.lease_expires_at <= now()
|
|
13024
13508
|
)
|
|
13025
|
-
RETURNING k, status, output_json, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13509
|
+
RETURNING k, status, output_json, output_ref, error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13026
13510
|
),
|
|
13027
13511
|
latest AS (
|
|
13028
13512
|
SELECT receipt.k,
|
|
13029
13513
|
receipt.status,
|
|
13030
13514
|
receipt.output_json,
|
|
13515
|
+
receipt.output_ref,
|
|
13031
13516
|
receipt.error,
|
|
13032
13517
|
receipt.error_payload,
|
|
13033
13518
|
receipt.failure_kind,
|
|
@@ -13064,6 +13549,7 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13064
13549
|
SELECT terminal.k,
|
|
13065
13550
|
terminal.status,
|
|
13066
13551
|
terminal.output_json,
|
|
13552
|
+
terminal.output_ref,
|
|
13067
13553
|
terminal.error,
|
|
13068
13554
|
terminal.error_payload,
|
|
13069
13555
|
terminal.failure_kind,
|
|
@@ -13080,6 +13566,7 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13080
13566
|
SELECT claimed.k,
|
|
13081
13567
|
claimed.status,
|
|
13082
13568
|
claimed.output_json,
|
|
13569
|
+
claimed.output_ref,
|
|
13083
13570
|
claimed.error,
|
|
13084
13571
|
claimed.error_payload,
|
|
13085
13572
|
claimed.failure_kind,
|
|
@@ -13097,6 +13584,7 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13097
13584
|
SELECT latest.k,
|
|
13098
13585
|
latest.status,
|
|
13099
13586
|
latest.output_json,
|
|
13587
|
+
latest.output_ref,
|
|
13100
13588
|
latest.error,
|
|
13101
13589
|
latest.error_payload,
|
|
13102
13590
|
latest.failure_kind,
|
|
@@ -13116,6 +13604,7 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13116
13604
|
SELECT unioned.k,
|
|
13117
13605
|
unioned.status,
|
|
13118
13606
|
unioned.output_json,
|
|
13607
|
+
unioned.output_ref,
|
|
13119
13608
|
unioned.error,
|
|
13120
13609
|
unioned.error_payload,
|
|
13121
13610
|
unioned.failure_kind,
|
|
@@ -13156,22 +13645,88 @@ export async function claimPostgresSchedulerReceipts(
|
|
|
13156
13645
|
);
|
|
13157
13646
|
}
|
|
13158
13647
|
|
|
13648
|
+
/**
|
|
13649
|
+
* A storage completion whose value is raw or already canonicalized by the
|
|
13650
|
+
* gateway. Transport callers retain the public `output` requirement; the
|
|
13651
|
+
* gateway intentionally supplies only `outputJson` or `outputRef` after it has
|
|
13652
|
+
* released that duplicate structured graph.
|
|
13653
|
+
*/
|
|
13654
|
+
type RuntimeStepReceiptCompletionValue = {
|
|
13655
|
+
output?: unknown;
|
|
13656
|
+
outputJson?: string | null;
|
|
13657
|
+
outputRef?: RuntimeReceiptValueReference | null;
|
|
13658
|
+
};
|
|
13659
|
+
|
|
13660
|
+
type RuntimeStepReceiptCompletionInput = Omit<
|
|
13661
|
+
CompleteRuntimeStepReceiptInput,
|
|
13662
|
+
'output'
|
|
13663
|
+
> &
|
|
13664
|
+
RuntimeStepReceiptCompletionValue;
|
|
13665
|
+
|
|
13666
|
+
type RuntimeStepReceiptCompletionBatchInput = Omit<
|
|
13667
|
+
CompleteRuntimeStepReceiptsInput['receipts'][number],
|
|
13668
|
+
'output'
|
|
13669
|
+
> &
|
|
13670
|
+
RuntimeStepReceiptCompletionValue;
|
|
13671
|
+
|
|
13672
|
+
/**
|
|
13673
|
+
* Canonical `jsonb` text for one receipt completion.
|
|
13674
|
+
*
|
|
13675
|
+
* The receipt gateway already redacted and serialized the output once, and
|
|
13676
|
+
* hands that exact text through as `outputJson`, so the same value is not
|
|
13677
|
+
* walked a second time here. Callers that pass only a value get the identical
|
|
13678
|
+
* bytes from `stringifyPostgresJson`.
|
|
13679
|
+
*/
|
|
13680
|
+
function receiptOutputJson(
|
|
13681
|
+
input: RuntimeStepReceiptCompletionValue,
|
|
13682
|
+
): string | null {
|
|
13683
|
+
if (input.outputRef) {
|
|
13684
|
+
if (input.outputJson !== null && input.outputJson !== undefined) {
|
|
13685
|
+
throw new Error(
|
|
13686
|
+
'Receipt completion cannot contain inline output and outputRef.',
|
|
13687
|
+
);
|
|
13688
|
+
}
|
|
13689
|
+
return null;
|
|
13690
|
+
}
|
|
13691
|
+
return (
|
|
13692
|
+
input.outputJson ??
|
|
13693
|
+
stringifyRequiredReceiptJson(input.output ?? null, 'Receipt output')
|
|
13694
|
+
);
|
|
13695
|
+
}
|
|
13696
|
+
|
|
13697
|
+
function receiptOutputRefJson(
|
|
13698
|
+
input: RuntimeStepReceiptCompletionValue,
|
|
13699
|
+
): string | null {
|
|
13700
|
+
return input.outputRef
|
|
13701
|
+
? stringifyRequiredReceiptJson(input.outputRef, 'Receipt output reference')
|
|
13702
|
+
: null;
|
|
13703
|
+
}
|
|
13704
|
+
|
|
13705
|
+
function stringifyRequiredReceiptJson(value: unknown, field: string): string {
|
|
13706
|
+
const serialized = stringifyPostgresJson(value);
|
|
13707
|
+
if (serialized === undefined) {
|
|
13708
|
+
throw new TypeError(`${field} must be JSON-serializable.`);
|
|
13709
|
+
}
|
|
13710
|
+
return serialized;
|
|
13711
|
+
}
|
|
13712
|
+
|
|
13159
13713
|
export async function completePostgresSchedulerReceipt(
|
|
13160
13714
|
client: PostgresSchedulerQueryClient,
|
|
13161
|
-
input:
|
|
13715
|
+
input: RuntimeStepReceiptCompletionInput & { returnOutput?: boolean },
|
|
13162
13716
|
options: PostgresSchedulerReceiptOptions,
|
|
13163
13717
|
): Promise<RuntimeStepReceipt | null> {
|
|
13164
13718
|
if (input.leaseId === COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID) {
|
|
13165
13719
|
const inserted = await client.query<ReceiptRow>(
|
|
13166
13720
|
`
|
|
13167
13721
|
INSERT INTO ${tableName('receipts', options)} AS receipt (
|
|
13168
|
-
org_id, k, status, output_json, error, error_payload, failure_kind, run_id,
|
|
13722
|
+
org_id, k, status, output_json, output_ref, error, error_payload, failure_kind, run_id,
|
|
13169
13723
|
lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13170
|
-
) VALUES ($1, $2, 'completed', $3::jsonb, NULL, NULL, 0, $
|
|
13724
|
+
) VALUES ($1, $2, 'completed', $3::jsonb, $4::jsonb, NULL, NULL, 0, $5,
|
|
13171
13725
|
NULL, NULL, NULL, NULL)
|
|
13172
13726
|
ON CONFLICT (org_id, k) DO UPDATE
|
|
13173
13727
|
SET status = EXCLUDED.status,
|
|
13174
13728
|
output_json = EXCLUDED.output_json,
|
|
13729
|
+
output_ref = EXCLUDED.output_ref,
|
|
13175
13730
|
error = NULL,
|
|
13176
13731
|
error_payload = NULL,
|
|
13177
13732
|
failure_kind = 0,
|
|
@@ -13183,25 +13738,33 @@ export async function completePostgresSchedulerReceipt(
|
|
|
13183
13738
|
updated_at = now()
|
|
13184
13739
|
WHERE receipt.status NOT IN ('completed', 'skipped')
|
|
13185
13740
|
RETURNING k, status,
|
|
13186
|
-
CASE WHEN $
|
|
13741
|
+
CASE WHEN $6::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
|
|
13742
|
+
output_ref,
|
|
13187
13743
|
error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id,
|
|
13188
13744
|
lease_owner_attempt, lease_expires_at
|
|
13189
13745
|
`,
|
|
13190
13746
|
[
|
|
13191
13747
|
receiptOrgId(options),
|
|
13192
13748
|
input.key,
|
|
13193
|
-
|
|
13749
|
+
receiptOutputJson(input),
|
|
13750
|
+
receiptOutputRefJson(input),
|
|
13194
13751
|
input.runId,
|
|
13195
13752
|
input.returnOutput !== false,
|
|
13196
13753
|
],
|
|
13197
13754
|
);
|
|
13198
|
-
if (inserted.rows[0])
|
|
13755
|
+
if (inserted.rows[0]) {
|
|
13756
|
+
return {
|
|
13757
|
+
...mapReceiptRow(inserted.rows[0]),
|
|
13758
|
+
completionDisposition: 'persisted',
|
|
13759
|
+
};
|
|
13760
|
+
}
|
|
13199
13761
|
// A separate statement gets a fresh READ COMMITTED snapshot after a
|
|
13200
13762
|
// concurrent insert wins and commits.
|
|
13201
13763
|
const winner = await client.query<ReceiptRow>(
|
|
13202
13764
|
`
|
|
13203
13765
|
SELECT k, status,
|
|
13204
13766
|
CASE WHEN $3::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
|
|
13767
|
+
output_ref,
|
|
13205
13768
|
error, failure_kind, run_id, lease_id, lease_owner_run_id,
|
|
13206
13769
|
lease_owner_attempt, lease_expires_at
|
|
13207
13770
|
FROM ${tableName('receipts', options)}
|
|
@@ -13212,7 +13775,12 @@ export async function completePostgresSchedulerReceipt(
|
|
|
13212
13775
|
`,
|
|
13213
13776
|
[receiptOrgId(options), input.key, input.returnOutput !== false],
|
|
13214
13777
|
);
|
|
13215
|
-
return winner.rows[0]
|
|
13778
|
+
return winner.rows[0]
|
|
13779
|
+
? {
|
|
13780
|
+
...mapReceiptRow(winner.rows[0]),
|
|
13781
|
+
completionDisposition: 'existing',
|
|
13782
|
+
}
|
|
13783
|
+
: null;
|
|
13216
13784
|
}
|
|
13217
13785
|
const runAttempt = normalizePostgresSchedulerReceiptRunAttempt(
|
|
13218
13786
|
input.runAttempt,
|
|
@@ -13223,6 +13791,7 @@ export async function completePostgresSchedulerReceipt(
|
|
|
13223
13791
|
UPDATE ${tableName('receipts', options)}
|
|
13224
13792
|
SET status = 'completed',
|
|
13225
13793
|
output_json = $3::jsonb,
|
|
13794
|
+
output_ref = $7::jsonb,
|
|
13226
13795
|
error = NULL,
|
|
13227
13796
|
error_payload = NULL,
|
|
13228
13797
|
failure_kind = 0,
|
|
@@ -13243,51 +13812,226 @@ export async function completePostgresSchedulerReceipt(
|
|
|
13243
13812
|
)
|
|
13244
13813
|
)
|
|
13245
13814
|
RETURNING k, status,
|
|
13246
|
-
CASE WHEN $
|
|
13815
|
+
CASE WHEN $8::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
|
|
13816
|
+
output_ref,
|
|
13247
13817
|
error, failure_kind, run_id, lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13248
13818
|
`,
|
|
13249
13819
|
[
|
|
13250
13820
|
input.key,
|
|
13251
13821
|
input.runId,
|
|
13252
|
-
|
|
13822
|
+
receiptOutputJson(input),
|
|
13253
13823
|
runAttempt,
|
|
13254
13824
|
leaseId,
|
|
13255
13825
|
receiptOrgId(options),
|
|
13826
|
+
receiptOutputRefJson(input),
|
|
13256
13827
|
input.returnOutput !== false,
|
|
13257
13828
|
],
|
|
13258
13829
|
);
|
|
13259
|
-
return result.rows[0]
|
|
13830
|
+
return result.rows[0]
|
|
13831
|
+
? {
|
|
13832
|
+
...mapReceiptRow(result.rows[0]),
|
|
13833
|
+
completionDisposition: 'persisted',
|
|
13834
|
+
}
|
|
13835
|
+
: null;
|
|
13836
|
+
}
|
|
13837
|
+
|
|
13838
|
+
/**
|
|
13839
|
+
* Publish a batch of immutable completed receipts (receipt-cache inserts) in
|
|
13840
|
+
* one INSERT ... ON CONFLICT statement, with a second statement only for the
|
|
13841
|
+
* keys a concurrent publisher already won. Semantics match the scalar
|
|
13842
|
+
* `completePostgresSchedulerReceipt` cache-insert path receipt by receipt: a
|
|
13843
|
+
* key this batch persists reports `persisted`; a key already completed or
|
|
13844
|
+
* skipped by someone else (including an earlier duplicate of the same key in
|
|
13845
|
+
* this batch) reports the winner's row as `existing`; a key that is neither
|
|
13846
|
+
* reports null.
|
|
13847
|
+
*/
|
|
13848
|
+
async function completePostgresSchedulerCacheInsertReceipts(
|
|
13849
|
+
client: PostgresSchedulerQueryClient,
|
|
13850
|
+
input: {
|
|
13851
|
+
receipts: ReadonlyArray<RuntimeStepReceiptCompletionBatchInput>;
|
|
13852
|
+
returnOutput?: boolean;
|
|
13853
|
+
},
|
|
13854
|
+
options: PostgresSchedulerReceiptOptions,
|
|
13855
|
+
): Promise<Array<RuntimeStepReceipt | null>> {
|
|
13856
|
+
if (input.receipts.length === 0) return [];
|
|
13857
|
+
const returnOutput = input.returnOutput !== false;
|
|
13858
|
+
const orgId = receiptOrgId(options);
|
|
13859
|
+
// ON CONFLICT DO UPDATE rejects a statement that touches the same row
|
|
13860
|
+
// twice, so a key repeated inside one batch is inserted once; its later
|
|
13861
|
+
// duplicates resolve through the winner read exactly as the scalar path
|
|
13862
|
+
// resolved a second publisher.
|
|
13863
|
+
const firstOccurrence = new Map<string, number>();
|
|
13864
|
+
input.receipts.forEach((receipt, index) => {
|
|
13865
|
+
if (!firstOccurrence.has(receipt.key))
|
|
13866
|
+
firstOccurrence.set(receipt.key, index);
|
|
13867
|
+
});
|
|
13868
|
+
const insertIndexes = [...firstOccurrence.values()];
|
|
13869
|
+
const inserted = await client.query<ReceiptRow>(
|
|
13870
|
+
`
|
|
13871
|
+
INSERT INTO ${tableName('receipts', options)} AS receipt (
|
|
13872
|
+
org_id, k, status, output_json, output_ref, error, error_payload, failure_kind, run_id,
|
|
13873
|
+
lease_id, lease_owner_run_id, lease_owner_attempt, lease_expires_at
|
|
13874
|
+
)
|
|
13875
|
+
SELECT $1, key_values.k, 'completed', output_values.output_json::jsonb,
|
|
13876
|
+
ref_values.output_ref::jsonb, NULL, NULL, 0,
|
|
13877
|
+
run_values.run_id, NULL, NULL, NULL, NULL
|
|
13878
|
+
FROM unnest($2::text[]) WITH ORDINALITY AS key_values(k, ord)
|
|
13879
|
+
JOIN unnest($3::text[]) WITH ORDINALITY AS output_values(output_json, ord)
|
|
13880
|
+
ON output_values.ord = key_values.ord
|
|
13881
|
+
JOIN unnest($4::text[]) WITH ORDINALITY AS ref_values(output_ref, ord)
|
|
13882
|
+
ON ref_values.ord = key_values.ord
|
|
13883
|
+
JOIN unnest($5::text[]) WITH ORDINALITY AS run_values(run_id, ord)
|
|
13884
|
+
ON run_values.ord = key_values.ord
|
|
13885
|
+
ON CONFLICT (org_id, k) DO UPDATE
|
|
13886
|
+
SET status = EXCLUDED.status,
|
|
13887
|
+
output_json = EXCLUDED.output_json,
|
|
13888
|
+
output_ref = EXCLUDED.output_ref,
|
|
13889
|
+
error = NULL,
|
|
13890
|
+
error_payload = NULL,
|
|
13891
|
+
failure_kind = 0,
|
|
13892
|
+
run_id = EXCLUDED.run_id,
|
|
13893
|
+
lease_id = NULL,
|
|
13894
|
+
lease_owner_run_id = NULL,
|
|
13895
|
+
lease_owner_attempt = NULL,
|
|
13896
|
+
lease_expires_at = NULL,
|
|
13897
|
+
updated_at = now()
|
|
13898
|
+
WHERE receipt.status NOT IN ('completed', 'skipped')
|
|
13899
|
+
RETURNING k, status,
|
|
13900
|
+
CASE WHEN $6::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
|
|
13901
|
+
output_ref,
|
|
13902
|
+
error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id,
|
|
13903
|
+
lease_owner_attempt, lease_expires_at
|
|
13904
|
+
`,
|
|
13905
|
+
[
|
|
13906
|
+
orgId,
|
|
13907
|
+
insertIndexes.map((index) => input.receipts[index]!.key),
|
|
13908
|
+
insertIndexes.map((index) => receiptOutputJson(input.receipts[index]!)),
|
|
13909
|
+
insertIndexes.map((index) =>
|
|
13910
|
+
receiptOutputRefJson(input.receipts[index]!),
|
|
13911
|
+
),
|
|
13912
|
+
insertIndexes.map((index) => input.receipts[index]!.runId),
|
|
13913
|
+
returnOutput,
|
|
13914
|
+
],
|
|
13915
|
+
);
|
|
13916
|
+
const persistedByKey = new Map(inserted.rows.map((row) => [row.k, row]));
|
|
13917
|
+
const results: Array<RuntimeStepReceipt | null> = new Array(
|
|
13918
|
+
input.receipts.length,
|
|
13919
|
+
).fill(null);
|
|
13920
|
+
const unresolvedKeys = new Set<string>();
|
|
13921
|
+
input.receipts.forEach((receipt, index) => {
|
|
13922
|
+
const row = persistedByKey.get(receipt.key);
|
|
13923
|
+
if (row && firstOccurrence.get(receipt.key) === index) {
|
|
13924
|
+
results[index] = {
|
|
13925
|
+
...mapReceiptRow(row),
|
|
13926
|
+
completionDisposition: 'persisted',
|
|
13927
|
+
};
|
|
13928
|
+
} else {
|
|
13929
|
+
unresolvedKeys.add(receipt.key);
|
|
13930
|
+
}
|
|
13931
|
+
});
|
|
13932
|
+
if (unresolvedKeys.size === 0) return results;
|
|
13933
|
+
// A separate statement gets a fresh READ COMMITTED snapshot after a
|
|
13934
|
+
// concurrent publisher wins and commits.
|
|
13935
|
+
const winners = await client.query<ReceiptRow>(
|
|
13936
|
+
`
|
|
13937
|
+
SELECT k, status,
|
|
13938
|
+
CASE WHEN $3::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
|
|
13939
|
+
output_ref,
|
|
13940
|
+
error, error_payload, failure_kind, run_id, lease_id, lease_owner_run_id,
|
|
13941
|
+
lease_owner_attempt, lease_expires_at
|
|
13942
|
+
FROM ${tableName('receipts', options)}
|
|
13943
|
+
WHERE org_id = $1
|
|
13944
|
+
AND k = ANY($2::text[])
|
|
13945
|
+
AND status IN ('completed', 'skipped')
|
|
13946
|
+
`,
|
|
13947
|
+
[orgId, [...unresolvedKeys], returnOutput],
|
|
13948
|
+
);
|
|
13949
|
+
const winnerByKey = new Map(winners.rows.map((row) => [row.k, row]));
|
|
13950
|
+
let alreadyWon = 0;
|
|
13951
|
+
let duplicatedInBatch = 0;
|
|
13952
|
+
let unresolved = 0;
|
|
13953
|
+
input.receipts.forEach((receipt, index) => {
|
|
13954
|
+
if (results[index]) return;
|
|
13955
|
+
const row = winnerByKey.get(receipt.key);
|
|
13956
|
+
results[index] = row
|
|
13957
|
+
? { ...mapReceiptRow(row), completionDisposition: 'existing' }
|
|
13958
|
+
: null;
|
|
13959
|
+
if (!row) unresolved += 1;
|
|
13960
|
+
else if (firstOccurrence.get(receipt.key) === index) alreadyWon += 1;
|
|
13961
|
+
else duplicatedInBatch += 1;
|
|
13962
|
+
});
|
|
13963
|
+
// A losing publisher is the intended path, but every one costs the client
|
|
13964
|
+
// a full-output read-back. Count them per batch so a read-back rate above
|
|
13965
|
+
// the duplicate-call rate is visible in the gateway log without tracing.
|
|
13966
|
+
console.info('[postgres-scheduler.receipt_publication_batch]', {
|
|
13967
|
+
receipts: input.receipts.length,
|
|
13968
|
+
persisted:
|
|
13969
|
+
input.receipts.length - alreadyWon - duplicatedInBatch - unresolved,
|
|
13970
|
+
alreadyWon,
|
|
13971
|
+
duplicatedInBatch,
|
|
13972
|
+
unresolved,
|
|
13973
|
+
});
|
|
13974
|
+
return results;
|
|
13260
13975
|
}
|
|
13261
13976
|
|
|
13262
13977
|
export async function completePostgresSchedulerReceipts(
|
|
13263
13978
|
client: PostgresSchedulerQueryClient,
|
|
13264
|
-
input:
|
|
13979
|
+
input: {
|
|
13980
|
+
receipts: ReadonlyArray<RuntimeStepReceiptCompletionBatchInput>;
|
|
13981
|
+
returnOutput?: boolean;
|
|
13982
|
+
},
|
|
13265
13983
|
options: PostgresSchedulerReceiptOptions,
|
|
13266
13984
|
): Promise<Array<RuntimeStepReceipt | null>> {
|
|
13267
13985
|
if (input.receipts.length === 0) return [];
|
|
13268
|
-
|
|
13269
|
-
|
|
13270
|
-
|
|
13271
|
-
|
|
13272
|
-
|
|
13986
|
+
const cacheInsertIndexes: number[] = [];
|
|
13987
|
+
const leasedIndexes: number[] = [];
|
|
13988
|
+
input.receipts.forEach((receipt, index) => {
|
|
13989
|
+
(receipt.leaseId === COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID
|
|
13990
|
+
? cacheInsertIndexes
|
|
13991
|
+
: leasedIndexes
|
|
13992
|
+
).push(index);
|
|
13993
|
+
});
|
|
13994
|
+
if (cacheInsertIndexes.length > 0) {
|
|
13273
13995
|
// Immutable cache publication is a different operation from leased Work
|
|
13274
|
-
// Receipt completion
|
|
13275
|
-
//
|
|
13276
|
-
|
|
13277
|
-
|
|
13278
|
-
|
|
13279
|
-
|
|
13280
|
-
|
|
13281
|
-
|
|
13282
|
-
|
|
13283
|
-
|
|
13996
|
+
// Receipt completion, so a mixed batch runs as two set statements, never
|
|
13997
|
+
// as one receipt per query. A runner batch is whatever settled during one
|
|
13998
|
+
// gateway round trip, so under load it mixes both kinds every time; the
|
|
13999
|
+
// per-receipt fallback this replaces cost 229 serialized queries on one
|
|
14000
|
+
// connection for a single 1k-fixture completion request.
|
|
14001
|
+
const results: Array<RuntimeStepReceipt | null> = new Array(
|
|
14002
|
+
input.receipts.length,
|
|
14003
|
+
).fill(null);
|
|
14004
|
+
const published = await completePostgresSchedulerCacheInsertReceipts(
|
|
14005
|
+
client,
|
|
14006
|
+
{
|
|
14007
|
+
receipts: cacheInsertIndexes.map((index) => input.receipts[index]!),
|
|
14008
|
+
returnOutput: input.returnOutput,
|
|
14009
|
+
},
|
|
14010
|
+
options,
|
|
13284
14011
|
);
|
|
14012
|
+
cacheInsertIndexes.forEach((index, position) => {
|
|
14013
|
+
results[index] = published[position]!;
|
|
14014
|
+
});
|
|
14015
|
+
if (leasedIndexes.length > 0) {
|
|
14016
|
+
const completed = await completePostgresSchedulerReceipts(
|
|
14017
|
+
client,
|
|
14018
|
+
{
|
|
14019
|
+
receipts: leasedIndexes.map((index) => input.receipts[index]!),
|
|
14020
|
+
returnOutput: input.returnOutput,
|
|
14021
|
+
},
|
|
14022
|
+
options,
|
|
14023
|
+
);
|
|
14024
|
+
leasedIndexes.forEach((index, position) => {
|
|
14025
|
+
results[index] = completed[position]!;
|
|
14026
|
+
});
|
|
14027
|
+
}
|
|
14028
|
+
return results;
|
|
13285
14029
|
}
|
|
13286
14030
|
const normalized = input.receipts.map((receipt) => ({
|
|
13287
14031
|
...receipt,
|
|
13288
14032
|
runAttempt: normalizePostgresSchedulerReceiptRunAttempt(receipt.runAttempt),
|
|
13289
14033
|
leaseId: receipt.leaseId?.trim() || null,
|
|
13290
|
-
output:
|
|
14034
|
+
output: receiptOutputJson(receipt),
|
|
13291
14035
|
}));
|
|
13292
14036
|
const result = await client.query<ReceiptRow & { ord: number | string }>(
|
|
13293
14037
|
`
|
|
@@ -13297,6 +14041,7 @@ export async function completePostgresSchedulerReceipts(
|
|
|
13297
14041
|
attempt_values.run_attempt,
|
|
13298
14042
|
lease_values.lease_id,
|
|
13299
14043
|
output_values.output_json,
|
|
14044
|
+
ref_values.output_ref,
|
|
13300
14045
|
key_values.ord
|
|
13301
14046
|
FROM unnest($1::text[]) WITH ORDINALITY AS key_values(k, ord)
|
|
13302
14047
|
JOIN unnest($2::text[]) WITH ORDINALITY AS run_values(run_id, ord)
|
|
@@ -13307,11 +14052,14 @@ export async function completePostgresSchedulerReceipts(
|
|
|
13307
14052
|
ON lease_values.ord = key_values.ord
|
|
13308
14053
|
JOIN unnest($5::text[]) WITH ORDINALITY AS output_values(output_json, ord)
|
|
13309
14054
|
ON output_values.ord = key_values.ord
|
|
14055
|
+
JOIN unnest($6::text[]) WITH ORDINALITY AS ref_values(output_ref, ord)
|
|
14056
|
+
ON ref_values.ord = key_values.ord
|
|
13310
14057
|
),
|
|
13311
14058
|
completed AS (
|
|
13312
14059
|
UPDATE ${tableName('receipts', options)} AS target
|
|
13313
14060
|
SET status = 'completed',
|
|
13314
14061
|
output_json = inputs.output_json::jsonb,
|
|
14062
|
+
output_ref = inputs.output_ref::jsonb,
|
|
13315
14063
|
error = NULL,
|
|
13316
14064
|
error_payload = NULL,
|
|
13317
14065
|
failure_kind = 0,
|
|
@@ -13320,7 +14068,7 @@ export async function completePostgresSchedulerReceipts(
|
|
|
13320
14068
|
updated_at = now()
|
|
13321
14069
|
FROM inputs
|
|
13322
14070
|
WHERE target.k = inputs.k
|
|
13323
|
-
AND target.org_id = $
|
|
14071
|
+
AND target.org_id = $7
|
|
13324
14072
|
AND target.run_id = inputs.run_id
|
|
13325
14073
|
AND target.status = 'running'
|
|
13326
14074
|
AND COALESCE(target.lease_owner_run_id, target.run_id) = inputs.run_id
|
|
@@ -13334,7 +14082,8 @@ export async function completePostgresSchedulerReceipts(
|
|
|
13334
14082
|
)
|
|
13335
14083
|
RETURNING target.k,
|
|
13336
14084
|
target.status,
|
|
13337
|
-
CASE WHEN $
|
|
14085
|
+
CASE WHEN $8::boolean THEN target.output_json ELSE NULL::jsonb END AS output_json,
|
|
14086
|
+
target.output_ref,
|
|
13338
14087
|
target.error,
|
|
13339
14088
|
target.error_payload,
|
|
13340
14089
|
target.failure_kind,
|
|
@@ -13348,6 +14097,7 @@ export async function completePostgresSchedulerReceipts(
|
|
|
13348
14097
|
SELECT completed.k,
|
|
13349
14098
|
completed.status,
|
|
13350
14099
|
completed.output_json,
|
|
14100
|
+
completed.output_ref,
|
|
13351
14101
|
completed.error,
|
|
13352
14102
|
completed.error_payload,
|
|
13353
14103
|
completed.failure_kind,
|
|
@@ -13367,11 +14117,19 @@ export async function completePostgresSchedulerReceipts(
|
|
|
13367
14117
|
normalized.map((receipt) => receipt.runAttempt),
|
|
13368
14118
|
normalized.map((receipt) => receipt.leaseId),
|
|
13369
14119
|
normalized.map((receipt) => receipt.output),
|
|
14120
|
+
normalized.map((receipt) => receiptOutputRefJson(receipt)),
|
|
13370
14121
|
receiptOrgId(options),
|
|
13371
14122
|
input.returnOutput !== false,
|
|
13372
14123
|
],
|
|
13373
14124
|
);
|
|
13374
|
-
return result.rows.map((row) =>
|
|
14125
|
+
return result.rows.map((row) =>
|
|
14126
|
+
row.k
|
|
14127
|
+
? {
|
|
14128
|
+
...mapReceiptRow(row),
|
|
14129
|
+
completionDisposition: 'persisted',
|
|
14130
|
+
}
|
|
14131
|
+
: null,
|
|
14132
|
+
);
|
|
13375
14133
|
}
|
|
13376
14134
|
|
|
13377
14135
|
export async function failPostgresSchedulerReceipt(
|
|
@@ -13388,6 +14146,7 @@ export async function failPostgresSchedulerReceipt(
|
|
|
13388
14146
|
UPDATE ${tableName('receipts', options)}
|
|
13389
14147
|
SET status = 'failed',
|
|
13390
14148
|
output_json = NULL,
|
|
14149
|
+
output_ref = NULL,
|
|
13391
14150
|
error = $3,
|
|
13392
14151
|
error_payload = $8::jsonb,
|
|
13393
14152
|
failure_kind = $6::smallint,
|
|
@@ -13467,6 +14226,7 @@ export async function failPostgresSchedulerReceipts(
|
|
|
13467
14226
|
UPDATE ${tableName('receipts', options)} AS target
|
|
13468
14227
|
SET status = 'failed',
|
|
13469
14228
|
output_json = NULL,
|
|
14229
|
+
output_ref = NULL,
|
|
13470
14230
|
error = inputs.error,
|
|
13471
14231
|
error_payload = inputs.error_payload,
|
|
13472
14232
|
failure_kind = inputs.failure_kind,
|
|
@@ -13546,6 +14306,7 @@ export async function releasePostgresSchedulerReceipt(
|
|
|
13546
14306
|
-- return to that claimable state for a later run to retry it.
|
|
13547
14307
|
SET status = 'pending',
|
|
13548
14308
|
output_json = NULL,
|
|
14309
|
+
output_ref = NULL,
|
|
13549
14310
|
error = NULL,
|
|
13550
14311
|
error_payload = NULL,
|
|
13551
14312
|
failure_kind = 0,
|
|
@@ -13572,6 +14333,22 @@ export async function releasePostgresSchedulerReceipt(
|
|
|
13572
14333
|
return result.rows[0] ? mapReceiptRow(result.rows[0]) : null;
|
|
13573
14334
|
}
|
|
13574
14335
|
|
|
14336
|
+
/**
|
|
14337
|
+
* The write that returns a claimed receipt to the free pool. Every release
|
|
14338
|
+
* path shares it so a successor's non-forced claim — whose status set is
|
|
14339
|
+
* `('pending','failed')` and whose lease guard requires no live lease — always
|
|
14340
|
+
* matches what a dead owner left behind.
|
|
14341
|
+
*/
|
|
14342
|
+
const RELEASE_RECEIPT_TO_PENDING_SET_CLAUSE = `status = 'pending',
|
|
14343
|
+
output_json = NULL,
|
|
14344
|
+
output_ref = NULL,
|
|
14345
|
+
error = NULL,
|
|
14346
|
+
error_payload = NULL,
|
|
14347
|
+
failure_kind = 0,
|
|
14348
|
+
lease_id = NULL,
|
|
14349
|
+
lease_expires_at = NULL,
|
|
14350
|
+
updated_at = now()`;
|
|
14351
|
+
|
|
13575
14352
|
export async function releasePostgresSchedulerReceiptsForAttempt(
|
|
13576
14353
|
client: PostgresSchedulerQueryClient,
|
|
13577
14354
|
input: { runId: string; runAttempt: number },
|
|
@@ -13583,14 +14360,7 @@ export async function releasePostgresSchedulerReceiptsForAttempt(
|
|
|
13583
14360
|
const released = await client.query<{ k: string }>(
|
|
13584
14361
|
`
|
|
13585
14362
|
UPDATE ${tableName('receipts', options)}
|
|
13586
|
-
SET
|
|
13587
|
-
output_json = NULL,
|
|
13588
|
-
error = NULL,
|
|
13589
|
-
error_payload = NULL,
|
|
13590
|
-
failure_kind = 0,
|
|
13591
|
-
lease_id = NULL,
|
|
13592
|
-
lease_expires_at = NULL,
|
|
13593
|
-
updated_at = now()
|
|
14363
|
+
SET ${RELEASE_RECEIPT_TO_PENDING_SET_CLAUSE}
|
|
13594
14364
|
WHERE org_id = $1
|
|
13595
14365
|
AND run_id = $2
|
|
13596
14366
|
AND status IN ('queued', 'running')
|
|
@@ -13843,6 +14613,7 @@ export async function skipPostgresSchedulerReceipt(
|
|
|
13843
14613
|
lease_owner_attempt = $4,
|
|
13844
14614
|
lease_expires_at = NULL,
|
|
13845
14615
|
output_json = $3::jsonb,
|
|
14616
|
+
output_ref = NULL,
|
|
13846
14617
|
error = NULL,
|
|
13847
14618
|
error_payload = NULL,
|
|
13848
14619
|
failure_kind = 0,
|
|
@@ -14945,8 +15716,14 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
14945
15716
|
];
|
|
14946
15717
|
}
|
|
14947
15718
|
if (event.eventType === 'run.completed') {
|
|
15719
|
+
const progressEvents = ledgerEventsPayloadField(
|
|
15720
|
+
event.payload,
|
|
15721
|
+
'terminalProgressEvents',
|
|
15722
|
+
event.runId,
|
|
15723
|
+
);
|
|
14948
15724
|
const lines = stringArrayPayloadField(event.payload, 'terminalLogLines');
|
|
14949
15725
|
return [
|
|
15726
|
+
...progressEvents,
|
|
14950
15727
|
...(lines.length === 0
|
|
14951
15728
|
? []
|
|
14952
15729
|
: [
|
|
@@ -14970,10 +15747,28 @@ export function buildRunLedgerEventsFromPostgresSchedulerOutbox(
|
|
|
14970
15747
|
];
|
|
14971
15748
|
}
|
|
14972
15749
|
if (event.eventType === 'run.failed') {
|
|
15750
|
+
const progressEvents = ledgerEventsPayloadField(
|
|
15751
|
+
event.payload,
|
|
15752
|
+
'terminalProgressEvents',
|
|
15753
|
+
event.runId,
|
|
15754
|
+
);
|
|
15755
|
+
const lines = stringArrayPayloadField(event.payload, 'terminalLogLines');
|
|
14973
15756
|
const result = terminalRunResultForLedger(
|
|
14974
15757
|
payloadField(event.payload, 'result'),
|
|
14975
15758
|
);
|
|
14976
15759
|
return [
|
|
15760
|
+
...progressEvents,
|
|
15761
|
+
...(lines.length === 0
|
|
15762
|
+
? []
|
|
15763
|
+
: [
|
|
15764
|
+
{
|
|
15765
|
+
type: 'log.appended',
|
|
15766
|
+
runId: event.runId,
|
|
15767
|
+
occurredAt,
|
|
15768
|
+
source: 'worker',
|
|
15769
|
+
lines,
|
|
15770
|
+
} as PlayRunLedgerEvent,
|
|
15771
|
+
]),
|
|
14977
15772
|
{
|
|
14978
15773
|
type: 'run.failed',
|
|
14979
15774
|
runId: event.runId,
|
|
@@ -15930,6 +16725,26 @@ function stringArrayPayloadField(payload: unknown, key: string): string[] {
|
|
|
15930
16725
|
: [];
|
|
15931
16726
|
}
|
|
15932
16727
|
|
|
16728
|
+
function ledgerEventsPayloadField(
|
|
16729
|
+
payload: unknown,
|
|
16730
|
+
key: string,
|
|
16731
|
+
runId: string,
|
|
16732
|
+
): PlayRunLedgerIngressEvent[] {
|
|
16733
|
+
const value = payloadField(payload, key);
|
|
16734
|
+
if (!Array.isArray(value)) return [];
|
|
16735
|
+
return value.filter((candidate): candidate is PlayRunLedgerIngressEvent => {
|
|
16736
|
+
const record = recordPayload(candidate);
|
|
16737
|
+
return (
|
|
16738
|
+
record !== null &&
|
|
16739
|
+
record.runId === runId &&
|
|
16740
|
+
typeof record.occurredAt === 'number' &&
|
|
16741
|
+
Number.isFinite(record.occurredAt) &&
|
|
16742
|
+
typeof record.source === 'string' &&
|
|
16743
|
+
(typeof record.type === 'string' || typeof record.key === 'string')
|
|
16744
|
+
);
|
|
16745
|
+
});
|
|
16746
|
+
}
|
|
16747
|
+
|
|
15933
16748
|
function numberPayloadField(payload: unknown, key: string): number | null {
|
|
15934
16749
|
const value = payloadField(payload, key);
|
|
15935
16750
|
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
@@ -16162,15 +16977,27 @@ export function postgresSchedulerRunHandle(
|
|
|
16162
16977
|
},
|
|
16163
16978
|
cancel: async () => {
|
|
16164
16979
|
const poolOptions = resolve();
|
|
16165
|
-
await
|
|
16166
|
-
|
|
16980
|
+
await withRuntimeSchedulerTransaction(
|
|
16981
|
+
poolOptions,
|
|
16982
|
+
{
|
|
16983
|
+
lockTimeoutMs: RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS,
|
|
16984
|
+
statementTimeoutMs: RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS,
|
|
16985
|
+
},
|
|
16986
|
+
(client) => cancelPostgresSchedulerRun(client, { runId }, options),
|
|
16987
|
+
{ runId, phase: 'cancel_run', priority: 'critical' },
|
|
16167
16988
|
);
|
|
16168
16989
|
},
|
|
16169
16990
|
signal: async (payload) => {
|
|
16170
16991
|
if (payload.kind === 'cancel') {
|
|
16171
16992
|
const poolOptions = resolve();
|
|
16172
|
-
await
|
|
16173
|
-
|
|
16993
|
+
await withRuntimeSchedulerTransaction(
|
|
16994
|
+
poolOptions,
|
|
16995
|
+
{
|
|
16996
|
+
lockTimeoutMs: RUNTIME_SCHEDULER_CANCEL_LOCK_TIMEOUT_MS,
|
|
16997
|
+
statementTimeoutMs: RUNTIME_SCHEDULER_CANCEL_STATEMENT_TIMEOUT_MS,
|
|
16998
|
+
},
|
|
16999
|
+
(client) => cancelPostgresSchedulerRun(client, { runId }, options),
|
|
17000
|
+
{ runId, phase: 'cancel_run', priority: 'critical' },
|
|
16174
17001
|
);
|
|
16175
17002
|
return;
|
|
16176
17003
|
}
|