deepline 0.3.128 → 0.3.130

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 (130) 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/daytona-sandbox-janitor.ts +4 -1
  11. package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +7 -0
  12. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +229 -7
  13. package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +25 -4
  14. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-capacity.ts +71 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +1 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +4 -1
  17. package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +694 -28
  18. package/dist/bundling-sources/shared_libs/play-runtime/egress-bandwidth-probe.ts +167 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/execute-server-timing.ts +103 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-receipt-output-reference.ts +34 -0
  21. package/dist/bundling-sources/shared_libs/play-runtime/execution-gateway-response-size.ts +27 -0
  22. package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +66 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +425 -71
  24. package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +210 -32
  25. package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +27 -13
  26. package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +19 -6
  27. package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +61 -2
  28. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/contracts.ts +33 -1
  29. package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/job.ts +42 -1
  30. package/dist/bundling-sources/shared_libs/play-runtime/launch-source-policy.ts +11 -11
  31. package/dist/bundling-sources/shared_libs/play-runtime/ledger-safe-payload.ts +49 -16
  32. package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +1653 -98
  33. package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +807 -0
  34. package/dist/bundling-sources/shared_libs/play-runtime/memory-pressure-signal.ts +138 -0
  35. package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +85 -3
  36. package/dist/bundling-sources/shared_libs/play-runtime/persistence-latch.ts +60 -2
  37. package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +46 -1
  38. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/contracts.ts +2 -0
  39. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/job.ts +1 -0
  40. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/ports.ts +1 -0
  41. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/terminal/postgres-adapter.ts +1 -0
  42. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +70 -27
  43. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +56 -0
  44. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/adaptive-provider-capacity-controller.ts +192 -0
  45. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/epoch-aimd-rate-controller.ts +194 -0
  46. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/fixture-capacity-simulator.ts +220 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/gradient2-concurrency-controller.ts +214 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-capacity-policy.ts +43 -0
  49. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-concurrency-policy.ts +30 -0
  50. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-rate-policy.ts +95 -0
  51. package/dist/bundling-sources/shared_libs/play-runtime/provider-capacity/provider-throttle-classifier.ts +40 -0
  52. package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +1535 -11
  53. package/dist/bundling-sources/shared_libs/play-runtime/run-failure.ts +49 -2
  54. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +6 -1
  55. package/dist/bundling-sources/shared_libs/play-runtime/run-log-buffer.ts +55 -0
  56. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/egress-probe.ts +221 -0
  57. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +339 -29
  58. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +28 -1
  59. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +192 -165
  60. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +6 -0
  61. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-payload-transport.ts +90 -5
  62. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +281 -6
  63. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +27 -1
  64. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/local-process.ts +19 -0
  65. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +108 -0
  66. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +40 -0
  67. package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +11 -5
  68. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +30 -0
  69. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api-compression.ts +276 -0
  70. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +723 -115
  71. package/dist/bundling-sources/shared_libs/play-runtime/runtime-capacity-policy.ts +114 -4
  72. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +3 -2
  73. package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +41 -4
  74. package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +448 -0
  75. package/dist/bundling-sources/shared_libs/play-runtime/runtime-map-row-feed.ts +215 -0
  76. package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +11 -0
  77. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +161 -14
  78. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-value.ts +184 -0
  79. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +607 -121
  80. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sandbox-placement-policy.ts +4 -0
  81. package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-writer.ts +21 -0
  82. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +15 -2
  83. package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +71 -14
  84. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-play-deploy-task.ts +2 -2
  85. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +288 -119
  86. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-shared.ts +8 -6
  87. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +28 -3
  88. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd.ts +28 -8
  89. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/canonical-integration-operation-store.ts +179 -0
  90. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-heavy-request-admission.ts +80 -2
  91. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/gateway-progress-registry.ts +31 -2
  92. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-store-contract.ts +104 -0
  93. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-reference.ts +47 -0
  94. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-retirement.ts +193 -0
  95. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/integration-operation-value-storage.ts +420 -0
  96. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +146 -15
  97. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +134 -3
  98. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-rate-state.ts +1657 -95
  99. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +904 -77
  100. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-completion-coalescer.ts +452 -0
  101. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +601 -42
  102. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-terminal-transition.ts +1 -0
  103. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-receipt-value-storage.ts +371 -0
  104. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/worker-resource-metrics.ts +86 -7
  105. package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +153 -22
  106. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +1 -0
  107. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +188 -38
  108. package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +28 -13
  109. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +44 -1
  110. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +56 -4
  111. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +2 -0
  112. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +16 -10
  113. package/dist/bundling-sources/shared_libs/plays/dataset.ts +4 -1
  114. package/dist/cli/index.js +250 -38
  115. package/dist/cli/index.mjs +250 -38
  116. package/dist/{compiler-manifest-DfSnbCTW.d.mts → compiler-manifest-BIqRyj5m.d.mts} +1 -1
  117. package/dist/{compiler-manifest-DfSnbCTW.d.ts → compiler-manifest-BIqRyj5m.d.ts} +1 -1
  118. package/dist/index.d.mts +4 -2
  119. package/dist/index.d.ts +4 -2
  120. package/dist/index.js +231 -35
  121. package/dist/index.mjs +231 -35
  122. package/dist/install-integrity.json +31 -2
  123. package/dist/plays/bundle-play-file.d.mts +2 -2
  124. package/dist/plays/bundle-play-file.d.ts +2 -2
  125. package/dist/plays/bundle-play-file.mjs +65 -1
  126. package/dist/release.d.mts +1 -1
  127. package/dist/release.d.ts +1 -1
  128. package/dist/release.js +1 -1
  129. package/dist/release.mjs +1 -1
  130. package/package.json +1 -1
