@namzu/sandbox 16.0.0 → 17.0.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/CHANGELOG.md +238 -0
- package/README.md +164 -0
- package/dist/backends/aci-standby-pool/index.d.ts +22 -4
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
- package/dist/backends/aci-standby-pool/index.js +31 -7
- package/dist/backends/aci-standby-pool/index.js.map +1 -1
- package/dist/backends/docker/index.d.ts +243 -24
- package/dist/backends/docker/index.d.ts.map +1 -1
- package/dist/backends/docker/index.js +717 -108
- package/dist/backends/docker/index.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +156 -1
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +223 -29
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/http-worker-client.d.ts +64 -2
- package/dist/backends/http-worker-client.d.ts.map +1 -1
- package/dist/backends/http-worker-client.js +78 -7
- package/dist/backends/http-worker-client.js.map +1 -1
- package/dist/backends/kubernetes/transport.d.ts +7 -0
- package/dist/backends/kubernetes/transport.d.ts.map +1 -1
- package/dist/backends/kubernetes/transport.js.map +1 -1
- package/dist/egress/proxy.d.ts +47 -2
- package/dist/egress/proxy.d.ts.map +1 -1
- package/dist/egress/proxy.js +31 -7
- package/dist/egress/proxy.js.map +1 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/backends/aci-standby-pool/index.ts +37 -7
- package/src/backends/docker/index.ts +909 -126
- package/src/backends/firecracker/transport.ts +387 -36
- package/src/backends/http-worker-client.ts +89 -5
- package/src/backends/kubernetes/transport.ts +7 -0
- package/src/egress/proxy.ts +65 -8
- package/src/index.ts +89 -0
|
@@ -58,6 +58,21 @@ import tls from 'node:tls';
|
|
|
58
58
|
import { OperationDeadline, OperationDeadlineExpired } from '../readiness.js';
|
|
59
59
|
import { REMOTE_EXECUTION_PROTOCOL_VERSION, RemoteCancellationUnknownError, RemoteExecutionController, RemoteProtocolError, } from '../remote-execution-controller.js';
|
|
60
60
|
import { ExecResultAccumulator, MIN_STREAM_HEARTBEAT_MS, READ_FILE_STREAM_FEATURE, STREAM_HEARTBEAT_MAX_ECHO_FACTOR, STREAM_HEARTBEAT_MISS_LIMIT, WRITE_FILE_PARTS_FEATURE, parseExecLine, } from './protocol.js';
|
|
61
|
+
/** The reportable view of a ledger — see {@link FirecrackerTransportTiming}. */
|
|
62
|
+
function timingOf(ledger) {
|
|
63
|
+
return {
|
|
64
|
+
dialMs: ledger.dialMs,
|
|
65
|
+
reserveMs: ledger.reserveMs,
|
|
66
|
+
executeMs: ledger.executeMs,
|
|
67
|
+
drainMs: ledger.executeSettledAt > 0 ? Date.now() - ledger.executeSettledAt : 0,
|
|
68
|
+
// Spread conditionally, not set to a sentinel: "this phase was never
|
|
69
|
+
// reached" and "this phase took no measurable time" are different
|
|
70
|
+
// statements, and only an absent field says the first.
|
|
71
|
+
...(ledger.firstFrameMs !== undefined ? { firstFrameMs: ledger.firstFrameMs } : {}),
|
|
72
|
+
...(ledger.terminatorMs !== undefined ? { terminatorMs: ledger.terminatorMs } : {}),
|
|
73
|
+
...(ledger.peerCloseMs !== undefined ? { peerCloseMs: ledger.peerCloseMs } : {}),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
61
76
|
const DEFAULT_CONNECT_TIMEOUT_MS = 5_000;
|
|
62
77
|
const DEFAULT_CONNECT_RETRY_BUDGET_MS = 30_000;
|
|
63
78
|
const DEFAULT_CONNECT_RETRY_INTERVAL_MS = 100;
|
|
@@ -68,6 +83,37 @@ const DEFAULT_EXECUTION_TIMEOUT_MS = 5 * 60_000;
|
|
|
68
83
|
// bounded TERM -> KILL confirmation window so a quiet but correctly
|
|
69
84
|
// terminating command can still deliver its terminal frame and output tail.
|
|
70
85
|
const EXECUTION_TRANSPORT_GRACE_MS = 10_000;
|
|
86
|
+
/**
|
|
87
|
+
* How long a reply that has already TERMINATED waits for the peer's own close
|
|
88
|
+
* before this transport gives up on it. Three callers, all of them read-reply
|
|
89
|
+
* loops that resolve on that close: {@link VsockAgentTransport.request} (one
|
|
90
|
+
* control or file reply), {@link VsockAgentTransport.executeRaw} (an exec's
|
|
91
|
+
* zero-length terminator frame) and `streamFramedRequest` (a
|
|
92
|
+
* `read-file-stream`'s end frame).
|
|
93
|
+
*
|
|
94
|
+
* **A reject-only guard, and deliberately not a budget to spend.** It can
|
|
95
|
+
* only turn a socket whose peer never closes into a named failure
|
|
96
|
+
* (`exec peer did not close after terminator`, or the `stream`/`control`
|
|
97
|
+
* wordings); it can never resolve a call, because every resolution path in
|
|
98
|
+
* this file requires the peer's `close` event. Raising it therefore buys no
|
|
99
|
+
* slow success — it delays a diagnosis — and lowering it fails a peer that
|
|
100
|
+
* was merely slow to close. It is not a wait any caller is expected to pay:
|
|
101
|
+
* a call that resolves has paid a real close, and how long the peer took to
|
|
102
|
+
* deliver it is reported as
|
|
103
|
+
* {@link FirecrackerTransportTiming.peerCloseMs}.
|
|
104
|
+
*
|
|
105
|
+
* **The contract this states for a host-owned relay.** After writing the
|
|
106
|
+
* terminator frame the agent calls `socket.end()` — a half-close — and this
|
|
107
|
+
* transport resolves the call on the resulting `close`. A relay between the
|
|
108
|
+
* guest and this process that forwards the payload but holds the FIN (its
|
|
109
|
+
* own idle timer, a full-duplex buffering policy, a proxy that waits for the
|
|
110
|
+
* guest process to exit) transfers that wait onto every call: under this
|
|
111
|
+
* bound it is reported as `peerCloseMs`, and at or past it the call REJECTS
|
|
112
|
+
* by name, with no `peerCloseMs` in the report — the number a host would
|
|
113
|
+
* otherwise be reading is one this transport chose, and it does not supply
|
|
114
|
+
* it. A relay that forwards the FIN promptly costs the guest's RTT and
|
|
115
|
+
* nothing else.
|
|
116
|
+
*/
|
|
71
117
|
const POST_RESPONSE_CLOSE_TIMEOUT_MS = 1_000;
|
|
72
118
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
73
119
|
/**
|
|
@@ -344,7 +390,7 @@ export class VsockAgentTransport {
|
|
|
344
390
|
*/
|
|
345
391
|
guestFeatureList;
|
|
346
392
|
permanentDialFailure;
|
|
347
|
-
|
|
393
|
+
onExecTiming;
|
|
348
394
|
constructor(handle, options = {}) {
|
|
349
395
|
this.handle = handle;
|
|
350
396
|
this.connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
@@ -363,24 +409,65 @@ export class VsockAgentTransport {
|
|
|
363
409
|
this.writeFilePartBytes = Math.max(1, Math.floor(options.writeFilePartBytes));
|
|
364
410
|
}
|
|
365
411
|
this.permanentDialFailure = options.permanentDialFailure;
|
|
366
|
-
|
|
412
|
+
this.onExecTiming = options.onExecTiming;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* The reserve-before-admission triple this transport hands the shared
|
|
416
|
+
* {@link RemoteExecutionController}, with `ledger` — when a timing hook
|
|
417
|
+
* asked for one — accumulating the phases it wraps.
|
|
418
|
+
*
|
|
419
|
+
* A FACTORY rather than a constructor-built field, because the ledger is
|
|
420
|
+
* per call: this transport holds no cross-call connection state (it dials
|
|
421
|
+
* fresh every time), so building an adapter per `exec()` is free and
|
|
422
|
+
* makes concurrent `exec()` calls correctly independent — each gets its
|
|
423
|
+
* own accumulator, with no shared mutable field for two in-flight calls
|
|
424
|
+
* to race on. Same arrangement, and the same reason, as the kubernetes
|
|
425
|
+
* tier's `KubernetesAgentTransport.exec`.
|
|
426
|
+
*/
|
|
427
|
+
executionAdapter(ledger) {
|
|
428
|
+
return {
|
|
367
429
|
label: 'framed microVM agent',
|
|
368
|
-
reserve: async (signal) =>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
430
|
+
reserve: async (signal) => {
|
|
431
|
+
if (ledger === undefined)
|
|
432
|
+
return await this.reserveExecution(signal);
|
|
433
|
+
const startedAt = Date.now();
|
|
434
|
+
try {
|
|
435
|
+
return await this.reserveExecution(signal, ledger);
|
|
436
|
+
}
|
|
437
|
+
finally {
|
|
438
|
+
ledger.reserveMs += Date.now() - startedAt;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
cancel: async (executionId, signal) => await this.cancelExecution(executionId, signal, ledger),
|
|
442
|
+
execute: async (executionId, command, argv, opts, signal, context) => {
|
|
443
|
+
const body = {
|
|
444
|
+
...(executionId ? { executionId } : {}),
|
|
445
|
+
command,
|
|
446
|
+
args: argv ?? [],
|
|
447
|
+
...(opts?.cwd !== undefined ? { cwd: opts.cwd } : {}),
|
|
448
|
+
...(opts?.env !== undefined ? { env: opts.env } : {}),
|
|
449
|
+
...(opts?.timeout !== undefined ? { timeoutMs: opts.timeout } : {}),
|
|
450
|
+
...(context?.stdin !== undefined ? { stdin: context.stdin } : {}),
|
|
451
|
+
...(context?.maxOutputBytes !== undefined
|
|
452
|
+
? { maxOutputBytes: context.maxOutputBytes }
|
|
453
|
+
: {}),
|
|
454
|
+
};
|
|
455
|
+
if (ledger === undefined)
|
|
456
|
+
return await this.executeRaw(body, opts, signal);
|
|
457
|
+
const startedAt = Date.now();
|
|
458
|
+
try {
|
|
459
|
+
return await this.executeRaw(body, opts, signal, ledger);
|
|
460
|
+
}
|
|
461
|
+
finally {
|
|
462
|
+
ledger.executeMs += Date.now() - startedAt;
|
|
463
|
+
// Stamped in the `finally` so it is set on the failure path
|
|
464
|
+
// too: a drain interval is only worth reporting when the
|
|
465
|
+
// round trip settled, and "settled" includes "settled by
|
|
466
|
+
// rejecting".
|
|
467
|
+
ledger.executeSettledAt = Date.now();
|
|
468
|
+
}
|
|
469
|
+
},
|
|
382
470
|
};
|
|
383
|
-
this.executionController = new RemoteExecutionController(adapter);
|
|
384
471
|
}
|
|
385
472
|
/**
|
|
386
473
|
* Dial the agent with the resume-survival retry budget. Resolves a
|
|
@@ -390,7 +477,7 @@ export class VsockAgentTransport {
|
|
|
390
477
|
* {@link VsockTransportOptions.permanentDialFailure} says this particular
|
|
391
478
|
* failure is not one waiting will cure.
|
|
392
479
|
*/
|
|
393
|
-
async dial(signal) {
|
|
480
|
+
async dial(signal, ledger) {
|
|
394
481
|
const deadline = Date.now() + this.connectRetryBudgetMs;
|
|
395
482
|
const dialStartedAt = Date.now();
|
|
396
483
|
let lastErr;
|
|
@@ -403,7 +490,14 @@ export class VsockAgentTransport {
|
|
|
403
490
|
// is precisely the case a watcher needs to hear about.
|
|
404
491
|
this.onDialAttempt?.();
|
|
405
492
|
const socket = await this.connectOnce(signal);
|
|
406
|
-
|
|
493
|
+
const durationMs = Date.now() - dialStartedAt;
|
|
494
|
+
this.onDial?.(durationMs);
|
|
495
|
+
// The same interval the hook above reports, folded into the
|
|
496
|
+
// call's own ledger: a caller asking where an exec's wall time
|
|
497
|
+
// went wants it as one number per call, and this dial belongs
|
|
498
|
+
// to that call.
|
|
499
|
+
if (ledger !== undefined)
|
|
500
|
+
ledger.dialMs += durationMs;
|
|
407
501
|
return socket;
|
|
408
502
|
}
|
|
409
503
|
catch (err) {
|
|
@@ -675,10 +769,21 @@ export class VsockAgentTransport {
|
|
|
675
769
|
* is torn down rather than wedging the caller.
|
|
676
770
|
*/
|
|
677
771
|
async request(req, signal) {
|
|
772
|
+
return await this.requestFramed(req, signal);
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* {@link request}, with the one thing the public signature has no place
|
|
776
|
+
* for: the exec call's timing ledger, so the reserve round trip that
|
|
777
|
+
* reaches the guest through this method is counted against the call that
|
|
778
|
+
* paid for its dial. Private rather than a third parameter, because a
|
|
779
|
+
* public method whose extra argument exists only for internal
|
|
780
|
+
* instrumentation is a parameter a caller can only misuse.
|
|
781
|
+
*/
|
|
782
|
+
async requestFramed(req, signal, ledger) {
|
|
678
783
|
const envelope = this.withCredential(req);
|
|
679
784
|
const payload = JSON.stringify(envelope);
|
|
680
785
|
this.assertPreauthBudget(payload);
|
|
681
|
-
const socket = await this.dial(signal);
|
|
786
|
+
const socket = await this.dial(signal, ledger);
|
|
682
787
|
return await new Promise((resolve, reject) => {
|
|
683
788
|
const reader = new FrameReader();
|
|
684
789
|
let settled = false;
|
|
@@ -757,12 +862,21 @@ export class VsockAgentTransport {
|
|
|
757
862
|
* {@link SandboxExecResult} via the shared {@link ExecResultAccumulator}.
|
|
758
863
|
* The agent terminates the stream with a zero-length frame.
|
|
759
864
|
*/
|
|
760
|
-
async executeRaw(body, opts, signal) {
|
|
865
|
+
async executeRaw(body, opts, signal, ledger) {
|
|
761
866
|
const envelope = this.withCredential({ op: 'execute', body });
|
|
762
867
|
const payload = JSON.stringify(envelope);
|
|
763
868
|
this.assertPreauthBudget(payload);
|
|
764
|
-
const socket = await this.dial(signal);
|
|
869
|
+
const socket = await this.dial(signal, ledger);
|
|
765
870
|
const start = Date.now();
|
|
871
|
+
// The instant the request goes on the wire. The three execute
|
|
872
|
+
// sub-phases of {@link FirecrackerTransportTiming} are measured from
|
|
873
|
+
// here, so they answer "how long after the guest was asked" rather
|
|
874
|
+
// than "how long after this call began" — the dial that precedes it
|
|
875
|
+
// is already counted in `dialMs` and inside `executeMs`.
|
|
876
|
+
let writtenAt = 0;
|
|
877
|
+
// Stamped once, when the terminator frame is parsed, so `peerCloseMs`
|
|
878
|
+
// measures the peer's close and not the whole round trip.
|
|
879
|
+
let terminatorAt = 0;
|
|
766
880
|
return await new Promise((resolve, reject) => {
|
|
767
881
|
const reader = new FrameReader();
|
|
768
882
|
const acc = new ExecResultAccumulator(start, opts?.onOutput);
|
|
@@ -804,6 +918,12 @@ export class VsockAgentTransport {
|
|
|
804
918
|
finish(err instanceof Error ? err : new Error(String(err)));
|
|
805
919
|
return;
|
|
806
920
|
}
|
|
921
|
+
// Recorded before the terminator check below and before any
|
|
922
|
+
// parsing: the first frame ARRIVED, which stays true even if
|
|
923
|
+
// this call goes on to reject over the frame's contents.
|
|
924
|
+
if (ledger !== undefined && ledger.firstFrameMs === undefined && frames.length > 0) {
|
|
925
|
+
ledger.firstFrameMs = Date.now() - writtenAt;
|
|
926
|
+
}
|
|
807
927
|
for (const payload of frames) {
|
|
808
928
|
if (terminated) {
|
|
809
929
|
finish(new Error('vsock transport: exec stream emitted data after its terminator'));
|
|
@@ -828,6 +948,14 @@ export class VsockAgentTransport {
|
|
|
828
948
|
}
|
|
829
949
|
}
|
|
830
950
|
if (terminated) {
|
|
951
|
+
// The terminator WAS read, so it is reported even if this
|
|
952
|
+
// call is about to reject over what followed it: the
|
|
953
|
+
// question these numbers answer is where the time went,
|
|
954
|
+
// not whether the call ended well.
|
|
955
|
+
if (ledger !== undefined && ledger.terminatorMs === undefined) {
|
|
956
|
+
terminatorAt = Date.now();
|
|
957
|
+
ledger.terminatorMs = terminatorAt - writtenAt;
|
|
958
|
+
}
|
|
831
959
|
if (reader.bufferedBytes > 0) {
|
|
832
960
|
finish(new Error('vsock transport: exec stream has trailing partial data'));
|
|
833
961
|
return;
|
|
@@ -839,8 +967,25 @@ export class VsockAgentTransport {
|
|
|
839
967
|
});
|
|
840
968
|
socket.once('error', (err) => finish(err));
|
|
841
969
|
socket.once('close', () => {
|
|
842
|
-
|
|
970
|
+
// A close that arrives with this call ALREADY settled is one
|
|
971
|
+
// this transport caused itself: `finish` is the only writer of
|
|
972
|
+
// `settled` and its next statement is `socket.destroy()`, so
|
|
973
|
+
// the guard timer, the observation timer and the caller's abort
|
|
974
|
+
// each destroy the socket and then react to the `close` that
|
|
975
|
+
// destroy emits. Crediting the peer for it would report
|
|
976
|
+
// {@link POST_RESPONSE_CLOSE_TIMEOUT_MS} — a constant this
|
|
977
|
+
// transport chose — as a duration the peer took, in exactly the
|
|
978
|
+
// case a host is reading the field to diagnose. The same rule
|
|
979
|
+
// `readFileFrames` applies in the same position: an end this
|
|
980
|
+
// side already reached is not news.
|
|
981
|
+
if (settled)
|
|
982
|
+
return;
|
|
983
|
+
if (terminated && terminalResult) {
|
|
984
|
+
if (ledger !== undefined && terminatorAt > 0) {
|
|
985
|
+
ledger.peerCloseMs = Date.now() - terminatorAt;
|
|
986
|
+
}
|
|
843
987
|
finish(null, terminalResult);
|
|
988
|
+
}
|
|
844
989
|
else
|
|
845
990
|
finish(new Error('vsock transport: socket closed before exec stream terminator'));
|
|
846
991
|
});
|
|
@@ -849,6 +994,7 @@ export class VsockAgentTransport {
|
|
|
849
994
|
return;
|
|
850
995
|
}
|
|
851
996
|
signal?.addEventListener('abort', abort, { once: true });
|
|
997
|
+
writtenAt = Date.now();
|
|
852
998
|
socket.write(frame(payload));
|
|
853
999
|
});
|
|
854
1000
|
}
|
|
@@ -862,7 +1008,7 @@ export class VsockAgentTransport {
|
|
|
862
1008
|
if (body.executionId !== undefined) {
|
|
863
1009
|
throw new RemoteProtocolError('VsockAgentTransport.execute does not accept caller-owned execution ids');
|
|
864
1010
|
}
|
|
865
|
-
return await this.
|
|
1011
|
+
return await this.runExec(body.command, body.args ? [...body.args] : undefined, {
|
|
866
1012
|
...opts,
|
|
867
1013
|
...(body.cwd !== undefined ? { cwd: body.cwd } : {}),
|
|
868
1014
|
...(body.env !== undefined ? { env: body.env } : {}),
|
|
@@ -874,7 +1020,55 @@ export class VsockAgentTransport {
|
|
|
874
1020
|
});
|
|
875
1021
|
}
|
|
876
1022
|
async exec(command, argv, opts) {
|
|
877
|
-
return await this.
|
|
1023
|
+
return await this.runExec(command, argv, opts);
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* One command through a call-scoped adapter + controller, reporting the
|
|
1027
|
+
* call's phases to {@link VsockTransportOptions.onExecTiming} when a host
|
|
1028
|
+
* asked for them.
|
|
1029
|
+
*
|
|
1030
|
+
* The ledger is created HERE and nowhere else — because it is per call,
|
|
1031
|
+
* not per transport, so two concurrent `exec()` calls each get their own
|
|
1032
|
+
* accumulator. Constructing a controller per call costs an allocation and
|
|
1033
|
+
* nothing else: the controller's only per-instance state is its readonly
|
|
1034
|
+
* configuration, so a fresh one behaves exactly like a shared one, and
|
|
1035
|
+
* this transport dials fresh per request either way.
|
|
1036
|
+
*
|
|
1037
|
+
* The hook fires in a `finally`, on the failure paths as well as the
|
|
1038
|
+
* happy one: an exec that rejected after its reserve round trip is
|
|
1039
|
+
* precisely the call whose phase breakdown a host needs. Measuring into a
|
|
1040
|
+
* ledger that nobody reads is what "no hook, no cost" means here — with
|
|
1041
|
+
* no hook there is no ledger, and the `undefined` checks in `dial` and
|
|
1042
|
+
* `executeRaw` skip the clock reads entirely.
|
|
1043
|
+
*/
|
|
1044
|
+
async runExec(command, argv, opts, context) {
|
|
1045
|
+
const hook = this.onExecTiming;
|
|
1046
|
+
if (hook === undefined) {
|
|
1047
|
+
return await new RemoteExecutionController(this.executionAdapter()).exec(command, argv, opts, context);
|
|
1048
|
+
}
|
|
1049
|
+
const ledger = {
|
|
1050
|
+
dialMs: 0,
|
|
1051
|
+
reserveMs: 0,
|
|
1052
|
+
executeMs: 0,
|
|
1053
|
+
executeSettledAt: 0,
|
|
1054
|
+
};
|
|
1055
|
+
try {
|
|
1056
|
+
return await new RemoteExecutionController(this.executionAdapter(ledger)).exec(command, argv, opts, context);
|
|
1057
|
+
}
|
|
1058
|
+
finally {
|
|
1059
|
+
// Caught, not propagated: this hook is an OBSERVER, and a listener
|
|
1060
|
+
// that throws must not turn a command that ran into a caller's
|
|
1061
|
+
// exception — the rule {@link VsockTransportOptions.onGuestReply}
|
|
1062
|
+
// already states for this transport's other observers. A `finally`
|
|
1063
|
+
// that let it through would replace the call's own error with the
|
|
1064
|
+
// listener's, which is the worst version of that failure.
|
|
1065
|
+
try {
|
|
1066
|
+
hook(timingOf(ledger));
|
|
1067
|
+
}
|
|
1068
|
+
catch {
|
|
1069
|
+
// The listener's problem, and only the listener's.
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
878
1072
|
}
|
|
879
1073
|
/**
|
|
880
1074
|
* The raw `/execute` primitive with NO admission/reservation
|
|
@@ -892,8 +1086,8 @@ export class VsockAgentTransport {
|
|
|
892
1086
|
async executeStreamed(body, opts, signal) {
|
|
893
1087
|
return await this.executeRaw(body, opts, signal);
|
|
894
1088
|
}
|
|
895
|
-
async reserveExecution(signal) {
|
|
896
|
-
const response = await this.
|
|
1089
|
+
async reserveExecution(signal, ledger) {
|
|
1090
|
+
const response = await this.requestFramed({ op: 'reserve-execution' }, signal, ledger);
|
|
897
1091
|
if (response.ok === false &&
|
|
898
1092
|
typeof response.error === 'string' &&
|
|
899
1093
|
response.error.startsWith('unknown_op:')) {
|
|
@@ -904,8 +1098,8 @@ export class VsockAgentTransport {
|
|
|
904
1098
|
}
|
|
905
1099
|
return response;
|
|
906
1100
|
}
|
|
907
|
-
async cancelExecution(executionId, signal) {
|
|
908
|
-
return await this.
|
|
1101
|
+
async cancelExecution(executionId, signal, ledger) {
|
|
1102
|
+
return await this.requestFramed({ op: 'cancel-execution', body: { executionId } }, signal, ledger);
|
|
909
1103
|
}
|
|
910
1104
|
/** Readiness probe. A healthy guest must also speak the exact host protocol. */
|
|
911
1105
|
async healthz(signal) {
|