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