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.
- 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 +250 -38
- package/dist/cli/index.mjs +250 -38
- package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
- package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +231 -35
- package/dist/index.mjs +231 -35
- package/dist/install-integrity.json +31 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +65 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,11 @@ import {
|
|
|
2
2
|
normalizePlayRuntimeBackend,
|
|
3
3
|
type PlayRuntimeBackendId,
|
|
4
4
|
} from '../backend';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
resolveRuntimeSandboxPlacementPolicy,
|
|
7
|
+
RUNTIME_SANDBOX_PLACEMENT_POLICY_ENV,
|
|
8
|
+
runtimeSandboxPlacementPoliciesForBackend,
|
|
9
|
+
} from '../runtime-sandbox-placement-policy';
|
|
6
10
|
|
|
7
11
|
/**
|
|
8
12
|
* Boot-time required-env contract for the Absurd runtime worker entrypoint.
|
|
@@ -34,16 +38,37 @@ export const ABSURD_WORKER_REQUIRED_ENV = [
|
|
|
34
38
|
|
|
35
39
|
export function runtimeSandboxProviderCapabilities(
|
|
36
40
|
backend: PlayRuntimeBackendId,
|
|
41
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
37
42
|
): Array<'daytona' | 'modal'> {
|
|
38
43
|
return [
|
|
39
44
|
...new Set(
|
|
40
|
-
|
|
45
|
+
runtimeSandboxPlacementPoliciesForWorkerEnv(backend, env).flatMap(
|
|
41
46
|
(policy) => policy.cleanupProviders,
|
|
42
47
|
),
|
|
43
48
|
),
|
|
44
49
|
];
|
|
45
50
|
}
|
|
46
51
|
|
|
52
|
+
/**
|
|
53
|
+
* A release that pins its queue to one placement policy needs credentials only
|
|
54
|
+
* for that policy. The unpinned production worker continues to prove it can
|
|
55
|
+
* drain every compatible historical policy before it starts.
|
|
56
|
+
*/
|
|
57
|
+
export function runtimeSandboxPlacementPoliciesForWorkerEnv(
|
|
58
|
+
backend: PlayRuntimeBackendId,
|
|
59
|
+
env: Readonly<Record<string, string | undefined>> = process.env,
|
|
60
|
+
) {
|
|
61
|
+
const configured = env[RUNTIME_SANDBOX_PLACEMENT_POLICY_ENV]?.trim();
|
|
62
|
+
if (!configured) return runtimeSandboxPlacementPoliciesForBackend(backend);
|
|
63
|
+
const policy = resolveRuntimeSandboxPlacementPolicy(configured);
|
|
64
|
+
if (policy.compatibilityRuntimeBackend !== backend) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`Sandbox placement policy ${policy.id} is incompatible with worker backend ${backend}.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return [policy] as const;
|
|
70
|
+
}
|
|
71
|
+
|
|
47
72
|
/**
|
|
48
73
|
* A signing-key env that the Fly worker must never require at boot. The worker
|
|
49
74
|
* is signing-key-free by design; requiring any of these would violate the lane-
|
|
@@ -61,7 +86,7 @@ export function assertAbsurdWorkerRequiredEnv(
|
|
|
61
86
|
);
|
|
62
87
|
const providerRequired = [
|
|
63
88
|
...new Set(
|
|
64
|
-
|
|
89
|
+
runtimeSandboxPlacementPoliciesForWorkerEnv(backend, env).flatMap(
|
|
65
90
|
(policy) => policy.requiredCredentialEnv,
|
|
66
91
|
),
|
|
67
92
|
),
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
IntegrationOperationRecord,
|
|
3
|
+
IntegrationOperationStore,
|
|
4
|
+
IntegrationOperationValueReader,
|
|
5
|
+
} from './integration-operation-store-contract';
|
|
6
|
+
|
|
7
|
+
import type { IntegrationOperationValuePersister } from './integration-operation-value-storage';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Composes the physical Integration Operation adapter with immutable-output
|
|
11
|
+
* storage. The supplied adapter operation owns database checkout/admission;
|
|
12
|
+
* hydration happens only after that operation resolves so R2 never occupies a
|
|
13
|
+
* scarce scheduler client.
|
|
14
|
+
*/
|
|
15
|
+
export function createGatewayCanonicalIntegrationOperationStore(input: {
|
|
16
|
+
orgId: string;
|
|
17
|
+
withStore<T>(input: {
|
|
18
|
+
lane: 'ordinary' | 'commit';
|
|
19
|
+
phase: string;
|
|
20
|
+
cancellable: boolean;
|
|
21
|
+
operation: (store: IntegrationOperationStore) => Promise<T>;
|
|
22
|
+
}): Promise<T>;
|
|
23
|
+
valueReader: IntegrationOperationValueReader;
|
|
24
|
+
valuePersister?: IntegrationOperationValuePersister;
|
|
25
|
+
}): IntegrationOperationStore {
|
|
26
|
+
const hydrate = async <T extends IntegrationOperationRecord | null>(
|
|
27
|
+
record: T,
|
|
28
|
+
): Promise<T> => {
|
|
29
|
+
if (!record?.outputRef || !record.result) return record;
|
|
30
|
+
const output = await input.valueReader({
|
|
31
|
+
orgId: input.orgId,
|
|
32
|
+
operationKey: record.operationKey,
|
|
33
|
+
reference: record.outputRef,
|
|
34
|
+
});
|
|
35
|
+
const { outputRef: _outputRef, ...hydrated } = record;
|
|
36
|
+
return {
|
|
37
|
+
...hydrated,
|
|
38
|
+
result: { ...record.result, output },
|
|
39
|
+
} as T;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
observe: async (request) =>
|
|
44
|
+
await hydrate(
|
|
45
|
+
await input.withStore({
|
|
46
|
+
lane: 'ordinary',
|
|
47
|
+
phase: 'operation_observe',
|
|
48
|
+
cancellable: true,
|
|
49
|
+
operation: (store) => store.observe(request),
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
claim: async (request) =>
|
|
53
|
+
await hydrate(
|
|
54
|
+
await input.withStore({
|
|
55
|
+
lane: 'ordinary',
|
|
56
|
+
phase: 'operation_claim',
|
|
57
|
+
cancellable: true,
|
|
58
|
+
operation: (store) => store.claim(request),
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
observeThenClaim: async (request) => {
|
|
62
|
+
const paired = await input.withStore({
|
|
63
|
+
lane: 'ordinary',
|
|
64
|
+
phase: 'operation_observe_then_claim',
|
|
65
|
+
cancellable: true,
|
|
66
|
+
operation: async (store) => {
|
|
67
|
+
if (!store.observeThenClaim) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'The Postgres Integration Operation store must support paired observe/claim.',
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return await store.observeThenClaim(request);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
observed: await hydrate(paired.observed),
|
|
77
|
+
claim: await hydrate(paired.claim),
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
...(input.valuePersister
|
|
81
|
+
? {
|
|
82
|
+
prepareResult: async ({
|
|
83
|
+
operationKey,
|
|
84
|
+
parentRunId,
|
|
85
|
+
requestDigest,
|
|
86
|
+
ownerId,
|
|
87
|
+
result,
|
|
88
|
+
}) => {
|
|
89
|
+
const prepared = input.valuePersister!.prepare({
|
|
90
|
+
orgId: input.orgId,
|
|
91
|
+
operationKey,
|
|
92
|
+
output: result.output,
|
|
93
|
+
});
|
|
94
|
+
if (prepared.kind === 'inline') return { result };
|
|
95
|
+
|
|
96
|
+
// The provider has already answered. Reserve its deterministic
|
|
97
|
+
// reference under the existing operation fence before R2 starts,
|
|
98
|
+
// so an upload loss is visible as pending durable work rather
|
|
99
|
+
// than an untracked object and never authorizes redispatch.
|
|
100
|
+
const reserved = await input.withStore({
|
|
101
|
+
lane: 'commit',
|
|
102
|
+
phase: 'operation_reserve_value',
|
|
103
|
+
cancellable: false,
|
|
104
|
+
operation: async (store) => {
|
|
105
|
+
if (!store.reserveResult) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
'The Postgres Integration Operation store must support value reservation.',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return await store.reserveResult({
|
|
111
|
+
operationKey,
|
|
112
|
+
parentRunId,
|
|
113
|
+
requestDigest,
|
|
114
|
+
ownerId,
|
|
115
|
+
outputRef: prepared.outputRef,
|
|
116
|
+
});
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
if (!reserved) {
|
|
120
|
+
throw new Error(
|
|
121
|
+
'Integration Operation value reservation lost its durable owner.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const persisted =
|
|
125
|
+
await input.valuePersister!.persistPrepared(prepared);
|
|
126
|
+
if (
|
|
127
|
+
persisted.kind !== 'reference' ||
|
|
128
|
+
persisted.outputRef.key !== prepared.outputRef.key ||
|
|
129
|
+
persisted.outputRef.digest !== prepared.outputRef.digest
|
|
130
|
+
) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
'Integration Operation value persistence changed its reserved immutable reference.',
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { result, outputRef: persisted.outputRef };
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
: {}),
|
|
139
|
+
complete: async (request) => {
|
|
140
|
+
const completed = await input.withStore({
|
|
141
|
+
lane: 'commit',
|
|
142
|
+
phase: 'operation_complete',
|
|
143
|
+
cancellable: false,
|
|
144
|
+
operation: (store) => store.complete(request),
|
|
145
|
+
});
|
|
146
|
+
// The exact live output is already in the dispatch result. Never redeem
|
|
147
|
+
// the reference just committed: it adds an R2 read and can make an
|
|
148
|
+
// already-durable completion look transiently unavailable.
|
|
149
|
+
if (completed?.outputRef && request.outputRef) {
|
|
150
|
+
const { outputRef: _outputRef, ...liveCompleted } = completed;
|
|
151
|
+
return { ...liveCompleted, result: request.result };
|
|
152
|
+
}
|
|
153
|
+
return await hydrate(completed);
|
|
154
|
+
},
|
|
155
|
+
markIndeterminate: async (request) =>
|
|
156
|
+
await hydrate(
|
|
157
|
+
await input.withStore({
|
|
158
|
+
lane: 'commit',
|
|
159
|
+
phase: 'operation_mark_indeterminate',
|
|
160
|
+
cancellable: false,
|
|
161
|
+
operation: (store) => store.markIndeterminate(request),
|
|
162
|
+
}),
|
|
163
|
+
),
|
|
164
|
+
releaseBeforeDispatch: async (request) =>
|
|
165
|
+
await input.withStore({
|
|
166
|
+
lane: 'ordinary',
|
|
167
|
+
phase: 'operation_release_before_dispatch',
|
|
168
|
+
cancellable: false,
|
|
169
|
+
operation: (store) => store.releaseBeforeDispatch(request),
|
|
170
|
+
}),
|
|
171
|
+
abandonReplaySafe: async (request) =>
|
|
172
|
+
await input.withStore({
|
|
173
|
+
lane: 'ordinary',
|
|
174
|
+
phase: 'operation_abandon_replay_safe',
|
|
175
|
+
cancellable: false,
|
|
176
|
+
operation: (store) => store.abandonReplaySafe(request),
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -8,12 +8,15 @@ export class GatewayHeavyRequestAdmission {
|
|
|
8
8
|
|
|
9
9
|
constructor(readonly maxActive: number) {
|
|
10
10
|
if (!Number.isInteger(maxActive) || maxActive < 1) {
|
|
11
|
-
throw new Error(
|
|
11
|
+
throw new Error(
|
|
12
|
+
'Gateway heavy-request admission requires maxActive >= 1.',
|
|
13
|
+
);
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
async acquire(signal?: AbortSignal): Promise<() => void> {
|
|
16
|
-
if (signal?.aborted)
|
|
18
|
+
if (signal?.aborted)
|
|
19
|
+
throw new Error('Gateway request admission was cancelled.');
|
|
17
20
|
if (this.#active < this.maxActive) return this.#grant();
|
|
18
21
|
return await new Promise<() => void>((resolve, reject) => {
|
|
19
22
|
const grant = () => {
|
|
@@ -45,3 +48,78 @@ export class GatewayHeavyRequestAdmission {
|
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
50
|
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Commit statements are not interchangeable. Measured across four concurrent
|
|
54
|
+
* 1k-row shapes sharing one preview receipt gateway: a shape whose receipts are
|
|
55
|
+
* a few kilobytes commits a ~100 KB statement in tens of milliseconds, while a
|
|
56
|
+
* shape carrying ~726 KB receipts commits multi-megabyte statements that hold a
|
|
57
|
+
* pooled client for seconds (gateway statementMs p90 was 481 ms in a
|
|
58
|
+
* small-batch window and 1-3 s once the large shape's receipts were in a
|
|
59
|
+
* batch). With one admission lane the small statement queues behind the large
|
|
60
|
+
* one, so the cheap shape's completion flight inherits the expensive shape's
|
|
61
|
+
* latency: head-of-line blocking across shapes at the commit lane.
|
|
62
|
+
*
|
|
63
|
+
* Classifying by payload size gives each class its own slots, so a small
|
|
64
|
+
* statement is never blocked by large ones that are merely slow, only by other
|
|
65
|
+
* small ones.
|
|
66
|
+
*/
|
|
67
|
+
export type GatewayCommitSizeClass = 'light' | 'heavy';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Payload bytes at or above which a coalesced commit takes the heavy lane.
|
|
71
|
+
*
|
|
72
|
+
* 256 KiB is where a completion statement stops being cheap. Below it the
|
|
73
|
+
* statement is a jsonb write of at most a quarter megabyte: tens of
|
|
74
|
+
* milliseconds on the pooled client, so a full light lane drains fast enough
|
|
75
|
+
* that no light statement waits meaningfully and no single one can monopolize a
|
|
76
|
+
* client. Above it the statement's cost is dominated by payload transfer and
|
|
77
|
+
* jsonb write amplification, which is exactly the work that must not sit in
|
|
78
|
+
* front of a cheap batch. It is also far above an ordinary small-receipt batch
|
|
79
|
+
* (measured: ~12 receipts of ~9 KB, ~100 KB per statement), so ordinary traffic
|
|
80
|
+
* classifies light and only genuinely large payloads take the heavy lane.
|
|
81
|
+
*/
|
|
82
|
+
export const RUNTIME_RECEIPT_GATEWAY_LIGHT_COMMIT_MAX_BYTES = 256 * 1024;
|
|
83
|
+
|
|
84
|
+
export function classifyGatewayCommitSizeClass(
|
|
85
|
+
bytes: number,
|
|
86
|
+
lightMaxBytes: number = RUNTIME_RECEIPT_GATEWAY_LIGHT_COMMIT_MAX_BYTES,
|
|
87
|
+
): GatewayCommitSizeClass {
|
|
88
|
+
return bytes >= lightMaxBytes ? 'heavy' : 'light';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Two independent admission lanes for commit statements, one per size class.
|
|
93
|
+
*
|
|
94
|
+
* The classes hold separate counters on purpose: a light statement must be
|
|
95
|
+
* admissible while the heavy lane is fully occupied. The caller owns the total
|
|
96
|
+
* budget, since only it knows the pooled client count that both lanes and the
|
|
97
|
+
* gateway's ordinary reads draw from.
|
|
98
|
+
*/
|
|
99
|
+
export class GatewayCommitAdmission {
|
|
100
|
+
readonly #lanes: Record<GatewayCommitSizeClass, GatewayHeavyRequestAdmission>;
|
|
101
|
+
|
|
102
|
+
constructor(maxActive: Record<GatewayCommitSizeClass, number>) {
|
|
103
|
+
this.#lanes = {
|
|
104
|
+
light: new GatewayHeavyRequestAdmission(maxActive.light),
|
|
105
|
+
heavy: new GatewayHeavyRequestAdmission(maxActive.heavy),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
acquire(
|
|
110
|
+
sizeClass: GatewayCommitSizeClass,
|
|
111
|
+
signal?: AbortSignal,
|
|
112
|
+
): Promise<() => void> {
|
|
113
|
+
return this.#lanes[sizeClass].acquire(signal);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
snapshot(): Record<
|
|
117
|
+
GatewayCommitSizeClass,
|
|
118
|
+
{ active: number; waiting: number }
|
|
119
|
+
> {
|
|
120
|
+
return {
|
|
121
|
+
light: this.#lanes.light.snapshot(),
|
|
122
|
+
heavy: this.#lanes.heavy.snapshot(),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -24,6 +24,12 @@ export interface GatewayProgressRegistry {
|
|
|
24
24
|
events: unknown[];
|
|
25
25
|
liveLogOffset: number;
|
|
26
26
|
isCurrentAttempt: () => Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* The request already read the durable attempt fence and it was current.
|
|
29
|
+
* The reporter's forced flush then reuses that single read instead of
|
|
30
|
+
* repeating it; the credit dies with this request's reporter.
|
|
31
|
+
*/
|
|
32
|
+
attemptAlreadyVerified?: boolean;
|
|
27
33
|
}) => Promise<void>;
|
|
28
34
|
/** Flush + drop a run's reporter (e.g. on the run's terminal). */
|
|
29
35
|
finalize: (runId: string, attempt: number) => Promise<void>;
|
|
@@ -39,6 +45,8 @@ export interface GatewayProgressRegistry {
|
|
|
39
45
|
result: PlayRunnerResult;
|
|
40
46
|
liveLogOffset: number;
|
|
41
47
|
isCurrentAttempt: () => Promise<boolean>;
|
|
48
|
+
/** See `ingest`: one already-verified fence read, reused once. */
|
|
49
|
+
attemptAlreadyVerified?: boolean;
|
|
42
50
|
replayTerminalLogs?: boolean;
|
|
43
51
|
events?: unknown[];
|
|
44
52
|
}) => Promise<void>;
|
|
@@ -60,6 +68,8 @@ export async function projectGatewayRunnerTerminalProgress(input: {
|
|
|
60
68
|
events: unknown[];
|
|
61
69
|
liveLogOffset: number;
|
|
62
70
|
isCurrentAttempt: () => Promise<boolean>;
|
|
71
|
+
/** Reuse the terminal request's successful attempt-fence read once. */
|
|
72
|
+
attemptAlreadyVerified?: boolean;
|
|
63
73
|
replayTerminalLogs?: boolean;
|
|
64
74
|
}): Promise<void> {
|
|
65
75
|
if (input.result.status === 'completed') {
|
|
@@ -75,6 +85,7 @@ export async function projectGatewayRunnerTerminalProgress(input: {
|
|
|
75
85
|
events: input.events,
|
|
76
86
|
liveLogOffset: input.liveLogOffset,
|
|
77
87
|
isCurrentAttempt: input.isCurrentAttempt,
|
|
88
|
+
attemptAlreadyVerified: input.attemptAlreadyVerified,
|
|
78
89
|
replayTerminalLogs: input.replayTerminalLogs,
|
|
79
90
|
});
|
|
80
91
|
} else if (input.events.length > 0) {
|
|
@@ -85,6 +96,7 @@ export async function projectGatewayRunnerTerminalProgress(input: {
|
|
|
85
96
|
events: input.events,
|
|
86
97
|
liveLogOffset: input.liveLogOffset,
|
|
87
98
|
isCurrentAttempt: input.isCurrentAttempt,
|
|
99
|
+
attemptAlreadyVerified: input.attemptAlreadyVerified,
|
|
88
100
|
});
|
|
89
101
|
}
|
|
90
102
|
}
|
|
@@ -95,25 +107,41 @@ export function createGatewayProgressRegistry(options?: {
|
|
|
95
107
|
attempt: number,
|
|
96
108
|
isCurrentAttempt: () => boolean | Promise<boolean>,
|
|
97
109
|
initialLiveLogTotalCount: number,
|
|
110
|
+
attemptAlreadyVerified: boolean,
|
|
98
111
|
) => PostgresSchedulerProgressReporter;
|
|
99
112
|
}): GatewayProgressRegistry {
|
|
100
113
|
const createReporter =
|
|
101
114
|
options?.createReporter ??
|
|
102
|
-
((
|
|
115
|
+
((
|
|
116
|
+
launch,
|
|
117
|
+
attempt,
|
|
118
|
+
isCurrentAttempt,
|
|
119
|
+
initialLiveLogTotalCount,
|
|
120
|
+
attemptAlreadyVerified,
|
|
121
|
+
) =>
|
|
103
122
|
createDirectWorkerProgressReporter({
|
|
104
123
|
launch,
|
|
105
124
|
producerAttempt: attempt,
|
|
106
125
|
isCurrentAttempt,
|
|
126
|
+
attemptAlreadyVerified,
|
|
107
127
|
initialLiveLogTotalCount,
|
|
108
128
|
}));
|
|
109
129
|
|
|
110
130
|
return {
|
|
111
|
-
async ingest({
|
|
131
|
+
async ingest({
|
|
132
|
+
launch,
|
|
133
|
+
attempt,
|
|
134
|
+
events,
|
|
135
|
+
liveLogOffset,
|
|
136
|
+
isCurrentAttempt,
|
|
137
|
+
attemptAlreadyVerified,
|
|
138
|
+
}) {
|
|
112
139
|
const reporter = createReporter(
|
|
113
140
|
launch,
|
|
114
141
|
attempt,
|
|
115
142
|
isCurrentAttempt,
|
|
116
143
|
liveLogOffset,
|
|
144
|
+
attemptAlreadyVerified === true,
|
|
117
145
|
);
|
|
118
146
|
for (const event of events) {
|
|
119
147
|
reporter.onRunnerEvent(event);
|
|
@@ -130,6 +158,7 @@ export function createGatewayProgressRegistry(options?: {
|
|
|
130
158
|
input.attempt,
|
|
131
159
|
input.isCurrentAttempt,
|
|
132
160
|
input.liveLogOffset,
|
|
161
|
+
input.attemptAlreadyVerified === true,
|
|
133
162
|
);
|
|
134
163
|
for (const event of input.events ?? []) reporter.onRunnerEvent(event);
|
|
135
164
|
await reporter.finalize(input.result, {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { IntegrationOperationValueReference } from './integration-operation-value-reference';
|
|
2
|
+
|
|
3
|
+
/** The provider-neutral result persisted by the Integration Operation fence. */
|
|
4
|
+
export type CanonicalIntegrationOperationResult = {
|
|
5
|
+
output: unknown;
|
|
6
|
+
providerResourceIds?: readonly string[];
|
|
7
|
+
providerOperationId?: string;
|
|
8
|
+
providerResponseStatus?: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type IntegrationOperationRecord = {
|
|
12
|
+
operationKey: string;
|
|
13
|
+
parentRunId: string;
|
|
14
|
+
requestDigest: string;
|
|
15
|
+
legacyRequestFingerprint?: string | null;
|
|
16
|
+
ownerId: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
state: 'running' | 'completed' | 'indeterminate';
|
|
19
|
+
result: CanonicalIntegrationOperationResult | null;
|
|
20
|
+
outputRef?: IntegrationOperationValueReference;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type IntegrationOperationValueReader = (input: {
|
|
24
|
+
orgId: string;
|
|
25
|
+
operationKey: string;
|
|
26
|
+
reference: IntegrationOperationValueReference;
|
|
27
|
+
}) => Promise<unknown>;
|
|
28
|
+
|
|
29
|
+
export type IntegrationOperationClaim = IntegrationOperationRecord & {
|
|
30
|
+
disposition: 'acquired' | 'observed';
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type IntegrationOperationPairedClaim = {
|
|
34
|
+
observed: IntegrationOperationRecord | null;
|
|
35
|
+
claim: IntegrationOperationClaim | null;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Provider-neutral durable operation port. Concrete database adapters stay at
|
|
40
|
+
* the composition edge; Foundation only coordinates its value lifecycle.
|
|
41
|
+
*/
|
|
42
|
+
export type IntegrationOperationStore = {
|
|
43
|
+
observe(input: {
|
|
44
|
+
operationKey: string;
|
|
45
|
+
}): Promise<IntegrationOperationRecord | null>;
|
|
46
|
+
claim(input: {
|
|
47
|
+
operationKey: string;
|
|
48
|
+
parentRunId: string;
|
|
49
|
+
requestDigest: string;
|
|
50
|
+
legacyRequestFingerprint?: string;
|
|
51
|
+
ownerId: string;
|
|
52
|
+
}): Promise<IntegrationOperationClaim>;
|
|
53
|
+
observeThenClaim?(input: {
|
|
54
|
+
operationKey: string;
|
|
55
|
+
parentRunId: string;
|
|
56
|
+
requestDigest: string;
|
|
57
|
+
legacyRequestFingerprint?: string;
|
|
58
|
+
ownerId: string;
|
|
59
|
+
claimable(record: IntegrationOperationRecord): boolean;
|
|
60
|
+
}): Promise<IntegrationOperationPairedClaim>;
|
|
61
|
+
prepareResult?(input: {
|
|
62
|
+
operationKey: string;
|
|
63
|
+
parentRunId: string;
|
|
64
|
+
requestDigest: string;
|
|
65
|
+
ownerId: string;
|
|
66
|
+
result: CanonicalIntegrationOperationResult;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
result: CanonicalIntegrationOperationResult;
|
|
69
|
+
outputRef?: IntegrationOperationValueReference;
|
|
70
|
+
}>;
|
|
71
|
+
reserveResult?(input: {
|
|
72
|
+
operationKey: string;
|
|
73
|
+
parentRunId: string;
|
|
74
|
+
requestDigest: string;
|
|
75
|
+
ownerId: string;
|
|
76
|
+
outputRef: IntegrationOperationValueReference;
|
|
77
|
+
}): Promise<boolean>;
|
|
78
|
+
complete(input: {
|
|
79
|
+
operationKey: string;
|
|
80
|
+
parentRunId: string;
|
|
81
|
+
requestDigest: string;
|
|
82
|
+
ownerId: string;
|
|
83
|
+
result: CanonicalIntegrationOperationResult;
|
|
84
|
+
outputRef?: IntegrationOperationValueReference;
|
|
85
|
+
}): Promise<IntegrationOperationRecord | null>;
|
|
86
|
+
markIndeterminate(input: {
|
|
87
|
+
operationKey: string;
|
|
88
|
+
parentRunId: string;
|
|
89
|
+
requestDigest: string;
|
|
90
|
+
ownerId: string;
|
|
91
|
+
}): Promise<IntegrationOperationRecord | null>;
|
|
92
|
+
releaseBeforeDispatch(input: {
|
|
93
|
+
operationKey: string;
|
|
94
|
+
parentRunId: string;
|
|
95
|
+
requestDigest: string;
|
|
96
|
+
ownerId: string;
|
|
97
|
+
}): Promise<boolean>;
|
|
98
|
+
abandonReplaySafe(input: {
|
|
99
|
+
operationKey: string;
|
|
100
|
+
parentRunId: string;
|
|
101
|
+
requestDigest: string;
|
|
102
|
+
ownerId: string;
|
|
103
|
+
}): Promise<boolean>;
|
|
104
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immutable bytes for a canonical Integration Operation outcome.
|
|
3
|
+
*
|
|
4
|
+
* The operation row, not this reference, remains the authority for whether a
|
|
5
|
+
* provider observation completed. The reference never grants a cache hit,
|
|
6
|
+
* lease, or redispatch permission.
|
|
7
|
+
*/
|
|
8
|
+
export type IntegrationOperationValueReference = {
|
|
9
|
+
storage: 'r2';
|
|
10
|
+
/** New manifests use JSON; the reader keeps the legacy spelling readable. */
|
|
11
|
+
codec: 'json' | 'json-v1';
|
|
12
|
+
key: string;
|
|
13
|
+
digest: string;
|
|
14
|
+
byteLength: number;
|
|
15
|
+
/** SHA-256 binding for the authenticated organization, never its raw id. */
|
|
16
|
+
orgBinding: string;
|
|
17
|
+
/** SHA-256 binding for the durable provider-operation identity. */
|
|
18
|
+
operationBinding: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function isIntegrationOperationValueReference(
|
|
22
|
+
value: unknown,
|
|
23
|
+
): value is IntegrationOperationValueReference {
|
|
24
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
25
|
+
const candidate = value as Record<string, unknown>;
|
|
26
|
+
return (
|
|
27
|
+
candidate.storage === 'r2' &&
|
|
28
|
+
(candidate.codec === 'json' || candidate.codec === 'json-v1') &&
|
|
29
|
+
typeof candidate.key === 'string' &&
|
|
30
|
+
candidate.key.length > 0 &&
|
|
31
|
+
/^[a-f0-9]{64}$/i.test(String(candidate.digest ?? '')) &&
|
|
32
|
+
Number.isSafeInteger(candidate.byteLength) &&
|
|
33
|
+
Number(candidate.byteLength) >= 0 &&
|
|
34
|
+
/^[a-f0-9]{64}$/i.test(String(candidate.orgBinding ?? '')) &&
|
|
35
|
+
/^[a-f0-9]{64}$/i.test(String(candidate.operationBinding ?? ''))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class IntegrationOperationValueReadError extends Error {
|
|
40
|
+
constructor(
|
|
41
|
+
readonly code: 'missing' | 'corrupt' | 'unavailable' | 'scope_mismatch',
|
|
42
|
+
message: string,
|
|
43
|
+
) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'IntegrationOperationValueReadError';
|
|
46
|
+
}
|
|
47
|
+
}
|