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.
Files changed (130) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +34 -9
  2. package/dist/bundling-sources/sdk/src/http.ts +22 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +4 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  18. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  27. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  31. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  32. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  36. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  43. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  55. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  67. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +114 -4
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  83. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +28 -8
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +146 -15
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +904 -77
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  103. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  104. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  105. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  109. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  110. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  111. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  112. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  113. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  114. package/dist/cli/index.js +250 -38
  115. package/dist/cli/index.mjs +250 -38
  116. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  117. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  118. package/dist/index.d.mts +4 -2
  119. package/dist/index.d.ts +4 -2
  120. package/dist/index.js +231 -35
  121. package/dist/index.mjs +231 -35
  122. package/dist/install-integrity.json +31 -2
  123. package/dist/plays/bundle-play-file.d.mts +2 -2
  124. package/dist/plays/bundle-play-file.d.ts +2 -2
  125. package/dist/plays/bundle-play-file.mjs +65 -1
  126. package/dist/release.d.mts +1 -1
  127. package/dist/release.d.ts +1 -1
  128. package/dist/release.js +1 -1
  129. package/dist/release.mjs +1 -1
  130. 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
- // v38 adds bounded health indexes for the runtime-owned outbox, sandbox
221
- // cleanup, and compute-billing queues. The indexes are additive: v37 cron
222
- // scheduling, admission, and outbox projection remain valid while a deploy
223
- // expands the shared schema, then the marker records the completed health
224
- // capability for deployment tooling.
225
- export const POSTGRES_SCHEDULER_SCHEMA_VERSION = 38;
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
- $24::boolean = false
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
- AND index_row.indisready
1092
- AND index_row.indisvalid
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
- $23::boolean = false
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
- $25::boolean = false
1370
+ $32::boolean = false
1259
1371
  OR (
1260
- to_regclass($26) IS NOT NULL
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($26)
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
- ) = 4
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
- $14::boolean
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 = $10
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($8, $9) FROM run_terminal
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($11, $12, 'null'::jsonb)
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($13, $11) FROM engine_wake
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
- (line): line is string =>
11920
- typeof line === 'string' &&
11921
- CTX_LOG_TIMESTAMP_PREFIX_RE.test(readProvenanceTag(line).line.trim()),
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: CompleteRuntimeStepReceiptInput & { returnOutput?: boolean },
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, $4,
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 $5::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
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
- stringifyPostgresJson(input.output ?? null),
13749
+ receiptOutputJson(input),
13750
+ receiptOutputRefJson(input),
13194
13751
  input.runId,
13195
13752
  input.returnOutput !== false,
13196
13753
  ],
13197
13754
  );
13198
- if (inserted.rows[0]) return mapReceiptRow(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] ? mapReceiptRow(winner.rows[0]) : null;
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 $7::boolean THEN output_json ELSE NULL::jsonb END AS output_json,
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
- stringifyPostgresJson(input.output ?? null),
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] ? mapReceiptRow(result.rows[0]) : null;
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: CompleteRuntimeStepReceiptsInput & { returnOutput?: boolean },
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
- if (
13269
- input.receipts.some(
13270
- (receipt) => receipt.leaseId === COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
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. Route it through the scalar implementation so mixed
13275
- // batches cannot silently fall back to the legacy UPDATE-only semantics.
13276
- return await Promise.all(
13277
- input.receipts.map((receipt) =>
13278
- completePostgresSchedulerReceipt(
13279
- client,
13280
- { ...receipt, returnOutput: input.returnOutput },
13281
- options,
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: stringifyPostgresJson(receipt.output ?? null),
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 = $6
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 $7::boolean THEN target.output_json ELSE NULL::jsonb END AS output_json,
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) => (row.k ? mapReceiptRow(row) : null));
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 status = 'pending',
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 withRuntimeSchedulerClient(poolOptions, (client) =>
16166
- cancelPostgresSchedulerRun(client, { runId }, options),
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 withRuntimeSchedulerClient(poolOptions, (client) =>
16173
- cancelPostgresSchedulerRun(client, { runId }, options),
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
  }