deepline 0.3.128 → 0.3.129

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +34 -9
  2. package/dist/bundling-sources/sdk/src/http.ts +22 -0
  3. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  4. package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +22 -0
  5. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +265 -2
  6. package/dist/bundling-sources/shared_libs/play-runtime/bounded-batch-function.ts +682 -0
  7. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +21 -5
  8. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +5390 -1732
  9. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +107 -1
  10. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  17. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  22. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  27. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  30. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  31. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  32. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  35. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  36. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  42. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  43. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  52. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  55. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  67. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +98 -2
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  83. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +41 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +885 -73
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  103. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  104. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  105. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  109. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  110. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  111. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  112. package/dist/cli/index.js +250 -38
  113. package/dist/cli/index.mjs +250 -38
  114. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  115. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  116. package/dist/index.d.mts +4 -2
  117. package/dist/index.d.ts +4 -2
  118. package/dist/index.js +231 -35
  119. package/dist/index.mjs +231 -35
  120. package/dist/install-integrity.json +31 -2
  121. package/dist/plays/bundle-play-file.d.mts +2 -2
  122. package/dist/plays/bundle-play-file.d.ts +2 -2
  123. package/dist/plays/bundle-play-file.mjs +65 -1
  124. package/dist/release.d.mts +1 -1
  125. package/dist/release.d.ts +1 -1
  126. package/dist/release.js +1 -1
  127. package/dist/release.mjs +1 -1
  128. package/package.json +1 -1
@@ -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 { RuntimeMapMemoryLimits } from './map-memory-limits';
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. */
@@ -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 = laneMachines
1083
- .map((machine) => `${machine.id}=${machine.state ?? 'unknown'}`)
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 reach ${input.count} healthy machines within ` +
1087
- `${timeoutMs}ms (last: ${states || 'none'}).`,
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] waiting for lane ${input.releaseId}: healthy=${healthy.length}/${input.count}`,
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
- /** Authenticate the local docker client against the Fly registry. */
17
- export function authenticateFlyRegistry(): void {
18
- if (!run('flyctl', ['auth', 'docker'])) {
19
- throw new Error('Fly registry authentication failed.');
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
+ }