rightsize 0.7.8 → 0.7.10

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 (75) hide show
  1. package/dist/backend-docker/backend.d.ts +4 -1
  2. package/dist/backend-docker/backend.d.ts.map +1 -1
  3. package/dist/backend-docker/backend.js +4 -0
  4. package/dist/backend-docker/backend.js.map +1 -1
  5. package/dist/backend-msb/backend.d.ts +618 -68
  6. package/dist/backend-msb/backend.d.ts.map +1 -1
  7. package/dist/backend-msb/backend.js +1236 -133
  8. package/dist/backend-msb/backend.js.map +1 -1
  9. package/dist/backend-msb/commands.d.ts +160 -16
  10. package/dist/backend-msb/commands.d.ts.map +1 -1
  11. package/dist/backend-msb/commands.js +197 -31
  12. package/dist/backend-msb/commands.js.map +1 -1
  13. package/dist/backend-msb/provisioner.d.ts +1 -1
  14. package/dist/backend-msb/provisioner.d.ts.map +1 -1
  15. package/dist/backend-msb/provisioner.js +1 -1
  16. package/dist/backend-msb/provisioner.js.map +1 -1
  17. package/dist/backend-msb/restore-access-denied.d.ts +50 -0
  18. package/dist/backend-msb/restore-access-denied.d.ts.map +1 -0
  19. package/dist/backend-msb/restore-access-denied.js +52 -0
  20. package/dist/backend-msb/restore-access-denied.js.map +1 -0
  21. package/dist/backend-msb/restore-broker.d.ts +181 -0
  22. package/dist/backend-msb/restore-broker.d.ts.map +1 -0
  23. package/dist/backend-msb/restore-broker.js +264 -0
  24. package/dist/backend-msb/restore-broker.js.map +1 -0
  25. package/dist/backend-msb/sandbox-already-exists.d.ts +32 -0
  26. package/dist/backend-msb/sandbox-already-exists.d.ts.map +1 -0
  27. package/dist/backend-msb/sandbox-already-exists.js +35 -0
  28. package/dist/backend-msb/sandbox-already-exists.js.map +1 -0
  29. package/dist/backend-msb/snapshot-create.d.ts +22 -0
  30. package/dist/backend-msb/snapshot-create.d.ts.map +1 -0
  31. package/dist/backend-msb/snapshot-create.js +33 -0
  32. package/dist/backend-msb/snapshot-create.js.map +1 -0
  33. package/dist/backend-msb/snapshot-import.d.ts +30 -14
  34. package/dist/backend-msb/snapshot-import.d.ts.map +1 -1
  35. package/dist/backend-msb/snapshot-import.js +35 -17
  36. package/dist/backend-msb/snapshot-import.js.map +1 -1
  37. package/dist/backend-msb/snapshot-rm.d.ts +28 -0
  38. package/dist/backend-msb/snapshot-rm.d.ts.map +1 -0
  39. package/dist/backend-msb/snapshot-rm.js +30 -0
  40. package/dist/backend-msb/snapshot-rm.js.map +1 -0
  41. package/dist/core/backend.d.ts +53 -22
  42. package/dist/core/backend.d.ts.map +1 -1
  43. package/dist/core/checkpoint/api.d.ts +2 -2
  44. package/dist/core/checkpoint/api.js +2 -2
  45. package/dist/core/checkpoint/registry.d.ts +34 -0
  46. package/dist/core/checkpoint/registry.d.ts.map +1 -1
  47. package/dist/core/checkpoint/registry.js +20 -1
  48. package/dist/core/checkpoint/registry.js.map +1 -1
  49. package/dist/core/errors.d.ts +49 -0
  50. package/dist/core/errors.d.ts.map +1 -1
  51. package/dist/core/errors.js +61 -0
  52. package/dist/core/errors.js.map +1 -1
  53. package/dist/core/generic-container.d.ts +58 -10
  54. package/dist/core/generic-container.d.ts.map +1 -1
  55. package/dist/core/generic-container.js +222 -27
  56. package/dist/core/generic-container.js.map +1 -1
  57. package/dist/core/model.d.ts +3 -2
  58. package/dist/core/model.d.ts.map +1 -1
  59. package/dist/core/sandbox-name.d.ts +3 -0
  60. package/dist/core/sandbox-name.d.ts.map +1 -0
  61. package/dist/core/sandbox-name.js +19 -0
  62. package/dist/core/sandbox-name.js.map +1 -0
  63. package/dist/index.d.ts +1 -1
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +1 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/modules/minio.d.ts +5 -3
  68. package/dist/modules/minio.d.ts.map +1 -1
  69. package/dist/modules/minio.js +14 -4
  70. package/dist/modules/minio.js.map +1 -1
  71. package/package.json +1 -1
  72. package/dist/backend-msb/snapshot-list.d.ts +0 -43
  73. package/dist/backend-msb/snapshot-list.d.ts.map +0 -1
  74. package/dist/backend-msb/snapshot-list.js +0 -53
  75. package/dist/backend-msb/snapshot-list.js.map +0 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * The Windows-only job-object-breakaway problem this whole module exists to
