deepline 0.3.127 → 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.
- package/dist/bundling-sources/sdk/src/client.ts +34 -9
- package/dist/bundling-sources/sdk/src/http.ts +22 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
- package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
- package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
- package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
- package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
- package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
- package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
- package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
- package/dist/cli/index.js +254 -38
- package/dist/cli/index.mjs +254 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts
CHANGED
|
@@ -18,14 +18,22 @@ import {
|
|
|
18
18
|
runtimeReceiptCompletionResponse,
|
|
19
19
|
type LegacyRuntimeReceiptWireFormat,
|
|
20
20
|
} from '../runtime-contract';
|
|
21
|
+
import { BoundedBatchCapacityError } from '../bounded-batch-function';
|
|
21
22
|
import type {
|
|
22
23
|
RuntimeReceiptAction,
|
|
23
24
|
RuntimeReceiptFailure,
|
|
24
25
|
RuntimeReceiptLease,
|
|
25
26
|
} from '../runtime-actions';
|
|
27
|
+
import type { RuntimeStepReceipt } from '../ctx-types';
|
|
28
|
+
import {
|
|
29
|
+
isRuntimeReceiptValueReference,
|
|
30
|
+
normalizeRuntimeReceiptValueInlineFields,
|
|
31
|
+
type RuntimeReceiptValueReference,
|
|
32
|
+
} from '../runtime-receipt-value';
|
|
26
33
|
import {
|
|
27
34
|
redactLedgerString,
|
|
28
35
|
sanitizeRuntimeReceiptPayload,
|
|
36
|
+
sanitizeRuntimeReceiptPayloadForStorage,
|
|
29
37
|
} from '../ledger-safe-payload';
|
|
30
38
|
import {
|
|
31
39
|
claimPostgresSchedulerReceipt,
|
|
@@ -53,6 +61,15 @@ import {
|
|
|
53
61
|
import { transientServiceUnavailableDetail } from '../transient-service-error';
|
|
54
62
|
import { isRuntimePostgresAdmissionCapacityError } from '../runtime-postgres-admission';
|
|
55
63
|
import { normalizeToolExecutionFailure } from '../../plays/tool-execution-error';
|
|
64
|
+
import {
|
|
65
|
+
assertRuntimeReceiptValueReferenceScope,
|
|
66
|
+
createRuntimeReceiptValuePersister,
|
|
67
|
+
createRuntimeReceiptValueReader,
|
|
68
|
+
RUNTIME_RECEIPT_VALUE_INLINE_MAX_BYTES,
|
|
69
|
+
type RuntimeReceiptValueBlobStore,
|
|
70
|
+
type RuntimeReceiptValuePersister,
|
|
71
|
+
type RuntimeReceiptValueReader,
|
|
72
|
+
} from './runtime-receipt-value-storage';
|
|
56
73
|
|
|
57
74
|
type RuntimeReceiptActionWithOptionalScope<
|
|
58
75
|
TAction extends RuntimeReceiptAction = RuntimeReceiptAction,
|
|
@@ -82,6 +99,7 @@ export const RECEIPT_GATEWAY_ACTIONS = new Set<string>([
|
|
|
82
99
|
'mark_runtime_step_receipts_queued',
|
|
83
100
|
'complete_runtime_step_receipt',
|
|
84
101
|
'complete_runtime_step_receipts',
|
|
102
|
+
'complete_runtime_step_receipts_by_reference',
|
|
85
103
|
'release_runtime_step_receipt',
|
|
86
104
|
'fail_runtime_step_receipt',
|
|
87
105
|
'fail_runtime_step_receipts',
|
|
@@ -213,6 +231,25 @@ export function receiptGatewayFailureResponse(
|
|
|
213
231
|
},
|
|
214
232
|
);
|
|
215
233
|
}
|
|
234
|
+
if (error instanceof BoundedBatchCapacityError) {
|
|
235
|
+
return receiptGatewayJsonResponse(
|
|
236
|
+
{
|
|
237
|
+
error: `${operationLabel} is waiting for bounded batch capacity.`,
|
|
238
|
+
code: 'runtime_batch_backpressure',
|
|
239
|
+
retryable: true,
|
|
240
|
+
retry_after_ms: 1_000,
|
|
241
|
+
detail: error.message,
|
|
242
|
+
...(context.action ? { action: context.action } : {}),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
status: 503,
|
|
246
|
+
headers: {
|
|
247
|
+
'retry-after': '1',
|
|
248
|
+
'x-deepline-error-code': 'runtime_batch_backpressure',
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
}
|
|
216
253
|
if (isRuntimeSchedulerCapacityError(error)) {
|
|
217
254
|
const retryAfterMs = runtimeSchedulerCapacityRetryAfterMs(error);
|
|
218
255
|
return receiptGatewayJsonResponse(
|
|
@@ -377,6 +414,216 @@ function receiptKeys(value: unknown): Array<string> | Response {
|
|
|
377
414
|
return keys;
|
|
378
415
|
}
|
|
379
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Redact, serialize, and size-check one receipt output exactly once.
|
|
419
|
+
*
|
|
420
|
+
* `outputJson` is the canonical Postgres JSON text for `output`, so the
|
|
421
|
+
* completion statement binds it straight to `jsonb` instead of re-running
|
|
422
|
+
* `stringifyPostgresJson` over the same value. Serializing a 600 KiB result
|
|
423
|
+
* four times (gate, pre-redaction measure, post-redaction measure, parameter
|
|
424
|
+
* build) was roughly half of gateway CPU on `complete_runtime_step_receipts`.
|
|
425
|
+
*/
|
|
426
|
+
async function receiptCompletionOutput(input: {
|
|
427
|
+
orgId: string;
|
|
428
|
+
key: string;
|
|
429
|
+
output: unknown;
|
|
430
|
+
inlineFields?: unknown;
|
|
431
|
+
persist: RuntimeReceiptValuePersister;
|
|
432
|
+
}): Promise<{
|
|
433
|
+
outputJson: string | null;
|
|
434
|
+
outputRef?: RuntimeReceiptValueReference;
|
|
435
|
+
/**
|
|
436
|
+
* Wall spent in the value persister after the value was redacted and
|
|
437
|
+
* serialized. For a referenced value this includes its bounded R2 admission
|
|
438
|
+
* wait and immutable PUT; for a small value it is the inline fast path.
|
|
439
|
+
*
|
|
440
|
+
* This is intentionally kept gateway-internal. The compact completion
|
|
441
|
+
* acknowledgement never carries storage timing or an object identity.
|
|
442
|
+
*/
|
|
443
|
+
valuePersistMs: number;
|
|
444
|
+
valuePersistedByReference: boolean;
|
|
445
|
+
}> {
|
|
446
|
+
const sanitized = sanitizeRuntimeReceiptPayloadForStorage(
|
|
447
|
+
input.output ?? null,
|
|
448
|
+
);
|
|
449
|
+
const inlineFields =
|
|
450
|
+
input.inlineFields === undefined
|
|
451
|
+
? undefined
|
|
452
|
+
: normalizeRuntimeReceiptValueInlineFields(
|
|
453
|
+
sanitizeRuntimeReceiptPayload(input.inlineFields),
|
|
454
|
+
);
|
|
455
|
+
const persistStartedAt = Date.now();
|
|
456
|
+
const stored = await input.persist({
|
|
457
|
+
orgId: input.orgId,
|
|
458
|
+
receiptKey: input.key,
|
|
459
|
+
serialized: sanitized.serialized,
|
|
460
|
+
inlineFields,
|
|
461
|
+
});
|
|
462
|
+
return {
|
|
463
|
+
outputJson: stored.outputJson,
|
|
464
|
+
...(stored.kind === 'reference' ? { outputRef: stored.outputRef } : {}),
|
|
465
|
+
valuePersistMs: Math.max(0, Date.now() - persistStartedAt),
|
|
466
|
+
valuePersistedByReference: stored.kind === 'reference',
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export type ReceiptGatewayValuePersistenceObservation = {
|
|
471
|
+
/** Number of receipt outputs in this HTTP completion request. */
|
|
472
|
+
values: number;
|
|
473
|
+
/** Outputs actually persisted through the bounded immutable-object writer. */
|
|
474
|
+
referencedValues: number;
|
|
475
|
+
/**
|
|
476
|
+
* Slowest individual persistence wall in this HTTP request. It includes the
|
|
477
|
+
* process-shared R2 writer's admission wait, so it is the useful value to
|
|
478
|
+
* compare with the gateway callback wall. It is zero only when every output
|
|
479
|
+
* took the inline path.
|
|
480
|
+
*/
|
|
481
|
+
maxPersistMs: number;
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
type ReceiptCompletionOutputWithPersistence = Awaited<
|
|
485
|
+
ReturnType<typeof receiptCompletionOutput>
|
|
486
|
+
>;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Publish a deliberately best-effort view of the value-persistence portion of
|
|
490
|
+
* a completion. The ordinary and bulk completion actions share this helper so
|
|
491
|
+
* a legacy single receipt cannot make the gateway look faster than it is.
|
|
492
|
+
*/
|
|
493
|
+
function observeReceiptValuePersistence(
|
|
494
|
+
outputs: readonly ReceiptCompletionOutputWithPersistence[],
|
|
495
|
+
observe?: (observation: ReceiptGatewayValuePersistenceObservation) => void,
|
|
496
|
+
): void {
|
|
497
|
+
try {
|
|
498
|
+
observe?.({
|
|
499
|
+
values: outputs.length,
|
|
500
|
+
referencedValues: outputs.filter(
|
|
501
|
+
(value) => value.valuePersistedByReference,
|
|
502
|
+
).length,
|
|
503
|
+
maxPersistMs: outputs.reduce(
|
|
504
|
+
(max, value) => Math.max(max, value.valuePersistMs),
|
|
505
|
+
0,
|
|
506
|
+
),
|
|
507
|
+
});
|
|
508
|
+
} catch {
|
|
509
|
+
// Observability is intentionally best-effort.
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Project one `complete_runtime_step_receipts` body into storage completions.
|
|
515
|
+
*
|
|
516
|
+
* Shared by the coalesced commit and the client-bound switch so both paths
|
|
517
|
+
* sanitize, serialize, and scope identically; `keys` are the already
|
|
518
|
+
* scope-checked receipt keys, in request order.
|
|
519
|
+
*/
|
|
520
|
+
async function buildReceiptCompletions(input: {
|
|
521
|
+
orgId: string;
|
|
522
|
+
persist: RuntimeReceiptValuePersister;
|
|
523
|
+
receipts: readonly unknown[];
|
|
524
|
+
keys: readonly string[];
|
|
525
|
+
runId: string;
|
|
526
|
+
onValuePersistence?: (
|
|
527
|
+
observation: ReceiptGatewayValuePersistenceObservation,
|
|
528
|
+
) => void;
|
|
529
|
+
}): Promise<ReceiptGatewayCompletionEntry[]> {
|
|
530
|
+
const built = await Promise.all(
|
|
531
|
+
(input.receipts as Array<Record<string, unknown>>).map(
|
|
532
|
+
async (receipt, index) => {
|
|
533
|
+
const output = await receiptCompletionOutput({
|
|
534
|
+
orgId: input.orgId,
|
|
535
|
+
key: input.keys[index]!,
|
|
536
|
+
output: receipt.output,
|
|
537
|
+
inlineFields: receipt.inlineFields,
|
|
538
|
+
persist: input.persist,
|
|
539
|
+
});
|
|
540
|
+
return {
|
|
541
|
+
key: input.keys[index]!,
|
|
542
|
+
runId: input.runId,
|
|
543
|
+
runAttempt:
|
|
544
|
+
typeof receipt.runAttempt === 'number' ? receipt.runAttempt : null,
|
|
545
|
+
leaseId: typeof receipt.leaseId === 'string' ? receipt.leaseId : null,
|
|
546
|
+
...output,
|
|
547
|
+
};
|
|
548
|
+
},
|
|
549
|
+
),
|
|
550
|
+
);
|
|
551
|
+
observeReceiptValuePersistence(built, input.onValuePersistence);
|
|
552
|
+
return built.map(
|
|
553
|
+
({
|
|
554
|
+
valuePersistMs: _valuePersistMs,
|
|
555
|
+
valuePersistedByReference: _ref,
|
|
556
|
+
...completion
|
|
557
|
+
}) => completion,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* The execution relay writes a complete, serialized ToolExecuteResult to the
|
|
563
|
+
* receipt-owned immutable key before it attaches the reference to its normal
|
|
564
|
+
* provider response. This receiver validates that the reference belongs to
|
|
565
|
+
* the same authenticated receipt and commits only its compact manifest.
|
|
566
|
+
*/
|
|
567
|
+
async function buildReferencedReceiptCompletions(input: {
|
|
568
|
+
orgId: string;
|
|
569
|
+
receipts: readonly unknown[];
|
|
570
|
+
keys: readonly string[];
|
|
571
|
+
runId: string;
|
|
572
|
+
store?: RuntimeReceiptValueBlobStore;
|
|
573
|
+
}): Promise<ReceiptGatewayCompletionEntry[]> {
|
|
574
|
+
return await Promise.all(
|
|
575
|
+
(input.receipts as Array<Record<string, unknown>>).map(
|
|
576
|
+
async (receipt, index) => {
|
|
577
|
+
const outputRef = receipt.outputRef;
|
|
578
|
+
if (!isRuntimeReceiptValueReference(outputRef)) {
|
|
579
|
+
throw new Error(
|
|
580
|
+
'Referenced receipt completion requires a valid outputRef.',
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const key = input.keys[index]!;
|
|
584
|
+
assertRuntimeReceiptValueReferenceScope({
|
|
585
|
+
orgId: input.orgId,
|
|
586
|
+
receiptKey: key,
|
|
587
|
+
reference: outputRef,
|
|
588
|
+
});
|
|
589
|
+
// A reference must not point at an object a later receipt read cannot
|
|
590
|
+
// redeem. The execution relay has already recovered an ambiguous PUT
|
|
591
|
+
// before sending it, so this is a cheap integrity fence, not a second
|
|
592
|
+
// provider-side operation.
|
|
593
|
+
if (input.store && !(await input.store.exists(outputRef.key))) {
|
|
594
|
+
throw new Error(
|
|
595
|
+
'Referenced receipt output is not present in immutable storage.',
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
return {
|
|
599
|
+
key,
|
|
600
|
+
runId: input.runId,
|
|
601
|
+
runAttempt:
|
|
602
|
+
typeof receipt.runAttempt === 'number' ? receipt.runAttempt : null,
|
|
603
|
+
leaseId: typeof receipt.leaseId === 'string' ? receipt.leaseId : null,
|
|
604
|
+
outputJson: null,
|
|
605
|
+
outputRef,
|
|
606
|
+
};
|
|
607
|
+
},
|
|
608
|
+
),
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/** Serialized completion size, used only for the coalescer's byte bound. */
|
|
613
|
+
function receiptCompletionBytes(
|
|
614
|
+
completions: readonly ReceiptGatewayCompletionEntry[],
|
|
615
|
+
): number {
|
|
616
|
+
let bytes = 0;
|
|
617
|
+
for (const completion of completions) {
|
|
618
|
+
const storedPayload =
|
|
619
|
+
completion.outputJson ?? JSON.stringify(completion.outputRef ?? null);
|
|
620
|
+
bytes +=
|
|
621
|
+
Buffer.byteLength(completion.key, 'utf8') +
|
|
622
|
+
Buffer.byteLength(storedPayload, 'utf8');
|
|
623
|
+
}
|
|
624
|
+
return bytes;
|
|
625
|
+
}
|
|
626
|
+
|
|
380
627
|
export type ReceiptGatewayDispatcher = (
|
|
381
628
|
scope: ReceiptGatewayScope,
|
|
382
629
|
body: unknown,
|
|
@@ -397,6 +644,15 @@ export type ReceiptGatewayDispatchOptions = {
|
|
|
397
644
|
receipts: readonly unknown[];
|
|
398
645
|
forceRefresh: boolean;
|
|
399
646
|
}) => void;
|
|
647
|
+
/**
|
|
648
|
+
* On-the-wire body bytes of the HTTP request being dispatched, when the
|
|
649
|
+
* transport knows them and they differ from the decoded size — that is, when
|
|
650
|
+
* the body arrived gzipped. Telemetry only: it is forwarded to the completion
|
|
651
|
+
* commit so a coalesced statement can report what the network actually moved
|
|
652
|
+
* beside the decoded bytes it was admitted on. Absent (uncompressed request,
|
|
653
|
+
* PGlite, tests) the commit's wire bytes default to its decoded estimate.
|
|
654
|
+
*/
|
|
655
|
+
requestWireBytes?: number | null;
|
|
400
656
|
};
|
|
401
657
|
|
|
402
658
|
async function injectReceiptCompletionFailure(input: {
|
|
@@ -436,13 +692,152 @@ async function injectReceiptCompletionFailure(input: {
|
|
|
436
692
|
}
|
|
437
693
|
}
|
|
438
694
|
|
|
695
|
+
/**
|
|
696
|
+
* One receipt's completion, already scope-checked and serialized once by the
|
|
697
|
+
* gateway. It carries either canonical inline `jsonb` text or a compact,
|
|
698
|
+
* already-published value reference, so a coalescing commit never re-walks a
|
|
699
|
+
* provider body.
|
|
700
|
+
*/
|
|
701
|
+
export type ReceiptGatewayCompletionEntry = {
|
|
702
|
+
key: string;
|
|
703
|
+
runId: string;
|
|
704
|
+
runAttempt: number | null;
|
|
705
|
+
leaseId: string | null;
|
|
706
|
+
outputJson: string | null;
|
|
707
|
+
outputRef?: RuntimeReceiptValueReference;
|
|
708
|
+
};
|
|
709
|
+
|
|
439
710
|
export function createReceiptGatewayDispatcher(input: {
|
|
440
711
|
withClient: <T>(
|
|
441
712
|
fn: (client: RuntimeSchedulerQueryClient) => Promise<T>,
|
|
442
713
|
) => Promise<T>;
|
|
443
714
|
schema?: string;
|
|
715
|
+
/** Test seam and the Fly value-store adapter for large receipt bodies. */
|
|
716
|
+
runtimeReceiptValueStore?: RuntimeReceiptValueBlobStore;
|
|
717
|
+
runtimeReceiptValueInlineMaxBytes?: number;
|
|
718
|
+
/**
|
|
719
|
+
* The Fly reader-fence adapter enables this only after every durable gateway
|
|
720
|
+
* reader proves it understands referenced values. Tests and local adapters
|
|
721
|
+
* retain their explicit injected-store behavior when this is omitted.
|
|
722
|
+
*/
|
|
723
|
+
runtimeReceiptValueReferencesEnabled?: boolean;
|
|
724
|
+
runtimeReceiptValuePersister?: RuntimeReceiptValuePersister;
|
|
725
|
+
runtimeReceiptValueReader?: RuntimeReceiptValueReader;
|
|
726
|
+
/**
|
|
727
|
+
* Optional gateway-local observation of large receipt-value persistence.
|
|
728
|
+
* It is diagnostic-only: a slow or throwing observer must not affect the
|
|
729
|
+
* durable completion it observes.
|
|
730
|
+
*/
|
|
731
|
+
onReceiptValuePersistence?: (
|
|
732
|
+
observation: ReceiptGatewayValuePersistenceObservation,
|
|
733
|
+
) => void;
|
|
444
734
|
consumeReceiptCompleteFault?: (runId: string) => boolean | Promise<boolean>;
|
|
735
|
+
/**
|
|
736
|
+
* Commit bulk receipt completions outside this dispatcher's own client.
|
|
737
|
+
*
|
|
738
|
+
* The production gateway supplies a coalescing commit that merges concurrent
|
|
739
|
+
* requests of the same org/schema/wire format into one statement under one
|
|
740
|
+
* heavy-admission slot. It MUST return results index-aligned with the
|
|
741
|
+
* supplied receipts, and MUST throw the error the caller would have seen for
|
|
742
|
+
* an uncoalesced request so every merged request stays retry-safe. Absent
|
|
743
|
+
* (tests, PGlite), completions run through `withClient` exactly as before.
|
|
744
|
+
*/
|
|
745
|
+
commitReceiptCompletions?: (commit: {
|
|
746
|
+
orgId: string;
|
|
747
|
+
schema: string | null;
|
|
748
|
+
wireFormat: LegacyRuntimeReceiptWireFormat;
|
|
749
|
+
receipts: ReceiptGatewayCompletionEntry[];
|
|
750
|
+
bytes: number;
|
|
751
|
+
/**
|
|
752
|
+
* On-the-wire body bytes of the request that carried these receipts.
|
|
753
|
+
* Equals `bytes` when the request arrived uncompressed. Telemetry only.
|
|
754
|
+
*/
|
|
755
|
+
wireBytes: number;
|
|
756
|
+
}) => Promise<Array<RuntimeStepReceipt | null>>;
|
|
445
757
|
}): ReceiptGatewayDispatcher {
|
|
758
|
+
const inlineMaxBytes =
|
|
759
|
+
input.runtimeReceiptValueInlineMaxBytes ??
|
|
760
|
+
RUNTIME_RECEIPT_VALUE_INLINE_MAX_BYTES;
|
|
761
|
+
const persistReceiptValue: RuntimeReceiptValuePersister =
|
|
762
|
+
input.runtimeReceiptValueReferencesEnabled === false
|
|
763
|
+
? async (value) => ({ kind: 'inline', outputJson: value.serialized })
|
|
764
|
+
: (input.runtimeReceiptValuePersister ??
|
|
765
|
+
(input.runtimeReceiptValueStore
|
|
766
|
+
? createRuntimeReceiptValuePersister({
|
|
767
|
+
store: input.runtimeReceiptValueStore,
|
|
768
|
+
inlineMaxBytes,
|
|
769
|
+
})
|
|
770
|
+
: async (value) => {
|
|
771
|
+
// Expand readers ship independently from writers. Test/local dispatchers
|
|
772
|
+
// remain inline until their Fly host explicitly supplies the authenticated
|
|
773
|
+
// value store; a reference is never published merely because a process
|
|
774
|
+
// happens to have ambient R2 credentials.
|
|
775
|
+
return { kind: 'inline', outputJson: value.serialized };
|
|
776
|
+
}));
|
|
777
|
+
|
|
778
|
+
const readReceiptValue: RuntimeReceiptValueReader =
|
|
779
|
+
input.runtimeReceiptValueReader ??
|
|
780
|
+
createRuntimeReceiptValueReader(
|
|
781
|
+
input.runtimeReceiptValueStore
|
|
782
|
+
? { store: input.runtimeReceiptValueStore }
|
|
783
|
+
: {},
|
|
784
|
+
);
|
|
785
|
+
const hydrateReceipt = async (
|
|
786
|
+
receipt: RuntimeStepReceipt | null,
|
|
787
|
+
orgId: string,
|
|
788
|
+
): Promise<RuntimeStepReceipt | null> => {
|
|
789
|
+
if (!receipt?.outputRef) return receipt;
|
|
790
|
+
const output = await readReceiptValue({
|
|
791
|
+
orgId,
|
|
792
|
+
receiptKey: receipt.key,
|
|
793
|
+
reference: receipt.outputRef,
|
|
794
|
+
});
|
|
795
|
+
const { outputRef: _outputRef, ...hydrated } = receipt;
|
|
796
|
+
return {
|
|
797
|
+
...hydrated,
|
|
798
|
+
...(receipt.outputRef.inlineFields
|
|
799
|
+
? { inlineFields: receipt.outputRef.inlineFields }
|
|
800
|
+
: {}),
|
|
801
|
+
output,
|
|
802
|
+
};
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* A completed-cache probe needs terminal state and a safe byte price, not a
|
|
806
|
+
* second copy of the result. Keep R2 references gateway-internal: only the
|
|
807
|
+
* scalar size crosses to the runner, and a later normal read still redeems
|
|
808
|
+
* and validates the body before it can become a cache hit.
|
|
809
|
+
*/
|
|
810
|
+
const receiptManifest = (
|
|
811
|
+
receipt: RuntimeStepReceipt | null,
|
|
812
|
+
): RuntimeStepReceipt | null => {
|
|
813
|
+
if (!receipt) return null;
|
|
814
|
+
const { output, outputRef, ...manifest } = receipt;
|
|
815
|
+
const outputByteLength =
|
|
816
|
+
outputRef?.byteLength ??
|
|
817
|
+
(output === undefined
|
|
818
|
+
? null
|
|
819
|
+
: new TextEncoder().encode(JSON.stringify(output)).byteLength);
|
|
820
|
+
return {
|
|
821
|
+
...manifest,
|
|
822
|
+
...(outputByteLength === null ? {} : { outputByteLength }),
|
|
823
|
+
};
|
|
824
|
+
};
|
|
825
|
+
const hydrateReceipts = async (
|
|
826
|
+
receipts: readonly (RuntimeStepReceipt | null)[],
|
|
827
|
+
orgId: string,
|
|
828
|
+
): Promise<Array<RuntimeStepReceipt | null>> =>
|
|
829
|
+
await Promise.all(
|
|
830
|
+
receipts.map(async (receipt) => await hydrateReceipt(receipt, orgId)),
|
|
831
|
+
);
|
|
832
|
+
const presentCompletionReceipt = async (input: {
|
|
833
|
+
receipt: RuntimeStepReceipt | null;
|
|
834
|
+
orgId: string;
|
|
835
|
+
wireFormat: LegacyRuntimeReceiptWireFormat;
|
|
836
|
+
}): Promise<RuntimeStepReceipt | null> =>
|
|
837
|
+
input.wireFormat < 3
|
|
838
|
+
? await hydrateReceipt(input.receipt, input.orgId)
|
|
839
|
+
: input.receipt;
|
|
840
|
+
|
|
446
841
|
return async (scope, rawBody, dispatchOptions = {}) => {
|
|
447
842
|
if (!rawBody || typeof rawBody !== 'object') {
|
|
448
843
|
return receiptGatewayJsonResponse(
|
|
@@ -454,6 +849,11 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
454
849
|
const scopeError = requireReceiptScope(scope, body);
|
|
455
850
|
if (scopeError) return scopeError;
|
|
456
851
|
const runId = String(body.runId || scope.runId);
|
|
852
|
+
// Additive read presentation. Older callers omit this and receive the
|
|
853
|
+
// exact hydrated body they did before; only a Foundation cache probe asks
|
|
854
|
+
// for the compact manifest.
|
|
855
|
+
const hydrateOutput =
|
|
856
|
+
!('hydrateOutput' in body) || body.hydrateOutput !== false;
|
|
457
857
|
const wireFormat = dispatchOptions.wireFormat ?? 3;
|
|
458
858
|
// Org scope is proven by the executor token at the transport (production:
|
|
459
859
|
// apps/runtime-receipt-gateway/index.ts passes auth.orgId here). The
|
|
@@ -461,6 +861,67 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
461
861
|
// through identically for the test/PGlite dispatch to match production.
|
|
462
862
|
const options = { schema: input.schema, orgId: scope.orgId };
|
|
463
863
|
|
|
864
|
+
// A coalescing commit owns admission, the pooled client, and the statement
|
|
865
|
+
// for a whole window of requests, so this path must not take a client of
|
|
866
|
+
// its own — that is the entire cost being removed. The injected
|
|
867
|
+
// `receipt_complete_write_fail` path still needs one, so it falls through
|
|
868
|
+
// to the client-bound switch with its fault decision already consumed.
|
|
869
|
+
let completionFaultInjected: boolean | null = null;
|
|
870
|
+
if (
|
|
871
|
+
body.action === 'complete_runtime_step_receipts' &&
|
|
872
|
+
input.commitReceiptCompletions
|
|
873
|
+
) {
|
|
874
|
+
const sameRunError = requireSameRunReceipts(runId, body.receipts);
|
|
875
|
+
if (sameRunError) return sameRunError;
|
|
876
|
+
const keys = receiptKeys(
|
|
877
|
+
(body.receipts as Array<Record<string, unknown>>).map(
|
|
878
|
+
(receipt) => receipt.key,
|
|
879
|
+
),
|
|
880
|
+
);
|
|
881
|
+
if (keys instanceof Response) return keys;
|
|
882
|
+
completionFaultInjected =
|
|
883
|
+
(await input.consumeReceiptCompleteFault?.(runId)) === true;
|
|
884
|
+
if (!completionFaultInjected) {
|
|
885
|
+
const completions = await buildReceiptCompletions({
|
|
886
|
+
orgId: scope.orgId,
|
|
887
|
+
persist: persistReceiptValue,
|
|
888
|
+
receipts: body.receipts,
|
|
889
|
+
keys,
|
|
890
|
+
runId,
|
|
891
|
+
onValuePersistence: input.onReceiptValuePersistence,
|
|
892
|
+
});
|
|
893
|
+
const bytes = receiptCompletionBytes(completions);
|
|
894
|
+
const committed = await input.commitReceiptCompletions({
|
|
895
|
+
orgId: scope.orgId,
|
|
896
|
+
schema: input.schema ?? null,
|
|
897
|
+
wireFormat,
|
|
898
|
+
receipts: completions,
|
|
899
|
+
bytes,
|
|
900
|
+
// A transport that did not report a wire size sent these bytes
|
|
901
|
+
// uncompressed, so the decoded estimate IS the wire cost.
|
|
902
|
+
wireBytes:
|
|
903
|
+
typeof dispatchOptions.requestWireBytes === 'number' &&
|
|
904
|
+
Number.isFinite(dispatchOptions.requestWireBytes)
|
|
905
|
+
? Math.max(0, Math.floor(dispatchOptions.requestWireBytes))
|
|
906
|
+
: bytes,
|
|
907
|
+
});
|
|
908
|
+
return receiptGatewayJsonResponse(
|
|
909
|
+
await Promise.all(
|
|
910
|
+
committed.map(async (receipt) =>
|
|
911
|
+
runtimeReceiptCompletionResponse(
|
|
912
|
+
wireFormat,
|
|
913
|
+
await presentCompletionReceipt({
|
|
914
|
+
receipt,
|
|
915
|
+
orgId: scope.orgId,
|
|
916
|
+
wireFormat,
|
|
917
|
+
}),
|
|
918
|
+
),
|
|
919
|
+
),
|
|
920
|
+
),
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
464
925
|
return await input.withClient(async (client) => {
|
|
465
926
|
switch (body.action) {
|
|
466
927
|
case 'get_runtime_step_receipt': {
|
|
@@ -478,7 +939,11 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
478
939
|
keys: [key],
|
|
479
940
|
receipts: [receipt],
|
|
480
941
|
});
|
|
481
|
-
return receiptGatewayJsonResponse(
|
|
942
|
+
return receiptGatewayJsonResponse(
|
|
943
|
+
hydrateOutput
|
|
944
|
+
? await hydrateReceipt(receipt, scope.orgId)
|
|
945
|
+
: receiptManifest(receipt),
|
|
946
|
+
);
|
|
482
947
|
}
|
|
483
948
|
case 'get_runtime_step_receipts': {
|
|
484
949
|
const keys = receiptKeys(body.keys);
|
|
@@ -495,7 +960,11 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
495
960
|
keys,
|
|
496
961
|
receipts,
|
|
497
962
|
});
|
|
498
|
-
return receiptGatewayJsonResponse(
|
|
963
|
+
return receiptGatewayJsonResponse(
|
|
964
|
+
hydrateOutput
|
|
965
|
+
? await hydrateReceipts(receipts, scope.orgId)
|
|
966
|
+
: receipts.map(receiptManifest),
|
|
967
|
+
);
|
|
499
968
|
}
|
|
500
969
|
case 'claim_runtime_step_receipt': {
|
|
501
970
|
const key = receiptKey(body.key);
|
|
@@ -524,7 +993,11 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
524
993
|
receipts: [receipt],
|
|
525
994
|
forceRefresh: body.forceRefresh === true,
|
|
526
995
|
});
|
|
527
|
-
return receiptGatewayJsonResponse(
|
|
996
|
+
return receiptGatewayJsonResponse(
|
|
997
|
+
hydrateOutput
|
|
998
|
+
? await hydrateReceipt(receipt, scope.orgId)
|
|
999
|
+
: receiptManifest(receipt),
|
|
1000
|
+
);
|
|
528
1001
|
}
|
|
529
1002
|
case 'claim_runtime_step_receipts': {
|
|
530
1003
|
const keys = receiptKeys(body.keys);
|
|
@@ -553,7 +1026,11 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
553
1026
|
receipts,
|
|
554
1027
|
forceRefresh: body.forceRefresh === true,
|
|
555
1028
|
});
|
|
556
|
-
return receiptGatewayJsonResponse(
|
|
1029
|
+
return receiptGatewayJsonResponse(
|
|
1030
|
+
hydrateOutput
|
|
1031
|
+
? await hydrateReceipts(receipts, scope.orgId)
|
|
1032
|
+
: receipts.map(receiptManifest),
|
|
1033
|
+
);
|
|
557
1034
|
}
|
|
558
1035
|
case 'mark_runtime_step_receipts_running': {
|
|
559
1036
|
const sameRunError = requireSameRunReceipts(runId, body.receipts);
|
|
@@ -597,25 +1074,43 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
597
1074
|
{ status: 409 },
|
|
598
1075
|
);
|
|
599
1076
|
}
|
|
1077
|
+
const completionOutput = await receiptCompletionOutput({
|
|
1078
|
+
orgId: scope.orgId,
|
|
1079
|
+
key,
|
|
1080
|
+
output: body.output,
|
|
1081
|
+
inlineFields: body.inlineFields,
|
|
1082
|
+
persist: persistReceiptValue,
|
|
1083
|
+
});
|
|
1084
|
+
observeReceiptValuePersistence(
|
|
1085
|
+
[completionOutput],
|
|
1086
|
+
input.onReceiptValuePersistence,
|
|
1087
|
+
);
|
|
1088
|
+
const {
|
|
1089
|
+
valuePersistMs: _valuePersistMs,
|
|
1090
|
+
valuePersistedByReference: _valuePersistedByReference,
|
|
1091
|
+
...storedCompletionOutput
|
|
1092
|
+
} = completionOutput;
|
|
1093
|
+
const completed = await completePostgresSchedulerReceipt(
|
|
1094
|
+
client,
|
|
1095
|
+
{
|
|
1096
|
+
key,
|
|
1097
|
+
runId,
|
|
1098
|
+
runAttempt:
|
|
1099
|
+
typeof body.runAttempt === 'number' ? body.runAttempt : null,
|
|
1100
|
+
leaseId: typeof body.leaseId === 'string' ? body.leaseId : null,
|
|
1101
|
+
...storedCompletionOutput,
|
|
1102
|
+
returnOutput: wireFormat < 3,
|
|
1103
|
+
},
|
|
1104
|
+
options,
|
|
1105
|
+
);
|
|
600
1106
|
return receiptGatewayJsonResponse(
|
|
601
1107
|
runtimeReceiptCompletionResponse(
|
|
602
1108
|
wireFormat,
|
|
603
|
-
await
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
runAttempt:
|
|
609
|
-
typeof body.runAttempt === 'number'
|
|
610
|
-
? body.runAttempt
|
|
611
|
-
: null,
|
|
612
|
-
leaseId:
|
|
613
|
-
typeof body.leaseId === 'string' ? body.leaseId : null,
|
|
614
|
-
output: sanitizeRuntimeReceiptPayload(body.output ?? null),
|
|
615
|
-
returnOutput: wireFormat < 3,
|
|
616
|
-
},
|
|
617
|
-
options,
|
|
618
|
-
),
|
|
1109
|
+
await presentCompletionReceipt({
|
|
1110
|
+
receipt: completed,
|
|
1111
|
+
orgId: scope.orgId,
|
|
1112
|
+
wireFormat,
|
|
1113
|
+
}),
|
|
619
1114
|
),
|
|
620
1115
|
);
|
|
621
1116
|
}
|
|
@@ -628,18 +1123,18 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
628
1123
|
),
|
|
629
1124
|
);
|
|
630
1125
|
if (keys instanceof Response) return keys;
|
|
631
|
-
const completions =
|
|
632
|
-
|
|
1126
|
+
const completions = await buildReceiptCompletions({
|
|
1127
|
+
orgId: scope.orgId,
|
|
1128
|
+
persist: persistReceiptValue,
|
|
1129
|
+
receipts: body.receipts,
|
|
1130
|
+
keys,
|
|
633
1131
|
runId,
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
output: sanitizeRuntimeReceiptPayload(receipt.output ?? null),
|
|
641
|
-
}));
|
|
642
|
-
if (await input.consumeReceiptCompleteFault?.(runId)) {
|
|
1132
|
+
onValuePersistence: input.onReceiptValuePersistence,
|
|
1133
|
+
});
|
|
1134
|
+
if (
|
|
1135
|
+
completionFaultInjected ??
|
|
1136
|
+
(await input.consumeReceiptCompleteFault?.(runId))
|
|
1137
|
+
) {
|
|
643
1138
|
const [skippedReceipt, ...siblingReceipts] = completions;
|
|
644
1139
|
if (siblingReceipts.length > 0) {
|
|
645
1140
|
await completePostgresSchedulerReceipts(
|
|
@@ -669,18 +1164,82 @@ export function createReceiptGatewayDispatcher(input: {
|
|
|
669
1164
|
{ status: 409 },
|
|
670
1165
|
);
|
|
671
1166
|
}
|
|
1167
|
+
const completed = await completePostgresSchedulerReceipts(
|
|
1168
|
+
client,
|
|
1169
|
+
{
|
|
1170
|
+
receipts: completions,
|
|
1171
|
+
returnOutput: wireFormat < 3,
|
|
1172
|
+
},
|
|
1173
|
+
options,
|
|
1174
|
+
);
|
|
1175
|
+
return receiptGatewayJsonResponse(
|
|
1176
|
+
await Promise.all(
|
|
1177
|
+
completed.map(async (receipt) =>
|
|
1178
|
+
runtimeReceiptCompletionResponse(
|
|
1179
|
+
wireFormat,
|
|
1180
|
+
await presentCompletionReceipt({
|
|
1181
|
+
receipt,
|
|
1182
|
+
orgId: scope.orgId,
|
|
1183
|
+
wireFormat,
|
|
1184
|
+
}),
|
|
1185
|
+
),
|
|
1186
|
+
),
|
|
1187
|
+
),
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
case 'complete_runtime_step_receipts_by_reference': {
|
|
1191
|
+
// This action is the writer half of an expand/enable rollout. A
|
|
1192
|
+
// runner never relies on it without retaining its full live result,
|
|
1193
|
+
// so an unavailable receiver safely falls back to the legacy action
|
|
1194
|
+
// rather than turning a receipt reference into a null output.
|
|
1195
|
+
if (
|
|
1196
|
+
input.runtimeReceiptValueReferencesEnabled === false ||
|
|
1197
|
+
!input.runtimeReceiptValueStore
|
|
1198
|
+
) {
|
|
1199
|
+
return receiptGatewayJsonResponse(
|
|
1200
|
+
{
|
|
1201
|
+
error:
|
|
1202
|
+
'Receipt-output reference completion is not enabled on this gateway.',
|
|
1203
|
+
code: 'receipt_output_reference_unsupported',
|
|
1204
|
+
},
|
|
1205
|
+
{ status: 409 },
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
const sameRunError = requireSameRunReceipts(runId, body.receipts);
|
|
1209
|
+
if (sameRunError) return sameRunError;
|
|
1210
|
+
const keys = receiptKeys(
|
|
1211
|
+
(body.receipts as Array<Record<string, unknown>>).map(
|
|
1212
|
+
(receipt) => receipt.key,
|
|
1213
|
+
),
|
|
1214
|
+
);
|
|
1215
|
+
if (keys instanceof Response) return keys;
|
|
1216
|
+
const completions = await buildReferencedReceiptCompletions({
|
|
1217
|
+
orgId: scope.orgId,
|
|
1218
|
+
receipts: body.receipts,
|
|
1219
|
+
keys,
|
|
1220
|
+
runId,
|
|
1221
|
+
store: input.runtimeReceiptValueStore,
|
|
1222
|
+
});
|
|
1223
|
+
const completed = await completePostgresSchedulerReceipts(
|
|
1224
|
+
client,
|
|
1225
|
+
{
|
|
1226
|
+
receipts: completions,
|
|
1227
|
+
returnOutput: wireFormat < 3,
|
|
1228
|
+
},
|
|
1229
|
+
options,
|
|
1230
|
+
);
|
|
672
1231
|
return receiptGatewayJsonResponse(
|
|
673
|
-
(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1232
|
+
await Promise.all(
|
|
1233
|
+
completed.map(async (receipt) =>
|
|
1234
|
+
runtimeReceiptCompletionResponse(
|
|
1235
|
+
wireFormat,
|
|
1236
|
+
await presentCompletionReceipt({
|
|
1237
|
+
receipt,
|
|
1238
|
+
orgId: scope.orgId,
|
|
1239
|
+
wireFormat,
|
|
1240
|
+
}),
|
|
1241
|
+
),
|
|
1242
|
+
),
|
|
684
1243
|
),
|
|
685
1244
|
);
|
|
686
1245
|
}
|