@getmikie/cli 1.1.103 → 1.1.104
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/bin/mikie.mjs +39 -1
- package/lib/launcher.mjs +460 -23
- package/lib/update.mjs +217 -22
- package/package.json +5 -5
- package/scripts/reconcile-install.mjs +477 -1
package/bin/mikie.mjs
CHANGED
|
@@ -9,7 +9,12 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import {
|
|
10
10
|
launchNativeCaptured,
|
|
11
11
|
launchNative,
|
|
12
|
+
abandonMaterializedRuntime,
|
|
13
|
+
materializeWindowsRuntime,
|
|
14
|
+
materializePortableRuntime,
|
|
12
15
|
prepareNativeLaunch,
|
|
16
|
+
retireHistoricalWindowsRuntimes,
|
|
17
|
+
retireHistoricalRuntimes,
|
|
13
18
|
resolveDependencyPackageJson,
|
|
14
19
|
resolveNativeBinary,
|
|
15
20
|
selectPlatformPackage,
|
|
@@ -26,6 +31,10 @@ import {
|
|
|
26
31
|
parseTrajectoryReconciliationReceipt,
|
|
27
32
|
runUpdateTransaction,
|
|
28
33
|
} from "../lib/update.mjs";
|
|
34
|
+
import {
|
|
35
|
+
installedClientRuntimeDigests,
|
|
36
|
+
reconcileInstalledClients,
|
|
37
|
+
} from "../scripts/reconcile-install.mjs";
|
|
29
38
|
|
|
30
39
|
const require = createRequire(import.meta.url);
|
|
31
40
|
const packageMetadata = require("../package.json");
|
|
@@ -143,7 +152,31 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
143
152
|
ownership: path.join(customerHome, ".claude", "mikie-evidence-hook-ownership.json"),
|
|
144
153
|
},
|
|
145
154
|
transactionRoot,
|
|
146
|
-
|
|
155
|
+
prepareFinalLauncher: (candidate, version) =>
|
|
156
|
+
process.platform === "win32"
|
|
157
|
+
? materializeWindowsRuntime(candidate.binary, {
|
|
158
|
+
version,
|
|
159
|
+
cleanupHistorical: false,
|
|
160
|
+
})
|
|
161
|
+
: materializePortableRuntime(candidate.binary, {
|
|
162
|
+
version,
|
|
163
|
+
cleanupHistorical: false,
|
|
164
|
+
}),
|
|
165
|
+
retireFinalLauncher: async ({ path: launcher, sha256 }) => {
|
|
166
|
+
const preserveSha256 = await installedClientRuntimeDigests();
|
|
167
|
+
return process.platform === "win32"
|
|
168
|
+
? retireHistoricalWindowsRuntimes(launcher, {
|
|
169
|
+
expectedSha256: sha256,
|
|
170
|
+
preserveSha256,
|
|
171
|
+
})
|
|
172
|
+
: retireHistoricalRuntimes(launcher, {
|
|
173
|
+
expectedSha256: sha256,
|
|
174
|
+
preserveSha256,
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
abandonFinalLauncher: ({ path: launcher, sha256 }) =>
|
|
178
|
+
abandonMaterializedRuntime(launcher, { expectedSha256: sha256 }),
|
|
179
|
+
settleProviderRepair: async (phase, snapshot, launcherState) => {
|
|
147
180
|
const settlementBinary = snapshot?.preparedCandidate?.binary;
|
|
148
181
|
const finalLauncher = snapshot?.preparedCandidate?.finalLauncher;
|
|
149
182
|
if (!settlementBinary || !finalLauncher) {
|
|
@@ -156,6 +189,8 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
156
189
|
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
157
190
|
MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
|
|
158
191
|
MIKIE_NPM_UPDATE_REPAIR_PHASE: phase,
|
|
192
|
+
MIKIE_UPDATE_REPAIR_LAUNCHER_STATE:
|
|
193
|
+
launcherState ?? (phase === "commit" ? "activated" : "prior"),
|
|
159
194
|
MIKIE_UPDATE_REPAIR_CANDIDATE: settlementBinary,
|
|
160
195
|
MIKIE_UPDATE_REPAIR_LAUNCHER: finalLauncher,
|
|
161
196
|
},
|
|
@@ -191,6 +226,8 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
191
226
|
for (const setting of result.foreignSettings) {
|
|
192
227
|
console.log(`Preserved foreign setting: ${setting}`);
|
|
193
228
|
}
|
|
229
|
+
} else if (result.status === "reconciled") {
|
|
230
|
+
console.log(`Reconciled Mikie ${result.version} client launchers.`);
|
|
194
231
|
}
|
|
195
232
|
} catch (error) {
|
|
196
233
|
const message = error instanceof Error ? error.message : "Mikie update failed";
|
|
@@ -199,6 +236,7 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
199
236
|
}
|
|
200
237
|
} else {
|
|
201
238
|
try {
|
|
239
|
+
await reconcileInstalledClients({ version: packageMetadata.version });
|
|
202
240
|
const nativeArguments = translateArguments(arguments_);
|
|
203
241
|
const binary = await prepareNativeLaunch(await resolveNativeBinary(), nativeArguments, {
|
|
204
242
|
version: packageMetadata.version,
|
package/lib/launcher.mjs
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { constants as fsConstants, createReadStream } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
access,
|
|
6
|
+
copyFile,
|
|
7
|
+
lstat,
|
|
8
|
+
mkdir,
|
|
9
|
+
open,
|
|
10
|
+
readFile,
|
|
11
|
+
readdir,
|
|
12
|
+
rename,
|
|
13
|
+
rm,
|
|
14
|
+
rmdir,
|
|
15
|
+
stat,
|
|
16
|
+
} from "node:fs/promises";
|
|
5
17
|
import path from "node:path";
|
|
6
18
|
import { spawn } from "node:child_process";
|
|
7
19
|
|
|
@@ -22,6 +34,10 @@ const MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
|
|
|
22
34
|
const SAFE_RUNTIME_VERSION = /^[0-9A-Za-z][0-9A-Za-z._-]{0,127}$/;
|
|
23
35
|
const SAFE_RUNTIME_DIRECTORY = /^[0-9A-Za-z][0-9A-Za-z._-]{0,127}-[0-9a-f]{64}$/;
|
|
24
36
|
const HISTORICAL_RUNTIME_GRACE_MS = 24 * 60 * 60 * 1000;
|
|
37
|
+
const MAX_RETAINED_WINDOWS_RUNTIMES = 8;
|
|
38
|
+
const MAX_MATERIALIZED_WINDOWS_RUNTIMES = MAX_RETAINED_WINDOWS_RUNTIMES + 1;
|
|
39
|
+
const ABANDONED_RUNTIME_SCHEMA = "mikie.windows-runtime-abandonment.v1";
|
|
40
|
+
const SAFE_ABANDONMENT_MARKER = /^abandon-[0-9a-f]{64}\.json$/;
|
|
25
41
|
|
|
26
42
|
async function sha256File(file) {
|
|
27
43
|
const digest = createHash("sha256");
|
|
@@ -47,20 +63,33 @@ async function verifyMaterializedRuntime(file, expectedSha256, { allowMissing =
|
|
|
47
63
|
}
|
|
48
64
|
}
|
|
49
65
|
|
|
50
|
-
async function cleanupHistoricalWindowsRuntimes(
|
|
66
|
+
async function cleanupHistoricalWindowsRuntimes(
|
|
67
|
+
runtimeRoot,
|
|
68
|
+
selectedDirectory,
|
|
69
|
+
{ graceMs = HISTORICAL_RUNTIME_GRACE_MS, preserveSha256 = [] } = {},
|
|
70
|
+
) {
|
|
71
|
+
if (
|
|
72
|
+
!Array.isArray(preserveSha256) ||
|
|
73
|
+
preserveSha256.length > MAX_MATERIALIZED_WINDOWS_RUNTIMES ||
|
|
74
|
+
preserveSha256.some((digest) => !/^[0-9a-f]{64}$/.test(digest))
|
|
75
|
+
) {
|
|
76
|
+
throw new Error("Mikie cached runtime preservation authority is invalid");
|
|
77
|
+
}
|
|
78
|
+
const preserved = new Set(preserveSha256);
|
|
51
79
|
let entries;
|
|
52
80
|
try {
|
|
53
81
|
entries = await readdir(runtimeRoot, { withFileTypes: true });
|
|
54
82
|
} catch {
|
|
55
83
|
return;
|
|
56
84
|
}
|
|
57
|
-
const cutoff = Date.now() -
|
|
85
|
+
const cutoff = Date.now() - graceMs;
|
|
58
86
|
await Promise.all(
|
|
59
87
|
entries.map(async (entry) => {
|
|
60
88
|
if (
|
|
61
89
|
!entry.isDirectory() ||
|
|
62
90
|
entry.name === selectedDirectory ||
|
|
63
|
-
!SAFE_RUNTIME_DIRECTORY.test(entry.name)
|
|
91
|
+
!SAFE_RUNTIME_DIRECTORY.test(entry.name) ||
|
|
92
|
+
[...preserved].some((digest) => entry.name.endsWith(`-${digest}`))
|
|
64
93
|
) {
|
|
65
94
|
return;
|
|
66
95
|
}
|
|
@@ -75,35 +104,344 @@ async function cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory)
|
|
|
75
104
|
);
|
|
76
105
|
}
|
|
77
106
|
|
|
107
|
+
function abandonmentMarkerForExecutable(
|
|
108
|
+
runtimeRoot,
|
|
109
|
+
selectedDirectory,
|
|
110
|
+
expectedSha256,
|
|
111
|
+
executable,
|
|
112
|
+
) {
|
|
113
|
+
const value = {
|
|
114
|
+
schema_version: ABANDONED_RUNTIME_SCHEMA,
|
|
115
|
+
directory: selectedDirectory,
|
|
116
|
+
executable,
|
|
117
|
+
sha256: expectedSha256,
|
|
118
|
+
};
|
|
119
|
+
const bytes = Buffer.from(`${JSON.stringify(value)}\n`);
|
|
120
|
+
const name = `abandon-${createHash("sha256").update(bytes).digest("hex")}.json`;
|
|
121
|
+
return { path: path.join(runtimeRoot, name), value, bytes };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function removeAbandonedRuntime(runtimeRoot, markerPath, value) {
|
|
125
|
+
const runtimeDirectory = path.join(runtimeRoot, value.directory);
|
|
126
|
+
const binary = path.join(runtimeDirectory, value.executable);
|
|
127
|
+
let directoryMetadata;
|
|
128
|
+
try {
|
|
129
|
+
directoryMetadata = await lstat(runtimeDirectory);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
if (error?.code === "ENOENT") {
|
|
132
|
+
await rm(markerPath, { force: true });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) {
|
|
138
|
+
throw new Error("Mikie abandoned Windows runtime authority is invalid");
|
|
139
|
+
}
|
|
140
|
+
const entries = await readdir(runtimeDirectory, { withFileTypes: true });
|
|
141
|
+
if (entries.length === 0) {
|
|
142
|
+
try {
|
|
143
|
+
await rmdir(runtimeDirectory);
|
|
144
|
+
await rm(markerPath, { force: true });
|
|
145
|
+
return true;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (["EACCES", "EBUSY", "EPERM", "ENOTEMPTY"].includes(error?.code)) return false;
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (
|
|
152
|
+
entries.length !== 1 ||
|
|
153
|
+
entries[0].name !== value.executable ||
|
|
154
|
+
!entries[0].isFile() ||
|
|
155
|
+
entries[0].isSymbolicLink()
|
|
156
|
+
) {
|
|
157
|
+
throw new Error("Mikie abandoned Windows runtime contents changed");
|
|
158
|
+
}
|
|
159
|
+
await verifyMaterializedRuntime(binary, value.sha256);
|
|
160
|
+
try {
|
|
161
|
+
await rm(binary, { force: false, maxRetries: 0 });
|
|
162
|
+
await rmdir(runtimeDirectory);
|
|
163
|
+
await rm(markerPath, { force: true });
|
|
164
|
+
return true;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (["EACCES", "EBUSY", "EPERM", "ENOTEMPTY"].includes(error?.code)) return false;
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function persistAbandonmentMarker(marker) {
|
|
172
|
+
const temporary = `${marker.path}.${process.pid}.${randomUUID()}.tmp`;
|
|
173
|
+
let handle;
|
|
174
|
+
try {
|
|
175
|
+
handle = await open(temporary, "wx", 0o600);
|
|
176
|
+
await handle.writeFile(marker.bytes);
|
|
177
|
+
await handle.sync();
|
|
178
|
+
await handle.close();
|
|
179
|
+
handle = undefined;
|
|
180
|
+
try {
|
|
181
|
+
await rename(temporary, marker.path);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (
|
|
184
|
+
!["EEXIST", "EPERM"].includes(error?.code) ||
|
|
185
|
+
!(await readFile(marker.path)).equals(marker.bytes)
|
|
186
|
+
) {
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} finally {
|
|
191
|
+
await handle?.close().catch(() => {});
|
|
192
|
+
await rm(temporary, { force: true }).catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function scavengeAbandonedWindowsRuntimes(runtimeRoot) {
|
|
197
|
+
let entries;
|
|
198
|
+
try {
|
|
199
|
+
entries = await readdir(runtimeRoot, { withFileTypes: true });
|
|
200
|
+
} catch (error) {
|
|
201
|
+
if (error?.code === "ENOENT") return;
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
const markers = entries.filter(
|
|
205
|
+
(entry) => entry.isFile() && !entry.isSymbolicLink() && SAFE_ABANDONMENT_MARKER.test(entry.name),
|
|
206
|
+
);
|
|
207
|
+
if (markers.length > MAX_MATERIALIZED_WINDOWS_RUNTIMES) {
|
|
208
|
+
throw new Error("Mikie abandoned Windows runtime inventory is invalid");
|
|
209
|
+
}
|
|
210
|
+
for (const entry of markers) {
|
|
211
|
+
const markerPath = path.join(runtimeRoot, entry.name);
|
|
212
|
+
const metadata = await lstat(markerPath);
|
|
213
|
+
if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > 1024) {
|
|
214
|
+
throw new Error("Mikie abandoned Windows runtime marker is invalid");
|
|
215
|
+
}
|
|
216
|
+
const bytes = await readFile(markerPath);
|
|
217
|
+
let value;
|
|
218
|
+
try {
|
|
219
|
+
value = JSON.parse(bytes.toString("utf8"));
|
|
220
|
+
} catch {
|
|
221
|
+
throw new Error("Mikie abandoned Windows runtime marker is invalid");
|
|
222
|
+
}
|
|
223
|
+
if (
|
|
224
|
+
value === null ||
|
|
225
|
+
typeof value !== "object" ||
|
|
226
|
+
Array.isArray(value) ||
|
|
227
|
+
Object.keys(value).sort().join(",") !== "directory,executable,schema_version,sha256" ||
|
|
228
|
+
value.schema_version !== ABANDONED_RUNTIME_SCHEMA ||
|
|
229
|
+
!SAFE_RUNTIME_DIRECTORY.test(value.directory ?? "") ||
|
|
230
|
+
!["mikie", "mikie.exe"].includes(value.executable) ||
|
|
231
|
+
!/^[0-9a-f]{64}$/.test(value.sha256 ?? "") ||
|
|
232
|
+
!value.directory.endsWith(`-${value.sha256}`)
|
|
233
|
+
) {
|
|
234
|
+
throw new Error("Mikie abandoned Windows runtime marker is invalid");
|
|
235
|
+
}
|
|
236
|
+
const expected = abandonmentMarkerForExecutable(
|
|
237
|
+
runtimeRoot,
|
|
238
|
+
value.directory,
|
|
239
|
+
value.sha256,
|
|
240
|
+
value.executable,
|
|
241
|
+
);
|
|
242
|
+
if (entry.name !== path.basename(expected.path) || !bytes.equals(expected.bytes)) {
|
|
243
|
+
throw new Error("Mikie abandoned Windows runtime marker is invalid");
|
|
244
|
+
}
|
|
245
|
+
await removeAbandonedRuntime(runtimeRoot, markerPath, value);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async function enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory) {
|
|
250
|
+
await scavengeAbandonedWindowsRuntimes(runtimeRoot);
|
|
251
|
+
let entries;
|
|
252
|
+
try {
|
|
253
|
+
entries = await readdir(runtimeRoot, { withFileTypes: true });
|
|
254
|
+
} catch (error) {
|
|
255
|
+
if (error?.code === "ENOENT") return;
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
const materialized = entries.filter(
|
|
259
|
+
(entry) => entry.isDirectory() && SAFE_RUNTIME_DIRECTORY.test(entry.name),
|
|
260
|
+
);
|
|
261
|
+
if (
|
|
262
|
+
materialized.some((entry) => entry.name === selectedDirectory) ||
|
|
263
|
+
materialized.length < MAX_MATERIALIZED_WINDOWS_RUNTIMES
|
|
264
|
+
) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
throw new Error(
|
|
268
|
+
"Mikie cached Windows runtime retention capacity is full; reconcile exact-owned clients before installing another runtime",
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Retire one digest-pinned candidate after its update transaction fully rolled back. */
|
|
273
|
+
export async function abandonMaterializedWindowsRuntime(
|
|
274
|
+
selectedBinary,
|
|
275
|
+
{ expectedSha256 } = {},
|
|
276
|
+
) {
|
|
277
|
+
if (
|
|
278
|
+
!path.isAbsolute(selectedBinary ?? "") ||
|
|
279
|
+
!/^[a-f0-9]{64}$/.test(expectedSha256 ?? "") ||
|
|
280
|
+
path.basename(selectedBinary).toLowerCase() !== "mikie.exe"
|
|
281
|
+
) {
|
|
282
|
+
throw new Error("Mikie abandoned Windows runtime authority is invalid");
|
|
283
|
+
}
|
|
284
|
+
const selectedDirectory = path.dirname(selectedBinary);
|
|
285
|
+
const runtimeRoot = path.dirname(selectedDirectory);
|
|
286
|
+
const selectedName = path.basename(selectedDirectory);
|
|
287
|
+
if (
|
|
288
|
+
path.basename(runtimeRoot).toLowerCase() !== "runtime-v1" ||
|
|
289
|
+
!SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
|
|
290
|
+
!selectedName.endsWith(`-${expectedSha256}`)
|
|
291
|
+
) {
|
|
292
|
+
throw new Error("Mikie abandoned Windows runtime authority is invalid");
|
|
293
|
+
}
|
|
294
|
+
for (const directory of [runtimeRoot, selectedDirectory]) {
|
|
295
|
+
const metadata = await lstat(directory);
|
|
296
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
297
|
+
throw new Error("Mikie abandoned Windows runtime authority is invalid");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
await verifyMaterializedRuntime(selectedBinary, expectedSha256);
|
|
301
|
+
const marker = abandonmentMarkerForExecutable(
|
|
302
|
+
runtimeRoot,
|
|
303
|
+
selectedName,
|
|
304
|
+
expectedSha256,
|
|
305
|
+
"mikie.exe",
|
|
306
|
+
);
|
|
307
|
+
await persistAbandonmentMarker(marker);
|
|
308
|
+
await removeAbandonedRuntime(runtimeRoot, marker.path, marker.value);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Retire one cross-platform content-addressed candidate after a proven rollback. */
|
|
312
|
+
export async function abandonMaterializedRuntime(selectedBinary, { expectedSha256 } = {}) {
|
|
313
|
+
if (path.basename(selectedBinary ?? "") === "mikie.exe") {
|
|
314
|
+
return abandonMaterializedWindowsRuntime(selectedBinary, { expectedSha256 });
|
|
315
|
+
}
|
|
316
|
+
if (
|
|
317
|
+
!path.isAbsolute(selectedBinary ?? "") ||
|
|
318
|
+
path.basename(selectedBinary) !== "mikie" ||
|
|
319
|
+
!/^[a-f0-9]{64}$/.test(expectedSha256 ?? "")
|
|
320
|
+
) {
|
|
321
|
+
throw new Error("Mikie abandoned runtime authority is invalid");
|
|
322
|
+
}
|
|
323
|
+
const selectedDirectory = path.dirname(selectedBinary);
|
|
324
|
+
const runtimeRoot = path.dirname(selectedDirectory);
|
|
325
|
+
const selectedName = path.basename(selectedDirectory);
|
|
326
|
+
if (
|
|
327
|
+
path.basename(runtimeRoot) !== "runtime-v1" ||
|
|
328
|
+
!SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
|
|
329
|
+
!selectedName.endsWith(`-${expectedSha256}`)
|
|
330
|
+
) {
|
|
331
|
+
throw new Error("Mikie abandoned runtime authority is invalid");
|
|
332
|
+
}
|
|
333
|
+
await verifyMaterializedRuntime(selectedBinary, expectedSha256);
|
|
334
|
+
const marker = abandonmentMarkerForExecutable(
|
|
335
|
+
runtimeRoot,
|
|
336
|
+
selectedName,
|
|
337
|
+
expectedSha256,
|
|
338
|
+
"mikie",
|
|
339
|
+
);
|
|
340
|
+
await persistAbandonmentMarker(marker);
|
|
341
|
+
await removeAbandonedRuntime(runtimeRoot, marker.path, marker.value);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function retireHistoricalWindowsRuntimes(
|
|
345
|
+
selectedBinary,
|
|
346
|
+
{ expectedSha256, preserveSha256 = [] } = {},
|
|
347
|
+
) {
|
|
348
|
+
if (
|
|
349
|
+
!path.isAbsolute(selectedBinary ?? "") ||
|
|
350
|
+
!/^[a-f0-9]{64}$/.test(expectedSha256 ?? "") ||
|
|
351
|
+
path.basename(selectedBinary).toLowerCase() !== "mikie.exe"
|
|
352
|
+
) {
|
|
353
|
+
throw new Error("Mikie cached Windows runtime retirement authority is invalid");
|
|
354
|
+
}
|
|
355
|
+
const selectedDirectory = path.dirname(selectedBinary);
|
|
356
|
+
const runtimeRoot = path.dirname(selectedDirectory);
|
|
357
|
+
const selectedName = path.basename(selectedDirectory);
|
|
358
|
+
if (
|
|
359
|
+
path.basename(runtimeRoot).toLowerCase() !== "runtime-v1" ||
|
|
360
|
+
!SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
|
|
361
|
+
!selectedName.endsWith(`-${expectedSha256}`)
|
|
362
|
+
) {
|
|
363
|
+
throw new Error("Mikie cached Windows runtime retirement authority is invalid");
|
|
364
|
+
}
|
|
365
|
+
for (const directory of [runtimeRoot, selectedDirectory]) {
|
|
366
|
+
const metadata = await lstat(directory);
|
|
367
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
368
|
+
throw new Error("Mikie cached Windows runtime retirement authority is invalid");
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
await verifyMaterializedRuntime(selectedBinary, expectedSha256);
|
|
372
|
+
await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedName, {
|
|
373
|
+
graceMs: 0,
|
|
374
|
+
preserveSha256,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Retire content-addressed runtimes only after every owned client committed migration. */
|
|
379
|
+
export async function retireHistoricalRuntimes(
|
|
380
|
+
selectedBinary,
|
|
381
|
+
{ expectedSha256, preserveSha256 = [] } = {},
|
|
382
|
+
) {
|
|
383
|
+
if (path.basename(selectedBinary ?? "") === "mikie.exe") {
|
|
384
|
+
return retireHistoricalWindowsRuntimes(selectedBinary, {
|
|
385
|
+
expectedSha256,
|
|
386
|
+
preserveSha256,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
if (
|
|
390
|
+
!path.isAbsolute(selectedBinary ?? "") ||
|
|
391
|
+
path.basename(selectedBinary) !== "mikie" ||
|
|
392
|
+
!/^[a-f0-9]{64}$/.test(expectedSha256 ?? "")
|
|
393
|
+
) {
|
|
394
|
+
throw new Error("Mikie cached runtime retirement authority is invalid");
|
|
395
|
+
}
|
|
396
|
+
const selectedDirectory = path.dirname(selectedBinary);
|
|
397
|
+
const runtimeRoot = path.dirname(selectedDirectory);
|
|
398
|
+
const selectedName = path.basename(selectedDirectory);
|
|
399
|
+
if (
|
|
400
|
+
path.basename(runtimeRoot) !== "runtime-v1" ||
|
|
401
|
+
!SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
|
|
402
|
+
!selectedName.endsWith(`-${expectedSha256}`)
|
|
403
|
+
) {
|
|
404
|
+
throw new Error("Mikie cached runtime retirement authority is invalid");
|
|
405
|
+
}
|
|
406
|
+
await verifyMaterializedRuntime(selectedBinary, expectedSha256);
|
|
407
|
+
await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedName, {
|
|
408
|
+
graceMs: 0,
|
|
409
|
+
preserveSha256,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
78
413
|
export function shouldStageWindowsRuntime(arguments_) {
|
|
79
414
|
if (arguments_.length === 0) return true;
|
|
80
|
-
if (["mcp", "serve", "run-relay"].includes(arguments_[0])) return true;
|
|
415
|
+
if (["mcp", "serve", "run-relay", "doctor"].includes(arguments_[0])) return true;
|
|
416
|
+
if (arguments_.length === 1 && arguments_[0] === "uninstall") return true;
|
|
417
|
+
if (
|
|
418
|
+
arguments_.length === 2 &&
|
|
419
|
+
arguments_[0] === "setup" &&
|
|
420
|
+
["codex", "claude", "claude_code"].includes(arguments_[1])
|
|
421
|
+
) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
81
424
|
return arguments_[0] === "control" && arguments_[1] === "run";
|
|
82
425
|
}
|
|
83
426
|
|
|
84
|
-
|
|
427
|
+
async function materializeRuntime(
|
|
85
428
|
binary,
|
|
86
|
-
{ version,
|
|
429
|
+
{ version, runtimeRoot, executableName, cleanupHistorical },
|
|
87
430
|
) {
|
|
88
|
-
if (
|
|
89
|
-
!path.isAbsolute(binary) ||
|
|
90
|
-
!path.isAbsolute(localAppData ?? "") ||
|
|
91
|
-
!SAFE_RUNTIME_VERSION.test(version ?? "")
|
|
92
|
-
) {
|
|
93
|
-
throw new Error("Mikie cached Windows runtime authority is invalid");
|
|
94
|
-
}
|
|
95
431
|
const sourceMetadata = await lstat(binary);
|
|
96
432
|
if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink() || sourceMetadata.size === 0) {
|
|
97
|
-
throw new Error("Mikie native
|
|
433
|
+
throw new Error("Mikie native runtime is invalid");
|
|
98
434
|
}
|
|
99
435
|
const sourceSha256 = await sha256File(binary);
|
|
100
|
-
const runtimeRoot = path.join(localAppData, "Mikie", "runtime-v1");
|
|
101
436
|
const selectedDirectory = `${version}-${sourceSha256}`;
|
|
102
437
|
const runtimeDirectory = path.join(runtimeRoot, selectedDirectory);
|
|
103
|
-
const materialized = path.join(runtimeDirectory,
|
|
438
|
+
const materialized = path.join(runtimeDirectory, executableName);
|
|
439
|
+
await enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory);
|
|
104
440
|
await mkdir(runtimeDirectory, { recursive: true });
|
|
105
441
|
if (await verifyMaterializedRuntime(materialized, sourceSha256, { allowMissing: true })) {
|
|
106
|
-
|
|
442
|
+
if (cleanupHistorical) {
|
|
443
|
+
await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
|
|
444
|
+
}
|
|
107
445
|
return materialized;
|
|
108
446
|
}
|
|
109
447
|
|
|
@@ -118,7 +456,7 @@ export async function materializeWindowsRuntime(
|
|
|
118
456
|
sha256File(binary),
|
|
119
457
|
]);
|
|
120
458
|
if (copiedSha256 !== sourceSha256 || stableSourceSha256 !== sourceSha256) {
|
|
121
|
-
throw new Error("Mikie native
|
|
459
|
+
throw new Error("Mikie native runtime changed during materialization");
|
|
122
460
|
}
|
|
123
461
|
try {
|
|
124
462
|
await rename(temporary, materialized);
|
|
@@ -131,13 +469,110 @@ export async function materializeWindowsRuntime(
|
|
|
131
469
|
}
|
|
132
470
|
}
|
|
133
471
|
await verifyMaterializedRuntime(materialized, sourceSha256);
|
|
134
|
-
|
|
472
|
+
if (cleanupHistorical) {
|
|
473
|
+
await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
|
|
474
|
+
}
|
|
135
475
|
return materialized;
|
|
136
476
|
} finally {
|
|
137
477
|
await rm(temporary, { force: true }).catch(() => {});
|
|
138
478
|
}
|
|
139
479
|
}
|
|
140
480
|
|
|
481
|
+
export async function materializeWindowsRuntime(
|
|
482
|
+
binary,
|
|
483
|
+
{
|
|
484
|
+
version,
|
|
485
|
+
localAppData = process.env.LOCALAPPDATA,
|
|
486
|
+
cleanupHistorical = true,
|
|
487
|
+
} = {},
|
|
488
|
+
) {
|
|
489
|
+
if (
|
|
490
|
+
!path.isAbsolute(binary) ||
|
|
491
|
+
!path.isAbsolute(localAppData ?? "") ||
|
|
492
|
+
!SAFE_RUNTIME_VERSION.test(version ?? "")
|
|
493
|
+
) {
|
|
494
|
+
throw new Error("Mikie cached Windows runtime authority is invalid");
|
|
495
|
+
}
|
|
496
|
+
return materializeRuntime(binary, {
|
|
497
|
+
version,
|
|
498
|
+
runtimeRoot: path.join(localAppData, "Mikie", "runtime-v1"),
|
|
499
|
+
executableName: "mikie.exe",
|
|
500
|
+
cleanupHistorical,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function portableRuntimeRoot(platform, environment) {
|
|
505
|
+
if (platform === "win32") {
|
|
506
|
+
return path.join(environment.LOCALAPPDATA ?? "", "Mikie", "runtime-v1");
|
|
507
|
+
}
|
|
508
|
+
if (platform === "darwin") {
|
|
509
|
+
return path.join(
|
|
510
|
+
environment.HOME ?? "",
|
|
511
|
+
"Library",
|
|
512
|
+
"Application Support",
|
|
513
|
+
"Mikie",
|
|
514
|
+
"runtime-v1",
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const state = environment.XDG_STATE_HOME || path.join(environment.HOME ?? "", ".local", "state");
|
|
518
|
+
return path.join(state, "mikie", "runtime-v1");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Derive the one immutable runtime path bound to package version and bytes. */
|
|
522
|
+
export function materializedRuntimePath({
|
|
523
|
+
version,
|
|
524
|
+
sha256,
|
|
525
|
+
platform = process.platform,
|
|
526
|
+
environment = process.env,
|
|
527
|
+
} = {}) {
|
|
528
|
+
const runtimeRoot = portableRuntimeRoot(platform, environment);
|
|
529
|
+
if (
|
|
530
|
+
!path.isAbsolute(runtimeRoot) ||
|
|
531
|
+
!SAFE_RUNTIME_VERSION.test(version ?? "") ||
|
|
532
|
+
!/^[0-9a-f]{64}$/.test(sha256 ?? "")
|
|
533
|
+
) {
|
|
534
|
+
throw new Error("Mikie cached runtime authority is invalid");
|
|
535
|
+
}
|
|
536
|
+
return path.join(
|
|
537
|
+
runtimeRoot,
|
|
538
|
+
`${version}-${sha256}`,
|
|
539
|
+
platform === "win32" ? "mikie.exe" : "mikie",
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/** Materialize an immutable runtime so npm package replacement never strands a client. */
|
|
544
|
+
export async function materializePortableRuntime(
|
|
545
|
+
binary,
|
|
546
|
+
{
|
|
547
|
+
version,
|
|
548
|
+
platform = process.platform,
|
|
549
|
+
environment = process.env,
|
|
550
|
+
cleanupHistorical = true,
|
|
551
|
+
} = {},
|
|
552
|
+
) {
|
|
553
|
+
const runtimeRoot = portableRuntimeRoot(platform, environment);
|
|
554
|
+
if (
|
|
555
|
+
!path.isAbsolute(binary) ||
|
|
556
|
+
!path.isAbsolute(runtimeRoot) ||
|
|
557
|
+
!SAFE_RUNTIME_VERSION.test(version ?? "")
|
|
558
|
+
) {
|
|
559
|
+
throw new Error("Mikie cached runtime authority is invalid");
|
|
560
|
+
}
|
|
561
|
+
if (platform === "win32") {
|
|
562
|
+
return materializeWindowsRuntime(binary, {
|
|
563
|
+
version,
|
|
564
|
+
localAppData: environment.LOCALAPPDATA,
|
|
565
|
+
cleanupHistorical,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
return materializeRuntime(binary, {
|
|
569
|
+
version,
|
|
570
|
+
runtimeRoot,
|
|
571
|
+
executableName: "mikie",
|
|
572
|
+
cleanupHistorical,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
141
576
|
export async function prepareNativeLaunch(
|
|
142
577
|
binary,
|
|
143
578
|
arguments_,
|
|
@@ -147,12 +582,14 @@ export async function prepareNativeLaunch(
|
|
|
147
582
|
environment = process.env,
|
|
148
583
|
} = {},
|
|
149
584
|
) {
|
|
150
|
-
if (
|
|
585
|
+
if (!shouldStageWindowsRuntime(arguments_)) {
|
|
151
586
|
return binary;
|
|
152
587
|
}
|
|
153
|
-
return
|
|
588
|
+
return materializePortableRuntime(binary, {
|
|
154
589
|
version,
|
|
155
|
-
|
|
590
|
+
platform,
|
|
591
|
+
environment,
|
|
592
|
+
cleanupHistorical: false,
|
|
156
593
|
});
|
|
157
594
|
}
|
|
158
595
|
|