3
+ * route around. EMPIRICALLY VERIFIED across a four-round live diagnostic
4
+ * campaign on Windows CI (2026-09-18): msb's detached spawn on Windows
5
+ * (`sdk/rust/lib/runtime/spawn.rs`, msb 0.7.1/0.7.2) always passes
6
+ * `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_BREAKAWAY_FROM_JOB`
7
+ * when it launches the new `msb.exe` that supervises a restored sandbox —
8
+ * `msb restore` is detached-by-design (see `MsbCliBackend.bootRestoreOnce`'s
9
+ * own doc), so it must spawn that new process every time. When the CALLING
10
+ * `msb.exe` itself sits inside a Windows job object that was never granted
11
+ * `JOB_OBJECT_LIMIT_BREAKAWAY_OK`, `CreateProcess` fails outright with
12
+ * `ERROR_ACCESS_DENIED`, and msb surfaces the bare `error: io error: Access
13
+ * is denied. (os error 5)` — the exact signature `isRestoreAccessDeniedFailure`
14
+ * matches, previously attributed entirely to a Windows file-handle release
15
+ * lag. Direct probes at failure time ruled the lag theory out: no msb.exe
16
+ * processes alive, the artifact readable/writable/hardlinkable from another
17
+ * process, nothing locked — the denial is the spawn itself, not the file.
18
+ *
19
+ * Gradle test workers and cargo-test binaries run inside exactly such
20
+ * breakaway-denying job objects (`inJob=True, limitFlags=0x0`, live-probed),
21
+ * which is why the sibling rust/kotlin libraries hit this deterministically
22
+ * on their own Windows CI while this repository's node test workers —
23
+ * confirmed to add no such job object — never have.
24
+ *
25
+ * THE MITIGATION IS LIVE-VALIDATED: the identical `msb restore <artifact>
26
+ * --name X` launched through WMI (`Invoke-CimMethod -ClassName Win32_Process
27
+ * -MethodName Create`), whose children run under `WmiPrvSE` OUTSIDE the
28
+ * caller's job hierarchy, returns `ReturnValue=0` and the restored sandbox
29
+ * shows Running in `msb ls` — reproduced in two separate cycles in the exact
30
+ * environment where a direct spawn is denied every time.
31
+ *
32
+ * This module is the resulting escape hatch: `realRestoreBroker` shells the
33
+ * exact same `msb restore ...` invocation through a WMI-created process
34
+ * instead of a direct child, for the callers in `backend.ts`
35
+ * (`rebootUnderFreshName`/`retryRestoreAfterAccessDenied`) that escalate to
36
+ * it once — and only once — a restore attempt on a Windows host hits that
37
+ * classified access-denied failure (POLICY v2; see those methods' own doc).
38
+ * It changes nothing for a healthy environment: the first attempt of any
39
+ * reboot is always a direct spawn, and a host that never hits the access-
40
+ * denied signature never touches this module at all.
41
+ */
42
+ /** One brokered restore attempt whose own outcome was captured — classify `output` with the exact same predicates a direct attempt's output goes through. */
43
+ export interface RestoreBrokerCompleted {
44
+ kind: "completed";
45
+ exitCode: number;
46
+ output: string;
47
+ }
48
+ /**
49
+ * The broker's own `ecFile` (see `buildOuterBrokerScript`) never appeared
50
+ * within its bounded wait, so no exit code/output could be read back — but
51
+ * the WMI `Create` call itself was accepted (`ReturnValue=0`), so the
52
+ * restore may well be running in the background regardless. The caller
53
+ * (`MsbCliBackend.bootRestoreOnce`) treats this exactly like a confirmed
54
+ * exit 0: skip classification and fall through to its own `msb ls`
55
+ * readiness poll, which — being activation-gated — settles the question on
56
+ * its own either way.
57
+ */
58
+ export interface RestoreBrokerUnconfirmed {
59
+ kind: "unconfirmed";
60
+ }
61
+ export type RestoreBrokerResult = RestoreBrokerCompleted | RestoreBrokerUnconfirmed;
62
+ /**
63
+ * The injectable seam `MsbCliBackend` calls through for every brokered
64
+ * restore attempt — `msbPath`/`argv` are exactly what a direct attempt would
65
+ * spawn (`MsbCommands.restore(spec)`'s own argv, unmodified), `timeoutMs` the
66
+ * same boot budget a direct attempt is held to. Rejects only for a broker-
67
+ * INFRASTRUCTURE failure (missing `powershell.exe`, a script-file write
68
+ * failure, WMI refusing to even create the process) — the caller falls back
69
+ * to a direct attempt on a rejection (POLICY v2 point 5: the broker must
70
+ * never become a new single point of failure). Resolves for every other
71
+ * outcome, including the brokered restore itself failing (access-denied
72
+ * again, an "already exists" collision, or any other msb refusal) — that is
73
+ * ordinary classified output for the caller to interpret, not a broker
74
+ * failure.
75
+ */
76
+ export type RestoreBrokerLauncher = (msbPath: string, argv: readonly string[], timeoutMs: number) => Promise<RestoreBrokerResult>;
77
+ /**
78
+ * Doubles every embedded `'` so `value` can be dropped verbatim into a
79
+ * PowerShell single-quoted string literal (`'...'`) — PowerShell's own
80
+ * escaping rule for that quoting style, the same one a hand-written `''`
81
+ * inside a single-quoted string represents a literal `'`. Applied to every
82
+ * interpolated value this module builds a script around (the msb binary
83
+ * path, every restore argv token, and the temp out/ec file paths) — never
84
+ * only the ones expected to need it, since a caller-controlled mount path or
85
+ * artifact ref is exactly the kind of value that might one day carry one.
86
+ */
87
+ export declare function escapePowerShellSingleQuoted(value: string): string;
88
+ /**
89
+ * The script text the WMI-created powershell process itself runs: the real
90
+ * `msb <argv>` invocation (the call operator `&`, since `msbPath` is a
91
+ * runtime string, not a literal command name), its combined stdout+stderr
92
+ * merged into `outFile` via PowerShell's `*>` all-streams redirection —
93
+ * mirroring `fetchStdoutExact`'s own reason for treating stdout+stderr as
94
+ * one channel — followed by writing the invocation's own `$LASTEXITCODE` to
95
+ * `ecFile`. Both files are how this reaches back across the WMI boundary:
96
+ * `Invoke-CimMethod`'s `Create` has no stdout/exit-code channel of its own
97
+ * (it returns only a `ProcessId` and a `ReturnValue` for the CREATE call
98
+ * itself, never anything about what that process goes on to do). Every path
99
+ * and argv token is its own single-quoted literal (`quoteSingle`) — never
100
+ * string-interpolated — so a value carrying spaces, `$`, or a literal `'`
101
+ * (an exotic mount path, in principle) round-trips as data, not script
102
+ * syntax.
103
+ */
104
+ export declare function buildInnerRestoreScript(msbPath: string, argv: readonly string[], outFile: string, ecFile: string): string;
105
+ /**
106
+ * Encodes `script` the way `powershell -EncodedCommand` requires: UTF-16LE
107
+ * bytes, base64. Chosen over `-Command "<script>"` specifically to sidestep
108
+ * a second layer of quote-escaping entirely — `buildInnerRestoreScript`'s
109
+ * own output already carries single-quoted literals of its own, and nesting
110
+ * that inside ANOTHER quoted string (the `CommandLine` this module hands to
111
+ * `Invoke-CimMethod`) would mean escaping quotes-within-quotes-within-quotes.
112
+ * A base64 payload is alphanumeric-plus-`+/=` only, so embedding it inside
113
+ * the OUTER script's own single-quoted `CommandLine` literal (see
114
+ * `buildOuterBrokerScript`) needs no escaping of its own at all.
115
+ */
116
+ export declare function encodeAsEncodedCommand(script: string): string;
117
+ /** How long `buildOuterBrokerScript`'s own polling loop waits for `ecFile` to appear before giving up and letting the caller fall through to its own `msb ls` readiness poll (see `RestoreBrokerUnconfirmed`'s own doc) — generous for `msb restore`'s own detach, which the rest of this codebase documents as "typically within seconds". */
118
+ export declare const RESTORE_BROKER_ECFILE_WAIT_SECONDS = 30;
119
+ /**
120
+ * The OUTER script — the one actually run as `powershell -NoProfile -File
121
+ * <script>` (see `realRestoreBroker`) — that performs the WMI escape itself
122
+ * and reports back what happened, all through plain stdout this module's own
123
+ * node-side caller can read directly (no temp file needed for THIS layer,
124
+ * unlike the inner restore's own output — this process's stdout pipe is
125
+ * ordinary, since it is never the one msb's job object would deny).
126
+ *
127
+ * `encodedInner` (see `encodeAsEncodedCommand`) is embedded inside this
128
+ * script's own single-quoted `$cmdLine` literal — safe with no escaping of
129
+ * its own, being pure base64 — as the `-EncodedCommand` argument of the
130
+ * powershell invocation `Invoke-CimMethod` launches. `outFile`/`ecFile` are
131
+ * each their own single-quoted literal via `quoteSingle`, the same as every
132
+ * path `buildInnerRestoreScript` embeds.
133
+ *
134
+ * `powershell.exe` (never the bare `powershell`, nor `pwsh`) both here and as
135
+ * the WMI-launched process's own executable — the task this whole module
136
+ * exists for names it explicitly "for maximum compatibility": every
137
+ * supported Windows release ships Windows PowerShell under that exact name,
138
+ * while PowerShell 7 (`pwsh`) is an opt-in install this backend has no
139
+ * business depending on.
140
+ */
141
+ export declare function buildOuterBrokerScript(params: {
142
+ encodedInner: string;
143
+ outFile: string;
144
+ ecFile: string;
145
+ waitSeconds?: number;
146
+ }): string;
147
+ export interface ParsedBrokerScriptOutput {
148
+ /** The WMI `Create` call's own `ReturnValue` (0 == accepted), or `undefined` if the marker line itself never showed up (the outer script died before printing it). */
149
+ returnValue: number | undefined;
150
+ /** The WMI-created process's own pid, purely diagnostic — never load-bearing for classification. */
151
+ processId: number | undefined;
152
+ /** `outFile`'s contents (the brokered `msb restore`'s combined stdout+stderr), or `undefined` if `outFile` never existed when the outer script checked. */
153
+ outText: string | undefined;
154
+ /** `ecFile`'s contents (the brokered invocation's own `$LASTEXITCODE`, as text), or `undefined` if it never appeared within the wait bound. */
155
+ ecText: string | undefined;
156
+ }
157
+ /** Parses `buildOuterBrokerScript`'s own stdout back into its four parts — see `ParsedBrokerScriptOutput`'s own doc on each field. Tolerant of CRLF (PowerShell's own native line ending) and of any marker being absent, since the outer script can die (or its own wait bound expire) after printing only some of them. */
158
+ export declare function parseBrokerScriptOutput(stdout: string): ParsedBrokerScriptOutput;
159
+ /**
160
+ * The real, production `RestoreBrokerLauncher` — writes the outer script
161
+ * (see `buildOuterBrokerScript`) to a unique temp file, runs it via
162
+ * `powershell.exe -NoProfile -NonInteractive -File <script>`, and turns its
163
+ * parsed output (`parseBrokerScriptOutput`) into a `RestoreBrokerResult`:
164
+ *
165
+ * - WMI's own `ReturnValue` missing or non-zero (the process was never even
166
+ * created) throws — broker-infrastructure failure, per this function's own
167
+ * `RestoreBrokerLauncher` contract, for the caller to catch and fall back
168
+ * to a direct attempt.
169
+ * - `ecFile` never read back (`ecText === undefined`, or unparsable as an
170
+ * integer) resolves `{ kind: "unconfirmed" }` — see that type's own doc.
171
+ * - Otherwise resolves `{ kind: "completed", exitCode, output: outText ?? ""
172
+ * }` — the brokered restore's own result, for the caller to classify
173
+ * exactly like a direct attempt's.
174
+ *
175
+ * Every temp file (script, `outFile`, `ecFile`) is unique per call
176
+ * (`randomBytes`-suffixed, under `os.tmpdir()`) and best-effort removed in a
177
+ * `finally` — cleanup failures are swallowed, never allowed to mask the real
178
+ * outcome above.
179
+ */
180
+ export declare const realRestoreBroker: RestoreBrokerLauncher;
181
+ //# sourceMappingURL=restore-broker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore-broker.d.ts","sourceRoot":"","sources":["../../src/backend-msb/restore-broker.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,6JAA6J;AAC7J,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,MAAM,mBAAmB,GAAG,sBAAsB,GAAG,wBAAwB,CAAC;AAEpF;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,SAAS,EAAE,MAAM,KACd,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAElC;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElE;AAOD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMzH;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,+UAA+U;AAC/U,eAAO,MAAM,kCAAkC,KAAK,CAAC;AASrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAwBT;AAED,MAAM,WAAW,wBAAwB;IACvC,sKAAsK;IACtK,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,oGAAoG;IACpG,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,2JAA2J;IAC3J,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,+IAA+I;IAC/I,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAkCD,6TAA6T;AAC7T,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,wBAAwB,CAShF;AAoDD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,iBAAiB,EAAE,qBA2C/B,CAAC"}
@@ -0,0 +1,264 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import * as fs from "node:fs/promises";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import { CLOSED_STDIN } from "./invoke.js";
7
+ /**
8
+ * Doubles every embedded `'` so `value` can be dropped verbatim into a
9
+ * PowerShell single-quoted string literal (`'...'`) — PowerShell's own
10
+ * escaping rule for that quoting style, the same one a hand-written `''`
11
+ * inside a single-quoted string represents a literal `'`. Applied to every
12
+ * interpolated value this module builds a script around (the msb binary
13
+ * path, every restore argv token, and the temp out/ec file paths) — never
14
+ * only the ones expected to need it, since a caller-controlled mount path or
15
+ * artifact ref is exactly the kind of value that might one day carry one.
16
+ */
17
+ export function escapePowerShellSingleQuoted(value) {
18
+ return value.replace(/'/g, "''");
19
+ }
20
+ /** `value` wrapped as its own single-quoted PowerShell literal (see `escapePowerShellSingleQuoted`). */
21
+ function quoteSingle(value) {
22
+ return `'${escapePowerShellSingleQuoted(value)}'`;
23
+ }
24
+ /**
25
+ * The script text the WMI-created powershell process itself runs: the real
26
+ * `msb <argv>` invocation (the call operator `&`, since `msbPath` is a
27
+ * runtime string, not a literal command name), its combined stdout+stderr
28
+ * merged into `outFile` via PowerShell's `*>` all-streams redirection —
29
+ * mirroring `fetchStdoutExact`'s own reason for treating stdout+stderr as
30
+ * one channel — followed by writing the invocation's own `$LASTEXITCODE` to
31
+ * `ecFile`. Both files are how this reaches back across the WMI boundary:
32
+ * `Invoke-CimMethod`'s `Create` has no stdout/exit-code channel of its own
33
+ * (it returns only a `ProcessId` and a `ReturnValue` for the CREATE call
34
+ * itself, never anything about what that process goes on to do). Every path
35
+ * and argv token is its own single-quoted literal (`quoteSingle`) — never
36
+ * string-interpolated — so a value carrying spaces, `$`, or a literal `'`
37
+ * (an exotic mount path, in principle) round-trips as data, not script
38
+ * syntax.
39
+ */
40
+ export function buildInnerRestoreScript(msbPath, argv, outFile, ecFile) {
41
+ const quotedArgv = argv.map(quoteSingle).join(" ");
42
+ return [
43
+ `& ${quoteSingle(msbPath)} ${quotedArgv} *> ${quoteSingle(outFile)}`,
44
+ `Set-Content -LiteralPath ${quoteSingle(ecFile)} -Value $LASTEXITCODE -NoNewline`,
45
+ ].join("\n");
46
+ }
47
+ /**
48
+ * Encodes `script` the way `powershell -EncodedCommand` requires: UTF-16LE
49
+ * bytes, base64. Chosen over `-Command "<script>"` specifically to sidestep
50
+ * a second layer of quote-escaping entirely — `buildInnerRestoreScript`'s
51
+ * own output already carries single-quoted literals of its own, and nesting
52
+ * that inside ANOTHER quoted string (the `CommandLine` this module hands to
53
+ * `Invoke-CimMethod`) would mean escaping quotes-within-quotes-within-quotes.
54
+ * A base64 payload is alphanumeric-plus-`+/=` only, so embedding it inside
55
+ * the OUTER script's own single-quoted `CommandLine` literal (see
56
+ * `buildOuterBrokerScript`) needs no escaping of its own at all.
57
+ */
58
+ export function encodeAsEncodedCommand(script) {
59
+ return Buffer.from(script, "utf16le").toString("base64");
60
+ }
61
+ /** How long `buildOuterBrokerScript`'s own polling loop waits for `ecFile` to appear before giving up and letting the caller fall through to its own `msb ls` readiness poll (see `RestoreBrokerUnconfirmed`'s own doc) — generous for `msb restore`'s own detach, which the rest of this codebase documents as "typically within seconds". */
62
+ export const RESTORE_BROKER_ECFILE_WAIT_SECONDS = 30;
63
+ const RETURN_VALUE_PREFIX = "RIGHTSIZE-BROKER-RETURNVALUE=";
64
+ const PROCESS_ID_PREFIX = "RIGHTSIZE-BROKER-PROCESSID=";
65
+ const OUT_BEGIN = "RIGHTSIZE-BROKER-OUT-BEGIN";
66
+ const OUT_END = "RIGHTSIZE-BROKER-OUT-END";
67
+ const EC_BEGIN = "RIGHTSIZE-BROKER-EC-BEGIN";
68
+ const EC_END = "RIGHTSIZE-BROKER-EC-END";
69
+ /**
70
+ * The OUTER script — the one actually run as `powershell -NoProfile -File
71
+ * <script>` (see `realRestoreBroker`) — that performs the WMI escape itself
72
+ * and reports back what happened, all through plain stdout this module's own
73
+ * node-side caller can read directly (no temp file needed for THIS layer,
74
+ * unlike the inner restore's own output — this process's stdout pipe is
75
+ * ordinary, since it is never the one msb's job object would deny).
76
+ *
77
+ * `encodedInner` (see `encodeAsEncodedCommand`) is embedded inside this
78
+ * script's own single-quoted `$cmdLine` literal — safe with no escaping of
79
+ * its own, being pure base64 — as the `-EncodedCommand` argument of the
80
+ * powershell invocation `Invoke-CimMethod` launches. `outFile`/`ecFile` are
81
+ * each their own single-quoted literal via `quoteSingle`, the same as every
82
+ * path `buildInnerRestoreScript` embeds.
83
+ *
84
+ * `powershell.exe` (never the bare `powershell`, nor `pwsh`) both here and as
85
+ * the WMI-launched process's own executable — the task this whole module
86
+ * exists for names it explicitly "for maximum compatibility": every
87
+ * supported Windows release ships Windows PowerShell under that exact name,
88
+ * while PowerShell 7 (`pwsh`) is an opt-in install this backend has no
89
+ * business depending on.
90
+ */
91
+ export function buildOuterBrokerScript(params) {
92
+ const { encodedInner, outFile, ecFile } = params;
93
+ const waitSeconds = params.waitSeconds ?? RESTORE_BROKER_ECFILE_WAIT_SECONDS;
94
+ return [
95
+ `$ErrorActionPreference = 'Stop'`,
96
+ `$cmdLine = 'powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -EncodedCommand ${encodedInner}'`,
97
+ `$create = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = $cmdLine }`,
98
+ `Write-Output "${RETURN_VALUE_PREFIX}$($create.ReturnValue)"`,
99
+ `Write-Output "${PROCESS_ID_PREFIX}$($create.ProcessId)"`,
100
+ `$deadline = (Get-Date).AddSeconds(${waitSeconds})`,
101
+ `while (-not (Test-Path -LiteralPath ${quoteSingle(ecFile)}) -and (Get-Date) -lt $deadline) {`,
102
+ ` Start-Sleep -Milliseconds 250`,
103
+ `}`,
104
+ `if (Test-Path -LiteralPath ${quoteSingle(outFile)}) {`,
105
+ ` Write-Output '${OUT_BEGIN}'`,
106
+ ` Get-Content -LiteralPath ${quoteSingle(outFile)} -Raw`,
107
+ ` Write-Output '${OUT_END}'`,
108
+ `}`,
109
+ `if (Test-Path -LiteralPath ${quoteSingle(ecFile)}) {`,
110
+ ` Write-Output '${EC_BEGIN}'`,
111
+ ` Get-Content -LiteralPath ${quoteSingle(ecFile)} -Raw`,
112
+ ` Write-Output '${EC_END}'`,
113
+ `}`,
114
+ ].join("\n");
115
+ }
116
+ function extractPrefixedInt(text, prefix) {
117
+ for (const line of text.split("\n")) {
118
+ const trimmed = line.trim();
119
+ if (trimmed.startsWith(prefix)) {
120
+ const parsed = Number.parseInt(trimmed.slice(prefix.length).trim(), 10);
121
+ return Number.isFinite(parsed) ? parsed : undefined;
122
+ }
123
+ }
124
+ return undefined;
125
+ }
126
+ function extractBetween(text, beginMarker, endMarker) {
127
+ const beginIdx = text.indexOf(beginMarker);
128
+ if (beginIdx === -1) {
129
+ return undefined;
130
+ }
131
+ const contentStart = text.indexOf("\n", beginIdx);
132
+ if (contentStart === -1) {
133
+ return undefined;
134
+ }
135
+ const endIdx = text.indexOf(endMarker, contentStart);
136
+ if (endIdx === -1) {
137
+ return undefined;
138
+ }
139
+ const raw = text.slice(contentStart + 1, endIdx);
140
+ // The captured content always carries exactly one trailing "\n" of its
141
+ // own — the line break between the last content line and the end
142
+ // marker's own line — which is an artifact of this scan, never part of
143
+ // what `Write-Output`/`Get-Content -Raw` actually produced; strip it.
144
+ return raw.endsWith("\n") ? raw.slice(0, -1) : raw;
145
+ }
146
+ /** Parses `buildOuterBrokerScript`'s own stdout back into its four parts — see `ParsedBrokerScriptOutput`'s own doc on each field. Tolerant of CRLF (PowerShell's own native line ending) and of any marker being absent, since the outer script can die (or its own wait bound expire) after printing only some of them. */
147
+ export function parseBrokerScriptOutput(stdout) {
148
+ const normalized = stdout.replace(/\r\n/g, "\n");
149
+ const ecText = extractBetween(normalized, EC_BEGIN, EC_END);
150
+ return {
151
+ returnValue: extractPrefixedInt(normalized, RETURN_VALUE_PREFIX),
152
+ processId: extractPrefixedInt(normalized, PROCESS_ID_PREFIX),
153
+ outText: extractBetween(normalized, OUT_BEGIN, OUT_END),
154
+ ecText: ecText?.trim(),
155
+ };
156
+ }
157
+ function runPowerShellFile(scriptFile, timeoutMs) {
158
+ return new Promise((resolveRun, rejectRun) => {
159
+ const child = spawn("powershell.exe", ["-NoProfile", "-NonInteractive", "-File", scriptFile], {
160
+ stdio: [CLOSED_STDIN, "pipe", "pipe"],
161
+ });
162
+ let stdout = "";
163
+ let stderr = "";
164
+ child.stdout.setEncoding("utf8");
165
+ child.stderr.setEncoding("utf8");
166
+ child.stdout.on("data", (chunk) => {
167
+ stdout += chunk;
168
+ });
169
+ child.stderr.on("data", (chunk) => {
170
+ stderr += chunk;
171
+ });
172
+ let settled = false;
173
+ const timer = setTimeout(() => {
174
+ if (settled) {
175
+ return;
176
+ }
177
+ settled = true;
178
+ child.kill("SIGKILL");
179
+ rejectRun(new Error(`restore broker: its own powershell.exe driver did not exit within ${timeoutMs}ms`));
180
+ }, timeoutMs);
181
+ child.once("error", (err) => {
182
+ if (settled) {
183
+ return;
184
+ }
185
+ settled = true;
186
+ clearTimeout(timer);
187
+ rejectRun(new Error(`restore broker: failed to spawn powershell.exe: ${err.message}`));
188
+ });
189
+ child.once("close", () => {
190
+ if (settled) {
191
+ return;
192
+ }
193
+ settled = true;
194
+ clearTimeout(timer);
195
+ // Combined for the same reason `fetchStdoutExact`'s sibling calls
196
+ // treat stdout/stderr as one channel elsewhere in this backend: the
197
+ // outer script's own diagnostics (a PowerShell terminating error,
198
+ // say) could land on either.
199
+ resolveRun({ stdout: stderr.length > 0 ? `${stdout}\n${stderr}` : stdout });
200
+ });
201
+ });
202
+ }
203
+ /**
204
+ * The real, production `RestoreBrokerLauncher` — writes the outer script
205
+ * (see `buildOuterBrokerScript`) to a unique temp file, runs it via
206
+ * `powershell.exe -NoProfile -NonInteractive -File <script>`, and turns its
207
+ * parsed output (`parseBrokerScriptOutput`) into a `RestoreBrokerResult`:
208
+ *
209
+ * - WMI's own `ReturnValue` missing or non-zero (the process was never even
210
+ * created) throws — broker-infrastructure failure, per this function's own
211
+ * `RestoreBrokerLauncher` contract, for the caller to catch and fall back
212
+ * to a direct attempt.
213
+ * - `ecFile` never read back (`ecText === undefined`, or unparsable as an
214
+ * integer) resolves `{ kind: "unconfirmed" }` — see that type's own doc.
215
+ * - Otherwise resolves `{ kind: "completed", exitCode, output: outText ?? ""
216
+ * }` — the brokered restore's own result, for the caller to classify
217
+ * exactly like a direct attempt's.
218
+ *
219
+ * Every temp file (script, `outFile`, `ecFile`) is unique per call
220
+ * (`randomBytes`-suffixed, under `os.tmpdir()`) and best-effort removed in a
221
+ * `finally` — cleanup failures are swallowed, never allowed to mask the real
222
+ * outcome above.
223
+ */
224
+ export const realRestoreBroker = async (msbPath, argv, timeoutMs) => {
225
+ const unique = `rightsize-restore-broker-${process.pid}-${randomBytes(6).toString("hex")}`;
226
+ const base = path.join(os.tmpdir(), unique);
227
+ const scriptFile = `${base}.ps1`;
228
+ const outFile = `${base}.out.txt`;
229
+ const ecFile = `${base}.ec.txt`;
230
+ const inner = buildInnerRestoreScript(msbPath, argv, outFile, ecFile);
231
+ const outer = buildOuterBrokerScript({ encodedInner: encodeAsEncodedCommand(inner), outFile, ecFile });
232
+ try {
233
+ try {
234
+ await fs.writeFile(scriptFile, outer, "utf8");
235
+ }
236
+ catch (err) {
237
+ throw new Error(`restore broker: failed to write its own script file '${scriptFile}': ${err instanceof Error ? err.message : String(err)}`);
238
+ }
239
+ const { stdout } = await runPowerShellFile(scriptFile, timeoutMs);
240
+ const parsed = parseBrokerScriptOutput(stdout);
241
+ if (parsed.returnValue === undefined) {
242
+ throw new Error(`restore broker: could not find a WMI ReturnValue in its own driver script's output:\n${stdout}`);
243
+ }
244
+ if (parsed.returnValue !== 0) {
245
+ throw new Error(`restore broker: WMI 'Win32_Process Create' refused to launch the restore (ReturnValue=${parsed.returnValue}):\n${stdout}`);
246
+ }
247
+ if (parsed.ecText === undefined) {
248
+ return { kind: "unconfirmed" };
249
+ }
250
+ const exitCode = Number.parseInt(parsed.ecText, 10);
251
+ if (!Number.isFinite(exitCode)) {
252
+ return { kind: "unconfirmed" };
253
+ }
254
+ return { kind: "completed", exitCode, output: parsed.outText ?? "" };
255
+ }
256
+ finally {
257
+ await Promise.all([
258
+ fs.rm(scriptFile, { force: true }).catch(() => { }),
259
+ fs.rm(outFile, { force: true }).catch(() => { }),
260
+ fs.rm(ecFile, { force: true }).catch(() => { }),
261
+ ]);
262
+ }
263
+ };
264
+ //# sourceMappingURL=restore-broker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"restore-broker.js","sourceRoot":"","sources":["../../src/backend-msb/restore-broker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * True if `output` (an `msb restore` invocation's combined stdout/stderr)
3
+ * names msb's own sandbox-name collision refusal — msb refuses to create a
4
+ * second sandbox under a name it already has one for. Captured shape:
5
+ *
6
+ * ```text
7
+ * error: sandbox 'rz-abc-1' already exists
8
+ * ```
9
+ *
10
+ * EMPIRICALLY VERIFIED against msb 0.7.1's own source
11
+ * (`prepare_create_target` in `sdk/rust/lib/backend/local/sandbox/create.rs`):
12
+ * the check is `existing.is_some() || dir_exists` — the refusal fires when
13
+ * EITHER the sandbox's database record still exists OR its on-disk directory
14
+ * does, two independent things that can each still be mid-release on
15
+ * Windows. `MsbCliBackend.createCheckpoint`'s own stop/snapshot/`rm`/restore
16
+ * cycle removes the source sandbox and immediately restores a fresh one
17
+ * under the SAME name, so a lingering release on either side races the very
18
+ * next `restore` straight into this refusal.
19
+ *
20
+ * Deliberately a broad, case-insensitive substring match (mirrors this
21
+ * backend's own `isPortBindConflictOutput`, and the sibling classifiers this
22
+ * whole family of msb backends — rust, kotlin — use for the identical
23
+ * signature) rather than a strict parse: msb has no structured/typed error
24
+ * for this, only combined process output, and a collision can be worded
25
+ * around either the sandbox's name or (rarely, alternate msb builds) its
26
+ * dedicated "already in use" phrasing. Only ever checked against a
27
+ * `msb restore` invocation's own output — a `msb snapshot load` "already
28
+ * exists" (see `isSnapshotAlreadyExistsError`) is a completely different
29
+ * command and never appears here.
30
+ */
31
+ export declare function isSandboxAlreadyExistsFailure(output: string): boolean;
32
+ //# sourceMappingURL=sandbox-already-exists.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-already-exists.d.ts","sourceRoot":"","sources":["../../src/backend-msb/sandbox-already-exists.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAGrE"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * True if `output` (an `msb restore` invocation's combined stdout/stderr)
3
+ * names msb's own sandbox-name collision refusal — msb refuses to create a
4
+ * second sandbox under a name it already has one for. Captured shape:
5
+ *
6
+ * ```text
7
+ * error: sandbox 'rz-abc-1' already exists
8
+ * ```
9
+ *
10
+ * EMPIRICALLY VERIFIED against msb 0.7.1's own source
11
+ * (`prepare_create_target` in `sdk/rust/lib/backend/local/sandbox/create.rs`):
12
+ * the check is `existing.is_some() || dir_exists` — the refusal fires when
13
+ * EITHER the sandbox's database record still exists OR its on-disk directory
14
+ * does, two independent things that can each still be mid-release on
15
+ * Windows. `MsbCliBackend.createCheckpoint`'s own stop/snapshot/`rm`/restore
16
+ * cycle removes the source sandbox and immediately restores a fresh one
17
+ * under the SAME name, so a lingering release on either side races the very
18
+ * next `restore` straight into this refusal.
19
+ *
20
+ * Deliberately a broad, case-insensitive substring match (mirrors this
21
+ * backend's own `isPortBindConflictOutput`, and the sibling classifiers this
22
+ * whole family of msb backends — rust, kotlin — use for the identical
23
+ * signature) rather than a strict parse: msb has no structured/typed error
24
+ * for this, only combined process output, and a collision can be worded
25
+ * around either the sandbox's name or (rarely, alternate msb builds) its
26
+ * dedicated "already in use" phrasing. Only ever checked against a
27
+ * `msb restore` invocation's own output — a `msb snapshot load` "already
28
+ * exists" (see `isSnapshotAlreadyExistsError`) is a completely different
29
+ * command and never appears here.
30
+ */
31
+ export function isSandboxAlreadyExistsFailure(output) {
32
+ const m = output.toLowerCase();
33
+ return m.includes("already exists") || (m.includes("already in use") && m.includes("name"));
34
+ }
35
+ //# sourceMappingURL=sandbox-already-exists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox-already-exists.js","sourceRoot":"","sources":["../../src/backend-msb/sandbox-already-exists.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,6BAA6B,CAAC,MAAc;IAC1D,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAC/B,OAAO,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9F,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Extracts the artifact path msb prints as the LAST line of a successful
3
+ * `msb snapshot create --from-sandbox ...` invocation's stdout. EMPIRICALLY
4
+ * VERIFIED against a real msb 0.7.1 binary: the command prints the snapshot
5
+ * ID line first, then the absolute artifact path as its final line —
6
+ * `<destDir-or-default>/<sourceSandbox>/snap_<32-hex-digest>` (see
7
+ * `MsbCommands.snapshotCreate`'s own doc for why the caller-supplied name
8
+ * never determines this path). `MsbCliBackend.createCheckpoint` uses the
9
+ * returned path — never the ref it asked msb to create the snapshot under —
10
+ * as the effective checkpoint ref for every later restore/rm/inspect call.
11
+ *
12
+ * Deliberately conservative, matching the HARD CONSTRAINT this parser was
13
+ * written to satisfy: trims the whole output, keeps only non-empty lines,
14
+ * takes the LAST one, and requires it to be an absolute path (`path.isAbsolute`)
15
+ * — anything else (empty output, a relative-looking last line, some future
16
+ * extra trailing line this parser has never seen) resolves `undefined`
17
+ * rather than guessing, so the caller can fail loudly with the raw,
18
+ * unparsed output quoted verbatim instead of minting a checkpoint ref from a
19
+ * misread line.
20
+ */
21
+ export declare function parseSnapshotCreateArtifactPath(stdout: string): string | undefined;
22
+ //# sourceMappingURL=snapshot-create.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-create.d.ts","sourceRoot":"","sources":["../../src/backend-msb/snapshot-create.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,+BAA+B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAUlF"}
@@ -0,0 +1,33 @@
1
+ import * as path from "node:path";
2
+ /**
3
+ * Extracts the artifact path msb prints as the LAST line of a successful
4
+ * `msb snapshot create --from-sandbox ...` invocation's stdout. EMPIRICALLY
5
+ * VERIFIED against a real msb 0.7.1 binary: the command prints the snapshot
6
+ * ID line first, then the absolute artifact path as its final line —
7
+ * `<destDir-or-default>/<sourceSandbox>/snap_<32-hex-digest>` (see
8
+ * `MsbCommands.snapshotCreate`'s own doc for why the caller-supplied name
9
+ * never determines this path). `MsbCliBackend.createCheckpoint` uses the
10
+ * returned path — never the ref it asked msb to create the snapshot under —
11
+ * as the effective checkpoint ref for every later restore/rm/inspect call.
12
+ *
13
+ * Deliberately conservative, matching the HARD CONSTRAINT this parser was
14
+ * written to satisfy: trims the whole output, keeps only non-empty lines,
15
+ * takes the LAST one, and requires it to be an absolute path (`path.isAbsolute`)
16
+ * — anything else (empty output, a relative-looking last line, some future
17
+ * extra trailing line this parser has never seen) resolves `undefined`
18
+ * rather than guessing, so the caller can fail loudly with the raw,
19
+ * unparsed output quoted verbatim instead of minting a checkpoint ref from a
20
+ * misread line.
21
+ */
22
+ export function parseSnapshotCreateArtifactPath(stdout) {
23
+ const lines = stdout
24
+ .split("\n")
25
+ .map((line) => line.trim())
26
+ .filter((line) => line.length > 0);
27
+ const lastLine = lines.at(-1);
28
+ if (lastLine === undefined || !path.isAbsolute(lastLine)) {
29
+ return undefined;
30
+ }
31
+ return lastLine;
32
+ }
33
+ //# sourceMappingURL=snapshot-create.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-create.js","sourceRoot":"","sources":["../../src/backend-msb/snapshot-create.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,+BAA+B,CAAC,MAAc;IAC5D,MAAM,KAAK,GAAG,MAAM;SACjB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,QAAQ,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}