deadbyte-mcp 0.15.34 → 0.15.35

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 (68) hide show
  1. package/MANIFEST.SHA256 +67 -22
  2. package/PROVENANCE.json +57 -30
  3. package/README.txt +18 -14
  4. package/RELEASE.json +40 -31
  5. package/SBOM.cdx.json +25 -3
  6. package/TEST-MATRIX.json +129 -48
  7. package/bridge/package.json +11 -0
  8. package/bridge/src/bridge.mjs +412 -0
  9. package/bridge/src/node-http-adapter.mjs +56 -0
  10. package/bridge/src/public-tool-definitions.mjs +14 -0
  11. package/bridge/src/r40-governor.mjs +160 -0
  12. package/controller/deadbyte-controller.ps1 +84 -8
  13. package/controller/deadbyte-process-policy.json +4 -4
  14. package/package.json +5 -3
  15. package/scripts/bridge-benchmark-r58.mjs +67 -0
  16. package/scripts/deploy-r48.mjs +2 -3
  17. package/scripts/gate-windows-r58.ps1 +33 -0
  18. package/scripts/owner-probe-r58.mjs +31 -0
  19. package/scripts/performance-r58.mjs +57 -0
  20. package/scripts/public-baseline-r58.mjs +72 -0
  21. package/scripts/public-surface-r58.mjs +84 -0
  22. package/scripts/r58-cross-plane.ps1 +6 -0
  23. package/scripts/r58-focused.ps1 +6 -0
  24. package/scripts/windows-gate-evidence-r58.mjs +93 -0
  25. package/src/deadbyte-cli.mjs +40 -12
  26. package/src/doctor-foreground-r58.mjs +44 -0
  27. package/src/doctor-installation-r58.mjs +63 -0
  28. package/src/doctor-r45.mjs +21 -4
  29. package/src/doctor-runtime-r58.mjs +82 -0
  30. package/src/doctor-transactions-r58.mjs +40 -0
  31. package/src/foreground-process-identity.mjs +27 -0
  32. package/src/foreground-session-lock.mjs +20 -0
  33. package/src/foreground-session-lock.ps1 +15 -0
  34. package/src/release-generation.mjs +1 -0
  35. package/src/release-slot.mjs +2 -1
  36. package/src/remote-session-guardian.mjs +8 -1
  37. package/src/runtime-update.mjs +85 -2
  38. package/src/version.mjs +1 -1
  39. package/test/controller-r211.test.mjs +4 -1
  40. package/test/controller.test.mjs +2 -1
  41. package/test/helpers/r58-doctor-fixture.mjs +26 -0
  42. package/test/r49-windows-gate.test.mjs +2 -2
  43. package/test/r57-release-contract.test.mjs +5 -3
  44. package/test/r58-bridge-dependency-binding.test.mjs +62 -0
  45. package/test/r58-bundled-bridge.test.mjs +22 -0
  46. package/test/r58-cleanup-retention.test.mjs +45 -0
  47. package/test/r58-completed-update-receipt.test.mjs +76 -0
  48. package/test/r58-concurrent-recovery.test.mjs +54 -0
  49. package/test/r58-controller-core-observation.test.mjs +36 -0
  50. package/test/r58-controller-json.test.mjs +48 -0
  51. package/test/r58-controller-live-local.test.mjs +85 -0
  52. package/test/r58-controller-readonly.test.mjs +23 -0
  53. package/test/r58-controller-state-coherence.test.mjs +45 -0
  54. package/test/r58-doctor-cli.test.mjs +20 -0
  55. package/test/r58-doctor-foreground.test.mjs +50 -0
  56. package/test/r58-doctor-installation.test.mjs +55 -0
  57. package/test/r58-doctor-pointer.test.mjs +41 -0
  58. package/test/r58-doctor-runtime.test.mjs +45 -0
  59. package/test/r58-doctor-transactions.test.mjs +21 -0
  60. package/test/r58-guardian-channel.test.mjs +26 -0
  61. package/test/r58-launcher-shell.test.mjs +28 -0
  62. package/test/r58-owner-identity.test.mjs +20 -0
  63. package/test/r58-release-contract.test.mjs +49 -0
  64. package/test/r58-session-lock.test.mjs +26 -0
  65. package/test/r58-stale-owner.test.mjs +71 -0
  66. package/test/r58-windows-gate.test.mjs +31 -0
  67. package/test/release-generation.test.mjs +4 -1
  68. package/test/remote-session-cli.test.mjs +55 -2
@@ -8,6 +8,10 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
8
  import { resolveStateRoot } from './state-root.mjs';
9
9
 
10
10
  const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
11
+ // This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
12
+ // poll. A reused PID cannot keep a new foreground guardian alive.
13
+ let ownerDisconnected=false;
14
+ process.once('disconnect',()=>{ownerDisconnected=true;});
11
15
 
