dsh-plugin-rea 2.0.6 → 2.0.8

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.
@@ -213,7 +213,10 @@ const SHIM_SOURCE = path.join(path.dirname(fileURLToPath(import.meta.url)), ".."
213
213
  function shimBundleRoot(shimFile, env) {
214
214
  const run = spawnSync(process.execPath, [shimFile, "--shim-status"], {
215
215
  encoding: "utf8",
216
- env: { ...process.env, REA_BUNDLE_AUTO_PROVISION: "0", ...env },
216
+ // These tests assert bundle-root resolution, not the launch decision. Pinning
217
+ // the mode keeps them independent of whether THIS machine has a REA on PATH
218
+ // (and keeps them from paying for a probe on every spawn).
219
+ env: { ...process.env, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle", ...env },
217
220
  });
218
221
  // --shim-status prints its report even when no bundle exists yet (exit 78).
219
222
  return JSON.parse(run.stdout).bundleRoot;
@@ -821,6 +824,10 @@ function runShim(bundle, args, extraEnv = {}) {
821
824
  ...process.env,
822
825
  DSH_HOME: path.join(path.dirname(bundle), "dsh-home"),
823
826
  REA_BUNDLE_ROOT: bundle,
827
+ // The bundle path is what these tests assert. Without this pin the shim
828
+ // would take its `auto` branch and, on a machine that happens to have a REA
829
+ // on PATH, delegate to it instead of exercising the code under test.
830
+ REA_BUNDLE_LAUNCH: "bundle",
824
831
  ...extraEnv,
825
832
  };
826
833
  delete env.REA_BUNDLE_AUTO_PROVISION;
@@ -944,7 +951,7 @@ test("a second REA registration is reported instead of silently duplicated", ()
944
951
 
945
952
  const status = spawnSync(process.execPath, [path.join(bundle, "bin", "rea-shim.mjs"), "--shim-status"], {
946
953
  encoding: "utf8",
947
- env: { ...process.env, DSH_HOME: home, REA_BUNDLE_ROOT: bundle },
954
+ env: { ...process.env, DSH_HOME: home, REA_BUNDLE_ROOT: bundle, REA_BUNDLE_LAUNCH: "bundle" },
948
955
  });
949
956
  assert.equal(JSON.parse(status.stdout).duplicateRegistration.id, "rea-mcp");
950
957
  } finally {
@@ -1123,7 +1130,7 @@ test("the fallback vendored-REA path matches where setup installs it", () => {
1123
1130
  const run = spawnSync(process.execPath, [path.join(REPO_ROOT, "bin", "rea-shim.mjs"), "--shim-status"], {
1124
1131
  encoding: "utf8",
1125
1132
  timeout: 60000,
1126
- env: { ...process.env, REA_BUNDLE_ROOT: bundle, REA_BUNDLE_AUTO_PROVISION: "0" },
1133
+ env: { ...process.env, REA_BUNDLE_ROOT: bundle, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle" },
1127
1134
  });
1128
1135
  const report = JSON.parse(run.stdout);
1129
1136
  assert.equal(report.rea.ok, true, JSON.stringify(report.rea));
@@ -1212,16 +1219,38 @@ test("the entry loads as a cordis plugin and does nothing", async () => {
1212
1219
  test("the manifest declares a DSH host range the market can read", () => {
1213
1220
  const range = declaredHostRange(REPO_ROOT);
1214
1221
  assert.equal(typeof range, "string", "engines.dsh must be declared; without it the market reports the plugin as unknown");
1222
+ // The string is pinned, not just parsed: the ceiling decides which hosts are
1223
+ // admitted, and `<0.3.0` vs `<0.3.0-0` is exactly the difference between
1224
+ // "no 0.3.0" and "0.3.0-alpha.1 is fine" once prereleases are included.
1225
+ assert.equal(range, ">=0.1.7-rc.2 <0.3.0-0");
1215
1226
  assert.throws(() => satisfiesDeclaredRange("0.2.0-rc.2", "not a range"), "the gate must reject an unparseable range");
1216
1227
  assert.doesNotThrow(() => satisfiesDeclaredRange("0.2.0-rc.2", range));
1217
1228
  });
1218
1229
 
1230
+ test("the release list this gate checks is the published one", () => {
1231
+ // Pinned literally: deleting an entry from the list would otherwise shrink the
1232
+ // coverage claim silently, and every test below would still pass.
1233
+ assert.deepEqual(HOST_RELEASES, [
1234
+ "0.2.1-rc.2",
1235
+ "0.2.1-rc.1",
1236
+ "0.2.1-alpha.5",
1237
+ "0.2.1-alpha.4",
1238
+ "0.2.1-alpha.3",
1239
+ "0.2.1-alpha.2",
1240
+ "0.2.1-alpha.1",
1241
+ "0.2.0-rc.2",
1242
+ "0.2.0-rc.1",
1243
+ "0.1.7-rc.2",
1244
+ ]);
1245
+ assert.deepEqual(OUTSIDE_HOSTS, ["0.3.0", "0.3.0-rc.1", "0.3.0-alpha.1", "0.1.7-rc.1", "0.1.7-alpha.2"]);
1246
+ });
1247
+
1219
1248
  test("every released DSH host is inside the declared range, and nothing untested is", () => {
1220
1249
  const range = declaredHostRange(REPO_ROOT);
1221
1250
  const { covered, uncovered, leaked } = checkHostCoverage(range);
1222
1251
  assert.deepEqual(uncovered, [], `these releases fall outside ${range}`);
1223
1252
  assert.deepEqual(leaked, [], `the declaration also admits ${leaked.join(", ")}, which nobody tested`);
1224
- assert.equal(covered.length, HOST_RELEASES.length, "every release in the list must be covered");
1253
+ assert.deepEqual(covered, HOST_RELEASES, "every release in the list must be covered");
1225
1254
  });
1226
1255
 
1227
1256
  test("the host-range gate rejects a plausible but wrong declaration", () => {
@@ -1230,10 +1259,16 @@ test("the host-range gate rejects a plausible but wrong declaration", () => {
1230
1259
  // prerelease sorts BELOW its release, it drops the whole 0.2.0-rc line as well
1231
1260
  // as 0.1.7. That is the trap this test exists for: the typo looks like it names
1232
1261
  // the running host.
1233
- const drifted = checkHostCoverage(">=0.2.0 <0.3.0");
1262
+ const drifted = checkHostCoverage(">=0.2.0 <0.3.0-0");
1234
1263
  assert.deepEqual(drifted.uncovered, ["0.2.0-rc.2", "0.2.0-rc.1", "0.1.7-rc.2"], "a 0.2.0 floor must be seen dropping the 0.2.0 prereleases");
1235
- // And the ceiling is load bearing: the next minor line is a host nobody audited.
1236
- assert.equal(satisfiesDeclaredRange("0.3.0", ">=0.1.7-rc.2 <0.3.0"), false);
1264
+ // The OTHER half of the gate needs its own control: a floor low enough to admit
1265
+ // a host nobody audited must be reported as a leak, not silently accepted.
1266
+ const loose = checkHostCoverage(">=0.1.0 <0.3.0-0");
1267
+ assert.deepEqual(loose.leaked, ["0.1.7-rc.1", "0.1.7-alpha.2"], "a low floor must be seen admitting the earlier prereleases");
1268
+ // And the ceiling is load bearing: the next minor line is a host nobody audited,
1269
+ // including its prereleases, which `<0.3.0` would have let through.
1270
+ assert.equal(satisfiesDeclaredRange("0.3.0", ">=0.1.7-rc.2 <0.3.0-0"), false);
1271
+ assert.equal(satisfiesDeclaredRange("0.3.0-rc.1", ">=0.1.7-rc.2 <0.3.0-0"), false);
1237
1272
  });
1238
1273
 
1239
1274
  test("prerelease ordering matches semver, not string order", () => {
@@ -1242,5 +1277,471 @@ test("prerelease ordering matches semver, not string order", () => {
1242
1277
  assert.equal(compareVersions("0.2.0-rc.2", "0.2.0-rc.1"), 1);
1243
1278
  // String comparison would sort "alpha.10" before "alpha.5"; semver does not.
1244
1279
  assert.equal(compareVersions("0.2.1-alpha.10", "0.2.1-alpha.5"), 1);
1245
- assert.equal(OUTSIDE_HOSTS.length > 0, true);
1280
+ });
1281
+
1282
+ test("the evaluator reads the two tolerances semver does, and refuses the rest", () => {
1283
+ // Build metadata carries no precedence: a host reporting it is that version.
1284
+ assert.equal(satisfiesDeclaredRange("0.2.0-rc.2+build.7", ">=0.1.7-rc.2 <0.3.0-0"), true);
1285
+ // Everything else is refused rather than guessed at — a gate that accepts `v`
1286
+ // prefixes and zero-padded identifiers would also accept a typo.
1287
+ assert.throws(() => satisfiesDeclaredRange("v0.2.0-rc.2", ">=0.1.7-rc.2 <0.3.0-0"));
1288
+ assert.throws(() => satisfiesDeclaredRange("0.2.1-01", ">=0.1.7-rc.2 <0.3.0-0"));
1289
+ });
1290
+
1291
+ /* ------------------------------------------------------- the launch decision */
1292
+
1293
+ // 2.x supplies a runtime the machine may not have; when it HAS one, the row must
1294
+ // use it — the 1.x contract. These tests own that decision, so every one of them
1295
+ // isolates PATH: without it the suite would silently depend on whatever `rea`
1296
+ // the machine running it happens to have, which is the one thing a test of a
1297
+ // machine-dependent decision must not do.
1298
+
1299
+ function writeFakeRea(dir, { version = "6.9.0", exitForVersion = 0, runExit = 0, record = null } = {}) {
1300
+ const file = path.join(dir, process.platform === "win32" ? "rea.cmd" : "rea");
1301
+ if (process.platform === "win32") {
1302
+ const lines = ["@echo off", 'if "%1"=="--version" (', ` echo ${version}`, ` exit /b ${exitForVersion}`, ")"];
1303
+ if (record !== null) lines.push(`echo %* > "${record}"`);
1304
+ lines.push(`exit /b ${runExit}`);
1305
+ writeFileSync(file, lines.join("\r\n") + "\r\n");
1306
+ } else {
1307
+ const lines = ["#!/bin/sh", `if [ "$1" = "--version" ]; then echo ${version}; exit ${exitForVersion}; fi`];
1308
+ if (record !== null) lines.push(`echo "$*" > "${record}"`);
1309
+ lines.push(`exit ${runExit}`);
1310
+ writeFileSync(file, lines.join("\n") + "\n");
1311
+ chmodSync(file, 0o755);
1312
+ }
1313
+ return file;
1314
+ }
1315
+
1316
+ function runShimIsolated(env, args) {
1317
+ const merged = { ...process.env, ...env };
1318
+ // Windows keeps PATH and Path as separate variables (both are "in" the
1319
+ // environment), and a child that reads Path while the test set PATH would see
1320
+ // the machine's real search path instead of the fixture's.
1321
+ if (process.platform === "win32" && Object.prototype.hasOwnProperty.call(env, "PATH")) delete merged.Path;
1322
+ return spawnSync(process.execPath, [path.join(REPO_ROOT, "bin", "rea-shim.mjs"), ...args], {
1323
+ encoding: "utf8",
1324
+ timeout: 60000,
1325
+ env: merged,
1326
+ });
1327
+ }
1328
+
1329
+ function launchFixture() {
1330
+ const root = mkdtempSync(path.join(tmpdir(), "rea-launch-"));
1331
+ const emptyPath = path.join(root, "empty-path");
1332
+ mkdirSync(emptyPath, { recursive: true });
1333
+ return {
1334
+ root,
1335
+ emptyPath,
1336
+ bundleRoot: path.join(root, "bundle"),
1337
+ bin: path.join(root, "bin"),
1338
+ record: path.join(root, "record.txt"),
1339
+ clean() {
1340
+ rmSync(root, { recursive: true, force: true });
1341
+ },
1342
+ };
1343
+ }
1344
+
1345
+ test("a machine with its own REA runs the 1.x way, and no bundle is required", () => {
1346
+ const f = launchFixture();
1347
+ mkdirSync(f.bin, { recursive: true });
1348
+ const rea = writeFakeRea(f.bin, { version: "6.9.0", record: f.record });
1349
+ try {
1350
+ const env = {
1351
+ PATH: f.emptyPath,
1352
+ REA_BUNDLE_SYSTEM_REA: rea,
1353
+ REA_BUNDLE_ROOT: f.bundleRoot,
1354
+ REA_BUNDLE_AUTO_PROVISION: "0",
1355
+ REA_BUNDLE_LAUNCH: "",
1356
+ };
1357
+ const status = runShimIsolated(env, ["--shim-status"]);
1358
+ const report = JSON.parse(status.stdout);
1359
+ assert.equal(status.status, 0, status.stderr);
1360
+ assert.equal(report.launch.mode, "system", JSON.stringify(report.launch));
1361
+ assert.equal(report.launch.systemRea.version, "6.9.0");
1362
+ // Unprovisioned and still healthy: the row does not depend on the bundle.
1363
+ assert.equal(report.provisioned, false);
1364
+ assert.equal(existsSync(path.join(f.bundleRoot, "bundle.json")), false, "no provisioning may be started");
1365
+
1366
+ const run = runShimIsolated(env, ["mcp"]);
1367
+ assert.equal(run.status, 0, run.stderr);
1368
+ assert.equal(readFileSync(f.record, "utf8").trim(), "mcp", "the system REA must receive the row's arguments");
1369
+ } finally {
1370
+ f.clean();
1371
+ }
1372
+ });
1373
+
1374
+ test("REA_BUNDLE_LAUNCH=bundle keeps the bundle path even with a REA on PATH", () => {
1375
+ const f = launchFixture();
1376
+ mkdirSync(f.bin, { recursive: true });
1377
+ const rea = writeFakeRea(f.bin);
1378
+ try {
1379
+ const run = runShimIsolated(
1380
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle" },
1381
+ ["--shim-status"],
1382
+ );
1383
+ const report = JSON.parse(run.stdout);
1384
+ assert.equal(report.launch.mode, "bundle");
1385
+ assert.equal(report.launch.reason, "pinned by REA_BUNDLE_LAUNCH=bundle");
1386
+ assert.equal(run.status, 78, "an unpinned bundle that is not provisioned still reports EX_CONFIG");
1387
+ } finally {
1388
+ f.clean();
1389
+ }
1390
+ });
1391
+
1392
+ test("an older system REA does not displace a newer provisioned one unless pinned", () => {
1393
+ const f = launchFixture();
1394
+ mkdirSync(f.bin, { recursive: true });
1395
+ mkdirSync(f.bundleRoot, { recursive: true });
1396
+ const rea = writeFakeRea(f.bin, { version: "6.4.0" });
1397
+ try {
1398
+ writeFileSync(
1399
+ path.join(f.bundleRoot, "bundle.json"),
1400
+ JSON.stringify({ schema: 1, paths: { node: process.execPath }, versions: { reaAgents: "6.5.0" } }),
1401
+ );
1402
+ const base = { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0" };
1403
+ const auto = JSON.parse(runShimIsolated({ ...base, REA_BUNDLE_LAUNCH: "" }, ["--shim-status"]).stdout);
1404
+ assert.equal(auto.launch.mode, "bundle");
1405
+ assert.match(auto.launch.reason, /older than the provisioned one/);
1406
+ const pinned = JSON.parse(runShimIsolated({ ...base, REA_BUNDLE_LAUNCH: "system" }, ["--shim-status"]).stdout);
1407
+ assert.equal(pinned.launch.mode, "system");
1408
+ assert.match(pinned.launch.reason, /pinned by REA_BUNDLE_LAUNCH=system/);
1409
+ } finally {
1410
+ f.clean();
1411
+ }
1412
+ });
1413
+
1414
+ test("a candidate that cannot answer --version is rejected, not launched", () => {
1415
+ const f = launchFixture();
1416
+ mkdirSync(f.bin, { recursive: true });
1417
+ const rea = writeFakeRea(f.bin, { exitForVersion: 7 });
1418
+ try {
1419
+ const run = runShimIsolated(
1420
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" },
1421
+ ["--shim-status"],
1422
+ );
1423
+ const report = JSON.parse(run.stdout);
1424
+ assert.equal(report.launch.mode, "bundle");
1425
+ assert.equal(report.launch.systemRea, null);
1426
+ assert.deepEqual(report.launch.rejected.map(r => r.reason), ["exit 7"]);
1427
+ assert.equal(run.status, 78);
1428
+ } finally {
1429
+ f.clean();
1430
+ }
1431
+ });
1432
+
1433
+ test("REA_BUNDLE_LAUNCH=system refuses instead of provisioning", () => {
1434
+ const f = launchFixture();
1435
+ const missing = path.join(f.bin, "no-such-rea");
1436
+ try {
1437
+ const run = runShimIsolated(
1438
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: missing, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "1", REA_BUNDLE_LAUNCH: "system" },
1439
+ ["mcp"],
1440
+ );
1441
+ assert.equal(run.status, 78, run.stdout);
1442
+ assert.match(run.stderr, /REA_BUNDLE_LAUNCH=system was requested/);
1443
+ assert.equal(existsSync(f.bundleRoot), false, "a pinned system launch must not create a bundle");
1444
+ } finally {
1445
+ f.clean();
1446
+ }
1447
+ });
1448
+
1449
+ test("an unknown REA_BUNDLE_LAUNCH value is reported and treated as auto", () => {
1450
+ const f = launchFixture();
1451
+ mkdirSync(f.bin, { recursive: true });
1452
+ const rea = writeFakeRea(f.bin, { version: "6.9.0" });
1453
+ try {
1454
+ const run = runShimIsolated(
1455
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "nonsense" },
1456
+ ["--shim-status"],
1457
+ );
1458
+ assert.match(run.stderr, /REA_BUNDLE_LAUNCH="nonsense" is not one of/);
1459
+ assert.equal(JSON.parse(run.stdout).launch.mode, "system");
1460
+ } finally {
1461
+ f.clean();
1462
+ }
1463
+ });
1464
+
1465
+ test("the system REA is found through PATH itself, not only through the override", () => {
1466
+ const f = launchFixture();
1467
+ mkdirSync(f.bin, { recursive: true });
1468
+ writeFakeRea(f.bin, { version: "6.9.0" });
1469
+ try {
1470
+ const run = runShimIsolated(
1471
+ { PATH: f.bin, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" },
1472
+ ["--shim-status"],
1473
+ );
1474
+ const report = JSON.parse(run.stdout);
1475
+ assert.equal(report.launch.mode, "system", JSON.stringify(report.launch));
1476
+ assert.equal(report.launch.systemRea.version, "6.9.0");
1477
+ assert.equal(run.status, 0);
1478
+ } finally {
1479
+ f.clean();
1480
+ }
1481
+ });
1482
+
1483
+ test("a REA installed under a path with a space is probed and launched", () => {
1484
+ const f = launchFixture();
1485
+ const spaced = path.join(f.root, "rea space");
1486
+ mkdirSync(spaced, { recursive: true });
1487
+ const rea = writeFakeRea(spaced, { version: "6.9.0", record: f.record });
1488
+ try {
1489
+ const env = { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" };
1490
+ const status = JSON.parse(runShimIsolated(env, ["--shim-status"]).stdout);
1491
+ assert.equal(status.launch.mode, "system", JSON.stringify(status.launch));
1492
+ const run = runShimIsolated(env, ["mcp"]);
1493
+ assert.equal(run.status, 0, run.stderr);
1494
+ assert.equal(readFileSync(f.record, "utf8").trim(), "mcp");
1495
+ } finally {
1496
+ f.clean();
1497
+ }
1498
+ });
1499
+
1500
+ test("the delegated process's exit status is the shim's exit status", () => {
1501
+ const f = launchFixture();
1502
+ mkdirSync(f.bin, { recursive: true });
1503
+ const rea = writeFakeRea(f.bin, { version: "6.9.0", runExit: 9 });
1504
+ try {
1505
+ const run = runShimIsolated(
1506
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" },
1507
+ ["mcp"],
1508
+ );
1509
+ assert.equal(run.status, 9, `the row must see REA's own status, got ${run.status}`);
1510
+ } finally {
1511
+ f.clean();
1512
+ }
1513
+ });
1514
+
1515
+ test("a REA inside the bundle is not treated as a system install", () => {
1516
+ const f = launchFixture();
1517
+ mkdirSync(f.bundleRoot, { recursive: true });
1518
+ const inside = writeFakeRea(f.bundleRoot, { version: "6.9.0" });
1519
+ try {
1520
+ const run = runShimIsolated(
1521
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: inside, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" },
1522
+ ["--shim-status"],
1523
+ );
1524
+ const report = JSON.parse(run.stdout);
1525
+ assert.equal(report.launch.mode, "bundle");
1526
+ assert.equal(report.launch.systemRea, null);
1527
+ assert.deepEqual(report.launch.rejected.map(r => r.reason), ["inside the bundle"]);
1528
+ } finally {
1529
+ f.clean();
1530
+ }
1531
+ });
1532
+
1533
+ test("REA_BUNDLE_LAUNCH=bundle never probes the machine", () => {
1534
+ const f = launchFixture();
1535
+ mkdirSync(f.bin, { recursive: true });
1536
+ // Any invocation at all leaves the record behind — including --version, which
1537
+ // is exactly what `auto` would run.
1538
+ const rea = writeFakeRea(f.bin, { version: "6.9.0", record: f.record });
1539
+ try {
1540
+ runShimIsolated(
1541
+ { PATH: f.bin, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle" },
1542
+ ["--shim-status"],
1543
+ );
1544
+ assert.equal(existsSync(f.record), false, "the pinned bundle path must not pay for a probe");
1545
+ } finally {
1546
+ f.clean();
1547
+ }
1548
+ });
1549
+
1550
+ test("--shim-print-env reports the system launch instead of a child environment", () => {
1551
+ const f = launchFixture();
1552
+ mkdirSync(f.bin, { recursive: true });
1553
+ const rea = writeFakeRea(f.bin, { version: "6.9.0", record: f.record });
1554
+ try {
1555
+ const run = runShimIsolated(
1556
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: rea, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" },
1557
+ ["--shim-print-env"],
1558
+ );
1559
+ const printed = JSON.parse(run.stdout);
1560
+ assert.equal(printed.launch, "system");
1561
+ assert.equal(printed.version, "6.9.0");
1562
+ assert.equal(existsSync(f.record), false, "printing the environment must not start REA");
1563
+ } finally {
1564
+ f.clean();
1565
+ }
1566
+ });
1567
+
1568
+ test("a candidate that wraps this shim is rejected instead of recursing", () => {
1569
+ // The probe asks the candidate for `--version`; a wrapper answers by running
1570
+ // this very shim, which would probe the wrapper again. The probe therefore runs
1571
+ // with REA_BUNDLE_DELEGATED=1, so the nested run takes the bundle path and
1572
+ // returns. Without that, one wrapper on PATH spawns a process chain that keeps
1573
+ // respawning after the parent is killed.
1574
+ const f = launchFixture();
1575
+ mkdirSync(f.bin, { recursive: true });
1576
+ const shim = path.join(REPO_ROOT, "bin", "rea-shim.mjs");
1577
+ const wrapper = path.join(f.bin, process.platform === "win32" ? "rea.cmd" : "rea");
1578
+ if (process.platform === "win32") {
1579
+ writeFileSync(wrapper, `@echo off\r\n"${process.execPath}" "${shim}" %*\r\n`);
1580
+ } else {
1581
+ writeFileSync(wrapper, `#!/bin/sh\nexec "${process.execPath}" "${shim}" "$@"\n`);
1582
+ chmodSync(wrapper, 0o755);
1583
+ }
1584
+ try {
1585
+ const started = Date.now();
1586
+ const run = runShimIsolated(
1587
+ { PATH: f.emptyPath, REA_BUNDLE_SYSTEM_REA: wrapper, REA_BUNDLE_ROOT: f.bundleRoot, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "" },
1588
+ ["--shim-status"],
1589
+ );
1590
+ const elapsed = Date.now() - started;
1591
+ const report = JSON.parse(run.stdout);
1592
+ assert.equal(report.launch.mode, "bundle");
1593
+ assert.deepEqual(report.launch.rejected.map(r => r.reason), ["exit 78"]);
1594
+ assert.ok(elapsed < 20000, `the probe must not recurse; it took ${elapsed} ms`);
1595
+ } finally {
1596
+ f.clean();
1597
+ }
1598
+ });
1599
+
1600
+ test("a ComSpec that points at nothing falls back to the real shell", () => {
1601
+ // Trusting ComSpec blindly would judge every .cmd REA "spawn-failed" — i.e.
1602
+ // report the machine as having no REA because its shell pointer is broken.
1603
+ const f = launchFixture();
1604
+ mkdirSync(f.bin, { recursive: true });
1605
+ const rea = writeFakeRea(f.bin, { version: "6.9.0" });
1606
+ try {
1607
+ const run = runShimIsolated(
1608
+ {
1609
+ PATH: f.emptyPath,
1610
+ ComSpec: path.join(f.root, "no-such-cmd.exe"),
1611
+ REA_BUNDLE_SYSTEM_REA: rea,
1612
+ REA_BUNDLE_ROOT: f.bundleRoot,
1613
+ REA_BUNDLE_AUTO_PROVISION: "0",
1614
+ REA_BUNDLE_LAUNCH: "",
1615
+ },
1616
+ ["--shim-status"],
1617
+ );
1618
+ const report = JSON.parse(run.stdout);
1619
+ if (process.platform === "win32") {
1620
+ assert.equal(report.launch.mode, "system", JSON.stringify(report.launch));
1621
+ assert.equal(report.launch.systemRea.version, "6.9.0");
1622
+ } else {
1623
+ // POSIX never consults ComSpec; the assertion above would be incidental.
1624
+ assert.equal(report.launch.mode, "system");
1625
+ }
1626
+ } finally {
1627
+ f.clean();
1628
+ }
1629
+ });
1630
+
1631
+ /* --------------------------------------------------- the mirrored launcher */
1632
+
1633
+ // The row launches `<bundle>/bin/rea-shim.mjs`, a COPY. A market upgrade replaces
1634
+ // the package and runs no setup, so without this the copy keeps running the code
1635
+ // it was made from — every fix silently stops at the old mirror. These tests pin
1636
+ // the repair: the copy re-execs the plugin's launcher when they differ, once.
1637
+
1638
+ function mirrorFixture() {
1639
+ const root = mkdtempSync(path.join(tmpdir(), "rea-mirror-"));
1640
+ const bundle = path.join(root, "bundle");
1641
+ const checkout = path.join(root, "checkout");
1642
+ mkdirSync(path.join(bundle, "bin"), { recursive: true });
1643
+ mkdirSync(path.join(checkout, "bin"), { recursive: true });
1644
+ copyFileSync(path.join(REPO_ROOT, "bin", "rea-shim.mjs"), path.join(bundle, "bin", "rea-shim.mjs"));
1645
+ writeFileSync(path.join(bundle, "mirror.json"), JSON.stringify({ schema: 1, pluginRoot: checkout, mirroredAt: "2026-10-18T00:00:00Z" }));
1646
+ return { root, bundle, checkout, clean() { rmSync(root, { recursive: true, force: true }); } };
1647
+ }
1648
+
1649
+ function checkoutStub(checkout, marker) {
1650
+ const file = path.join(checkout, "bin", "rea-shim.mjs");
1651
+ writeFileSync(
1652
+ file,
1653
+ 'import { writeFileSync } from "node:fs";\n' +
1654
+ `writeFileSync(${JSON.stringify(marker)}, "checkout");\n` +
1655
+ "process.exit(7);\n",
1656
+ );
1657
+ return file;
1658
+ }
1659
+
1660
+ test("a mirrored launcher re-execs the plugin's launcher when they differ", () => {
1661
+ const f = mirrorFixture();
1662
+ const marker = path.join(f.root, "checkout-ran");
1663
+ checkoutStub(f.checkout, marker);
1664
+ try {
1665
+ const run = runShimIsolated({ REA_BUNDLE_ROOT: f.bundle, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle" }, ["mcp"]);
1666
+ assert.equal(run.status, 7, `expected the checkout's status, got ${run.status}: ${run.stderr}`);
1667
+ assert.equal(existsSync(marker), true, "the checkout launcher must have run");
1668
+ } finally {
1669
+ f.clean();
1670
+ }
1671
+ });
1672
+
1673
+ test("the re-exec happens once, and a pinned guard stops it", () => {
1674
+ const f = mirrorFixture();
1675
+ const marker = path.join(f.root, "checkout-ran");
1676
+ checkoutStub(f.checkout, marker);
1677
+ try {
1678
+ const run = runShimIsolated(
1679
+ { REA_BUNDLE_ROOT: f.bundle, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle", REA_BUNDLE_SHIM_REEXEC: "1" },
1680
+ ["mcp"],
1681
+ );
1682
+ // The guard is set, so the mirrored copy runs its own logic: bundle pinned and
1683
+ // nothing provisioned means EX_CONFIG, and the checkout never starts.
1684
+ assert.equal(run.status, 78, run.stderr);
1685
+ assert.equal(existsSync(marker), false, "the guard must prevent a second hop");
1686
+ } finally {
1687
+ f.clean();
1688
+ }
1689
+ });
1690
+
1691
+ test("--shim-status reports a stale mirror instead of re-execing it", () => {
1692
+ const f = mirrorFixture();
1693
+ const marker = path.join(f.root, "checkout-ran");
1694
+ checkoutStub(f.checkout, marker);
1695
+ try {
1696
+ const run = runShimIsolated({ REA_BUNDLE_ROOT: f.bundle, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle" }, ["--shim-status"]);
1697
+ const report = JSON.parse(run.stdout);
1698
+ assert.equal(report.mirror.stale, true, JSON.stringify(report.mirror));
1699
+ assert.match(report.mirror.reason, /different launcher/);
1700
+ assert.equal(existsSync(marker), false, "status is a query; it must not start anything");
1701
+ } finally {
1702
+ f.clean();
1703
+ }
1704
+ });
1705
+
1706
+ test("an identical mirror is not stale and is not re-exec'd", () => {
1707
+ const f = mirrorFixture();
1708
+ const same = path.join(f.checkout, "bin", "rea-shim.mjs");
1709
+ copyFileSync(path.join(f.bundle, "bin", "rea-shim.mjs"), same);
1710
+ try {
1711
+ const run = runShimIsolated({ REA_BUNDLE_ROOT: f.bundle, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle" }, ["--shim-status"]);
1712
+ const report = JSON.parse(run.stdout);
1713
+ assert.equal(report.mirror.stale, false, JSON.stringify(report.mirror));
1714
+ assert.equal(report.mirror.reason, "this copy matches the plugin");
1715
+ } finally {
1716
+ f.clean();
1717
+ }
1718
+ });
1719
+
1720
+ test("a mirrored shim finds the real DSH home when the environment is scrubbed", () => {
1721
+ // The harness strips DSH_* from an MCP child, so with a custom DSH_HOME the
1722
+ // old code read ~/.dsh — the wrong file — and could never report upstream's
1723
+ // duplicate `serverName: rea`. setup records the home; the shim must use it.
1724
+ const f = mirrorFixture();
1725
+ const home = path.join(f.root, "custom-home");
1726
+ mkdirSync(home, { recursive: true });
1727
+ writeFileSync(
1728
+ path.join(home, "cordis.patch.yml"),
1729
+ "- insert:\n - id: rea-mcp\n name: '@deepseek-ai/dsh-mcp-client'\n config:\n serverName: rea\n",
1730
+ );
1731
+ writeFileSync(
1732
+ path.join(f.bundle, "mirror.json"),
1733
+ JSON.stringify({ schema: 1, pluginRoot: f.checkout, mirroredAt: "2026-10-18T00:00:00Z", dshHome: home }),
1734
+ );
1735
+ try {
1736
+ const run = runShimIsolated(
1737
+ // DSH_HOME="" models the scrubbed environment: the variable exists, empty.
1738
+ { REA_BUNDLE_ROOT: f.bundle, REA_BUNDLE_AUTO_PROVISION: "0", REA_BUNDLE_LAUNCH: "bundle", DSH_HOME: "" },
1739
+ ["--shim-status"],
1740
+ );
1741
+ const report = JSON.parse(run.stdout);
1742
+ assert.equal(report.duplicateRegistration?.id, "rea-mcp", run.stdout);
1743
+ assert.equal(report.duplicateRegistration.patch, path.join(home, "cordis.patch.yml"));
1744
+ } finally {
1745
+ f.clean();
1746
+ }
1246
1747
  });
@@ -90,6 +90,14 @@ Check 'main resolves to a shipped file' ($pkg.main -and (Test-Path (Join-Path $r
90
90
  Check 'package.json declares engines.dsh' (-not [string]::IsNullOrWhiteSpace($pkg.engines.dsh)) ''
91
91
  $compat = & node scripts\host-compat.mjs 2>&1
92
92
  Check 'engines.dsh covers every released DSH host' ($LASTEXITCODE -eq 0) (($compat | Select-Object -Last 2) -join ' ')
93
+ # Both READMEs quote the range verbatim, and a stale quote is invisible: JSON,
94
+ # host-compat and the audit all agree while the sentence a human reads names a
95
+ # different ceiling. That is exactly how `<0.3.0` survived in the docs after the
96
+ # declaration moved to `<0.3.0-0`, so the comparison lives here now.
97
+ foreach ($doc in @('README.md', 'README.zh-CN.md')) {
98
+ $text = Get-Content (Join-Path $root $doc) -Raw -Encoding UTF8
99
+ Check "$doc quotes the declared engines.dsh" $text.Contains([string]$pkg.engines.dsh) "expected $($pkg.engines.dsh)"
100
+ }
93
101
  foreach ($script in @('bundle:setup', 'bundle:status', 'bundle:audit', 'bundle:test', 'bundle:host-compat')) {
94
102
  Check "scripts.$script exists" ($pkg.scripts.PSObject.Properties.Name -contains $script) ''
95
103
  }