@opengeni/core 0.11.2 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +61 -9
- package/dist/index.js +496 -129
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
- package/src/application/new-session-drafts.ts +120 -4
- package/src/dependencies.ts +2 -0
- package/src/domain/resources.ts +9 -0
- package/src/domain/scheduled-tasks.ts +31 -1
- package/src/domain/session-tool-policy.ts +25 -13
- package/src/domain/sessions.ts +207 -0
- package/src/domain/slack-bot.ts +130 -0
- package/src/index.ts +1 -0
- package/src/sandbox/routing.ts +292 -225
package/src/sandbox/routing.ts
CHANGED
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
// real DB pointer + the live NATS control plane for the API-DIRECT Channel-A path
|
|
3
3
|
// (M7). Symmetric with apps/worker/src/sandbox-routing.ts (the turn path).
|
|
4
4
|
//
|
|
5
|
-
// A Channel-A op
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// established group session is wrapped in a `RoutingSandboxSession` that re-reads
|
|
5
|
+
// A Channel-A op runs against one established home: either a lease-owned provider
|
|
6
|
+
// box or a Connected Machine home that intentionally has no cloud lease. With
|
|
7
|
+
// hot-swap, the op must land on the session's CURRENTLY-active sandbox. The
|
|
8
|
+
// established session is wrapped in a `RoutingSandboxSession` that re-reads
|
|
10
9
|
// (active_sandbox_id, active_epoch) and dispatches to the active backend.
|
|
11
10
|
//
|
|
12
11
|
// The DB-coupled glue (readActiveSandbox / getSandbox / the selfhosted ControlRpc
|
|
@@ -16,10 +15,12 @@ import type { Settings } from "@opengeni/config";
|
|
|
16
15
|
import {
|
|
17
16
|
advanceWorkspaceGenerationForDirectRequest,
|
|
18
17
|
advanceWorkspaceGenerationForRetainedProcess,
|
|
18
|
+
getRetainedProcess,
|
|
19
19
|
getSandbox,
|
|
20
20
|
markWarmLeaseInstanceLost,
|
|
21
21
|
readActiveSandbox,
|
|
22
22
|
retainWorkspaceMutationProcess,
|
|
23
|
+
retainedProcessSettlementIdentity,
|
|
23
24
|
settleRetainedProcess,
|
|
24
25
|
verifyDirectWorkspaceMutationSettlement,
|
|
25
26
|
verifyRetainedProcessMutationSettlement,
|
|
@@ -55,7 +56,12 @@ export type ChannelARoutingServices = {
|
|
|
55
56
|
export function relayConfigFromSettings(settings: Settings): SelfhostedRelayConfig {
|
|
56
57
|
const raw = settings.selfhostedRelayUrl?.trim();
|
|
57
58
|
if (!raw) {
|
|
58
|
-
return {
|
|
59
|
+
return {
|
|
60
|
+
host: "relay.opengeni.local",
|
|
61
|
+
port: 443,
|
|
62
|
+
tls: true,
|
|
63
|
+
path: "/stream",
|
|
64
|
+
};
|
|
59
65
|
}
|
|
60
66
|
try {
|
|
61
67
|
const url = new URL(raw.includes("://") ? raw : `wss://${raw}`);
|
|
@@ -105,11 +111,10 @@ export function routingEnabled(settings: Settings): boolean {
|
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/**
|
|
108
|
-
* Wrap an established
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* unchanged; a selfhosted active pointer routes the op to the machine.
|
|
114
|
+
* Wrap an established home session in a `RoutingSandboxSession` so a Channel-A
|
|
115
|
+
* op routes to the session's currently-active sandbox. Provider homes supply a
|
|
116
|
+
* lease; machine homes supply a pinned selfhosted identity and no lease. Returns
|
|
117
|
+
* the established handle with its `session` replaced by the stable proxy.
|
|
113
118
|
*/
|
|
114
119
|
export function wrapChannelABoxWithRouting(
|
|
115
120
|
services: ChannelARoutingServices,
|
|
@@ -117,12 +122,19 @@ export function wrapChannelABoxWithRouting(
|
|
|
117
122
|
accountId: string;
|
|
118
123
|
workspaceId: string;
|
|
119
124
|
sessionId: string;
|
|
120
|
-
homeLease
|
|
125
|
+
homeLease?: {
|
|
121
126
|
sandboxGroupId: string;
|
|
122
127
|
leaseEpoch: number;
|
|
123
128
|
instanceId: string;
|
|
124
129
|
backend: string;
|
|
125
130
|
};
|
|
131
|
+
/** A machine-home Channel-A request has no cloud/home lease. Its already
|
|
132
|
+
* established SelfhostedSession is pinned to the durable active pointer so
|
|
133
|
+
* the first command uses the exact machine instance and epoch. */
|
|
134
|
+
pinnedSelfhosted?: {
|
|
135
|
+
sandboxId: string;
|
|
136
|
+
epoch: number;
|
|
137
|
+
};
|
|
126
138
|
directRequest: {
|
|
127
139
|
requestId: string;
|
|
128
140
|
holderId: string;
|
|
@@ -131,180 +143,212 @@ export function wrapChannelABoxWithRouting(
|
|
|
131
143
|
established: EstablishedSandboxSession,
|
|
132
144
|
): EstablishedSandboxSession {
|
|
133
145
|
const { db, settings, bus } = services;
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
146
|
+
const homeLease = ids.homeLease;
|
|
147
|
+
const beforeMutation = homeLease
|
|
148
|
+
? async ({
|
|
149
|
+
op,
|
|
150
|
+
backend,
|
|
151
|
+
}: {
|
|
152
|
+
op: string;
|
|
153
|
+
backend: ResolvedActiveBackend;
|
|
154
|
+
}): Promise<SandboxWorkspaceMutationAdmission | null> => {
|
|
155
|
+
// Connected Machines and other non-persistable targets intentionally do
|
|
156
|
+
// not dirty or advance the cloud-home archive generation.
|
|
157
|
+
if (
|
|
158
|
+
backend.sandboxId !== null ||
|
|
159
|
+
backend.leaseEpoch === undefined ||
|
|
160
|
+
backend.providerInstanceId === undefined
|
|
161
|
+
) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
if (backend.activeEpoch === undefined) {
|
|
165
|
+
throw new Error("API-direct workspace mutation resolved without an active route epoch");
|
|
166
|
+
}
|
|
167
|
+
return await advanceWorkspaceGenerationForDirectRequest(db, {
|
|
168
|
+
accountId: ids.accountId,
|
|
169
|
+
workspaceId: ids.workspaceId,
|
|
170
|
+
sessionId: ids.sessionId,
|
|
171
|
+
requestId: ids.directRequest.requestId,
|
|
172
|
+
holderId: ids.directRequest.holderId,
|
|
173
|
+
sandboxGroupId: homeLease.sandboxGroupId,
|
|
174
|
+
expectedEpoch: backend.leaseEpoch,
|
|
175
|
+
expectedInstanceId: backend.providerInstanceId,
|
|
176
|
+
routeTargetId: backend.sandboxId,
|
|
177
|
+
routeEpoch: backend.activeEpoch,
|
|
178
|
+
operation: op,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
: undefined;
|
|
182
|
+
const afterMutation = homeLease
|
|
183
|
+
? async ({
|
|
184
|
+
op,
|
|
185
|
+
backend,
|
|
186
|
+
admission,
|
|
187
|
+
outcome,
|
|
188
|
+
retainedProcess,
|
|
189
|
+
}: {
|
|
190
|
+
op: string;
|
|
191
|
+
backend: ResolvedActiveBackend;
|
|
192
|
+
admission: unknown;
|
|
193
|
+
outcome: "resolved" | "rejected";
|
|
194
|
+
result?: unknown;
|
|
195
|
+
retainedProcess?: RoutingRetainedProcess;
|
|
196
|
+
}): Promise<void> => {
|
|
197
|
+
if (admission === null) return;
|
|
198
|
+
if (
|
|
199
|
+
!admission ||
|
|
200
|
+
typeof admission !== "object" ||
|
|
201
|
+
typeof (admission as Partial<SandboxWorkspaceMutationAdmission>).id !== "string" ||
|
|
202
|
+
typeof (admission as Partial<SandboxWorkspaceMutationAdmission>).workspaceGeneration !==
|
|
203
|
+
"number" ||
|
|
204
|
+
backend.leaseEpoch === undefined ||
|
|
205
|
+
backend.providerInstanceId === undefined ||
|
|
206
|
+
backend.activeEpoch === undefined
|
|
207
|
+
) {
|
|
208
|
+
throw new Error("API-direct workspace mutation settlement lacked its exact admission");
|
|
209
|
+
}
|
|
210
|
+
const exactAdmission = admission as SandboxWorkspaceMutationAdmission;
|
|
211
|
+
if (outcome === "resolved" && retainedProcess) {
|
|
212
|
+
await retainWorkspaceMutationProcess(db, {
|
|
213
|
+
accountId: ids.accountId,
|
|
214
|
+
workspaceId: ids.workspaceId,
|
|
215
|
+
sessionId: ids.sessionId,
|
|
216
|
+
processId: retainedProcess.id,
|
|
217
|
+
providerSessionId: retainedProcess.providerSessionId,
|
|
218
|
+
admissionId: exactAdmission.id,
|
|
219
|
+
admittedWorkspaceGeneration: exactAdmission.workspaceGeneration,
|
|
220
|
+
operation: op,
|
|
221
|
+
owner: {
|
|
222
|
+
kind: "direct",
|
|
223
|
+
requestId: ids.directRequest.requestId,
|
|
224
|
+
holderId: ids.directRequest.holderId,
|
|
225
|
+
sandboxGroupId: homeLease.sandboxGroupId,
|
|
226
|
+
expectedEpoch: backend.leaseEpoch,
|
|
227
|
+
expectedInstanceId: backend.providerInstanceId,
|
|
228
|
+
routeTargetId: exactAdmission.routeTargetId,
|
|
229
|
+
routeEpoch: exactAdmission.routeEpoch,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
await verifyDirectWorkspaceMutationSettlement(db, {
|
|
235
|
+
accountId: ids.accountId,
|
|
236
|
+
workspaceId: ids.workspaceId,
|
|
237
|
+
sessionId: ids.sessionId,
|
|
207
238
|
requestId: ids.directRequest.requestId,
|
|
208
239
|
holderId: ids.directRequest.holderId,
|
|
209
|
-
sandboxGroupId:
|
|
240
|
+
sandboxGroupId: homeLease.sandboxGroupId,
|
|
210
241
|
expectedEpoch: backend.leaseEpoch,
|
|
211
242
|
expectedInstanceId: backend.providerInstanceId,
|
|
212
243
|
routeTargetId: exactAdmission.routeTargetId,
|
|
213
244
|
routeEpoch: exactAdmission.routeEpoch,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
245
|
+
admission: exactAdmission,
|
|
246
|
+
operation: op,
|
|
247
|
+
outcome,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
: undefined;
|
|
251
|
+
const beforeProcessMutation = homeLease
|
|
252
|
+
? async ({
|
|
253
|
+
op,
|
|
254
|
+
process,
|
|
255
|
+
}: {
|
|
256
|
+
op: string;
|
|
257
|
+
backend: ResolvedActiveBackend;
|
|
258
|
+
process: RoutingRetainedProcess;
|
|
259
|
+
}): Promise<SandboxWorkspaceMutationAdmission> =>
|
|
260
|
+
await advanceWorkspaceGenerationForRetainedProcess(db, {
|
|
261
|
+
accountId: ids.accountId,
|
|
262
|
+
workspaceId: ids.workspaceId,
|
|
263
|
+
sessionId: ids.sessionId,
|
|
264
|
+
processId: process.id,
|
|
265
|
+
operation: op,
|
|
266
|
+
})
|
|
267
|
+
: undefined;
|
|
268
|
+
const afterProcessMutation = homeLease
|
|
269
|
+
? async ({
|
|
270
|
+
op,
|
|
271
|
+
process,
|
|
272
|
+
admission,
|
|
273
|
+
outcome,
|
|
274
|
+
}: {
|
|
275
|
+
op: string;
|
|
276
|
+
backend: ResolvedActiveBackend;
|
|
277
|
+
process: RoutingRetainedProcess;
|
|
278
|
+
admission: unknown;
|
|
279
|
+
outcome: "resolved" | "rejected";
|
|
280
|
+
result?: unknown;
|
|
281
|
+
}): Promise<void> => {
|
|
282
|
+
if (
|
|
283
|
+
!admission ||
|
|
284
|
+
typeof admission !== "object" ||
|
|
285
|
+
typeof (admission as Partial<SandboxWorkspaceMutationAdmission>).id !== "string" ||
|
|
286
|
+
typeof (admission as Partial<SandboxWorkspaceMutationAdmission>).workspaceGeneration !==
|
|
287
|
+
"number"
|
|
288
|
+
) {
|
|
289
|
+
throw new Error("API retained-process mutation settlement lacked its exact admission");
|
|
290
|
+
}
|
|
291
|
+
await verifyRetainedProcessMutationSettlement(db, {
|
|
292
|
+
accountId: ids.accountId,
|
|
293
|
+
workspaceId: ids.workspaceId,
|
|
294
|
+
sessionId: ids.sessionId,
|
|
295
|
+
processId: process.id,
|
|
296
|
+
admission: admission as SandboxWorkspaceMutationAdmission,
|
|
297
|
+
operation: op,
|
|
298
|
+
outcome,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
: undefined;
|
|
302
|
+
const settleProcess = homeLease
|
|
303
|
+
? async ({
|
|
304
|
+
backend,
|
|
305
|
+
process,
|
|
306
|
+
proof,
|
|
307
|
+
}: {
|
|
308
|
+
backend: ResolvedActiveBackend;
|
|
309
|
+
process: RoutingRetainedProcess;
|
|
310
|
+
proof: RoutingRetainedProcessTerminalProof;
|
|
311
|
+
}): Promise<void> => {
|
|
312
|
+
if (
|
|
313
|
+
backend.sandboxId !== null ||
|
|
314
|
+
backend.leaseEpoch === undefined ||
|
|
315
|
+
backend.providerInstanceId === undefined ||
|
|
316
|
+
backend.activeEpoch === undefined
|
|
317
|
+
) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const durable = await getRetainedProcess(db, {
|
|
321
|
+
workspaceId: ids.workspaceId,
|
|
322
|
+
sessionId: ids.sessionId,
|
|
323
|
+
processId: process.id,
|
|
324
|
+
});
|
|
325
|
+
if (
|
|
326
|
+
!durable ||
|
|
327
|
+
durable.providerSessionId !== process.providerSessionId ||
|
|
328
|
+
durable.providerBackend !== backend.kind ||
|
|
329
|
+
durable.providerInstanceId !== backend.providerInstanceId ||
|
|
330
|
+
durable.leaseEpoch !== backend.leaseEpoch ||
|
|
331
|
+
durable.routeKind !== (backend.sandboxId === null ? "home" : "active") ||
|
|
332
|
+
durable.routeTargetId !== backend.sandboxId ||
|
|
333
|
+
durable.routeEpoch !== backend.activeEpoch
|
|
334
|
+
) {
|
|
335
|
+
throw new Error(
|
|
336
|
+
"API retained-process settlement lost its exact durable backend identity",
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
await settleRetainedProcess(db, {
|
|
340
|
+
accountId: ids.accountId,
|
|
341
|
+
workspaceId: ids.workspaceId,
|
|
342
|
+
sessionId: ids.sessionId,
|
|
343
|
+
processId: process.id,
|
|
344
|
+
expected: retainedProcessSettlementIdentity(durable),
|
|
345
|
+
outcome: proof.outcome,
|
|
346
|
+
exitCode: proof.exitCode,
|
|
347
|
+
reason: proof.reason,
|
|
348
|
+
idleGraceMs: settings.sandboxIdleGraceMs,
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
: undefined;
|
|
308
352
|
const resolver = makeActiveBackendResolver({
|
|
309
353
|
workspaceId: ids.workspaceId,
|
|
310
354
|
defaultBackend: established.session as RoutableBackendSession,
|
|
@@ -322,13 +366,28 @@ export function wrapChannelABoxWithRouting(
|
|
|
322
366
|
},
|
|
323
367
|
controlRpcFactory: controlRpcFactory(bus),
|
|
324
368
|
relay: relayConfigFromSettings(settings),
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
369
|
+
selfhostedTimeoutMs: settings.sandboxSelfhostedControlTimeoutMs,
|
|
370
|
+
selfhostedExecTimeoutMs: settings.sandboxSelfhostedExecTimeoutMs,
|
|
371
|
+
...(ids.pinnedSelfhosted
|
|
372
|
+
? {
|
|
373
|
+
pinnedSelfhosted: {
|
|
374
|
+
sandboxId: ids.pinnedSelfhosted.sandboxId,
|
|
375
|
+
epoch: ids.pinnedSelfhosted.epoch,
|
|
376
|
+
session: established.session as RoutableBackendSession,
|
|
377
|
+
},
|
|
378
|
+
}
|
|
379
|
+
: {}),
|
|
380
|
+
...(homeLease
|
|
381
|
+
? {
|
|
382
|
+
resolveDefaultBackend: async () => ({
|
|
383
|
+
session: established.session as RoutableBackendSession,
|
|
384
|
+
sandboxId: null,
|
|
385
|
+
kind: established.backendId,
|
|
386
|
+
leaseEpoch: homeLease.leaseEpoch,
|
|
387
|
+
providerInstanceId: homeLease.instanceId,
|
|
388
|
+
}),
|
|
389
|
+
}
|
|
390
|
+
: {}),
|
|
332
391
|
});
|
|
333
392
|
|
|
334
393
|
const proxy = new RoutingSandboxSession({
|
|
@@ -336,8 +395,12 @@ export function wrapChannelABoxWithRouting(
|
|
|
336
395
|
session: established.session as RoutableBackendSession,
|
|
337
396
|
sandboxId: null,
|
|
338
397
|
kind: established.backendId,
|
|
339
|
-
|
|
340
|
-
|
|
398
|
+
...(homeLease
|
|
399
|
+
? {
|
|
400
|
+
leaseEpoch: homeLease.leaseEpoch,
|
|
401
|
+
providerInstanceId: homeLease.instanceId,
|
|
402
|
+
}
|
|
403
|
+
: {}),
|
|
341
404
|
},
|
|
342
405
|
readPointer: async () => {
|
|
343
406
|
if (!routingEnabled(settings)) {
|
|
@@ -347,43 +410,47 @@ export function wrapChannelABoxWithRouting(
|
|
|
347
410
|
return pointer ?? { activeSandboxId: null, activeEpoch: 0 };
|
|
348
411
|
},
|
|
349
412
|
resolveActiveBackend: resolver,
|
|
350
|
-
beforeMutation,
|
|
351
|
-
afterMutation,
|
|
352
|
-
beforeProcessMutation,
|
|
353
|
-
afterProcessMutation,
|
|
354
|
-
settleProcess,
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
413
|
+
...(beforeMutation ? { beforeMutation } : {}),
|
|
414
|
+
...(afterMutation ? { afterMutation } : {}),
|
|
415
|
+
...(beforeProcessMutation ? { beforeProcessMutation } : {}),
|
|
416
|
+
...(afterProcessMutation ? { afterProcessMutation } : {}),
|
|
417
|
+
...(settleProcess ? { settleProcess } : {}),
|
|
418
|
+
...(homeLease
|
|
419
|
+
? {
|
|
420
|
+
onDefaultBackendError: async ({ error }: { error: unknown }) => {
|
|
421
|
+
if (!isProviderSandboxGoneDuringRoutedOperation(homeLease.backend, error)) return null;
|
|
422
|
+
const marked = await markWarmLeaseInstanceLost(db, {
|
|
423
|
+
accountId: ids.accountId,
|
|
424
|
+
workspaceId: ids.workspaceId,
|
|
425
|
+
sandboxGroupId: homeLease.sandboxGroupId,
|
|
426
|
+
expectedEpoch: homeLease.leaseEpoch,
|
|
427
|
+
expectedInstanceId: homeLease.instanceId,
|
|
428
|
+
diagnostic: "provider_not_found_during_routed_operation",
|
|
429
|
+
});
|
|
430
|
+
if (marked.status === "marked" && bus) {
|
|
431
|
+
await appendAndPublishEvents(db, bus, ids.workspaceId, ids.sessionId, [
|
|
432
|
+
{
|
|
433
|
+
type: "sandbox.box.lost",
|
|
434
|
+
payload: { sandboxId: homeLease.instanceId },
|
|
435
|
+
},
|
|
436
|
+
]).catch(() => undefined);
|
|
437
|
+
}
|
|
438
|
+
const lease = marked.lease;
|
|
439
|
+
const restore = lease?.recovery.restore.status;
|
|
440
|
+
return {
|
|
441
|
+
leaseEpoch: lease?.leaseEpoch ?? homeLease.leaseEpoch,
|
|
442
|
+
recovery:
|
|
443
|
+
marked.status === "stale"
|
|
444
|
+
? ("superseded" as const)
|
|
445
|
+
: restore === "pending"
|
|
446
|
+
? ("pending" as const)
|
|
447
|
+
: restore === "degraded"
|
|
448
|
+
? ("degraded" as const)
|
|
449
|
+
: ("unrecoverable" as const),
|
|
450
|
+
};
|
|
370
451
|
},
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
const lease = marked.lease;
|
|
374
|
-
const restore = lease?.recovery.restore.status;
|
|
375
|
-
return {
|
|
376
|
-
leaseEpoch: lease?.leaseEpoch ?? ids.homeLease.leaseEpoch,
|
|
377
|
-
recovery:
|
|
378
|
-
marked.status === "stale"
|
|
379
|
-
? ("superseded" as const)
|
|
380
|
-
: restore === "pending"
|
|
381
|
-
? ("pending" as const)
|
|
382
|
-
: restore === "degraded"
|
|
383
|
-
? ("degraded" as const)
|
|
384
|
-
: ("unrecoverable" as const),
|
|
385
|
-
};
|
|
386
|
-
},
|
|
452
|
+
}
|
|
453
|
+
: {}),
|
|
387
454
|
});
|
|
388
455
|
|
|
389
456
|
return { ...established, session: proxy };
|