12
16
  function parseArgs(argv){
13
17
  const out={};
@@ -51,7 +55,10 @@ async function main(){
51
55
  const root=resolveStateRoot(args['state-root']);
52
56
  const ownerPid=Number(args['owner-pid']);
53
57
  const nonce=String(args['session-nonce']||'');
58
+ const ipcOwner=args['owner-channel']==='ipc';
59
+ if(ipcOwner&&typeof process.send!=='function')throw new Error('guardian owner IPC channel missing');
54
60
  if(!Number.isInteger(ownerPid)||ownerPid<1) throw new Error('guardian owner pid invalid');
61
+ if(ipcOwner&&process.ppid!==ownerPid)throw new Error('guardian IPC parent does not match declared owner');
55
62
  if(!/^[0-9a-f]{64}$/.test(nonce)) throw new Error('guardian session nonce invalid');
56
63
 
57
64
  const launcher=await requireRegular(path.join(root,'bin','DEADBYTE-MCP.ps1'),'stable DEADBYTE launcher');
@@ -63,7 +70,7 @@ async function main(){
63
70
  for(;;){
64
71
  const marker=await readMarker(markerPath);
65
72
  if(!marker||!markerMatches(marker,{ownerPid,nonce})) return;
66
- if(pidAlive(ownerPid)){
73
+ if(ipcOwner?!ownerDisconnected:pidAlive(ownerPid)){
67
74
  await sleep(250);
68
75
  continue;
69
76
  }
@@ -1,7 +1,7 @@
1
1
  import { createHash, createPrivateKey, createPublicKey, randomBytes, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
2
2
  import { spawn, spawnSync } from 'node:child_process';
3
3
  import { existsSync } from 'node:fs';
4
- import { lstat, mkdir, open, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, writeFile } from 'node:fs/promises';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
@@ -306,6 +306,69 @@ async function normalizeComponentIdentities(config){
306
306
  return next;
307
307
  }
308
308
 
309
+ function bridgeDependencyName(relativePath,usedNames){
310
+ const normalized=String(relativePath).replaceAll('\\','/').replaceAll('/','_');
311
+ let name=normalized;
312
+ if(!/^[A-Za-z0-9._-]{1,32}$/.test(name)||usedNames.has(name)){
313
+ name=`dep-${sha256Bytes(Buffer.from(String(relativePath),'utf8')).slice(0,20)}`;
314
+ }
315
+ if(usedNames.has(name)) throw new Error(`bridge dependency name collision: ${relativePath}`);
316
+ usedNames.add(name);
317
+ return name;
318
+ }
319
+ function bridgeStaticRelativeImports(source,file){
320
+ const found=[];
321
+ for(const [index,line] of String(source).split(/\r?\n/).entries()){
322
+ if(/\bimport\s*\(\s*['"]\.\.?\//.test(line)) throw new Error(`bridge dynamic relative import is forbidden: ${file}:${index+1}`);
323
+ const importStart=/^\s*import\b/.test(line);
324
+ const exportStart=/^\s*export\b/.test(line)&&/\bfrom\b/.test(line);
325
+ if(!importStart&&!exportStart) continue;
326
+ const match=importStart
327
+ ? /^\s*import\s+(?:(?:[^'"]+?)\s+from\s*)?['"]([^'"]+)['"]\s*;?\s*$/.exec(line)
328
+ : /^\s*export\s+[^'"]*?\s+from\s*['"]([^'"]+)['"]\s*;?\s*$/.exec(line);
329
+ if(!match) throw new Error(`bridge import syntax unsupported for dependency binding: ${file}:${index+1}`);
330
+ if(match[1].startsWith('.')) found.push(match[1]);
331
+ }
332
+ return found;
333
+ }
334
+ async function discoverBridgeLocalDependencies(root,entryScript){
335
+ const absoluteRoot=path.resolve(root),realRoot=await realpath(absoluteRoot);
336
+ const srcRoot=path.join(absoluteRoot,'src');
337
+ const queue=[path.resolve(entryScript)],visited=new Set(),files=[];
338
+ while(queue.length){
339
+ const current=queue.shift();
340
+ const currentReal=await realpath(current);
341
+ const relativeReal=path.relative(realRoot,currentReal);
342
+ if(relativeReal.startsWith('..')||path.isAbsolute(relativeReal)) throw new Error(`bridge dependency escapes bridge root: ${current}`);
343
+ if(visited.has(currentReal)) continue;
344
+ visited.add(currentReal);
345
+ if(visited.size>16) throw new Error('bridge local dependency closure exceeds 16 files');
346
+ const stat=await lstat(current);
347
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error(`bridge dependency must be a regular file: ${current}`);
348
+ const source=await readFile(current,'utf8');
349
+ const specifiers=bridgeStaticRelativeImports(source,current);
350
+ if(currentReal!==await realpath(entryScript)) files.push(currentReal);
351
+ for(const specifier of specifiers){
352
+ if(!specifier.startsWith('./')) throw new Error(`bridge relative import escapes bridge root: ${specifier}`);
353
+ if(specifier.includes('?')||specifier.includes('#')) throw new Error(`bridge relative import query/fragment unsupported: ${specifier}`);
354
+ const resolved=path.resolve(path.dirname(current),specifier);
355
+ const relative=path.relative(absoluteRoot,resolved);
356
+ if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency escapes bridge root: ${specifier}`);
357
+ if(!/\.(?:mjs|js)$/i.test(resolved)) throw new Error(`bridge local dependency must be .mjs/.js: ${specifier}`);
358
+ queue.push(resolved);
359
+ }
360
+ }
361
+ const unique=[...new Set(files)].sort((a,b)=>a.localeCompare(b));
362
+ const usedNames=new Set();
363
+ const dependencies=[];
364
+ for(const file of unique){
365
+ const relative=path.relative(srcRoot,file).replaceAll('\\','/');
366
+ if(relative.startsWith('..')||path.isAbsolute(relative)) throw new Error(`bridge dependency must remain under src: ${file}`);
367
+ dependencies.push({Name:bridgeDependencyName(relative,usedNames),Path:file,Sha256:await sha256File(file)});
368
+ }
369
+ return dependencies;
370
+ }
371
+
309
372
  export async function prepareBridgeSpec(config,bootstrap){
310
373
  if(!bootstrap) return structuredClone(config);
311
374
  if(typeof bootstrap.root!=='string'||!path.isAbsolute(bootstrap.root)||!HEX64.test(String(bootstrap.scriptSha256??''))) {
@@ -327,6 +390,7 @@ export async function prepareBridgeSpec(config,bootstrap){
327
390
  next.Bridge.Arguments='src\\bridge.mjs';
328
391
  next.Bridge.ScriptPath=script;
329
392
  next.Bridge.ScriptSha256=actual;
393
+ next.Bridge.Dependencies=await discoverBridgeLocalDependencies(root,script);
330
394
  return next;
331
395
  }
332
396
 
@@ -1334,6 +1398,23 @@ function handoffPriorPointer(body){
1334
1398
  function handoffNewPointer(body){
1335
1399
  return body.new_pointer?parseActivePointer(JSON.stringify(body.new_pointer)):null;
1336
1400
  }
1401
+ async function verifyCompletedUpdateReceipt(body,state,statePath,key){
1402
+ const candidate=handoffNewPointer(body),prior=handoffPriorPointer(body);
1403
+ if(!candidate||!samePublicPointer(candidate,statePointer(state,'new_pointer'))||!samePublicPointer(prior,statePointer(state,'prior_pointer'))) throw new Error('completed update handoff pointer binding mismatch');
1404
+ if(state.expected_version!==candidate.version||state.expected_manifest_sha256!==candidate.manifest_sha256) throw new Error('completed update journal subject mismatch');
1405
+ const expectedPath=path.join(path.dirname(statePath),'receipt.json');
1406
+ if(typeof state.receipt_path!=='string'||!path.isAbsolute(state.receipt_path)||path.resolve(state.receipt_path)!==expectedPath||!HEX64.test(String(state.receipt_sha256??''))) throw new Error('completed update receipt reference invalid');
1407
+ const info=await lstat(expectedPath);
1408
+ if(!info.isFile()||info.isSymbolicLink()) throw new Error('completed update receipt must be a regular file');
1409
+ const receipt=await readJson(expectedPath);
1410
+ if(!verifySlotUpdateEnvelope(receipt,key.publicKey)||receipt.receipt_sha256!==state.receipt_sha256) throw new Error('completed update receipt signature or digest mismatch');
1411
+ const subject=receipt.body;
1412
+ if(subject.schema!==UPDATE_SCHEMA||subject.rollback_performed!==false||subject.update_id!==body.update_id||subject.release_version!==candidate.version||subject.candidate_manifest_sha256!==candidate.manifest_sha256||
1413
+ !samePublicPointer(statePointer(subject,'active_pointer'),candidate)||!samePublicPointer(statePointer(subject,'prior_pointer'),prior)||
1414
+ subject.release_slot?.release_id!==candidate.release_id||subject.release_slot?.release_root!==candidate.release_root||subject.release_slot?.manifest_sha256!==candidate.manifest_sha256||
1415
+ subject.generation?.generation_root!==candidate.generation_root||subject.generation?.generation_manifest_sha256!==candidate.generation_manifest_sha256||subject.launcher?.sha256!==candidate.launcher_sha256) throw new Error('completed update receipt subject binding mismatch');
1416
+ return {receipt_path:expectedPath,receipt_sha256:receipt.receipt_sha256};
1417
+ }
1337
1418
  async function resumeSlotUpdate({handoffPath}){
1338
1419
  const absoluteHandoff=path.resolve(handoffPath),probe=await readJson(absoluteHandoff);
1339
1420
  const stateRoot=path.resolve(String(probe?.body?.state_root??path.dirname(path.dirname(path.dirname(path.dirname(absoluteHandoff))))));
@@ -1342,8 +1423,9 @@ async function resumeSlotUpdate({handoffPath}){
1342
1423
  if(releaseState.schema!=='deadbyte.r27-slot-update-state.v1'||releaseState.update_id!==body.update_id) throw new Error('handoff state binding mismatch');
1343
1424
  let phase=String(releaseState.phase??'');
1344
1425
  if(phase==='completed'){
1426
+ const verifiedReceipt=await verifyCompletedUpdateReceipt(body,releaseState,statePath,key);
1345
1427
  return {ok:true,resumed:false,completed:true,update_id:body.update_id,state_path:statePath,
1346
- receipt_path:releaseState.receipt_path??null,receipt_sha256:releaseState.receipt_sha256??null};
1428
+ ...verifiedReceipt,historical_receipt_verified:true,current_runtime_verified:false};
1347
1429
  }
1348
1430
  if(phase.startsWith('failed')) throw new Error('slot update already failed at phase='+phase);
1349
1431
  const priorPointer=handoffPriorPointer(body),candidatePointer=handoffNewPointer(body);
@@ -1735,6 +1817,7 @@ export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEF
1735
1817
  console.log('UPDATE_ID '+result.update_id);
1736
1818
  console.log('STATE_PATH '+result.state_path);
1737
1819
  if(result.receipt_sha256) console.log('RECEIPT_SHA256 '+result.receipt_sha256);
1820
+ if(result.historical_receipt_verified===true) console.log('VERIFICATION_SCOPE historical-signed-receipt; current-runtime-not-observed');
1738
1821
  return result;
1739
1822
  }
1740
1823
  if(argv.includes('--recover-interrupted')){
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.34';
1
+ export const DEADBYTE_VERSION = '0.15.35';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -17,5 +17,8 @@ test('R21.1 binds controller execution to CoreRoot controller directory', async
17
17
 
18
18
  test('R21.1 validates controller root before creating command runtime directories', async () => {
19
19
  const source = await readFile(controller, 'utf8');
20
- assert.match(source, /function Run-Command[\s\S]*?\$cfg = Get-Config\s+Assert-ControllerRootBound \$cfg\s+Ensure-Directory \$RuntimeDir/);
20
+ const dispatch = source.slice(source.indexOf('function Run-CommandCore('), source.indexOf('function Run-Command(', source.indexOf('function Run-CommandCore(')));
21
+ assert.match(dispatch, /\$cfg = Get-Config\s+Assert-ControllerRootBound \$cfg\s+\$readOnly = \$Name -in/);
22
+ assert.match(dispatch, /if \(-not \$readOnly\) \{ Ensure-Directory \$RuntimeDir; Ensure-Directory \$LogDir \}/);
23
+ assert.ok(dispatch.indexOf('Assert-ControllerRootBound $cfg') < dispatch.indexOf('Ensure-Directory $RuntimeDir'));
21
24
  });
@@ -12,7 +12,8 @@ test('controller derives runtime state from observed identities and readiness',
12
12
  assert.match(source, /'OFFLINE'/);
13
13
  assert.match(source, /'PARKED'/);
14
14
  assert.match(source, /'ONLINE'/);
15
- assert.match(source, /'DEGRADED'/);
15
+ assert.match(source, /'STARTING'/);
16
+ assert.match(source, /'FAILED'/);
16
17
  assert.doesNotMatch(source, /state\.txt/i);
17
18
  });
18
19
 
@@ -0,0 +1,26 @@
1
+ import {cp,mkdir,mkdtemp,readFile,writeFile,readdir,symlink} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {createHash} from 'node:crypto';
5
+
6
+ const source=fileURLToPath(new URL('../..',import.meta.url));
7
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
8
+ export async function makeDoctorCliFixture(){
9
+ const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
10
+ const root=await mkdtemp(path.join(base,'doctor-cli-')),pkg=path.join(root,'package'),state=path.join(root,'state'),temp=path.join(root,'temp');
11
+ await mkdir(pkg);await mkdir(state);await mkdir(temp);await mkdir(path.join(pkg,'bin'));
12
+ // Test-only package copy: current source bytes plus required doctor metadata.
13
+ // This is NOT an npm pack, immutable slot, or release acceptance artifact.
14
+ await cp(path.join(source,'src'),path.join(pkg,'src'),{recursive:true});
15
+ await cp(path.join(source,'bridge'),path.join(pkg,'bridge'),{recursive:true});
16
+ for(const file of ['package.json','RELEASE.json','TEST-MATRIX.json','PROVENANCE.json','SBOM.cdx.json','bin/deadbyte-hardware-observer-r45.exe'])await cp(path.join(source,file),path.join(pkg,file));
17
+ const rows=[];
18
+ const walk=async dir=>{for(const entry of await readdir(dir,{withFileTypes:true})){const file=path.join(dir,entry.name);if(entry.isDirectory())await walk(file);else if(entry.isFile())rows.push(sha(await readFile(file))+' '+path.relative(pkg,file).replaceAll('\\','/'));else throw new Error('unexpected fixture source link');}};
19
+ await walk(pkg);rows.sort();await writeFile(path.join(pkg,'MANIFEST.SHA256'),rows.join('\n')+'\n');
20
+ // Dependency junction belongs exclusively to this fresh fixture and points to
21
+ // candidate dependencies, never production state or a release slot.
22
+ await symlink(path.join(source,'node_modules'),path.join(pkg,'node_modules'),'junction');
23
+ const env={...process.env};for(const key of Object.keys(env))if(key.startsWith('DEADBYTE_'))delete env[key];
24
+ Object.assign(env,{DEADBYTE_STATE_ROOT:state,TEMP:temp,TMP:temp});
25
+ return {root,pkg,state,env,entry:path.join(pkg,'src','deadbyte-cli.mjs'),manifestFiles:rows.length};
26
+ }
@@ -4,7 +4,7 @@ import test from 'node:test';
4
4
 
5
5
  const read=path=>readFile(new URL(`../${path}`,import.meta.url),'utf8');
6
6
 
7
- test('R57 canonical Windows gate is successor-bound and preserves R56/R55/R54/R53/R52/R51/R50/R49 historical gates',async()=>{
7
+ test('R58 canonical Windows gate is successor-bound while the R57/R56/R55/R54/R53/R52/R51/R50/R49 historical gates stay frozen',async()=>{
8
8
  const [pkgText,gate,evidence,historicalR56,historicalEvidenceR56,historicalR55,historicalEvidenceR55,historicalR54,historicalEvidenceR54,historicalR53,historicalEvidenceR53,historicalR52,historicalEvidenceR52,historicalR51,historicalEvidenceR51,historicalR50,historicalR49,deferred]=await Promise.all([
9
9
  read('package.json'),
10
10
  read('scripts/gate-windows-r57.ps1'),
@@ -26,7 +26,7 @@ test('R57 canonical Windows gate is successor-bound and preserves R56/R55/R54/R5
26
26
  read('scripts/deferred-slot-operation-r46.mjs')
27
27
  ]);
28
28
  const pkg=JSON.parse(pkgText);
29
- assert.match(pkg.scripts['gate:windows'],/gate-windows-r57\.ps1$/);
29
+ assert.match(pkg.scripts['gate:windows'],/gate-windows-r58\.ps1$/);
30
30
  assert.match(gate,/DEADBYTE 0\.15\.34 \/ R57 WINDOWS GATE: PASS/);
31
31
  assert.match(gate,/r57-package-boundary/);
32
32
  assert.match(gate,/r57-focused-contracts/);
@@ -5,8 +5,10 @@ import { readFile } from 'node:fs/promises';
5
5
  const root=new URL('../',import.meta.url);
6
6
  const read=rel=>readFile(new URL(rel,root),'utf8');
7
7
  const json=rel=>read(rel).then(JSON.parse);
8
+ const currentPackage=await json('package.json');
9
+ const r57test=currentPackage.version==='0.15.34'?test:test.skip;
8
10
 
9
- test('R57 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
11
+ r57test('R57 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
10
12
  const [pkg,lock,release,provenance,matrix,sbom,versionSource]=await Promise.all([
11
13
  json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs')
12
14
  ]);
@@ -25,7 +27,7 @@ test('R57 release identity is coherent across package lock source metadata SBOM
25
27
  assert.match(pkg.scripts['r57:focused:test'],/r57-core-startup-race\.test\.mjs/);
26
28
  });
27
29
 
28
- test('R57 source and runtime predecessor are exact active published 0.15.33',async()=>{
30
+ r57test('R57 source and runtime predecessor are exact active published 0.15.33',async()=>{
29
31
  const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
30
32
  assert.equal(release.parent.version,'0.15.33');
31
33
  assert.equal(release.parent.release_id,'v0.15.33-776a51cb916e28dd');
@@ -44,7 +46,7 @@ test('R57 source and runtime predecessor are exact active published 0.15.33',asy
44
46
  assert.deepEqual(provenance.runtime_predecessor,release.runtime_predecessor);
45
47
  });
46
48
 
47
- test('R57 records bounded zero-child retry RED to GREEN and preserves fail-closed controls',async()=>{
49
+ r57test('R57 records bounded zero-child retry RED to GREEN and preserves fail-closed controls',async()=>{
48
50
  const [release,provenance,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
49
51
  assert.equal(release.hotfix.bounded_core_discovery_after_online,true);
50
52
  assert.equal(release.hotfix.retry_only_missing_core,true);
@@ -0,0 +1,62 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,writeFile,readFile} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {createHash} from 'node:crypto';
7
+
8
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
9
+ const target=process.env.R58_RUNTIME_UPDATE_PATH
10
+ ? path.resolve(process.env.R58_RUNTIME_UPDATE_PATH)
11
+ : path.resolve(new URL('../src/runtime-update.mjs',import.meta.url).pathname.replace(/^\/(?:[A-Za-z]:)/,m=>m.slice(1)));
12
+ const {prepareBridgeSpec}=await import(new URL('file:///'+target.replaceAll('\\','/')).href+'?r58-bridge-deps='+Date.now());
13
+
14
+ async function fixture(){
15
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r58-bridge-deps-'));
16
+ await mkdir(path.join(root,'src'),{recursive:true});
17
+ await writeFile(path.join(root,'package.json'),JSON.stringify({name:'deadbyte-chatgpt-bridge',version:'0.58.0'})+'\n');
18
+ await writeFile(path.join(root,'src','bridge.mjs'),
19
+ "import './a.mjs';\nimport {b} from './b.mjs';\nexport const ready=b;\n");
20
+ await writeFile(path.join(root,'src','a.mjs'),
21
+ "import './nested/c.mjs';\nexport const a=1;\n");
22
+ await mkdir(path.join(root,'src','nested'));
23
+ await writeFile(path.join(root,'src','nested','c.mjs'),'export const c=3;\n');
24
+ await writeFile(path.join(root,'src','b.mjs'),'export const b=2;\n');
25
+ const script=path.join(root,'src','bridge.mjs');
26
+ return {root,script,scriptSha:sha(await readFile(script))};
27
+ }
28
+ function baseConfig(){
29
+ return {Bridge:{FilePath:process.execPath,FileSha256:'0'.repeat(64),Arguments:'src\\old.mjs',WorkingDirectory:path.parse(process.execPath).root,ScriptPath:'x',ScriptSha256:'0'.repeat(64)},Cloud:{}};
30
+ }
31
+
32
+ test('R58 bridge bootstrap binds the complete recursive local static import closure',async()=>{
33
+ const f=await fixture();
34
+ const out=await prepareBridgeSpec(baseConfig(),{root:f.root,scriptSha256:f.scriptSha});
35
+ const deps=out.Bridge.Dependencies;
36
+ assert.ok(Array.isArray(deps),'Bridge.Dependencies must be materialized by bootstrap');
37
+ assert.deepEqual(deps.map(x=>x.Name),['a.mjs','b.mjs','nested_c.mjs']);
38
+ for(const dep of deps){
39
+ assert.match(dep.Sha256,/^[0-9a-f]{64}$/);
40
+ assert.equal(dep.Sha256,sha(await readFile(dep.Path)));
41
+ }
42
+ });
43
+
44
+ test('R58 bridge dependency discovery rejects relative dynamic imports',async()=>{
45
+ const f=await fixture();
46
+ await writeFile(f.script,"const x=await import('./a.mjs');\nexport default x;\n");
47
+ const scriptSha=sha(await readFile(f.script));
48
+ await assert.rejects(
49
+ prepareBridgeSpec(baseConfig(),{root:f.root,scriptSha256:scriptSha}),
50
+ /dynamic relative import/i
51
+ );
52
+ });
53
+
54
+ test('R58 bridge dependency discovery rejects path escape',async()=>{
55
+ const f=await fixture();
56
+ await writeFile(f.script,"import '../escape.mjs';\n");
57
+ const scriptSha=sha(await readFile(f.script));
58
+ await assert.rejects(
59
+ prepareBridgeSpec(baseConfig(),{root:f.root,scriptSha256:scriptSha}),
60
+ /escapes bridge root|relative import/i
61
+ );
62
+ });
@@ -0,0 +1,22 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+
5
+ test('R58 deployment activates the bridge bundled inside the candidate release, not predecessor mutable workspace',async()=>{
6
+ const deploy=await readFile(new URL('../scripts/deploy-r48.mjs',import.meta.url),'utf8');
7
+ assert.match(deploy,/const bridgeRoot=path\.join\(root,'bridge'\)/);
8
+ assert.doesNotMatch(deploy,/bridgeRoot=path\.resolve\(cfg\.Bridge\.WorkingDirectory\)/);
9
+ assert.match(deploy,/const bridgeScript=path\.join\(bridgeRoot,'src','bridge\.mjs'\)/);
10
+ });
11
+
12
+ test('R58 bundled bridge package is present and version-bound to 0.15.35',async()=>{
13
+ const [pkg,bridgePkg,bridge]=await Promise.all([
14
+ readFile(new URL('../package.json',import.meta.url),'utf8').then(JSON.parse),
15
+ readFile(new URL('../bridge/package.json',import.meta.url),'utf8').then(JSON.parse),
16
+ readFile(new URL('../bridge/src/bridge.mjs',import.meta.url),'utf8')
17
+ ]);
18
+ assert.equal(bridgePkg.name,'deadbyte-chatgpt-bridge');
19
+ assert.equal(bridgePkg.version,'0.15.35');
20
+ assert.equal(pkg.version,'0.15.35');
21
+ assert.match(bridge,/node-http-adapter\.mjs/);
22
+ });
@@ -0,0 +1,45 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
4
+ import { fileURLToPath } from 'node:url';
5
+ import path from 'node:path';
6
+ import { spawn } from 'node:child_process';
7
+ import { setTimeout as delay } from 'node:timers/promises';
8
+
9
+ test('R58 foreground cleanup failure retains ownership evidence for recovery', {timeout:30000}, async () => {
10
+ const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';
11
+ await mkdir(base,{recursive:true});
12
+ const root=await mkdtemp(path.join(base,'cleanup-'));
13
+ await mkdir(path.join(root,'bin'));
14
+ await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)
15
+ switch ($Command) {
16
+ 'status' { Write-Output 'DEADBYTE MCP = ONLINE'; Write-Output 'Bridge PID : 1001 [verified]'; Write-Output 'Cloud PID : 1002 [verified]'; Write-Output 'Local ready : True'; Write-Output 'Loop supervisor: RUNNING [verified]' }
17
+ 'processstatus' { Write-Output 'Process plane : ARMED [standing-grant]' }
18
+ 'codingstatus' { Write-Output 'Coding plane : ARMED [standing-grant]' }
19
+ 'autonomousstatus' { Write-Output 'Autonomous : ARMED [standing-grant]' }
20
+ 'desktopstatus' { Write-Output 'Desktop plane : DISARMED [disarmed]' }
21
+ 'hoststatus' { Write-Output 'Host plane : DISARMED [disarmed]' }
22
+ 'disarmprocess' { exit 9 }
23
+ default { Write-Output "$Command OK" }
24
+ }
25
+ exit 0
26
+ `);
27
+ const child=spawn(process.execPath,[fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url)),'remote'],{env:{...process.env,DEADBYTE_STATE_ROOT:root},windowsHide:true,stdio:['pipe','pipe','pipe']});
28
+ let stdout='',stderr='';
29
+ child.stdout.on('data',b=>{stdout+=b;});child.stderr.on('data',b=>{stderr+=b;});
30
+ const closed=new Promise(resolve=>child.once('close',code=>resolve(code)));
31
+ try {
32
+ const deadline=Date.now()+12000;
33
+ while(!stdout.includes('ARMED: process, coding, autonomous')&&child.exitCode===null&&Date.now()<deadline) await delay(50);
34
+ assert.match(stdout,/ARMED: process, coding, autonomous/,stderr);
35
+ child.stdin.end();
36
+ assert.notEqual(await closed,0,'cleanup failure must not report a successful foreground exit');
37
+ assert.match(stderr,/cleanup disarmprocess exit=9/);
38
+ const marker=JSON.parse(await readFile(path.join(root,'runtime','remote-session.json'),'utf8'));
39
+ assert.equal(marker.owner_pid,child.pid);
40
+ assert.match(marker.session_nonce,/^[0-9a-f]{64}$/);
41
+ } finally {
42
+ if(child.exitCode===null) child.kill();
43
+ // A failed cleanup intentionally leaves state as reproducible evidence.
44
+ }
45
+ });
@@ -0,0 +1,76 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdir,mkdtemp,writeFile,readFile,readdir} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import {generateKeyPairSync,createHash,sign} from 'node:crypto';
6
+ import path from 'node:path';
7
+ import {fileURLToPath} from 'node:url';
8
+ import {verifySlotUpdateEnvelope} from '../src/runtime-update.mjs';
9
+
10
+ const sha=b=>createHash('sha256').update(b).digest('hex');
11
+ const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'?Object.fromEntries(Object.keys(value).sort().map(k=>[k,canonical(value[k])])):value;
12
+ const json=value=>JSON.stringify(canonical(value));
13
+ const domain=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0');
14
+ const cli=fileURLToPath(new URL('../scripts/runtime-self-update.mjs',import.meta.url));
15
+
16
+ async function fixture(){
17
+ const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
18
+ const root=await mkdtemp(path.join(base,'completed-receipt-'));await mkdir(path.join(root,'trust'));
19
+ const keys=generateKeyPairSync('ed25519');
20
+ await writeFile(path.join(root,'trust','ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}));
21
+ await writeFile(path.join(root,'trust','ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
22
+ const envelope=body=>{const bytes=Buffer.from(json(body));const value={body,body_sha256:sha(bytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',key_id:sha(keys.publicKey.export({type:'spki',format:'der'})),signature_base64:sign(null,Buffer.concat([domain,bytes]),keys.privateKey).toString('base64')}};return {...value,receipt_sha256:sha(Buffer.from(json(value)))};};
23
+ const dir=path.join(root,'evidence','updates','fixture');await mkdir(dir,{recursive:true});
24
+ const statePath=path.join(dir,'state.json'),handoffPath=path.join(dir,'handoff.json'),supervisor=path.join(root,'supervisor.mjs');
25
+ await writeFile(supervisor,'// hash fixture; must never execute\n');
26
+ const pointer={schema:'deadbyte.active-release.v1',version:'0.15.34',manifest_sha256:'a'.repeat(64),release_id:'v0.15.34-'+('a'.repeat(16)),release_root:path.join(root,'releases','fixture'),generation_root:path.join(root,'generations','fixture'),generation_manifest_sha256:'b'.repeat(64),launcher_sha256:'c'.repeat(64),config_path:path.join(root,'generations','fixture','controller-config.psd1')};
27
+ const body={schema:'deadbyte.r27-slot-update-handoff.v1',update_id:'fixture',state_root:root,state_path:statePath,handoff_path:handoffPath,supervisor_script:supervisor,supervisor_script_sha256:sha(await readFile(supervisor)),prior_pointer:null,new_pointer:pointer};
28
+ await writeFile(handoffPath,JSON.stringify(envelope(body)));
29
+ const state={schema:'deadbyte.r27-slot-update-state.v1',update_id:'fixture',phase:'completed',expected_version:pointer.version,expected_manifest_sha256:pointer.manifest_sha256,prior_pointer:null,new_pointer:pointer};
30
+ await writeFile(statePath,JSON.stringify(state));
31
+ const run=()=>spawnSync(process.execPath,[cli,'--resume-handoff',handoffPath],{encoding:'utf8',timeout:15000,env:{...process.env,DEADBYTE_STATE_ROOT:root}});
32
+ return {root,dir,state,statePath,body,pointer,envelope,run,publicKey:keys.publicKey};
33
+ }
34
+
35
+ test('R58 completed update cannot return PASS without its signed subject-bound completion receipt',async()=>{
36
+ const f=await fixture(),before=await readFile(f.statePath,'utf8'),inventory=await readdir(f.dir);
37
+ const observed=f.run();await writeFile(path.join(f.root,'observation.json'),JSON.stringify({code:observed.status,stdout:observed.stdout,stderr:observed.stderr}),{flag:'wx'});
38
+ assert.notEqual(observed.status,0,'completed journal alone must not produce RESUME_PASS');
39
+ assert.doesNotMatch(observed.stdout,/RESUME_PASS/);assert.equal(await readFile(f.statePath,'utf8'),before);
40
+ assert.deepEqual(await readdir(f.dir),inventory,'no leases, recovery or controller effects');
41
+ });
42
+
43
+ test('R58 repeated completed update verifies signed subject, not merely a signature or journal flag',async()=>{
44
+ const f=await fixture(),receiptPath=path.join(f.dir,'receipt.json');
45
+ const subject={schema:'deadbyte.r27-slot-update-receipt.v1',rollback_performed:false,update_id:'fixture',release_version:f.pointer.version,candidate_manifest_sha256:f.pointer.manifest_sha256,prior_pointer:null,active_pointer:f.pointer,
46
+ release_slot:{release_id:f.pointer.release_id,release_root:f.pointer.release_root,manifest_sha256:f.pointer.manifest_sha256},generation:{generation_root:f.pointer.generation_root,generation_manifest_sha256:f.pointer.generation_manifest_sha256},launcher:{sha256:f.pointer.launcher_sha256}};
47
+ const invoke=async(body,changes={},tamper=null)=>{
48
+ let receipt=f.envelope(body);assert.equal(verifySlotUpdateEnvelope(receipt,f.publicKey),true,'independent fixture signing is valid before tampering');
49
+ if(tamper)receipt=tamper(receipt);
50
+ await writeFile(receiptPath,JSON.stringify(receipt));
51
+ const state={...f.state,receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256,...changes};await writeFile(f.statePath,JSON.stringify(state));
52
+ const before=await readFile(f.statePath,'utf8'),inventory=await readdir(f.dir),observed=f.run();
53
+ assert.equal(await readFile(f.statePath,'utf8'),before);assert.deepEqual(await readdir(f.dir),inventory);
54
+ return observed;
55
+ };
56
+ const accepted=await invoke(subject);assert.equal(accepted.status,0,accepted.stderr);assert.match(accepted.stdout,/RESUME_PASS/);assert.match(accepted.stdout,/historical-signed-receipt; current-runtime-not-observed/);
57
+ for(const changes of [
58
+ {schema:'deadbyte.r31-slot-rollback-receipt.v1'},{rollback_performed:true},{rollback_performed:undefined},{update_id:'other'},
59
+ {release_version:'0.15.33'},{candidate_manifest_sha256:'d'.repeat(64)},
60
+ {active_pointer:{...f.pointer,generation_manifest_sha256:'d'.repeat(64)}},
61
+ {prior_pointer:f.pointer},
62
+ {release_slot:{...subject.release_slot,release_root:path.join(f.root,'wrong')}},
63
+ {generation:{...subject.generation,generation_manifest_sha256:'d'.repeat(64)}},
64
+ {launcher:{sha256:'d'.repeat(64)}}
65
+ ]){
66
+ const observed=await invoke({...subject,...changes});assert.notEqual(observed.status,0,JSON.stringify(changes));assert.doesNotMatch(observed.stdout,/RESUME_PASS/);
67
+ }
68
+ for(const changes of [{receipt_sha256:'d'.repeat(64)},{receipt_path:path.join(f.dir,'other.json')},{expected_version:'0.15.33'},{new_pointer:{...f.pointer,launcher_sha256:'d'.repeat(64)}}]){
69
+ assert.notEqual((await invoke(subject,changes)).status,0,JSON.stringify(changes));
70
+ }
71
+ const badSignature=await invoke(subject,{},receipt=>{
72
+ const altered={...receipt,attestation:{...receipt.attestation,signature_base64:Buffer.alloc(64).toString('base64')}};
73
+ const {receipt_sha256,...unsigned}=altered;return {...unsigned,receipt_sha256:sha(Buffer.from(json(unsigned)))};
74
+ });
75
+ assert.notEqual(badSignature.status,0);assert.doesNotMatch(badSignature.stdout,/RESUME_PASS/);
76
+ });
@@ -0,0 +1,54 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdir,mkdtemp,writeFile,readFile} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {fileURLToPath} from 'node:url';
6
+ import {spawn} from 'node:child_process';
7
+ import {setTimeout as delay} from 'node:timers/promises';
8
+
9
+ test('R58 simultaneous stale recovery cannot both revoke the shared authority', {timeout:25000},async()=>{
10
+ const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
11
+ const root=await mkdtemp(path.join(base,'concurrent-recovery-'));await mkdir(path.join(root,'bin'));await mkdir(path.join(root,'runtime'));
12
+ const trace=path.join(root,'trace.txt'),markerPath=path.join(root,'runtime','remote-session.json');
13
+ await writeFile(markerPath,JSON.stringify({schema:'deadbyte.remote-session.v1',pid:2147483000,started_at_utc:'2000-01-01T00:00:00.000Z'}));
14
+ await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)
15
+ $ErrorActionPreference='Stop'
16
+ for($i=0;$i -lt 40;$i++){try{[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine);break}catch{if($i -eq 39){throw};Start-Sleep -Milliseconds 10}}
17
+ switch($Command){
18
+ 'status' {Write-Output 'DEADBYTE MCP = ONLINE';Write-Output 'Bridge PID : 1001 [verified]';Write-Output 'Cloud PID : 1002 [verified]';Write-Output 'Local ready : True';Write-Output 'Loop supervisor: RUNNING [verified]'}
19
+ 'processstatus' {Write-Output 'Process plane : ARMED [standing-grant]'}
20
+ 'codingstatus' {Write-Output 'Coding plane : ARMED [standing-grant]'}
21
+ 'autonomousstatus' {Write-Output 'Autonomous : ARMED [standing-grant]'}
22
+ 'desktopstatus' {Write-Output 'Desktop plane : DISARMED [disarmed]'}
23
+ 'hoststatus' {Write-Output 'Host plane : DISARMED [disarmed]'}
24
+ 'park' {
25
+ $first=$false
26
+ try{$f=[IO.File]::Open((Join-Path $env:DEADBYTE_STATE_ROOT 'park-claim'),[IO.FileMode]::CreateNew);$f.Dispose();$first=$true}catch{}
27
+ if(-not $first){
28
+ $until=[DateTime]::UtcNow.AddSeconds(6)
29
+ while([DateTime]::UtcNow -lt $until){
30
+ try{$m=[IO.File]::ReadAllText((Join-Path $env:DEADBYTE_STATE_ROOT 'runtime/remote-session.json'))|ConvertFrom-Json;if($m.schema -eq 'deadbyte.remote-session.v2'){break}}catch{}
31
+ Start-Sleep -Milliseconds 20
32
+ }
33
+ }
34
+ }
35
+ }
36
+ exit 0
37
+ `);
38
+ const children=[],outputs=[];
39
+ try{
40
+ for(let i=0;i<2;i++){
41
+ const c=spawn(process.execPath,[fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url)),'remote'],{env:{...process.env,DEADBYTE_STATE_ROOT:root},stdio:['pipe','pipe','pipe'],windowsHide:true});
42
+ const o={stdout:'',stderr:''};c.stdout.on('data',b=>{o.stdout+=b;});c.stderr.on('data',b=>{o.stderr+=b;});o.exit=new Promise(resolve=>c.once('exit',resolve));children.push(c);outputs.push(o);
43
+ }
44
+ const until=Date.now()+12000;let commands=[];
45
+ while(Date.now()<until){try{commands=(await readFile(trace,'utf8')).trim().split(/\r?\n/);}catch{}if(commands.includes('hoststatus'))break;await delay(50);}
46
+ await writeFile(path.join(root,'observation.json'),JSON.stringify({commands,outputs:outputs.map(o=>({stdout:o.stdout,stderr:o.stderr})),simulation:'concurrent stale marker recovery with delayed second park'}),{flag:'wx'});
47
+ assert.ok(commands.includes('hoststatus'),'a recovered foreground must become ready');
48
+ for(const cmd of ['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'])assert.equal(commands.filter(x=>x===cmd).length,1,`stale recovery must serialize ${cmd}`);
49
+ }finally{
50
+ for(const c of children)if(c.exitCode===null)c.stdin.end();
51
+ await Promise.race([Promise.all(outputs.map(o=>o.exit)),delay(7000)]);
52
+ for(const c of children)if(c.exitCode===null&&c.signalCode===null)c.kill();
53
+ }
54
+ });
@@ -0,0 +1,36 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile,mkdir,mkdtemp,writeFile} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import path from 'node:path';
6
+ import {windowsPowerShellEnvironment} from '../src/runtime-update.mjs';
7
+
8
+ test('R58 core observation is parent/generation bound, singular and never retries ambiguity',async()=>{
9
+ const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
10
+ const start=source.indexOf('function Get-DownstreamCoreObservation('),end=source.indexOf('function Get-StructuredObservation(',start);
11
+ assert.ok(start>=0&&end>start);
12
+ const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
13
+ const root=await mkdtemp(path.join(base,'core-observation-'));
14
+ const child={ProcessId:21,ParentProcessId:20,ExecutablePath:'C:/node.exe',CommandLine:'node.exe "C:/core/src/mcp-server.mjs"',CreationDate:'2026-10-01T00:00:00.000Z'};
15
+ const cases=[
16
+ {children:[child],reason:'verified',alive:true},
17
+ {children:[],reason:'startup-zero-child',alive:false},
18
+ {children:[child,{...child,ProcessId:22}],reason:'ambiguous-core',alive:false},
19
+ {children:[{...child,ParentProcessId:999}],reason:'core-identity-incomplete',alive:false},
20
+ {children:[{...child,CommandLine:'node.exe C:/old/src/mcp-server.mjs'}],reason:'wrong-core-generation',alive:false},
21
+ {children:[{...child,ExecutablePath:'C:/wrong/node.exe'}],reason:'core-image-mismatch',alive:false},
22
+ {children:[{...child,CommandLine:''}],reason:'verified',alive:true},
23
+ {children:[child],reason:'bridge-config-mismatch',alive:false,scriptDrift:true},
24
+ {children:[child],reason:'bridge-unavailable',alive:false,bridgeDead:true},
25
+ {children:[child],reason:'core-observation-failed',alive:false,queryError:true}
26
+ ];
27
+ const data=path.join(root,'cases.json');await writeFile(data,JSON.stringify(cases));
28
+ const script=path.join(root,'fixture.ps1');
29
+ await writeFile(script,`Set-StrictMode -Version 3.0\n$ErrorActionPreference='Stop'\n${source.slice(start,end)}\nfunction Get-CimInstance { param($ClassName,$Filter,$ErrorAction) $script:Queries++; if ($script:FixtureQueryError) { throw 'fixture failure' }; return $script:FixtureChildren }\n$cases=Get-Content -LiteralPath '${data.replaceAll("'","''")}' -Raw | ConvertFrom-Json\nforeach($case in $cases) {\n $script:Queries=0; $script:FixtureQueryError=($case.PSObject.Properties.Name -contains 'queryError'); $script:FixtureChildren=@($case.children)\n foreach($item in $script:FixtureChildren) { $item.CreationDate=[DateTime]::Parse($item.CreationDate).ToUniversalTime() }\n $cfg=@{CoreRoot='C:/core';Bridge=@{FilePath='C:/node.exe';ScriptPath='C:/bridge.mjs';ScriptSha256='${'a'.repeat(64)}'}}\n $ledger=[pscustomobject]@{pid=20;script_path='C:/bridge.mjs';script_sha256='${'a'.repeat(64)}'}\n if($case.PSObject.Properties.Name -contains 'scriptDrift') { $ledger.script_sha256='${'b'.repeat(64)}' }\n $bridge=[pscustomobject]@{Alive=(-not ($case.PSObject.Properties.Name -contains 'bridgeDead'));Ledger=$ledger}\n $observed=Get-DownstreamCoreObservation $cfg $bridge\n [pscustomobject]@{expected=$case.reason;observed=$observed;queries=$script:Queries} | ConvertTo-Json -Depth 5 -Compress | Write-Output\n}\n`);
30
+ const r=spawnSync(path.join(process.env.SystemRoot,'System32/WindowsPowerShell/v1.0/powershell.exe'),['-NoProfile','-File',script],{encoding:'utf8',timeout:20000,env:windowsPowerShellEnvironment(process.env)});
31
+ assert.equal(r.status,0,r.stderr);
32
+ const observed=r.stdout.trim().split(/\r?\n/).map(line=>JSON.parse(line));assert.equal(observed.length,cases.length);
33
+ observed.forEach((row,i)=>{assert.equal(row.observed.reason,cases[i].reason);assert.equal(row.observed.alive,cases[i].alive);assert.equal(row.queries,cases[i].scriptDrift||cases[i].bridgeDead?0:1);});
34
+ assert.equal(observed[6].observed.identity_basis,'verified-bridge-single-opaque-node-child');
35
+ assert.ok(!r.stdout.includes(child.CommandLine),'no raw command line publication');
36
+ });