@agen-ai/sandbox 0.0.0-stage → 0.4.4
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/LICENSE +21 -0
- package/README.md +141 -2
- package/dist/acp/index.d.ts +9 -0
- package/dist/acp/index.js +25 -0
- package/dist/backend/contracts.d.ts +41 -0
- package/dist/backend/contracts.js +0 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +64 -0
- package/dist/backend/spill.d.ts +15 -0
- package/dist/backend/spill.js +0 -0
- package/dist/contracts.d.ts +37 -0
- package/dist/contracts.js +0 -0
- package/dist/errors.d.ts +46 -0
- package/dist/errors.js +73 -0
- package/dist/files/bytes.d.ts +31 -0
- package/dist/files/bytes.js +134 -0
- package/dist/files/iterator.d.ts +13 -0
- package/dist/files/iterator.js +59 -0
- package/dist/files/metadata/index.d.ts +12 -0
- package/dist/files/metadata/index.js +193 -0
- package/dist/files/metadata/records.d.ts +21 -0
- package/dist/files/metadata/records.js +50 -0
- package/dist/files/metadata/sort.d.ts +13 -0
- package/dist/files/metadata/sort.js +63 -0
- package/dist/files/metadata/storage.d.ts +12 -0
- package/dist/files/metadata/storage.js +69 -0
- package/dist/files/metadata/types.d.ts +19 -0
- package/dist/files/metadata/types.js +0 -0
- package/dist/files/parse.d.ts +20 -0
- package/dist/files/parse.js +62 -0
- package/dist/files/pathSchemas.d.ts +5 -0
- package/dist/files/pathSchemas.js +16 -0
- package/dist/files/paths.d.ts +4 -0
- package/dist/files/paths.js +12 -0
- package/dist/files/quotas.d.ts +18 -0
- package/dist/files/quotas.js +37 -0
- package/dist/files/types.d.ts +81 -0
- package/dist/files/types.js +0 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +35 -0
- package/dist/internal/data.d.ts +6 -0
- package/dist/internal/data.js +68 -0
- package/dist/internal/operations.d.ts +4 -0
- package/dist/internal/operations.js +30 -0
- package/dist/internal/parse.d.ts +3 -0
- package/dist/internal/parse.js +12 -0
- package/dist/lifecycle/deadline.d.ts +3 -0
- package/dist/lifecycle/deadline.js +18 -0
- package/dist/lifecycle/journal.d.ts +14 -0
- package/dist/lifecycle/journal.js +51 -0
- package/dist/lifecycle/observation.d.ts +9 -0
- package/dist/lifecycle/observation.js +41 -0
- package/dist/lifecycle/owner.d.ts +38 -0
- package/dist/lifecycle/owner.js +288 -0
- package/dist/lifecycle/recovered.d.ts +16 -0
- package/dist/lifecycle/recovered.js +62 -0
- package/dist/lifecycle/recoveryIdentity.d.ts +3 -0
- package/dist/lifecycle/recoveryIdentity.js +18 -0
- package/dist/lifecycle/transfer.d.ts +10 -0
- package/dist/lifecycle/transfer.js +339 -0
- package/dist/node/directories/closing.d.ts +2 -0
- package/dist/node/directories/closing.js +19 -0
- package/dist/node/directories/export.d.ts +6 -0
- package/dist/node/directories/export.js +43 -0
- package/dist/node/directories/file.d.ts +26 -0
- package/dist/node/directories/file.js +104 -0
- package/dist/node/directories/import.d.ts +6 -0
- package/dist/node/directories/import.js +38 -0
- package/dist/node/directories/options.d.ts +4 -0
- package/dist/node/directories/options.js +10 -0
- package/dist/node/directories/ownership.d.ts +33 -0
- package/dist/node/directories/ownership.js +148 -0
- package/dist/node/directories/source.d.ts +25 -0
- package/dist/node/directories/source.js +143 -0
- package/dist/node/directories/stage.d.ts +43 -0
- package/dist/node/directories/stage.js +302 -0
- package/dist/node/directories/types.d.ts +23 -0
- package/dist/node/directories/types.js +17 -0
- package/dist/node/hostPath.d.ts +7 -0
- package/dist/node/hostPath.js +37 -0
- package/dist/node/index.d.ts +8 -0
- package/dist/node/index.js +14 -0
- package/dist/node/spill.d.ts +14 -0
- package/dist/node/spill.js +97 -0
- package/dist/node/spillDirectory.d.ts +4 -0
- package/dist/node/spillDirectory.js +17 -0
- package/dist/node/spillFile.d.ts +24 -0
- package/dist/node/spillFile.js +96 -0
- package/dist/policy/limits.d.ts +31 -0
- package/dist/policy/limits.js +35 -0
- package/dist/policy/parse.d.ts +11 -0
- package/dist/policy/parse.js +47 -0
- package/dist/policy/schemas.d.ts +148 -0
- package/dist/policy/schemas.js +75 -0
- package/dist/policy/types.d.ts +105 -0
- package/dist/policy/types.js +0 -0
- package/dist/process/input.d.ts +23 -0
- package/dist/process/input.js +93 -0
- package/dist/process/output.d.ts +13 -0
- package/dist/process/output.js +43 -0
- package/dist/process/parse.d.ts +4 -0
- package/dist/process/parse.js +32 -0
- package/dist/process/streams.d.ts +9 -0
- package/dist/process/streams.js +61 -0
- package/dist/process/types.d.ts +38 -0
- package/dist/process/types.js +0 -0
- package/dist/recovery/parse.d.ts +4 -0
- package/dist/recovery/parse.js +67 -0
- package/dist/recovery/types.d.ts +59 -0
- package/dist/recovery/types.js +0 -0
- package/dist/withSandbox.d.ts +4 -0
- package/dist/withSandbox.js +39 -0
- package/package.json +76 -3
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import { SandboxCleanupError, SandboxOperationError, SandboxStartError, SandboxStateError } from "../errors.js";
|
|
2
|
+
import { parseTransferOptions } from "../files/parse.js";
|
|
3
|
+
import { parseSandboxGuestPath } from "../files/paths.js";
|
|
4
|
+
import { assertSandboxEffectivePolicy, parseSandboxEffectivePolicy, parseSandboxProcessId } from "../policy/parse.js";
|
|
5
|
+
import { parseSandboxProcessSpec } from "../process/parse.js";
|
|
6
|
+
import { ownProcessStreams } from "../process/streams.js";
|
|
7
|
+
import { parseOperationOptions } from "../internal/operations.js";
|
|
8
|
+
import { RecoveryJournal } from "./journal.js";
|
|
9
|
+
import { scheduleDeadline } from "./deadline.js";
|
|
10
|
+
import { actualOperation, observe, settled } from "./observation.js";
|
|
11
|
+
import { importTree, exportTree } from "./transfer.js";
|
|
12
|
+
class SandboxOwner {
|
|
13
|
+
constructor(driver, request, recovery, record) {
|
|
14
|
+
this.driver = driver;
|
|
15
|
+
this.request = request;
|
|
16
|
+
this.journal = new RecoveryJournal(record, recovery, (error) => {
|
|
17
|
+
this.startupController.abort(error);
|
|
18
|
+
this.fence("start");
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
snapshot = Object.freeze({ kind: "reserved" });
|
|
22
|
+
journal;
|
|
23
|
+
startupController = new AbortController();
|
|
24
|
+
startup;
|
|
25
|
+
ready;
|
|
26
|
+
transfer;
|
|
27
|
+
processReserved = false;
|
|
28
|
+
process;
|
|
29
|
+
streams;
|
|
30
|
+
cancelExecutionDeadline;
|
|
31
|
+
stopActual;
|
|
32
|
+
destroyActual;
|
|
33
|
+
stopObservation;
|
|
34
|
+
destroyObservation;
|
|
35
|
+
get id() {
|
|
36
|
+
return this.driver.id;
|
|
37
|
+
}
|
|
38
|
+
get state() {
|
|
39
|
+
return this.snapshot;
|
|
40
|
+
}
|
|
41
|
+
get recoveryRecord() {
|
|
42
|
+
return this.journal.current;
|
|
43
|
+
}
|
|
44
|
+
start(input) {
|
|
45
|
+
const options = parseOperationOptions(input);
|
|
46
|
+
if (this.snapshot.kind !== "reserved") return Promise.reject(new SandboxStateError("Sandbox startup is once-only and cannot follow cleanup."));
|
|
47
|
+
this.snapshot = Object.freeze({ kind: "starting" });
|
|
48
|
+
if (options.signal?.aborted) this.startupController.abort(new SandboxOperationError("cancelled", "Sandbox startup was cancelled before entry."));
|
|
49
|
+
this.startup = actualOperation(async () => {
|
|
50
|
+
this.startupController.signal.throwIfAborted();
|
|
51
|
+
await this.journal.initialize();
|
|
52
|
+
this.startupController.signal.throwIfAborted();
|
|
53
|
+
const ready = await this.driver.start({ signal: this.startupController.signal, checkpoint: this.journal.checkpoint });
|
|
54
|
+
this.ready = ready;
|
|
55
|
+
if (this.snapshot.kind !== "starting" || this.startupController.signal.aborted) {
|
|
56
|
+
throw new SandboxOperationError("cancelled", "Sandbox startup settled after its admission was closed.");
|
|
57
|
+
}
|
|
58
|
+
const effective = parseSandboxEffectivePolicy(ready.effective);
|
|
59
|
+
const workspace = parseSandboxGuestPath(ready.workspace);
|
|
60
|
+
assertSandboxEffectivePolicy(this.request, effective);
|
|
61
|
+
const owner = this;
|
|
62
|
+
const sandbox = Object.freeze({
|
|
63
|
+
id: this.id,
|
|
64
|
+
effective,
|
|
65
|
+
workspace,
|
|
66
|
+
get state() {
|
|
67
|
+
return owner.state;
|
|
68
|
+
},
|
|
69
|
+
get recoveryRecord() {
|
|
70
|
+
return owner.recoveryRecord;
|
|
71
|
+
},
|
|
72
|
+
importTree: (entries, operation) => owner.importTree(entries, operation),
|
|
73
|
+
reserveProcess: (spec) => owner.reserveProcess(spec),
|
|
74
|
+
exportTree: (selection, consume, operation) => owner.exportTree(selection, consume, operation),
|
|
75
|
+
stop: () => owner.stop(),
|
|
76
|
+
destroy: () => owner.destroy()
|
|
77
|
+
});
|
|
78
|
+
this.snapshot = Object.freeze({ kind: "ready" });
|
|
79
|
+
return sandbox;
|
|
80
|
+
});
|
|
81
|
+
void this.startup.catch(() => this.fence("start"));
|
|
82
|
+
return observe(this.startup, this.request.policy.deadlines.startupMs, {
|
|
83
|
+
signal: operationSignal(this.startupController, options),
|
|
84
|
+
onInterrupt: (error) => {
|
|
85
|
+
this.startupController.abort(error);
|
|
86
|
+
this.fence("start");
|
|
87
|
+
void this.stop().catch(() => {
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}).catch((cause) => {
|
|
91
|
+
throw new SandboxStartError("Sandbox startup failed; its reservation retains cleanup ownership.", { cause, owner: this });
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
stop() {
|
|
95
|
+
if (this.snapshot.kind === "destroyed" || this.snapshot.kind === "stopped") return Promise.resolve();
|
|
96
|
+
if (this.stopObservation) return this.stopObservation;
|
|
97
|
+
const actual = this.beginStop();
|
|
98
|
+
this.stopObservation = observe(actual, this.request.policy.deadlines.cleanupMs).catch((cause) => {
|
|
99
|
+
this.fence("stop");
|
|
100
|
+
throw new SandboxCleanupError("Sandbox compute cleanup did not settle successfully.", { cause, owner: this, operation: "stop" });
|
|
101
|
+
}).finally(() => {
|
|
102
|
+
this.stopObservation = void 0;
|
|
103
|
+
});
|
|
104
|
+
return this.stopObservation;
|
|
105
|
+
}
|
|
106
|
+
destroy() {
|
|
107
|
+
if (this.snapshot.kind === "destroyed") return Promise.resolve();
|
|
108
|
+
if (this.destroyObservation) return this.destroyObservation;
|
|
109
|
+
this.snapshot = Object.freeze({ kind: "destroying" });
|
|
110
|
+
this.transfer?.controller.abort(new SandboxOperationError("cancelled", "Sandbox storage is being destroyed."));
|
|
111
|
+
if (!this.destroyActual) {
|
|
112
|
+
const transferCleanup = this.cleanupTransfer();
|
|
113
|
+
void transferCleanup.catch(() => {
|
|
114
|
+
});
|
|
115
|
+
const dispatch = Promise.withResolvers();
|
|
116
|
+
const actual = dispatch.promise.then(async () => {
|
|
117
|
+
await transferCleanup;
|
|
118
|
+
await this.driver.destroy();
|
|
119
|
+
this.snapshot = Object.freeze({ kind: "destroyed" });
|
|
120
|
+
}).catch((error) => {
|
|
121
|
+
this.destroyActual = void 0;
|
|
122
|
+
if (this.snapshot.kind !== "destroyed") this.snapshot = Object.freeze({ kind: "uncertain", operation: "destroy" });
|
|
123
|
+
throw error;
|
|
124
|
+
});
|
|
125
|
+
this.destroyActual = actual;
|
|
126
|
+
this.beginStop().then(dispatch.resolve, dispatch.reject);
|
|
127
|
+
}
|
|
128
|
+
this.destroyObservation = observe(this.destroyActual, this.request.policy.deadlines.cleanupMs).catch((cause) => {
|
|
129
|
+
this.fence("destroy");
|
|
130
|
+
throw new SandboxCleanupError("Sandbox destruction did not settle successfully.", { cause, owner: this, operation: "destroy" });
|
|
131
|
+
}).finally(() => {
|
|
132
|
+
this.destroyObservation = void 0;
|
|
133
|
+
});
|
|
134
|
+
return this.destroyObservation;
|
|
135
|
+
}
|
|
136
|
+
beginStop() {
|
|
137
|
+
if (this.stopActual) return this.stopActual;
|
|
138
|
+
if (this.snapshot.kind !== "destroying") this.snapshot = Object.freeze({ kind: "stopping" });
|
|
139
|
+
const physical = Promise.withResolvers();
|
|
140
|
+
let transferCleanup;
|
|
141
|
+
const actual = physical.promise.then(async () => {
|
|
142
|
+
await settled(this.startup);
|
|
143
|
+
await settled(this.process?.actual);
|
|
144
|
+
this.streams?.close();
|
|
145
|
+
await this.streams?.settled;
|
|
146
|
+
await transferCleanup;
|
|
147
|
+
if (this.snapshot.kind !== "destroying" && this.snapshot.kind !== "destroyed") this.snapshot = Object.freeze({ kind: "stopped" });
|
|
148
|
+
}).catch((error) => {
|
|
149
|
+
this.stopActual = void 0;
|
|
150
|
+
if (this.snapshot.kind !== "destroyed" && this.snapshot.kind !== "destroying") this.snapshot = Object.freeze({ kind: "uncertain", operation: "stop" });
|
|
151
|
+
throw error;
|
|
152
|
+
});
|
|
153
|
+
this.stopActual = actual;
|
|
154
|
+
void actual.catch(() => {
|
|
155
|
+
});
|
|
156
|
+
const reason = new SandboxOperationError("cancelled", "Sandbox compute is stopping.");
|
|
157
|
+
this.startupController.abort(reason);
|
|
158
|
+
this.process?.controller.abort(reason);
|
|
159
|
+
if (this.transfer?.kind === "import") this.transfer.controller.abort(reason);
|
|
160
|
+
this.cancelExecutionDeadline?.();
|
|
161
|
+
this.cancelExecutionDeadline = void 0;
|
|
162
|
+
actualOperation(() => this.driver.stop()).then(physical.resolve, physical.reject);
|
|
163
|
+
if (this.transfer?.kind === "import") {
|
|
164
|
+
transferCleanup = this.cleanupTransfer();
|
|
165
|
+
void transferCleanup.catch(() => {
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
this.streams?.close();
|
|
169
|
+
return actual;
|
|
170
|
+
}
|
|
171
|
+
importTree(entries, input) {
|
|
172
|
+
const options = parseTransferOptions(input);
|
|
173
|
+
if (this.snapshot.kind !== "ready" || this.processReserved || this.transfer) throw new SandboxStateError("File import requires ready compute, no entered import, and no process reservation.");
|
|
174
|
+
const ready = this.readyPort();
|
|
175
|
+
return this.enterFile("import", options, (controller) => importTree(ready, entries, {
|
|
176
|
+
signal: controller.signal,
|
|
177
|
+
quotas: options.quotas,
|
|
178
|
+
buffers: this.request.policy.transfers
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
exportTree(selection, consume, input) {
|
|
182
|
+
const options = parseTransferOptions(input);
|
|
183
|
+
if (this.snapshot.kind !== "stopped" || this.transfer) throw new SandboxStateError("File export requires stopped compute and no other entered transfer.");
|
|
184
|
+
const ready = this.readyPort();
|
|
185
|
+
return this.enterFile("export", options, (controller) => exportTree(ready, selection, consume, {
|
|
186
|
+
signal: controller.signal,
|
|
187
|
+
quotas: options.quotas,
|
|
188
|
+
buffers: this.request.policy.transfers
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
enterFile(kind, options, perform) {
|
|
192
|
+
if (options.signal?.aborted) return Promise.reject(new SandboxOperationError("cancelled", "Sandbox file transfer was cancelled before entry."));
|
|
193
|
+
if (options.deadlineAt !== void 0 && options.deadlineAt <= Date.now()) return Promise.reject(new SandboxOperationError("deadline_exceeded", "Sandbox file transfer deadline elapsed before entry."));
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const work = perform(controller);
|
|
196
|
+
const actual = work.actual;
|
|
197
|
+
const entry = { kind, controller, actual, work };
|
|
198
|
+
this.transfer = entry;
|
|
199
|
+
const release = () => {
|
|
200
|
+
if (this.transfer === entry && work.clean) this.transfer = void 0;
|
|
201
|
+
};
|
|
202
|
+
void actual.then(release, () => {
|
|
203
|
+
release();
|
|
204
|
+
if (kind === "import") this.fence("import");
|
|
205
|
+
});
|
|
206
|
+
const duration = options.deadlineAt === void 0 ? void 0 : Math.max(0, options.deadlineAt - Date.now());
|
|
207
|
+
const observation = observe(actual, duration, { signal: operationSignal(controller, options), onInterrupt: (error) => {
|
|
208
|
+
controller.abort(error);
|
|
209
|
+
this.fence(kind);
|
|
210
|
+
if (kind === "import") void this.stop().catch(() => {
|
|
211
|
+
});
|
|
212
|
+
} });
|
|
213
|
+
return observation;
|
|
214
|
+
}
|
|
215
|
+
async cleanupTransfer() {
|
|
216
|
+
const entry = this.transfer;
|
|
217
|
+
if (!entry) return;
|
|
218
|
+
await entry.work.cleanup();
|
|
219
|
+
if (this.transfer === entry && entry.work.clean) this.transfer = void 0;
|
|
220
|
+
}
|
|
221
|
+
reserveProcess(input) {
|
|
222
|
+
const spec = parseSandboxProcessSpec(input);
|
|
223
|
+
if (this.snapshot.kind !== "ready" || this.processReserved || this.transfer) throw new SandboxStateError("A sandbox admits one process reservation, after its imports settle.");
|
|
224
|
+
this.processReserved = true;
|
|
225
|
+
const reserved = this.readyPort().reserveProcess(spec);
|
|
226
|
+
const id = parseSandboxProcessId(reserved.id);
|
|
227
|
+
let entered = false;
|
|
228
|
+
return Object.freeze({ id, stopScope: "sandbox", start: (options) => {
|
|
229
|
+
const parsed = parseOperationOptions(options);
|
|
230
|
+
if (entered || this.snapshot.kind !== "ready") return Promise.reject(new SandboxStateError("Process startup is once-only and requires ready compute."));
|
|
231
|
+
entered = true;
|
|
232
|
+
const controller = new AbortController();
|
|
233
|
+
if (parsed.signal?.aborted) controller.abort(new SandboxOperationError("cancelled", "Process startup was cancelled before entry."));
|
|
234
|
+
const dispatch = Promise.withResolvers();
|
|
235
|
+
const actual = dispatch.promise;
|
|
236
|
+
this.process = { controller, actual };
|
|
237
|
+
const execution = this.request.policy.execution;
|
|
238
|
+
if (execution.kind === "deadline") {
|
|
239
|
+
this.cancelExecutionDeadline = scheduleDeadline(execution.timeoutMs, () => {
|
|
240
|
+
controller.abort(new SandboxOperationError("deadline_exceeded", "Sandbox execution exceeded its deadline."));
|
|
241
|
+
void this.stop().catch(() => {
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const observation = observe(actual, this.request.policy.deadlines.startupMs, { signal: operationSignal(controller, parsed), onInterrupt: (error) => {
|
|
246
|
+
controller.abort(error);
|
|
247
|
+
this.fence("process");
|
|
248
|
+
void this.stop().catch(() => {
|
|
249
|
+
});
|
|
250
|
+
} });
|
|
251
|
+
void actual.catch(() => {
|
|
252
|
+
this.fence("process");
|
|
253
|
+
void this.stop().catch(() => {
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
actualOperation(async () => {
|
|
257
|
+
controller.signal.throwIfAborted();
|
|
258
|
+
const raw = await reserved.start({ signal: controller.signal });
|
|
259
|
+
const owned = ownProcessStreams(raw, this.request.policy.streams, () => {
|
|
260
|
+
void this.stop().catch(() => {
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
this.streams = owned;
|
|
264
|
+
if (controller.signal.aborted || this.snapshot.kind !== "ready") {
|
|
265
|
+
owned.close();
|
|
266
|
+
throw new SandboxOperationError("cancelled", "Process startup settled after its admission was closed.");
|
|
267
|
+
}
|
|
268
|
+
return owned.streams;
|
|
269
|
+
}).then(dispatch.resolve, dispatch.reject);
|
|
270
|
+
return observation;
|
|
271
|
+
}, stop: () => this.stop() });
|
|
272
|
+
}
|
|
273
|
+
readyPort() {
|
|
274
|
+
if (!this.ready) throw new SandboxStateError("Sandbox startup has not yielded a workspace.");
|
|
275
|
+
return this.ready;
|
|
276
|
+
}
|
|
277
|
+
fence(operation) {
|
|
278
|
+
if (this.snapshot.kind !== "destroyed" && this.snapshot.kind !== "destroying" && this.snapshot.kind !== "stopping" && this.snapshot.kind !== "stopped") {
|
|
279
|
+
this.snapshot = Object.freeze({ kind: "uncertain", operation });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function operationSignal(controller, options) {
|
|
284
|
+
return options.signal === void 0 ? controller.signal : AbortSignal.any([controller.signal, options.signal]);
|
|
285
|
+
}
|
|
286
|
+
export {
|
|
287
|
+
SandboxOwner
|
|
288
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SandboxCleanupOwner, SandboxRecoveryRecord } from '../recovery/types.js';
|
|
2
|
+
/** Recovery grants cleanup only, using the driver that verified the original identities. */
|
|
3
|
+
export declare class RecoveredSandboxOwner implements SandboxCleanupOwner {
|
|
4
|
+
private readonly driver;
|
|
5
|
+
private record;
|
|
6
|
+
private stopActual;
|
|
7
|
+
private destroyActual;
|
|
8
|
+
private stopObservation;
|
|
9
|
+
private destroyObservation;
|
|
10
|
+
constructor(driver: SandboxCleanupOwner, record: SandboxRecoveryRecord);
|
|
11
|
+
get recoveryRecord(): SandboxRecoveryRecord;
|
|
12
|
+
stop(): Promise<void>;
|
|
13
|
+
destroy(): Promise<void>;
|
|
14
|
+
private beginStop;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=recovered.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SandboxCleanupError } from "../errors.js";
|
|
2
|
+
import { SANDBOX_LIMITS } from "../policy/limits.js";
|
|
3
|
+
import { parseSandboxRecoveryRecord } from "../recovery/parse.js";
|
|
4
|
+
import { actualOperation, observe } from "./observation.js";
|
|
5
|
+
import { assertRecoveryContinuation } from "./recoveryIdentity.js";
|
|
6
|
+
class RecoveredSandboxOwner {
|
|
7
|
+
constructor(driver, record) {
|
|
8
|
+
this.driver = driver;
|
|
9
|
+
this.record = record;
|
|
10
|
+
}
|
|
11
|
+
stopActual;
|
|
12
|
+
destroyActual;
|
|
13
|
+
stopObservation;
|
|
14
|
+
destroyObservation;
|
|
15
|
+
get recoveryRecord() {
|
|
16
|
+
const next = parseSandboxRecoveryRecord(this.driver.recoveryRecord);
|
|
17
|
+
assertRecoveryContinuation(this.record, next, "reconciliation");
|
|
18
|
+
this.record = next;
|
|
19
|
+
return next;
|
|
20
|
+
}
|
|
21
|
+
stop() {
|
|
22
|
+
this.stopObservation ??= observe(this.beginStop(), SANDBOX_LIMITS.cleanupMs).catch((cause) => {
|
|
23
|
+
throw new SandboxCleanupError("Recovered sandbox compute cleanup failed.", { cause, operation: "stop", owner: this });
|
|
24
|
+
}).finally(() => {
|
|
25
|
+
this.stopObservation = void 0;
|
|
26
|
+
});
|
|
27
|
+
return this.stopObservation;
|
|
28
|
+
}
|
|
29
|
+
destroy() {
|
|
30
|
+
this.destroyActual ??= this.beginStop().then(() => {
|
|
31
|
+
void this.recoveryRecord;
|
|
32
|
+
return this.driver.destroy();
|
|
33
|
+
}).catch((error) => {
|
|
34
|
+
this.destroyActual = void 0;
|
|
35
|
+
throw error;
|
|
36
|
+
});
|
|
37
|
+
this.destroyObservation ??= observe(this.destroyActual, SANDBOX_LIMITS.cleanupMs).catch((cause) => {
|
|
38
|
+
throw new SandboxCleanupError("Recovered sandbox destruction failed.", { cause, operation: "destroy", owner: this });
|
|
39
|
+
}).finally(() => {
|
|
40
|
+
this.destroyObservation = void 0;
|
|
41
|
+
});
|
|
42
|
+
return this.destroyObservation;
|
|
43
|
+
}
|
|
44
|
+
beginStop() {
|
|
45
|
+
if (!this.stopActual) {
|
|
46
|
+
const dispatch = Promise.withResolvers();
|
|
47
|
+
const actual = dispatch.promise.catch((error) => {
|
|
48
|
+
this.stopActual = void 0;
|
|
49
|
+
throw error;
|
|
50
|
+
});
|
|
51
|
+
this.stopActual = actual;
|
|
52
|
+
actualOperation(() => {
|
|
53
|
+
void this.recoveryRecord;
|
|
54
|
+
return this.driver.stop();
|
|
55
|
+
}).then(dispatch.resolve, dispatch.reject);
|
|
56
|
+
}
|
|
57
|
+
return this.stopActual;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
RecoveredSandboxOwner
|
|
62
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { SandboxRecoveryRecord } from '../recovery/types.js';
|
|
2
|
+
export declare function assertRecoveryContinuation(previous: SandboxRecoveryRecord, next: SandboxRecoveryRecord, operation: 'checkpoint' | 'reconciliation'): void;
|
|
3
|
+
//# sourceMappingURL=recoveryIdentity.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SandboxStateError } from "../errors.js";
|
|
2
|
+
function assertRecoveryContinuation(previous, next, operation) {
|
|
3
|
+
if (next.backendId !== previous.backendId || next.backendRevision !== previous.backendRevision || next.namespace !== previous.namespace || next.operationId !== previous.operationId || next.sandboxId !== previous.sandboxId || next.imageDigest !== previous.imageDigest || next.policyDigest !== previous.policyDigest || next.configurationDigest !== previous.configurationDigest || next.resources.length !== previous.resources.length) {
|
|
4
|
+
throw new SandboxStateError("Recovery observations cannot change their reserved ownership identity.");
|
|
5
|
+
}
|
|
6
|
+
if (previous.authority.kind === "verified" && (next.authority.kind !== "verified" || next.authority.backendIdentity !== previous.authority.backendIdentity)) {
|
|
7
|
+
throw new SandboxStateError("Recovery observations cannot replace verified backend authority.");
|
|
8
|
+
}
|
|
9
|
+
for (const resource of previous.resources) {
|
|
10
|
+
const replacement = next.resources.find((candidate) => candidate.role === resource.role);
|
|
11
|
+
if (!replacement || replacement.name !== resource.name || resource.identity.kind === "planned" && replacement.identity.kind === "identified" || resource.identity.kind === "planned" && replacement.identity.kind === "creating" && operation === "reconciliation" || resource.identity.kind === "creating" && replacement.identity.kind === "planned" || resource.identity.kind === "identified" && (replacement.identity.kind !== "identified" || replacement.identity.id !== resource.identity.id || replacement.identity.fingerprint !== resource.identity.fingerprint)) {
|
|
12
|
+
throw new SandboxStateError("Recovery observations cannot replace an owned resource identity or enter a new resource effect.");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
assertRecoveryContinuation
|
|
18
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SandboxDriverReady, SandboxDriverTransferOptions } from '../backend/contracts.js';
|
|
2
|
+
import type { SandboxExportConsumer, SandboxTreeSelection, SandboxTreeSource, SandboxTransferSummary } from '../files/types.js';
|
|
3
|
+
export interface OwnedTransfer {
|
|
4
|
+
readonly actual: Promise<SandboxTransferSummary>;
|
|
5
|
+
readonly clean: boolean;
|
|
6
|
+
cleanup(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function importTree(ready: SandboxDriverReady, entries: SandboxTreeSource, selected: SandboxDriverTransferOptions): OwnedTransfer;
|
|
9
|
+
export declare function exportTree(ready: SandboxDriverReady, input: SandboxTreeSelection, consume: SandboxExportConsumer, selectedOptions: SandboxDriverTransferOptions): OwnedTransfer;
|
|
10
|
+
//# sourceMappingURL=transfer.d.ts.map
|