deepline 0.3.128 → 0.3.129

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