@@ -33,6 +33,7 @@ export async function acceptRunnerTerminal(
33
33
  attempt: number;
34
34
  authority?: RunnerTerminalAttemptAuthority | null;
35
35
  result: unknown;
36
+ terminalProgressEvents?: readonly unknown[];
36
37
  },
37
38
  options?: { schema?: string },
38
39
  ): Promise<RunnerTerminalIngressOutcome> {
@@ -0,0 +1,371 @@
1
+ import { createHash } from 'node:crypto';
2
+ import {
3
+ normalizeRuntimeReceiptValueInlineFields,
4
+ RuntimeReceiptValueReadError,
5
+ type RuntimeReceiptValueInlineFields,
6
+ type RuntimeReceiptValueReference,
7
+ } from '../runtime-receipt-value';
8
+ import { BoundedBatchFunction } from '../bounded-batch-function';
9
+
10
+ /**
11
+ * Keep small receipts in the same Postgres row. Larger JSON bodies move once
12
+ * to the receipt-owned object namespace, so the row and gateway batch keep a
13
+ * compact manifest. The boundary is deliberately an injected policy in tests.
14
+ */
15
+ export const RUNTIME_RECEIPT_VALUE_INLINE_MAX_BYTES = 256 * 1024;
16
+
17
+ /** Shared object-store surface for all immutable runtime JSON values. */
18
+ export type RuntimeValueBlobStore = {
19
+ exists(key: string): Promise<boolean>;
20
+ put(input: { key: string; body: Buffer; contentType: string }): Promise<void>;
21
+ get(key: string): Promise<Buffer>;
22
+ remove(key: string): Promise<void>;
23
+ };
24
+
25
+ /** Backward-compatible receipt-specific name for the shared object store. */
26
+ export type RuntimeReceiptValueBlobStore = RuntimeValueBlobStore;
27
+
28
+ /**
29
+ * Foundation owns value references and bounded I/O admission; the deployment
30
+ * injects its object-store adapter. This keeps R2 credentials and transport
31
+ * mechanics at the gateway edge, without a reverse package dependency.
32
+ */
33
+ export function requireRuntimeValueBlobStore(
34
+ store: RuntimeValueBlobStore | undefined,
35
+ ): RuntimeValueBlobStore {
36
+ if (!store) {
37
+ throw new Error(
38
+ 'Runtime value storage requires an explicit object-store adapter.',
39
+ );
40
+ }
41
+ return store;
42
+ }
43
+
44
+ type RuntimeReceiptValueStorageInput = {
45
+ orgId: string;
46
+ receiptKey: string;
47
+ serialized: string;
48
+ inlineFields?: RuntimeReceiptValueInlineFields;
49
+ inlineMaxBytes?: number;
50
+ store?: RuntimeReceiptValueBlobStore;
51
+ };
52
+
53
+ export type PersistedRuntimeReceiptValue =
54
+ | { kind: 'inline'; outputJson: string }
55
+ | {
56
+ kind: 'reference';
57
+ outputJson: null;
58
+ outputRef: RuntimeReceiptValueReference;
59
+ };
60
+
61
+ export type RuntimeReceiptValuePersister = (input: {
62
+ orgId: string;
63
+ receiptKey: string;
64
+ serialized: string;
65
+ inlineFields?: RuntimeReceiptValueInlineFields;
66
+ }) => Promise<PersistedRuntimeReceiptValue>;
67
+
68
+ export type RuntimeReceiptValueReader = (input: {
69
+ orgId: string;
70
+ receiptKey: string;
71
+ reference: RuntimeReceiptValueReference;
72
+ }) => Promise<unknown>;
73
+
74
+ function sha256(value: string | Buffer): string {
75
+ return createHash('sha256').update(value).digest('hex');
76
+ }
77
+
78
+ function requiredNonempty(value: string, label: string): string {
79
+ const normalized = value.trim();
80
+ if (!normalized) throw new Error(`${label} must be non-empty.`);
81
+ return normalized;
82
+ }
83
+
84
+ function nonnegativeInteger(value: number, label: string): number {
85
+ if (!Number.isSafeInteger(value) || value < 0) {
86
+ throw new Error(`${label} must be a non-negative safe integer.`);
87
+ }
88
+ return value;
89
+ }
90
+
91
+ function positiveInteger(value: number, label: string): number {
92
+ if (!Number.isSafeInteger(value) || value <= 0) {
93
+ throw new Error(`${label} must be a positive safe integer.`);
94
+ }
95
+ return value;
96
+ }
97
+
98
+ function receiptValueBindings(input: { orgId: string; receiptKey: string }): {
99
+ orgBinding: string;
100
+ receiptBinding: string;
101
+ } {
102
+ return {
103
+ orgBinding: sha256(`runtime-receipt-value/org/v1/${input.orgId}`),
104
+ receiptBinding: sha256(
105
+ `runtime-receipt-value/receipt/v1/${input.orgId}\0${input.receiptKey}`,
106
+ ),
107
+ };
108
+ }
109
+
110
+ export function runtimeReceiptValueStorageKey(input: {
111
+ orgId: string;
112
+ receiptKey: string;
113
+ digest: string;
114
+ }): string {
115
+ const orgId = requiredNonempty(input.orgId, 'orgId');
116
+ const receiptKey = requiredNonempty(input.receiptKey, 'receiptKey');
117
+ if (!/^[a-f0-9]{64}$/i.test(input.digest)) {
118
+ throw new Error('digest must be a SHA-256 hex digest.');
119
+ }
120
+ const { orgBinding, receiptBinding } = receiptValueBindings({
121
+ orgId,
122
+ receiptKey,
123
+ });
124
+ return `runtime/receipt-values/v1/${orgBinding}/${receiptBinding}/${input.digest}.json`;
125
+ }
126
+
127
+ function referenceForBody(input: {
128
+ orgId: string;
129
+ receiptKey: string;
130
+ body: Buffer;
131
+ inlineFields?: RuntimeReceiptValueInlineFields;
132
+ }): RuntimeReceiptValueReference {
133
+ const orgId = requiredNonempty(input.orgId, 'orgId');
134
+ const receiptKey = requiredNonempty(input.receiptKey, 'receiptKey');
135
+ const digest = sha256(input.body);
136
+ const { orgBinding, receiptBinding } = receiptValueBindings({
137
+ orgId,
138
+ receiptKey,
139
+ });
140
+ return {
141
+ storage: 'r2',
142
+ codec: 'json',
143
+ key: runtimeReceiptValueStorageKey({ orgId, receiptKey, digest }),
144
+ digest,
145
+ byteLength: input.body.byteLength,
146
+ orgBinding,
147
+ receiptBinding,
148
+ ...(input.inlineFields ? { inlineFields: input.inlineFields } : {}),
149
+ };
150
+ }
151
+
152
+ /**
153
+ * Store a body under its deterministic immutable key before its receipt row
154
+ * points at it. If a PUT response is lost after object creation, the exact
155
+ * object is discovered and reused; a caller never regenerates provider work to
156
+ * repair that ambiguity.
157
+ */
158
+ export async function persistRuntimeReceiptValue(
159
+ input: RuntimeReceiptValueStorageInput,
160
+ ): Promise<PersistedRuntimeReceiptValue> {
161
+ const orgId = requiredNonempty(input.orgId, 'orgId');
162
+ const receiptKey = requiredNonempty(input.receiptKey, 'receiptKey');
163
+ const body = Buffer.from(input.serialized, 'utf8');
164
+ const inlineMaxBytes = positiveInteger(
165
+ input.inlineMaxBytes ?? RUNTIME_RECEIPT_VALUE_INLINE_MAX_BYTES,
166
+ 'inlineMaxBytes',
167
+ );
168
+ const inlineFields =
169
+ input.inlineFields === undefined
170
+ ? undefined
171
+ : normalizeRuntimeReceiptValueInlineFields(input.inlineFields);
172
+ if (body.byteLength <= inlineMaxBytes) {
173
+ return { kind: 'inline', outputJson: input.serialized };
174
+ }
175
+
176
+ const store = requireRuntimeValueBlobStore(input.store);
177
+ const outputRef = referenceForBody({ orgId, receiptKey, body, inlineFields });
178
+ try {
179
+ await store.put({
180
+ key: outputRef.key,
181
+ body,
182
+ contentType: 'application/json',
183
+ });
184
+ } catch (error) {
185
+ // An immutable deterministic object that is observable after a failed PUT
186
+ // is the same attempted publication. Treat it as a recovered write, never
187
+ // as permission to call the provider again.
188
+ if (!(await store.exists(outputRef.key))) throw error;
189
+ }
190
+ return { kind: 'reference', outputJson: null, outputRef };
191
+ }
192
+
193
+ /**
194
+ * Build one process-scoped persister for a Fly gateway. It batches admission
195
+ * only: R2 has no multi-PUT, so each physical call remains one immutable PUT,
196
+ * while the common primitive bounds queued and active bytes across requests.
197
+ */
198
+ export function createRuntimeReceiptValuePersister(
199
+ input: {
200
+ store?: RuntimeReceiptValueBlobStore;
201
+ inlineMaxBytes?: number;
202
+ } = {},
203
+ ): RuntimeReceiptValuePersister {
204
+ const inlineMaxBytes =
205
+ input.inlineMaxBytes ?? RUNTIME_RECEIPT_VALUE_INLINE_MAX_BYTES;
206
+ type Write = {
207
+ orgId: string;
208
+ receiptKey: string;
209
+ serialized: string;
210
+ inlineFields?: RuntimeReceiptValueInlineFields;
211
+ };
212
+ const writes = new BoundedBatchFunction<
213
+ Write,
214
+ PersistedRuntimeReceiptValue,
215
+ 'r2'
216
+ >({
217
+ keyId: () => 'r2',
218
+ estimateBytes: (value) => Buffer.byteLength(value.serialized, 'utf8'),
219
+ maxBatchItems: 4,
220
+ maxBatchBytes: 12 * 1024 * 1024,
221
+ maxBatchUnits: 4,
222
+ maxRetainedItems: 32,
223
+ maxRetainedBytes: 24 * 1024 * 1024,
224
+ maxRetainedUnits: 32,
225
+ maxInFlightBatches: 2,
226
+ flushMs: 0,
227
+ execute: async ({ values }) =>
228
+ await Promise.all(
229
+ values.map(
230
+ async (value) =>
231
+ await persistRuntimeReceiptValue({
232
+ ...value,
233
+ inlineMaxBytes,
234
+ ...(input.store ? { store: input.store } : {}),
235
+ }),
236
+ ),
237
+ ),
238
+ });
239
+ return async (value) => {
240
+ if (Buffer.byteLength(value.serialized, 'utf8') <= inlineMaxBytes) {
241
+ return await persistRuntimeReceiptValue({
242
+ ...value,
243
+ inlineMaxBytes,
244
+ ...(input.store ? { store: input.store } : {}),
245
+ });
246
+ }
247
+ return await writes.submit({ key: 'r2', value });
248
+ };
249
+ }
250
+
251
+ /**
252
+ * Build one process-scoped reader for a Fly gateway. R2 has no multi-GET,
253
+ * so the batch is an admission boundary rather than a protocol claim: it
254
+ * keeps active and queued body bytes bounded across every inbound request
255
+ * while allowing independent immutable objects to use the connection pool.
256
+ */
257
+ export function createRuntimeReceiptValueReader(
258
+ input: { store?: RuntimeReceiptValueBlobStore } = {},
259
+ ): RuntimeReceiptValueReader {
260
+ type Read = {
261
+ orgId: string;
262
+ receiptKey: string;
263
+ reference: RuntimeReceiptValueReference;
264
+ };
265
+ const reads = new BoundedBatchFunction<Read, unknown, 'r2'>({
266
+ keyId: () => 'r2',
267
+ estimateBytes: (value) => value.reference.byteLength,
268
+ maxBatchItems: 4,
269
+ maxBatchBytes: 12 * 1024 * 1024,
270
+ maxBatchUnits: 4,
271
+ maxRetainedItems: 32,
272
+ maxRetainedBytes: 24 * 1024 * 1024,
273
+ maxRetainedUnits: 32,
274
+ maxInFlightBatches: 2,
275
+ flushMs: 0,
276
+ execute: async ({ values }) =>
277
+ await Promise.all(
278
+ values.map(
279
+ async (value) =>
280
+ await readRuntimeReceiptValue({
281
+ ...value,
282
+ ...(input.store ? { store: input.store } : {}),
283
+ }),
284
+ ),
285
+ ),
286
+ });
287
+ return async (value) => await reads.submit({ key: 'r2', value });
288
+ }
289
+
290
+ /**
291
+ * Verify that a compact reference is bound to the authenticated receipt that
292
+ * is about to store it. Execution relays use the same proof before a receipt
293
+ * gateway accepts their already-persisted result reference.
294
+ */
295
+ export function assertRuntimeReceiptValueReferenceScope(input: {
296
+ orgId: string;
297
+ receiptKey: string;
298
+ reference: RuntimeReceiptValueReference;
299
+ }): void {
300
+ const orgId = requiredNonempty(input.orgId, 'orgId');
301
+ const receiptKey = requiredNonempty(input.receiptKey, 'receiptKey');
302
+ const bindings = receiptValueBindings({ orgId, receiptKey });
303
+ if (
304
+ input.reference.orgBinding !== bindings.orgBinding ||
305
+ input.reference.receiptBinding !== bindings.receiptBinding ||
306
+ input.reference.key !==
307
+ runtimeReceiptValueStorageKey({
308
+ orgId,
309
+ receiptKey,
310
+ digest: input.reference.digest,
311
+ })
312
+ ) {
313
+ throw new RuntimeReceiptValueReadError(
314
+ 'scope_mismatch',
315
+ 'Runtime receipt value reference is outside the authenticated receipt scope.',
316
+ );
317
+ }
318
+ }
319
+
320
+ function isMissingRuntimeReceiptValue(error: unknown): boolean {
321
+ if (!error || typeof error !== 'object') return false;
322
+ const record = error as Record<string, unknown>;
323
+ const code = String(record.code ?? record.name ?? '');
324
+ if (code === 'NoSuchKey' || code === 'NotFound') return true;
325
+ const metadata = record.$metadata;
326
+ return (
327
+ Boolean(metadata) &&
328
+ typeof metadata === 'object' &&
329
+ (metadata as Record<string, unknown>).httpStatusCode === 404
330
+ );
331
+ }
332
+
333
+ /** Read, authenticate, size-check, and decode a referenced JSON receipt. */
334
+ export async function readRuntimeReceiptValue(input: {
335
+ orgId: string;
336
+ receiptKey: string;
337
+ reference: RuntimeReceiptValueReference;
338
+ store?: RuntimeReceiptValueBlobStore;
339
+ }): Promise<unknown> {
340
+ assertRuntimeReceiptValueReferenceScope(input);
341
+ const store = requireRuntimeValueBlobStore(input.store);
342
+ let body: Buffer;
343
+ try {
344
+ body = await store.get(input.reference.key);
345
+ } catch (error) {
346
+ throw new RuntimeReceiptValueReadError(
347
+ isMissingRuntimeReceiptValue(error) ? 'missing' : 'unavailable',
348
+ `Runtime receipt value could not be read: ${
349
+ error instanceof Error ? error.message : 'unknown storage error'
350
+ }`,
351
+ );
352
+ }
353
+ if (
354
+ body.byteLength !==
355
+ nonnegativeInteger(input.reference.byteLength, 'byteLength') ||
356
+ sha256(body) !== input.reference.digest
357
+ ) {
358
+ throw new RuntimeReceiptValueReadError(
359
+ 'corrupt',
360
+ 'Runtime receipt value failed its stored size or digest check.',
361
+ );
362
+ }
363
+ try {
364
+ return JSON.parse(body.toString('utf8')) as unknown;
365
+ } catch {
366
+ throw new RuntimeReceiptValueReadError(
367
+ 'corrupt',
368
+ 'Runtime receipt value is not valid JSON for its declared codec.',
369
+ );
370
+ }
371
+ }
@@ -1,5 +1,6 @@
1
1
  import { statfsSync } from 'node:fs';
2
2
  import { cpus } from 'node:os';
3
+ import { getHeapStatistics } from 'node:v8';
3
4
 
4
5
  type ReadFile = (path: string, encoding: 'utf8') => string;
5
6
 
@@ -8,13 +9,62 @@ export type WorkerResourceMetrics = {
8
9
  cpuLimitCores: number;
9
10
  memoryUsageBytes: number;
10
11
  memoryLimitBytes: number;
12
+ memoryLimitSource: 'cgroup' | 'process_fallback';
11
13
  memoryUtilizationRatio: number;
14
+ heapUsedBytes: number;
15
+ heapLimitBytes: number;
16
+ heapUtilizationRatio: number;
12
17
  filesystemUsageBytes: number;
13
18
  filesystemAvailableBytes: number;
14
19
  filesystemTotalBytes: number;
15
20
  filesystemUtilizationRatio: number;
16
21
  };
17
22
 
23
+ export const WORKER_CPU_PRESSURE_RATIO = 0.95;
24
+ export const WORKER_MEMORY_PRESSURE_RATIO = 0.9;
25
+ export const WORKER_HEAP_PRESSURE_RATIO = 0.88;
26
+
27
+ export type WorkerResourcePressureReason = 'cpu' | 'memory' | 'heap';
28
+
29
+ /**
30
+ * Resource pressure is an admission signal, not a terminal outcome. A worker
31
+ * should stop claiming new durable work while the scaler replaces it; the
32
+ * engine still owns already-claimed tasks and drains them before close.
33
+ *
34
+ * Memory pressure requires a real cgroup limit. A local process fallback uses
35
+ * RSS as both the observed value and its fallback limit, so it must never
36
+ * masquerade as a saturated container.
37
+ */
38
+ export function workerResourcePressureReasons(
39
+ metrics: Pick<
40
+ WorkerResourceMetrics,
41
+ | 'cpuUtilizationRatio'
42
+ | 'memoryLimitSource'
43
+ | 'memoryUtilizationRatio'
44
+ | 'heapUtilizationRatio'
45
+ >,
46
+ thresholds: Readonly<{
47
+ cpu?: number;
48
+ memory?: number;
49
+ heap?: number;
50
+ }> = {},
51
+ ): WorkerResourcePressureReason[] {
52
+ const cpuThreshold = thresholds.cpu ?? WORKER_CPU_PRESSURE_RATIO;
53
+ const memoryThreshold =
54
+ thresholds.memory ?? WORKER_MEMORY_PRESSURE_RATIO;
55
+ const heapThreshold = thresholds.heap ?? WORKER_HEAP_PRESSURE_RATIO;
56
+ const reasons: WorkerResourcePressureReason[] = [];
57
+ if (metrics.cpuUtilizationRatio >= cpuThreshold) reasons.push('cpu');
58
+ if (
59
+ metrics.memoryLimitSource === 'cgroup' &&
60
+ metrics.memoryUtilizationRatio >= memoryThreshold
61
+ ) {
62
+ reasons.push('memory');
63
+ }
64
+ if (metrics.heapUtilizationRatio >= heapThreshold) reasons.push('heap');
65
+ return reasons;
66
+ }
67
+
18
68
  /** A zero-sized filesystem report means the probe itself is unavailable; a
19
69
  * nonzero filesystem with zero free bytes is the most urgent pressure state. */
20
70
  export function hasFilesystemPressure(
@@ -60,6 +110,15 @@ export function renderRuntimeProcessResourceMetrics(input: {
60
110
  '# HELP deepline_runtime_process_memory_utilization_percent Container memory utilization normalized to its memory limit.',
61
111
  '# TYPE deepline_runtime_process_memory_utilization_percent gauge',
62
112
  `deepline_runtime_process_memory_utilization_percent{${labels}} ${input.metrics.memoryUtilizationRatio * 100}`,
113
+ '# HELP deepline_runtime_process_heap_used_bytes V8 heap currently used.',
114
+ '# TYPE deepline_runtime_process_heap_used_bytes gauge',
115
+ `deepline_runtime_process_heap_used_bytes{${labels}} ${input.metrics.heapUsedBytes}`,
116
+ '# HELP deepline_runtime_process_heap_limit_bytes V8 heap limit.',
117
+ '# TYPE deepline_runtime_process_heap_limit_bytes gauge',
118
+ `deepline_runtime_process_heap_limit_bytes{${labels}} ${input.metrics.heapLimitBytes}`,
119
+ '# HELP deepline_runtime_process_heap_utilization_percent V8 heap utilization normalized to its heap limit.',
120
+ '# TYPE deepline_runtime_process_heap_utilization_percent gauge',
121
+ `deepline_runtime_process_heap_utilization_percent{${labels}} ${input.metrics.heapUtilizationRatio * 100}`,
63
122
  '# HELP deepline_runtime_process_filesystem_usage_bytes Local /tmp filesystem space used.',
64
123
  '# TYPE deepline_runtime_process_filesystem_usage_bytes gauge',
65
124
  `deepline_runtime_process_filesystem_usage_bytes{${labels}} ${input.metrics.filesystemUsageBytes}`,
@@ -158,11 +217,13 @@ export function createWorkerResourceMetricsReader(input: {
158
217
  now?: () => number;
159
218
  cpuCount?: () => number;
160
219
  memoryUsage?: () => NodeJS.MemoryUsage;
220
+ heapLimit?: () => number;
161
221
  statfs?: (path: string) => FileSystemStats;
162
222
  }) {
163
223
  const now = input.now ?? Date.now;
164
224
  const cpuCount = input.cpuCount ?? (() => cpus().length || 1);
165
225
  const memoryUsage = input.memoryUsage ?? process.memoryUsage;
226
+ const heapLimit = input.heapLimit ?? (() => getHeapStatistics().heap_size_limit);
166
227
  const statfs = input.statfs ?? statfsSync;
167
228
  let previousCpu: CpuSample | null = null;
168
229
 
@@ -208,7 +269,8 @@ export function createWorkerResourceMetricsReader(input: {
208
269
  previousCpu = { usageMicroseconds: cpuUsageMicroseconds, sampledAtMs };
209
270
  }
210
271
 
211
- const fallbackMemory = memoryUsage().rss;
272
+ const processMemory = memoryUsage();
273
+ const fallbackMemory = processMemory.rss;
212
274
  const filesystem = (() => {
213
275
  try {
214
276
  return parseFileSystemStats(statfs('/tmp'));
@@ -216,27 +278,44 @@ export function createWorkerResourceMetricsReader(input: {
216
278
  return parseFileSystemStats(null);
217
279
  }
218
280
  })();
219
- const memoryUsageBytes = parseCgroupLimit(
281
+ const cgroupMemoryUsage =
220
282
  read('/sys/fs/cgroup/memory.current') ??
221
- read('/sys/fs/cgroup/memory/memory.usage_in_bytes') ??
222
- '',
283
+ read('/sys/fs/cgroup/memory/memory.usage_in_bytes');
284
+ const cgroupMemoryLimit =
285
+ read('/sys/fs/cgroup/memory.max') ??
286
+ read('/sys/fs/cgroup/memory/memory.limit_in_bytes');
287
+ const memoryUsageBytes = parseCgroupLimit(
288
+ cgroupMemoryUsage ?? '',
223
289
  fallbackMemory,
224
290
  );
225
291
  const memoryLimitBytes = parseCgroupLimit(
226
- read('/sys/fs/cgroup/memory.max') ??
227
- read('/sys/fs/cgroup/memory/memory.limit_in_bytes') ??
228
- '',
292
+ cgroupMemoryLimit ?? '',
229
293
  fallbackMemory,
230
294
  );
295
+ const heapUsedBytes = finiteNonNegative(
296
+ processMemory.heapUsed,
297
+ 0,
298
+ );
299
+ const heapLimitBytes = finiteNonNegative(heapLimit(), 0);
231
300
  return {
232
301
  cpuUtilizationRatio,
233
302
  cpuLimitCores,
234
303
  memoryUsageBytes,
235
304
  memoryLimitBytes,
305
+ memoryLimitSource:
306
+ cgroupMemoryLimit === null ||
307
+ cgroupMemoryLimit === undefined ||
308
+ cgroupMemoryLimit.trim() === 'max'
309
+ ? 'process_fallback'
310
+ : 'cgroup',
236
311
  memoryUtilizationRatio:
237
312
  memoryLimitBytes > 0
238
313
  ? Math.min(1, memoryUsageBytes / memoryLimitBytes)
239
314
  : 0,
315
+ heapUsedBytes,
316
+ heapLimitBytes,
317
+ heapUtilizationRatio:
318
+ heapLimitBytes > 0 ? Math.min(1, heapUsedBytes / heapLimitBytes) : 0,
240
319
  filesystemUsageBytes: filesystem.usageBytes,
241
320
  filesystemAvailableBytes: filesystem.availableBytes,
242
321
  filesystemTotalBytes: filesystem.totalBytes,