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
|
@@ -38,7 +38,10 @@ import type {
|
|
|
38
38
|
} from './governor/governor';
|
|
39
39
|
import type { BudgetStateBackend } from './governor/budget-state-backend';
|
|
40
40
|
import type { DocflowObservationUpsert } from './docflow-observation';
|
|
41
|
-
import type {
|
|
41
|
+
import type {
|
|
42
|
+
RuntimeMapHeapSnapshot,
|
|
43
|
+
RuntimeMapMemoryLimits,
|
|
44
|
+
} from './map-memory-limits';
|
|
42
45
|
import type { AnyBatchOperationStrategy } from './batching-types';
|
|
43
46
|
import type { ToolResultMetadataInput } from './tool-result';
|
|
44
47
|
import type { PreviousCell } from '../plays/cell-staleness';
|
|
@@ -55,6 +58,10 @@ import type {
|
|
|
55
58
|
import type { FixtureBehavior } from './fixture-behavior';
|
|
56
59
|
import type { ToolResponseContract } from '../plays/tool-response-contract';
|
|
57
60
|
import type { PlayArtifactRuntimeContract } from './play-artifact-runtime-contract';
|
|
61
|
+
import type {
|
|
62
|
+
RuntimeReceiptValueInlineFields,
|
|
63
|
+
RuntimeReceiptValueReference,
|
|
64
|
+
} from './runtime-receipt-value';
|
|
58
65
|
|
|
59
66
|
export interface RowState {
|
|
60
67
|
results: Map<string, unknown>;
|
|
@@ -70,6 +77,8 @@ export interface ToolCallRequest {
|
|
|
70
77
|
cacheKey: string;
|
|
71
78
|
/** Stable external-operation identity, independent of receipt serialization. */
|
|
72
79
|
providerIdempotencyKeyBase?: string | null;
|
|
80
|
+
/** Action compatibility version captured when this request was enqueued. */
|
|
81
|
+
providerActionVersion?: string | null;
|
|
73
82
|
cacheable?: boolean;
|
|
74
83
|
receiptKey?: string | null;
|
|
75
84
|
/** Pre-cutover result-cache keys to read before executing a canonical miss. */
|
|
@@ -91,6 +100,35 @@ export interface ToolCallRequest {
|
|
|
91
100
|
tableNamespace?: string;
|
|
92
101
|
rowKey?: string | null;
|
|
93
102
|
description?: string;
|
|
103
|
+
/**
|
|
104
|
+
* Dispatcher-clock reading when this request entered the tool call queue.
|
|
105
|
+
* Measurement only: the dispatcher subtracts it at group launch to record
|
|
106
|
+
* `dispatchQueueWait`. Never read by scheduling decisions.
|
|
107
|
+
*/
|
|
108
|
+
dispatchEnqueuedAtMs?: number;
|
|
109
|
+
/**
|
|
110
|
+
* Runner-clock reading when the provider response body carrying this
|
|
111
|
+
* request's result was fully read and parsed. Stamped by the dispatcher at
|
|
112
|
+
* the parse site and consumed by the row's delivery site, which subtracts it
|
|
113
|
+
* to record `resultDelivery`. Absent on a request served from a cache or
|
|
114
|
+
* delivered as a follower: neither read a body of its own. Measurement only;
|
|
115
|
+
* never read by scheduling decisions.
|
|
116
|
+
*/
|
|
117
|
+
providerBodyCompletedAtMs?: number | null;
|
|
118
|
+
/**
|
|
119
|
+
* Runner-clock reading when this request's wrapped tool result existed —
|
|
120
|
+
* the end of `deliveryWrap` and the start of `deliveryPublishWait`. Stamped
|
|
121
|
+
* by the module dispatcher only for a request that read a provider body of
|
|
122
|
+
* its own, so every delivery sub-phase is recorded for exactly the calls
|
|
123
|
+
* `resultDelivery` is. Measurement only.
|
|
124
|
+
*/
|
|
125
|
+
deliveryWrappedAtMs?: number | null;
|
|
126
|
+
/**
|
|
127
|
+
* Runner-clock reading when the cohort publication carrying this request's
|
|
128
|
+
* receipt saw its `store.complete` settle — the start of `deliveryResolve`,
|
|
129
|
+
* closed at the row's delivery site. Measurement only.
|
|
130
|
+
*/
|
|
131
|
+
deliveryCompletionSettledAtMs?: number | null;
|
|
94
132
|
}
|
|
95
133
|
|
|
96
134
|
export interface ToolBatchResult {
|
|
@@ -100,8 +138,23 @@ export interface ToolBatchResult {
|
|
|
100
138
|
|
|
101
139
|
export interface RuntimeStepReceipt {
|
|
102
140
|
key: string;
|
|
141
|
+
/** Completion acknowledgement only; absent on older gateways. */
|
|
142
|
+
completionDisposition?: 'persisted' | 'existing';
|
|
103
143
|
status: 'queued' | 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
104
144
|
output?: unknown;
|
|
145
|
+
/**
|
|
146
|
+
* Canonical serialized output size when a metadata-only receipt read omitted
|
|
147
|
+
* the body. It is an advisory admission price, never a cache hit by itself:
|
|
148
|
+
* callers still redeem and validate the terminal receipt before reuse.
|
|
149
|
+
*/
|
|
150
|
+
outputByteLength?: number;
|
|
151
|
+
/** Bounded selected fields available without redeeming an R2 output body. */
|
|
152
|
+
inlineFields?: RuntimeReceiptValueInlineFields;
|
|
153
|
+
/**
|
|
154
|
+
* Gateway-internal representation. The gateway verifies and hydrates it
|
|
155
|
+
* before a receipt crosses back to a runner.
|
|
156
|
+
*/
|
|
157
|
+
outputRef?: RuntimeReceiptValueReference | null;
|
|
105
158
|
error?: string;
|
|
106
159
|
errorPayload?: ToolExecutionFailureV1 | null;
|
|
107
160
|
failureKind?: WorkReceiptFailureKind | null;
|
|
@@ -128,10 +181,14 @@ export type ReleaseRuntimeReceiptExecutionLockInput = {
|
|
|
128
181
|
|
|
129
182
|
export interface GetRuntimeStepReceiptInput {
|
|
130
183
|
key: string;
|
|
184
|
+
/** Read the bounded receipt manifest, not its output body. */
|
|
185
|
+
hydrateOutput?: boolean;
|
|
131
186
|
}
|
|
132
187
|
|
|
133
188
|
export interface ClaimRuntimeStepReceiptInput {
|
|
134
189
|
key: string;
|
|
190
|
+
/** Claim state without materializing a terminal output body. */
|
|
191
|
+
hydrateOutput?: boolean;
|
|
135
192
|
leaseId?: string;
|
|
136
193
|
runId: string;
|
|
137
194
|
runAttempt?: number | null;
|
|
@@ -154,6 +211,7 @@ export interface CompleteRuntimeStepReceiptInput {
|
|
|
154
211
|
runAttempt?: number | null;
|
|
155
212
|
leaseId?: string | null;
|
|
156
213
|
output: unknown | null;
|
|
214
|
+
inlineFields?: RuntimeReceiptValueInlineFields;
|
|
157
215
|
}
|
|
158
216
|
|
|
159
217
|
export interface ReleaseRuntimeStepReceiptInput {
|
|
@@ -183,10 +241,14 @@ export interface SkipRuntimeStepReceiptInput {
|
|
|
183
241
|
|
|
184
242
|
export interface GetRuntimeStepReceiptsInput {
|
|
185
243
|
keys: string[];
|
|
244
|
+
/** Read bounded manifests first; hydrate terminal outputs in a later turn. */
|
|
245
|
+
hydrateOutput?: boolean;
|
|
186
246
|
}
|
|
187
247
|
|
|
188
248
|
export interface ClaimRuntimeStepReceiptsInput {
|
|
189
249
|
keys: string[];
|
|
250
|
+
/** Claim state without materializing terminal output bodies. */
|
|
251
|
+
hydrateOutput?: boolean;
|
|
190
252
|
leaseIds?: string[];
|
|
191
253
|
runId: string;
|
|
192
254
|
runAttempt?: number | null;
|
|
@@ -221,6 +283,22 @@ export interface CompleteRuntimeStepReceiptsInput {
|
|
|
221
283
|
runAttempt?: number | null;
|
|
222
284
|
leaseId?: string | null;
|
|
223
285
|
output: unknown | null;
|
|
286
|
+
inlineFields?: RuntimeReceiptValueInlineFields;
|
|
287
|
+
}>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* A complete ToolExecuteResult that the trusted execution relay already put
|
|
292
|
+
* into the receipt-owned R2 namespace. This is a separate named runtime API
|
|
293
|
+
* operation; it is never an optional reinterpretation of a legacy completion.
|
|
294
|
+
*/
|
|
295
|
+
export interface CompleteRuntimeStepReceiptsByReferenceInput {
|
|
296
|
+
receipts: Array<{
|
|
297
|
+
key: string;
|
|
298
|
+
runId: string;
|
|
299
|
+
runAttempt?: number | null;
|
|
300
|
+
leaseId?: string | null;
|
|
301
|
+
outputRef: RuntimeReceiptValueReference;
|
|
224
302
|
}>;
|
|
225
303
|
}
|
|
226
304
|
|
|
@@ -682,6 +760,8 @@ export interface ContextOptions {
|
|
|
682
760
|
requestDurableInvocationFence?: boolean;
|
|
683
761
|
verifyDurableInvocationFence?: (signal?: AbortSignal) => Promise<boolean>;
|
|
684
762
|
runtimeTestFaultHeader?: string | null;
|
|
763
|
+
/** Runner-owned graceful deadline: stop new rows without aborting commits. */
|
|
764
|
+
rowAdmissionSignal?: AbortSignal;
|
|
685
765
|
/**
|
|
686
766
|
* Runtime-sheet transport selected by the runner. Daytona sandboxes use the
|
|
687
767
|
* execution gateway and must never fall back to minting direct DB sessions.
|
|
@@ -811,6 +891,15 @@ export interface ContextOptions {
|
|
|
811
891
|
* idempotent settlements, not output that belongs to a newer run.
|
|
812
892
|
*/
|
|
813
893
|
recoveredStaleKeys?: string[];
|
|
894
|
+
/**
|
|
895
|
+
* Stale keys that prove this attempt no longer owns the sheet: the rows
|
|
896
|
+
* carry a different write version than the one this attempt admitted them
|
|
897
|
+
* under. A handler that returns any (instead of throwing
|
|
898
|
+
* `RuntimeSheetAttemptSupersededError` itself) makes the Context throw it:
|
|
899
|
+
* the map fails and the persistence latch trips, so no further provider
|
|
900
|
+
* call is spent on rows that can never be persisted.
|
|
901
|
+
*/
|
|
902
|
+
supersededKeys?: string[];
|
|
814
903
|
}>;
|
|
815
904
|
/**
|
|
816
905
|
* Persists coalesced, non-terminal row patches before a map suspends.
|
|
@@ -854,6 +943,12 @@ export interface ContextOptions {
|
|
|
854
943
|
* pass a smaller/larger budget to make the OOM boundary deterministic.
|
|
855
944
|
*/
|
|
856
945
|
runtimeMapMemoryLimits?: Partial<RuntimeMapMemoryLimits>;
|
|
946
|
+
/**
|
|
947
|
+
* Heap reading used by map row admission. Omit to read the live process;
|
|
948
|
+
* tests pass a fixed snapshot so admission width does not depend on the
|
|
949
|
+
* host's memory state.
|
|
950
|
+
*/
|
|
951
|
+
runtimeMapHeapProbe?: () => RuntimeMapHeapSnapshot | null;
|
|
857
952
|
/**
|
|
858
953
|
* Return map PlayDataset handles backed by Runtime Sheet reads after a map
|
|
859
954
|
* has persisted. Runtime adapters with real sheet APIs should enable this;
|
|
@@ -1009,6 +1104,11 @@ export interface ContextOptions {
|
|
|
1009
1104
|
) =>
|
|
1010
1105
|
| Promise<Array<RuntimeStepReceipt | null>>
|
|
1011
1106
|
| Array<RuntimeStepReceipt | null>;
|
|
1107
|
+
completeRuntimeStepReceiptsByReference?: (
|
|
1108
|
+
input: CompleteRuntimeStepReceiptsByReferenceInput,
|
|
1109
|
+
) =>
|
|
1110
|
+
| Promise<Array<RuntimeStepReceipt | null>>
|
|
1111
|
+
| Array<RuntimeStepReceipt | null>;
|
|
1012
1112
|
failRuntimeStepReceipt?: (
|
|
1013
1113
|
input: FailRuntimeStepReceiptInput,
|
|
1014
1114
|
) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
|
|
@@ -1025,6 +1125,12 @@ export interface ContextOptions {
|
|
|
1025
1125
|
skipRuntimeStepReceipt?: (
|
|
1026
1126
|
input: SkipRuntimeStepReceiptInput,
|
|
1027
1127
|
) => Promise<RuntimeStepReceipt | null> | RuntimeStepReceipt | null;
|
|
1128
|
+
/**
|
|
1129
|
+
* Gateway request id of the most recent receipt completion request, so a
|
|
1130
|
+
* completion answered with no stored receipt can be traced to the gateway
|
|
1131
|
+
* record that produced it. Diagnostics only; never a control input.
|
|
1132
|
+
*/
|
|
1133
|
+
lastRuntimeStepReceiptCompletionRequestId?: () => string | null;
|
|
1028
1134
|
/** Inline composition scope used for recursion and shared-resource governance. */
|
|
1029
1135
|
governance?: GovernanceSnapshot;
|
|
1030
1136
|
/** Internal in-process child view sharing the root run's resource governor. */
|
package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts
CHANGED
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
gatewayMachineConfig,
|
|
26
26
|
productionGatewayTopology,
|
|
27
|
+
RUNTIME_GATEWAY_AUTOSCALED_METADATA_KEY,
|
|
27
28
|
RUNTIME_TOPOLOGY,
|
|
28
29
|
} from '../../deployment/runtime-topology';
|
|
29
30
|
|
|
@@ -360,6 +361,12 @@ async function prepare(sha: string): Promise<void> {
|
|
|
360
361
|
[EXECUTION_RELAY_ROLLOUT_METADATA]: sha,
|
|
361
362
|
[EXECUTION_RELAY_SOURCE_METADATA]: source.id!,
|
|
362
363
|
fly_process_group: topology.processGroup,
|
|
364
|
+
...(source.config?.metadata?.[
|
|
365
|
+
RUNTIME_GATEWAY_AUTOSCALED_METADATA_KEY
|
|
366
|
+
] === '1' ||
|
|
367
|
+
source.name?.startsWith('execution-relay-autoscale-') === true
|
|
368
|
+
? { [RUNTIME_GATEWAY_AUTOSCALED_METADATA_KEY]: '1' }
|
|
369
|
+
: {}),
|
|
363
370
|
},
|
|
364
371
|
}),
|
|
365
372
|
});
|
|
@@ -51,6 +51,13 @@ export const ABSURD_WORKER_CONFIG =
|
|
|
51
51
|
export const RELEASE_METADATA_KEY = 'release_id';
|
|
52
52
|
/** Content-addressed Vercel ↔ Fly runtime boundary, generated at build time. */
|
|
53
53
|
export const RUNTIME_SURFACE_METADATA_KEY = 'runtime_surface_sha256';
|
|
54
|
+
/**
|
|
55
|
+
* Preview release-gate deploy-input identity for the worker component.
|
|
56
|
+
* `scripts/lib/runtime-component-input.ts` owns the hash; the lane only
|
|
57
|
+
* stamps it so the next gate can prove the running fleet is that exact
|
|
58
|
+
* deployment instead of replacing it blindly.
|
|
59
|
+
*/
|
|
60
|
+
export const COMPONENT_INPUT_METADATA_KEY = 'deepline_component_input';
|
|
54
61
|
export const ROLLBACK_HOLD_UNTIL_METADATA_KEY = 'rollback_hold_until_ms';
|
|
55
62
|
export const RUNTIME_DEPLOY_VERSION_ENV = 'DEEPLINE_RUNTIME_DEPLOY_VERSION';
|
|
56
63
|
export const DRAIN_EXIT_ENV = 'DEEPLINE_ABSURD_DRAIN_EXIT';
|
|
@@ -67,6 +74,42 @@ export const ABSURD_RUNTIME_WORKER_EXEC = [
|
|
|
67
74
|
/** Default health/boot poll ceilings. Loud timeout, never a silent give-up. */
|
|
68
75
|
export const DEFAULT_BOOT_TIMEOUT_MS = 300_000;
|
|
69
76
|
export const DEFAULT_BOOT_POLL_INTERVAL_MS = 5_000;
|
|
77
|
+
/** Fail early after repeated exited-state polls and include each machine log. */
|
|
78
|
+
export const DEFAULT_BOOT_EXITED_POLL_LIMIT = 3;
|
|
79
|
+
const EXITED_MACHINE_STATES = new Set(['stopped', 'destroyed', 'failed']);
|
|
80
|
+
/**
|
|
81
|
+
* A worker receives SIGTERM, first releases its direct scheduler LISTEN
|
|
82
|
+
* session, then waits for accepted work to settle. This must match the
|
|
83
|
+
* worker's Fly kill timeout: destroying the Machine skips that cleanup and
|
|
84
|
+
* can leave the shared scheduler at its physical-session ceiling during a
|
|
85
|
+
* lane replacement.
|
|
86
|
+
*/
|
|
87
|
+
export const DEFAULT_LANE_REPLACEMENT_STOP_SIGNAL = 'SIGTERM';
|
|
88
|
+
export const DEFAULT_LANE_REPLACEMENT_STOP_TIMEOUT = '300s';
|
|
89
|
+
export const DEFAULT_LANE_REPLACEMENT_STOP_WAIT_MS = 6 * 60_000;
|
|
90
|
+
export const DEFAULT_LANE_REPLACEMENT_STOP_POLL_INTERVAL_MS = 2_000;
|
|
91
|
+
const STOPPED_MACHINE_STATES = new Set([
|
|
92
|
+
'stopped',
|
|
93
|
+
'suspended',
|
|
94
|
+
'destroyed',
|
|
95
|
+
'failed',
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
export function readMachineLogTail(app: string, machineId: string): string {
|
|
99
|
+
const result = spawnSync(
|
|
100
|
+
'flyctl',
|
|
101
|
+
['logs', '-a', app, '-i', machineId, '--no-tail'],
|
|
102
|
+
{ encoding: 'utf8', env: process.env },
|
|
103
|
+
);
|
|
104
|
+
if (result.error) {
|
|
105
|
+
return `flyctl logs failed to spawn: ${result.error.message}`;
|
|
106
|
+
}
|
|
107
|
+
return `${result.stdout ?? ''}${result.stderr ?? ''}`
|
|
108
|
+
.split('\n')
|
|
109
|
+
.filter((line) => line.trim().length > 0)
|
|
110
|
+
.slice(-60)
|
|
111
|
+
.join('\n');
|
|
112
|
+
}
|
|
70
113
|
/**
|
|
71
114
|
* A Machines config update is accepted before a later list necessarily shows
|
|
72
115
|
* the new version. Drain is a retirement precondition, so callers must not
|
|
@@ -320,6 +363,7 @@ export function buildBootMachineConfig(input: {
|
|
|
320
363
|
releaseId: string;
|
|
321
364
|
sha: string;
|
|
322
365
|
runtimeSurfaceHash?: string;
|
|
366
|
+
componentInputHash?: string;
|
|
323
367
|
region?: string;
|
|
324
368
|
rollbackHoldUntilMs?: number;
|
|
325
369
|
}): Record<string, unknown> {
|
|
@@ -332,6 +376,9 @@ export function buildBootMachineConfig(input: {
|
|
|
332
376
|
...(input.runtimeSurfaceHash
|
|
333
377
|
? { [RUNTIME_SURFACE_METADATA_KEY]: input.runtimeSurfaceHash }
|
|
334
378
|
: {}),
|
|
379
|
+
...(input.componentInputHash
|
|
380
|
+
? { [COMPONENT_INPUT_METADATA_KEY]: input.componentInputHash }
|
|
381
|
+
: {}),
|
|
335
382
|
...(input.rollbackHoldUntilMs
|
|
336
383
|
? {
|
|
337
384
|
[ROLLBACK_HOLD_UNTIL_METADATA_KEY]: String(
|
|
@@ -962,6 +1009,7 @@ export async function bootLane(input: {
|
|
|
962
1009
|
releaseId: string;
|
|
963
1010
|
sha: string;
|
|
964
1011
|
runtimeSurfaceHash?: string;
|
|
1012
|
+
componentInputHash?: string;
|
|
965
1013
|
count: number;
|
|
966
1014
|
region?: string;
|
|
967
1015
|
timeoutMs?: number;
|
|
@@ -969,6 +1017,8 @@ export async function bootLane(input: {
|
|
|
969
1017
|
imagePropagationRetryDelaysMs?: readonly number[];
|
|
970
1018
|
now?: () => number;
|
|
971
1019
|
wait?: (ms: number) => Promise<void>;
|
|
1020
|
+
machineLogs?: (machineId: string) => string;
|
|
1021
|
+
exitedPollLimit?: number;
|
|
972
1022
|
reuseExisting?: boolean;
|
|
973
1023
|
rollbackHoldUntilMs?: number;
|
|
974
1024
|
rollbackHoldScalerQuiescenceMs?: number;
|
|
@@ -1004,6 +1054,9 @@ export async function bootLane(input: {
|
|
|
1004
1054
|
(!input.runtimeSurfaceHash ||
|
|
1005
1055
|
machine.config?.metadata?.[RUNTIME_SURFACE_METADATA_KEY] ===
|
|
1006
1056
|
input.runtimeSurfaceHash) &&
|
|
1057
|
+
(!input.componentInputHash ||
|
|
1058
|
+
machine.config?.metadata?.[COMPONENT_INPUT_METADATA_KEY] ===
|
|
1059
|
+
input.componentInputHash) &&
|
|
1007
1060
|
(input.rollbackHoldUntilMs !== undefined ||
|
|
1008
1061
|
machine.config.env?.[DRAIN_EXIT_ENV] !== '1') &&
|
|
1009
1062
|
reusableStates.has(machine.state ?? ''),
|
|
@@ -1044,6 +1097,7 @@ export async function bootLane(input: {
|
|
|
1044
1097
|
releaseId: input.releaseId,
|
|
1045
1098
|
sha: input.sha,
|
|
1046
1099
|
runtimeSurfaceHash: input.runtimeSurfaceHash,
|
|
1100
|
+
componentInputHash: input.componentInputHash,
|
|
1047
1101
|
region: input.region,
|
|
1048
1102
|
rollbackHoldUntilMs: input.rollbackHoldUntilMs,
|
|
1049
1103
|
}),
|
|
@@ -1058,6 +1112,24 @@ export async function bootLane(input: {
|
|
|
1058
1112
|
|
|
1059
1113
|
const deadline = now() + timeoutMs;
|
|
1060
1114
|
const ids = new Set([...reused, ...created].map((machine) => machine.id));
|
|
1115
|
+
const machineLogs =
|
|
1116
|
+
input.machineLogs ?? ((id: string) => readMachineLogTail(input.app, id));
|
|
1117
|
+
const exitedPollLimit =
|
|
1118
|
+
input.exitedPollLimit ?? DEFAULT_BOOT_EXITED_POLL_LIMIT;
|
|
1119
|
+
let exitedPolls = 0;
|
|
1120
|
+
const failBoot = (cause: string, laneMachines: FlyMachine[]): never => {
|
|
1121
|
+
const states = laneMachines
|
|
1122
|
+
.map((machine) => `${machine.id}=${machine.state ?? 'unknown'}`)
|
|
1123
|
+
.join(', ');
|
|
1124
|
+
for (const machine of laneMachines) {
|
|
1125
|
+
console.error(
|
|
1126
|
+
`[fly-absurd-lane] boot log for machine ${machine.id} (${machine.state ?? 'unknown'}):\n${machineLogs(machine.id)}`,
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
throw new Error(
|
|
1130
|
+
`Lane ${input.releaseId} did not reach ${input.count} healthy machines ${cause} (last: ${states || 'none'}).`,
|
|
1131
|
+
);
|
|
1132
|
+
};
|
|
1061
1133
|
for (;;) {
|
|
1062
1134
|
const laneMachines = (
|
|
1063
1135
|
await input.client.machinesInLane(input.releaseId)
|
|
@@ -1078,20 +1150,124 @@ export async function bootLane(input: {
|
|
|
1078
1150
|
);
|
|
1079
1151
|
return laneMachines;
|
|
1080
1152
|
}
|
|
1153
|
+
const allExited =
|
|
1154
|
+
laneMachines.length > 0 &&
|
|
1155
|
+
laneMachines.every((machine) =>
|
|
1156
|
+
EXITED_MACHINE_STATES.has(machine.state ?? ''),
|
|
1157
|
+
);
|
|
1158
|
+
exitedPolls = allExited ? exitedPolls + 1 : 0;
|
|
1159
|
+
if (exitedPolls >= exitedPollLimit) {
|
|
1160
|
+
failBoot(
|
|
1161
|
+
`because every lane machine exited (${exitedPolls} consecutive polls)`,
|
|
1162
|
+
laneMachines,
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
if (now() >= deadline) failBoot(`within ${timeoutMs}ms`, laneMachines);
|
|
1166
|
+
console.info(
|
|
1167
|
+
`[fly-absurd-lane] waiting for lane ${input.releaseId}: healthy=${healthy.length}/${input.count}`,
|
|
1168
|
+
);
|
|
1169
|
+
await wait(pollIntervalMs);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
/**
|
|
1174
|
+
* Replace the Machines that serve one already-active lane without bypassing
|
|
1175
|
+
* their shutdown contract. The lane id stays stable: durable state remains in
|
|
1176
|
+
* the same queue, while the previous processes first release their direct
|
|
1177
|
+
* wake listeners and settle work. Only after the new fleet is healthy do we
|
|
1178
|
+
* delete the now-stopped predecessor Machines.
|
|
1179
|
+
*
|
|
1180
|
+
* This is intentionally a lane operation rather than a `flyctl destroy` loop.
|
|
1181
|
+
* A force destroy terminates the process before its SIGTERM handler can
|
|
1182
|
+
* unsubscribe the wake hook, which leaves direct Postgres sessions to expire
|
|
1183
|
+
* on the provider's schedule and turns a routine Preview update into a shared
|
|
1184
|
+
* scheduler capacity incident.
|
|
1185
|
+
*/
|
|
1186
|
+
export async function replaceLane(input: {
|
|
1187
|
+
client: FlyMachinesClient;
|
|
1188
|
+
app: string;
|
|
1189
|
+
releaseId: string;
|
|
1190
|
+
sha: string;
|
|
1191
|
+
runtimeSurfaceHash?: string;
|
|
1192
|
+
componentInputHash?: string;
|
|
1193
|
+
count: number;
|
|
1194
|
+
region?: string;
|
|
1195
|
+
stopTimeoutMs?: number;
|
|
1196
|
+
stopPollIntervalMs?: number;
|
|
1197
|
+
now?: () => number;
|
|
1198
|
+
wait?: (ms: number) => Promise<void>;
|
|
1199
|
+
machineLogs?: (machineId: string) => string;
|
|
1200
|
+
}): Promise<FlyMachine[]> {
|
|
1201
|
+
const now = input.now ?? (() => Date.now());
|
|
1202
|
+
const wait = input.wait ?? sleep;
|
|
1203
|
+
const stopTimeoutMs =
|
|
1204
|
+
input.stopTimeoutMs ?? DEFAULT_LANE_REPLACEMENT_STOP_WAIT_MS;
|
|
1205
|
+
const stopPollIntervalMs =
|
|
1206
|
+
input.stopPollIntervalMs ?? DEFAULT_LANE_REPLACEMENT_STOP_POLL_INTERVAL_MS;
|
|
1207
|
+
const predecessors = await input.client.machinesInLane(input.releaseId);
|
|
1208
|
+
const predecessorIds = new Set(predecessors.map((machine) => machine.id));
|
|
1209
|
+
|
|
1210
|
+
for (const machine of predecessors) {
|
|
1211
|
+
if (STOPPED_MACHINE_STATES.has(machine.state ?? '')) continue;
|
|
1212
|
+
await input.client.stopMachine(machine.id, {
|
|
1213
|
+
signal: DEFAULT_LANE_REPLACEMENT_STOP_SIGNAL,
|
|
1214
|
+
timeout: DEFAULT_LANE_REPLACEMENT_STOP_TIMEOUT,
|
|
1215
|
+
});
|
|
1216
|
+
console.info(
|
|
1217
|
+
`[fly-absurd-lane] requested graceful stop for machine ${machine.id} ` +
|
|
1218
|
+
`(release ${input.releaseId}).`,
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const deadline = now() + stopTimeoutMs;
|
|
1223
|
+
for (;;) {
|
|
1224
|
+
const observed = await input.client.machinesInLane(input.releaseId);
|
|
1225
|
+
const byId = new Map(observed.map((machine) => [machine.id, machine]));
|
|
1226
|
+
const live = [...predecessorIds].filter((id) => {
|
|
1227
|
+
const machine = byId.get(id);
|
|
1228
|
+
// A concurrently removed predecessor cannot retain a process or a
|
|
1229
|
+
// database session, so it is equivalent to an observed stopped state.
|
|
1230
|
+
return machine && !STOPPED_MACHINE_STATES.has(machine.state ?? '');
|
|
1231
|
+
});
|
|
1232
|
+
if (live.length === 0) break;
|
|
1081
1233
|
if (now() >= deadline) {
|
|
1082
|
-
const states =
|
|
1083
|
-
.map((
|
|
1234
|
+
const states = live
|
|
1235
|
+
.map((id) => `${id}=${byId.get(id)?.state ?? 'missing'}`)
|
|
1084
1236
|
.join(', ');
|
|
1085
1237
|
throw new Error(
|
|
1086
|
-
`Lane ${input.releaseId} did not
|
|
1087
|
-
`${
|
|
1238
|
+
`Lane ${input.releaseId} did not finish graceful shutdown within ` +
|
|
1239
|
+
`${stopTimeoutMs}ms; still active: ${states}. Refusing to force-destroy ` +
|
|
1240
|
+
'a worker that may retain shared scheduler connections.',
|
|
1088
1241
|
);
|
|
1089
1242
|
}
|
|
1243
|
+
await wait(stopPollIntervalMs);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const replacements = await bootLane({
|
|
1247
|
+
client: input.client,
|
|
1248
|
+
app: input.app,
|
|
1249
|
+
releaseId: input.releaseId,
|
|
1250
|
+
sha: input.sha,
|
|
1251
|
+
runtimeSurfaceHash: input.runtimeSurfaceHash,
|
|
1252
|
+
componentInputHash: input.componentInputHash,
|
|
1253
|
+
count: input.count,
|
|
1254
|
+
region: input.region,
|
|
1255
|
+
now,
|
|
1256
|
+
wait,
|
|
1257
|
+
machineLogs: input.machineLogs,
|
|
1258
|
+
// Replacements must use the candidate image. A stopped predecessor can
|
|
1259
|
+
// carry the same release id but must never be silently resurrected.
|
|
1260
|
+
reuseExisting: false,
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
for (const machineId of predecessorIds) {
|
|
1264
|
+
await input.client.deleteMachine(machineId, false);
|
|
1090
1265
|
console.info(
|
|
1091
|
-
`[fly-absurd-lane]
|
|
1266
|
+
`[fly-absurd-lane] removed gracefully stopped predecessor ${machineId} ` +
|
|
1267
|
+
`(release ${input.releaseId}).`,
|
|
1092
1268
|
);
|
|
1093
|
-
await wait(pollIntervalMs);
|
|
1094
1269
|
}
|
|
1270
|
+
return replacements;
|
|
1095
1271
|
}
|
|
1096
1272
|
|
|
1097
1273
|
async function cmdBoot(flags: Record<string, string | boolean>): Promise<void> {
|
|
@@ -1110,6 +1286,15 @@ async function cmdBoot(flags: Record<string, string | boolean>): Promise<void> {
|
|
|
1110
1286
|
'--runtime-surface-hash must be a lowercase SHA-256 hex digest.',
|
|
1111
1287
|
);
|
|
1112
1288
|
}
|
|
1289
|
+
const componentInputHash =
|
|
1290
|
+
typeof flags['component-input-hash'] === 'string'
|
|
1291
|
+
? flags['component-input-hash']
|
|
1292
|
+
: undefined;
|
|
1293
|
+
if (componentInputHash && !/^[a-f0-9]{64}$/.test(componentInputHash)) {
|
|
1294
|
+
throw new Error(
|
|
1295
|
+
'--component-input-hash must be a lowercase SHA-256 hex digest.',
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1113
1298
|
const rawRollbackHoldUntilMs = flags['rollback-hold-until-ms'];
|
|
1114
1299
|
const rollbackHoldUntilMs =
|
|
1115
1300
|
typeof rawRollbackHoldUntilMs === 'string'
|
|
@@ -1135,6 +1320,7 @@ async function cmdBoot(flags: Record<string, string | boolean>): Promise<void> {
|
|
|
1135
1320
|
releaseId,
|
|
1136
1321
|
sha,
|
|
1137
1322
|
runtimeSurfaceHash,
|
|
1323
|
+
componentInputHash,
|
|
1138
1324
|
count,
|
|
1139
1325
|
region,
|
|
1140
1326
|
reuseExisting,
|
|
@@ -1142,6 +1328,39 @@ async function cmdBoot(flags: Record<string, string | boolean>): Promise<void> {
|
|
|
1142
1328
|
});
|
|
1143
1329
|
}
|
|
1144
1330
|
|
|
1331
|
+
async function cmdReplace(
|
|
1332
|
+
flags: Record<string, string | boolean>,
|
|
1333
|
+
): Promise<void> {
|
|
1334
|
+
const app = resolveApp(flags);
|
|
1335
|
+
const releaseId = requireReleaseId(flags);
|
|
1336
|
+
const sha = requireSha(flags);
|
|
1337
|
+
const count = requireCount(flags);
|
|
1338
|
+
const region = typeof flags.region === 'string' ? flags.region : undefined;
|
|
1339
|
+
const componentInputHash =
|
|
1340
|
+
typeof flags['component-input-hash'] === 'string'
|
|
1341
|
+
? flags['component-input-hash']
|
|
1342
|
+
: undefined;
|
|
1343
|
+
if (componentInputHash && !/^[a-f0-9]{64}$/.test(componentInputHash)) {
|
|
1344
|
+
throw new Error(
|
|
1345
|
+
'--component-input-hash must be a lowercase SHA-256 hex digest.',
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
const client = new FlyMachinesClient(
|
|
1349
|
+
app,
|
|
1350
|
+
requireFlyToken(),
|
|
1351
|
+
globalThis.fetch as FetchLike,
|
|
1352
|
+
);
|
|
1353
|
+
await replaceLane({
|
|
1354
|
+
client,
|
|
1355
|
+
app,
|
|
1356
|
+
releaseId,
|
|
1357
|
+
sha,
|
|
1358
|
+
componentInputHash,
|
|
1359
|
+
count,
|
|
1360
|
+
region,
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1145
1364
|
export function formatMachineTable(machines: readonly FlyMachine[]): string {
|
|
1146
1365
|
if (machines.length === 0) return '(no machines)';
|
|
1147
1366
|
const header = ['ID', 'RELEASE', 'STATE', 'REGION', 'CHECKS', 'DRAIN'];
|
|
@@ -1791,7 +2010,8 @@ const USAGE = `fly-absurd-lane <command> [flags]
|
|
|
1791
2010
|
Commands:
|
|
1792
2011
|
push-image --sha <sha> --release <id> [--app <app>]
|
|
1793
2012
|
plan-image --release <id> [--image-ref <ref>] [--app <app>]
|
|
1794
|
-
boot --release <id> --sha <sha> --count <K> [--reuse-existing] [--rollback-hold-until-ms <ms>] [--app <app>] [--region <r>]
|
|
2013
|
+
boot --release <id> --sha <sha> --count <K> [--reuse-existing] [--component-input-hash <sha256>] [--rollback-hold-until-ms <ms>] [--app <app>] [--region <r>]
|
|
2014
|
+
replace --release <id> --sha <sha> --count <K> --environment preview [--component-input-hash <sha256>] [--app <app>] [--region <r>]
|
|
1795
2015
|
list [--release <id>] [--json] [--app <app>]
|
|
1796
2016
|
drain --release <id> [--if-present] [--app <app>]
|
|
1797
2017
|
stop-claiming --release <id> [--environment production|preview]
|
|
@@ -1822,6 +2042,8 @@ export async function main(argv: readonly string[]): Promise<void> {
|
|
|
1822
2042
|
return cmdPlanImage(flags);
|
|
1823
2043
|
case 'boot':
|
|
1824
2044
|
return cmdBoot(flags);
|
|
2045
|
+
case 'replace':
|
|
2046
|
+
return cmdReplace(flags);
|
|
1825
2047
|
case 'list':
|
|
1826
2048
|
return cmdList(flags);
|
|
1827
2049
|
case 'drain':
|
|
@@ -13,9 +13,30 @@ export function runtimeImageManifestExists(image: string): boolean {
|
|
|
13
13
|
return run('docker', ['manifest', 'inspect', image], true);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
export const FLY_REGISTRY_AUTH_ATTEMPTS = 3;
|
|
17
|
+
const FLY_REGISTRY_AUTH_RETRY_DELAY_MS = 1_500;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Authenticate the local docker client against the Fly registry. Concurrent
|
|
21
|
+
* release tracks each run this; on macOS two simultaneous credential-store
|
|
22
|
+
* writes race on the keychain item ("already exists", -25299) and one of
|
|
23
|
+
* them fails even though the credentials are in place, so a failed attempt
|
|
24
|
+
* is retried after a short pause instead of failing the track.
|
|
25
|
+
*/
|
|
26
|
+
export function authenticateFlyRegistry(
|
|
27
|
+
sleep: (ms: number) => void = sleepSync,
|
|
28
|
+
): void {
|
|
29
|
+
for (let attempt = 1; attempt <= FLY_REGISTRY_AUTH_ATTEMPTS; attempt += 1) {
|
|
30
|
+
if (run('flyctl', ['auth', 'docker'])) return;
|
|
31
|
+
if (attempt < FLY_REGISTRY_AUTH_ATTEMPTS) {
|
|
32
|
+
sleep(FLY_REGISTRY_AUTH_RETRY_DELAY_MS * attempt);
|
|
33
|
+
}
|
|
20
34
|
}
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Fly registry authentication failed after ${FLY_REGISTRY_AUTH_ATTEMPTS} attempts.`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sleepSync(ms: number): void {
|
|
41
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
21
42
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exact-machine capacity observations for the runtime gateway fleet.
|
|
3
|
+
*
|
|
4
|
+
* The public gateway metrics endpoint is useful for dashboards, but it cannot
|
|
5
|
+
* identify which Fly Machine answered a request. Autoscaling therefore probes
|
|
6
|
+
* the private metrics port through the Machine's 6PN hostname and requires the
|
|
7
|
+
* caller to validate the exact Machine identity header before using a sample.
|
|
8
|
+
*/
|
|
9
|
+
import type { FlyMachine } from './fly-machine-lifecycle';
|
|
10
|
+
|
|
11
|
+
export const GATEWAY_UPSTREAM_IN_FLIGHT_METRIC =
|
|
12
|
+
'deepline_runtime_gateway_upstream_admission_in_flight';
|
|
13
|
+
export const GATEWAY_UPSTREAM_WAITING_METRIC =
|
|
14
|
+
'deepline_runtime_gateway_upstream_admission_waiting';
|
|
15
|
+
|
|
16
|
+
export type RuntimeGatewayCapacity = {
|
|
17
|
+
inFlight: number;
|
|
18
|
+
waiting: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type RuntimeGatewayCapacitySnapshot = RuntimeGatewayCapacity & {
|
|
22
|
+
machineId: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function parseGauge(metrics: string, metricName: string): number {
|
|
26
|
+
const match = metrics.match(
|
|
27
|
+
new RegExp(
|
|
28
|
+
`^${metricName}(?:\\{[^\\n]*\\})?\\s+([0-9]+(?:\\.[0-9]+)?)\\s*$`,
|
|
29
|
+
'm',
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
if (!match) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Gateway capacity proof is missing ${metricName}; refusing to scale from an incomplete Machine sample.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const value = Number(match[1]);
|
|
38
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Gateway capacity proof reported an invalid ${metricName} value.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseGatewayCapacityMetrics(
|
|
47
|
+
metrics: string,
|
|
48
|
+
): RuntimeGatewayCapacity {
|
|
49
|
+
return {
|
|
50
|
+
inFlight: parseGauge(metrics, GATEWAY_UPSTREAM_IN_FLIGHT_METRIC),
|
|
51
|
+
waiting: parseGauge(metrics, GATEWAY_UPSTREAM_WAITING_METRIC),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function aggregateGatewayCapacity(
|
|
56
|
+
observations: readonly RuntimeGatewayCapacitySnapshot[],
|
|
57
|
+
): RuntimeGatewayCapacity {
|
|
58
|
+
return observations.reduce(
|
|
59
|
+
(total, observation) => ({
|
|
60
|
+
inFlight: total.inFlight + observation.inFlight,
|
|
61
|
+
waiting: total.waiting + observation.waiting,
|
|
62
|
+
}),
|
|
63
|
+
{ inFlight: 0, waiting: 0 },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function machineIsServingRuntimeGateway(
|
|
68
|
+
machine: Pick<FlyMachine, 'state' | 'cordoned'>,
|
|
69
|
+
): boolean {
|
|
70
|
+
return machine.state === 'started' && machine.cordoned === false;
|
|
71
|
+
}
|