deadbyte-mcp 0.15.28 → 0.15.30

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 (78) hide show
  1. package/MANIFEST.SHA256 +76 -54
  2. package/PROVENANCE.json +21 -10
  3. package/README.txt +16 -14
  4. package/RELEASE.json +29 -10
  5. package/SBOM.cdx.json +3 -3
  6. package/STABILIZATION-PROGRESS.md +60 -0
  7. package/TEST-MATRIX.json +51 -93
  8. package/bin/appcontainer-stage.obj +0 -0
  9. package/bin/bootstrap-advapi32.obj +0 -0
  10. package/bin/bootstrap-exitcode.obj +0 -0
  11. package/bin/bootstrap-kernel32.obj +0 -0
  12. package/bin/child-control-probe.obj +0 -0
  13. package/bin/child-control-stage.obj +0 -0
  14. package/bin/contained-reverse-worker.obj +0 -0
  15. package/bin/contained-transform-worker.obj +0 -0
  16. package/bin/containment-probe.obj +0 -0
  17. package/bin/deadbyte-contain.obj +0 -0
  18. package/bin/deadbyte-exec.obj +0 -0
  19. package/bin/deadbyte-hardware-observer-r45.obj +0 -0
  20. package/bin/deadbyte-process-host.exe +0 -0
  21. package/bin/deadbyte-process-host.obj +0 -0
  22. package/bin/deadbyte-tunnel-host.obj +0 -0
  23. package/controller/deadbyte-controller.ps1 +4 -1
  24. package/controller/deadbyte-desktop-policy.json +5 -5
  25. package/controller/deadbyte-host-policy.json +170 -0
  26. package/controller/deadbyte-process-policy.json +5 -5
  27. package/docs/R51-PROVENANCE.json +18 -0
  28. package/docs/R51-RELEASE.json +115 -0
  29. package/docs/R51-TEST-MATRIX.json +166 -0
  30. package/docs/R52-ACTIVATION-FORENSICS.md +85 -0
  31. package/native/deadbyte-process-host.cpp +5 -3
  32. package/package.json +96 -94
  33. package/proof/CONTAINMENT-BUILD.txt +6 -6
  34. package/scripts/gate-windows-r52.ps1 +58 -0
  35. package/scripts/gate-windows-r53.ps1 +58 -0
  36. package/scripts/verify-r42-parent.mjs +1 -1
  37. package/scripts/verify-r43-parent.mjs +1 -1
  38. package/scripts/verify-r44-parent.mjs +1 -1
  39. package/scripts/verify-r45-parent.mjs +1 -1
  40. package/scripts/windows-gate-evidence-r52.mjs +94 -0
  41. package/scripts/windows-gate-evidence-r53.mjs +102 -0
  42. package/src/authority-smoke.mjs +1 -0
  43. package/src/coding-plane.mjs +43 -12
  44. package/src/coding-task-runtime.mjs +10 -4
  45. package/src/deadbyte-cli.mjs +4 -2
  46. package/src/doctor-r45.mjs +2 -1
  47. package/src/machine-fs-smoke-client.mjs +1 -0
  48. package/src/mcp-contained-smoke-client.mjs +1 -0
  49. package/src/mcp-host-smoke-client.mjs +1 -0
  50. package/src/mcp-smoke-client.mjs +1 -0
  51. package/src/process-smoke-client.mjs +1 -0
  52. package/src/release-slot.mjs +5 -2
  53. package/src/remote-session-guardian.mjs +12 -4
  54. package/src/runtime-coherence.mjs +13 -4
  55. package/src/runtime-update.mjs +28 -7
  56. package/src/state-root.mjs +8 -0
  57. package/src/trust.mjs +2 -1
  58. package/src/tunnel-stdio-compat-smoke.mjs +1 -0
  59. package/src/version.mjs +1 -1
  60. package/test/coding-runtime.test.mjs +70 -1
  61. package/test/commander-biff-reference-r48.test.mjs +1 -1
  62. package/test/helpers/coding-disconnect-server.mjs +11 -0
  63. package/test/r44-finalizer.test.mjs +4 -3
  64. package/test/r49-windows-gate.test.mjs +19 -9
  65. package/test/r51-release-contract.test.mjs +22 -20
  66. package/test/r52-host-revocation.test.mjs +65 -0
  67. package/test/r52-offline-predecessor-recovery.test.mjs +77 -0
  68. package/test/r52-release-contract.test.mjs +64 -0
  69. package/test/r53-release-contract.test.mjs +66 -0
  70. package/test/release-slot.test.mjs +2 -1
  71. package/test/skill-mcp-r44.test.mjs +3 -0
  72. package/test/stabilization-guardian.test.mjs +25 -0
  73. package/test/stabilization-parent-context.test.mjs +18 -0
  74. package/test/stabilization-powershell.test.mjs +26 -0
  75. package/test/stabilization-semver.test.mjs +23 -0
  76. package/test/stabilization-smoke-root.test.mjs +23 -0
  77. package/test/stabilization-state-root.test.mjs +60 -0
  78. package/test/stabilization-update-child-root.test.mjs +20 -0
@@ -52,6 +52,7 @@ const client = new Client({ name: 'deadbyte-machine-fs-smoke', version: '0.8.0'
52
52
  const transport = new StdioClientTransport({
53
53
  command: process.execPath, args: [serverPath], cwd: repoRoot,
54
54
  env: { ...getDefaultEnvironment(), DEADBYTE_CODING_RUNTIME: '1', DEADBYTE_MACHINE_FS_RUNTIME: '1',
55
+ DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(scratch,'state')),
55
56
  DEADBYTE_PRIVILEGED_POLICY: '',
56
57
  DEADBYTE_CODING_POLICY: policyPath, DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
57
58
  DEADBYTE_VERIFY_KEY: keys.publicKeyPath }, stderr: 'pipe'
@@ -34,6 +34,7 @@ const transport = new StdioClientTransport({
34
34
  cwd: repoRoot,
35
35
  env: {
36
36
  ...getDefaultEnvironment(),
37
+ DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(trust,'state')),
37
38
  DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
38
39
  DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
39
40
  DEADBYTE_VERIFY_KEY: keys.publicKeyPath
@@ -66,6 +66,7 @@ const transport = new StdioClientTransport({
66
66
  cwd: repoRoot,
67
67
  env: {
68
68
  ...getDefaultEnvironment(),
69
+ DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(temp,'state')),
69
70
  DEADBYTE_WORKSPACE_ROOT: path.join(repoRoot, 'workspaces'),
70
71
  DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
71
72
  DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
@@ -37,6 +37,7 @@ const transport = new StdioClientTransport({
37
37
  cwd: repoRoot,
38
38
  env: {
39
39
  ...getDefaultEnvironment(),
40
+ DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(smokeTrust,'state')),
40
41
  DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
41
42
  DEADBYTE_SIGNING_KEY: keys.privateKeyPath,
42
43
  DEADBYTE_VERIFY_KEY: keys.publicKeyPath,
@@ -39,6 +39,7 @@ const client = new Client({ name: 'deadbyte-process-smoke', version: '0.8.0' },
39
39
  const transport = new StdioClientTransport({
40
40
  command: process.execPath, args: [serverPath], cwd: repoRoot,
41
41
  env: { ...getDefaultEnvironment(), DEADBYTE_PRIVILEGED_POLICY: '', DEADBYTE_MACHINE_RUNTIME:'0', DEADBYTE_AGENT_RUNTIME:'0',
42
+ DEADBYTE_STATE_ROOT: path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(smokeTrust,'state')),
42
43
  DEADBYTE_PROCESS_RUNTIME: '1', DEADBYTE_PROCESS_POLICY: smokePolicyPath, DEADBYTE_CODING_RUNTIME:'0', DEADBYTE_MACHINE_FS_RUNTIME:'0',
43
44
  DEADBYTE_OBSERVATION_RUNTIME:'0', DEADBYTE_DESKTOP_RUNTIME:'0', DEADBYTE_AUTONOMOUS_RUNTIME:'0', DEADBYTE_AUDIT_RUNTIME:'0',
44
45
  DEADBYTE_CAPABILITY_PROFILE:'full', DEADBYTE_SIGNING_KEY: keys.privateKeyPath, DEADBYTE_VERIFY_KEY: keys.publicKeyPath },
@@ -195,7 +195,8 @@ export function renderStableLauncherCmd(){
195
195
  return [
196
196
  '@echo off',
197
197
  'setlocal EnableExtensions DisableDelayedExpansion',
198
- 'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%USERPROFILE%\\.deadbyte-mcp\\bin\\DEADBYTE-MCP.ps1" %*',
198
+ 'if not defined DEADBYTE_STATE_ROOT set "DEADBYTE_STATE_ROOT=%USERPROFILE%\\.deadbyte-mcp"',
199
+ 'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
199
200
  'exit /b %ERRORLEVEL%',
200
201
  ''
201
202
  ].join('\r\n');
@@ -208,7 +209,9 @@ export function renderStableLauncherPs1(){
208
209
  'param([Parameter(ValueFromRemainingArguments=$true)][string[]]$ControllerArgs)',
209
210
  'Set-StrictMode -Version 3.0',
210
211
  "$ErrorActionPreference = 'Stop'",
211
- "$StateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'",
212
+ "$StateRoot = if ([string]::IsNullOrEmpty($env:DEADBYTE_STATE_ROOT)) { Join-Path $env:USERPROFILE '.deadbyte-mcp' } else { $env:DEADBYTE_STATE_ROOT }",
213
+ '$StateRoot = [IO.Path]::GetFullPath($StateRoot)',
214
+ '$env:DEADBYTE_STATE_ROOT = $StateRoot',
212
215
  "$PointerPath = Join-Path $StateRoot 'active-release.json'",
213
216
  "function Fail([string]$m) { throw ('DEADBYTE launcher: ' + $m) }",
214
217
  "if (-not (Test-Path -LiteralPath $PointerPath -PathType Leaf)) { Fail 'active release pointer missing' }",
@@ -4,6 +4,8 @@ import { spawnSync } from 'node:child_process';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { setTimeout as sleep } from 'node:timers/promises';
7
+ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
+ import { resolveStateRoot } from './state-root.mjs';
7
9
 
8
10
  const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
9
11
 
@@ -36,9 +38,9 @@ function markerMatches(marker,{ownerPid,nonce}){
36
38
  marker?.owner_pid===ownerPid&&
37
39
  marker?.session_nonce===nonce;
38
40
  }
39
- function invokeCleanup(powershell,launcher,command){
41
+ function invokeCleanup(powershell,launcher,command,stateRoot){
40
42
  const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{
41
- stdio:'ignore',windowsHide:true,shell:false
43
+ stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
42
44
  });
43
45
  return !result.error&&result.status===0;
44
46
  }
@@ -46,7 +48,7 @@ function invokeCleanup(powershell,launcher,command){
46
48
  async function main(){
47
49
  if(process.platform!=='win32') return;
48
50
  const args=parseArgs(process.argv.slice(2));
49
- const root=path.resolve(args['state-root']||path.join(os.homedir(),'.deadbyte-mcp'));
51
+ const root=resolveStateRoot(args['state-root']);
50
52
  const ownerPid=Number(args['owner-pid']);
51
53
  const nonce=String(args['session-nonce']||'');
52
54
  if(!Number.isInteger(ownerPid)||ownerPid<1) throw new Error('guardian owner pid invalid');
@@ -66,7 +68,13 @@ async function main(){
66
68
  continue;
67
69
  }
68
70
 
69
- for(const command of CLEANUP) invokeCleanup(powershell,launcher,command);
71
+ const failed=[];
72
+ for(const command of CLEANUP){
73
+ const latest=await readMarker(markerPath);
74
+ if(!latest||!markerMatches(latest,{ownerPid,nonce})) return;
75
+ if(!invokeCleanup(powershell,launcher,command,root)) failed.push(command);
76
+ }
77
+ if(failed.length) throw new Error('guardian cleanup failed; ownership marker retained: '+failed.join(','));
70
78
  const current=await readMarker(markerPath);
71
79
  if(current&&markerMatches(current,{ownerPid,nonce})) await rm(markerPath,{force:true});
72
80
  return;
@@ -1,10 +1,10 @@
1
- const SEMVER=/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
1
+ const SEMVER=/^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2
2
  const HEX64=/^[0-9a-f]{64}$/;
3
3
 
4
4
  function parseSemver(value){
5
5
  const match=SEMVER.exec(String(value??''));
6
- if(!match) throw new Error(`runtime version invalid: ${String(value)}`);
7
- return {major:Number(match[1]),minor:Number(match[2]),patch:Number(match[3]),prerelease:match[4]??null};
6
+ if(!match||match[0]!==String(value??'')||(match[4]??'').split('.').some(id=>/^0[0-9]+$/.test(id))) throw new Error(`runtime version invalid: ${String(value)}`);
7
+ return {major:BigInt(match[1]),minor:BigInt(match[2]),patch:BigInt(match[3]),prerelease:match[4]??null};
8
8
  }
9
9
  export function compareRuntimeVersions(a,b){
10
10
  const left=parseSemver(a),right=parseSemver(b);
@@ -12,7 +12,16 @@ export function compareRuntimeVersions(a,b){
12
12
  if(left.prerelease===right.prerelease) return 0;
13
13
  if(left.prerelease===null) return 1;
14
14
  if(right.prerelease===null) return -1;
15
- return left.prerelease.localeCompare(right.prerelease,'en',{numeric:true,sensitivity:'case'});
15
+ const aIds=left.prerelease.split('.'),bIds=right.prerelease.split('.');
16
+ for(let i=0;i<Math.min(aIds.length,bIds.length);i++){
17
+ const x=aIds[i],y=bIds[i];
18
+ if(x===y) continue;
19
+ const xn=/^[0-9]+$/.test(x),yn=/^[0-9]+$/.test(y);
20
+ if(xn&&yn) return BigInt(x)<BigInt(y)?-1:1;
21
+ if(xn!==yn) return xn?-1:1;
22
+ return x<y?-1:1;
23
+ }
24
+ return aIds.length<bIds.length?-1:aIds.length>bIds.length?1:0;
16
25
  }
17
26
  function assertManifest(value,label){
18
27
  if(!HEX64.test(String(value??''))) throw new Error(`${label} must be lowercase SHA-256`);
@@ -17,6 +17,7 @@ import {
17
17
  verifyManifestTree
18
18
  } from './release-slot.mjs';
19
19
  import { createGenerationBundle, verifyGenerationBundle } from './release-generation.mjs';
20
+ import { resolveStateRoot } from './state-root.mjs';
20
21
 
21
22
  const HEX64=/^[0-9a-f]{64}$/;
22
23
  const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -25,7 +26,7 @@ const UPDATE_SCHEMA='deadbyte.r27-slot-update-receipt.v1';
25
26
  const HANDOFF_SCHEMA='deadbyte.r27-slot-update-handoff.v1';
26
27
  const FAILURE_SCHEMA='deadbyte.r27-slot-update-failure.v1';
27
28
  const ROLLBACK_SCHEMA='deadbyte.r31-slot-rollback-receipt.v1';
28
- const DEFAULT_STATE_ROOT=path.join(os.homedir(),'.deadbyte-mcp');
29
+ const DEFAULT_STATE_ROOT=undefined;
29
30
  const TOP_LAUNCHER=path.join(os.homedir(),'Downloads','DEADBYTE-MCP.cmd');
30
31
  const RUNTIME_STABILITY_MS=12000;
31
32
  const RUNTIME_STABILITY_POLL_MS=500;
@@ -195,11 +196,21 @@ export function windowsPowerShellEnvironment(base=process.env){
195
196
  const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
196
197
  const userProfile=base.USERPROFILE||os.homedir();
197
198
  const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
198
- return {...base,PSModulePath:[
199
+ const environment={...base};
200
+ const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath').flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
201
+ for(const key of Object.keys(environment)) if(key.toLowerCase()==='psmodulepath') delete environment[key];
202
+ const modules=[
203
+ path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
199
204
  path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
200
205
  path.join(programFiles,'WindowsPowerShell','Modules'),
201
- path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules')
202
- ].join(';')};
206
+ ...inherited
207
+ ];
208
+ const seen=new Set();
209
+ return {...environment,PSModulePath:modules.filter(modulePath=>{
210
+ const key=modulePath.toLowerCase();
211
+ if(seen.has(key)) return false;
212
+ seen.add(key);return true;
213
+ }).join(';')};
203
214
  }
204
215
  function commandEnvironment(exe,base=process.env){
205
216
  return path.resolve(exe).toLowerCase()===path.resolve(powershellPath()).toLowerCase()
@@ -582,7 +593,7 @@ function controllerInvocation(ctx,command){
582
593
  exe:powershellPath(),
583
594
  args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],
584
595
  cwd:ctx.cwd,
585
- env:{...process.env,DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
596
+ env:{...process.env,DEADBYTE_STATE_ROOT:resolveStateRoot(ctx.state_root),DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
586
597
  };
587
598
  return {exe:powershellPath(),args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],cwd:ctx.cwd};
588
599
  }
@@ -704,7 +715,7 @@ async function captureRuntime(ctx){
704
715
  const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
705
716
  return {status,status_text:statusText.trim(),bridge,cloud,core};
706
717
  }
707
- async function capturePredecessor(ctx,recoverOfflinePredecessor){
718
+ export async function capturePredecessor(ctx,recoverOfflinePredecessor){
708
719
  let statusText,status;
709
720
  try{
710
721
  statusText=controllerCaptured(ctx,'status',15000);
@@ -738,7 +749,13 @@ async function capturePredecessor(ctx,recoverOfflinePredecessor){
738
749
  }
739
750
  if(!recoverOfflinePredecessor) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
740
751
  if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery requires clean OFFLINE controller state: ${statusText}`);
741
- if(status.host_armed) throw new Error('offline predecessor recovery requires host plane DISARMED');
752
+ if(status.host_armed){
753
+ controllerCaptured(ctx,'disarmhost',15000);
754
+ statusText=controllerCaptured(ctx,'status',15000);
755
+ status=parseControllerStatus(statusText);
756
+ if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery changed controller state while revoking Host authority: ${statusText}`);
757
+ if(status.host_armed) throw new Error('offline predecessor recovery requires host plane DISARMED');
758
+ }
742
759
  if(status.autonomous_armed&&!status.coding_armed) throw new Error('invalid runtime authority: autonomous armed without coding');
743
760
  return {mode:'offline-recovery',status,status_text:statusText.trim(),runtime:null};
744
761
  }
@@ -1405,6 +1422,7 @@ async function resumeSlotUpdate({handoffPath}){
1405
1422
  }
1406
1423
  }
1407
1424
  export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROOT}={}){
1425
+ stateRoot=resolveStateRoot(stateRoot);
1408
1426
  const evidenceRoot=path.join(path.resolve(stateRoot),'evidence','updates');
1409
1427
  if(!existsSync(evidenceRoot)) return {ok:true,launched:0,updates:[]};
1410
1428
  const entries=await readdir(evidenceRoot,{withFileTypes:true});
@@ -1437,6 +1455,7 @@ export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROO
1437
1455
  }
1438
1456
 
1439
1457
  export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,codingPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
1458
+ stateRoot=resolveStateRoot(stateRoot);
1440
1459
  if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
1441
1460
  const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
1442
1461
  const key=await loadUpdateKey(state),updateId='r27-'+expectedManifest.slice(0,16)+'-'+Date.now().toString(36)+'-'+randomBytes(4).toString('hex');
@@ -1521,6 +1540,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
1521
1540
  }
1522
1541
 
1523
1542
  export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_STATE_ROOT}){
1543
+ stateRoot=resolveStateRoot(stateRoot);
1524
1544
  if(process.platform!=='win32') throw new Error('R31 slot rollback is Windows-only');
1525
1545
  const state=path.resolve(stateRoot),receiptFile=path.resolve(activationReceipt);
1526
1546
  const key=await loadUpdateKey(state);
@@ -1572,6 +1592,7 @@ export async function performSlotRollback({activationReceipt,stateRoot=DEFAULT_S
1572
1592
  }
1573
1593
 
1574
1594
  export async function runSlotUpdateCli(argv=process.argv.slice(2),{stateRoot=DEFAULT_STATE_ROOT}={}){
1595
+ stateRoot=resolveStateRoot(stateRoot);
1575
1596
  if(argv.includes('--rollback-receipt')){
1576
1597
  const args=parseRollbackArgs(argv);
1577
1598
  const result=await performSlotRollback({activationReceipt:args.activationReceipt,stateRoot});
@@ -0,0 +1,8 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+
4
+ export function resolveStateRoot(explicit,environment=process.env){
5
+ const value=explicit===undefined?(environment.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')):explicit;
6
+ if(typeof value!=='string'||value.length===0||value.includes('\0')) throw new Error('state root invalid');
7
+ return path.resolve(value);
8
+ }
package/src/trust.mjs CHANGED
@@ -9,11 +9,12 @@ import { mkdir, readFile, realpath, writeFile } from 'node:fs/promises';
9
9
  import os from 'node:os';
10
10
  import path from 'node:path';
11
11
  import { sha256Bytes } from './hash.mjs';
12
+ import { resolveStateRoot } from './state-root.mjs';
12
13
 
13
14
  const DOMAIN = Buffer.from('DEADBYTE-RECEIPT-V1\0', 'utf8');
14
15
 
15
16
  export function defaultTrustDir() {
16
- return path.join(os.homedir(), '.deadbyte-mcp', 'trust');
17
+ return path.join(resolveStateRoot(), 'trust');
17
18
  }
18
19
 
19
20
  export function defaultPrivateKeyPath() {
@@ -101,6 +101,7 @@ async function modernPassThrough() {
101
101
  cwd: repoRoot,
102
102
  env: {
103
103
  ...getDefaultEnvironment(),
104
+ ...(process.env.DEADBYTE_STATE_ROOT?{DEADBYTE_STATE_ROOT:path.resolve(process.env.DEADBYTE_STATE_ROOT)}:{}),
104
105
  DEADBYTE_WORKSPACE_ROOT: workspaceRoot,
105
106
  DEADBYTE_PRIVILEGED_POLICY: '',
106
107
  DEADBYTE_TUNNEL_STDIO_PROBE_COMPAT: '1'
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.28';
1
+ export const DEADBYTE_VERSION = '0.15.30';
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';
@@ -7,6 +7,75 @@ import path from 'node:path';
7
7
  import { generateTrustKeypair } from '../src/trust.mjs';
8
8
  import { assertCodingArmed, createCodingRuntime, loadCodingPolicy, recheckCodingAuthority } from '../src/coding-plane.mjs';
9
9
  import { createCodingTaskRuntime } from '../src/coding-task-runtime.mjs';
10
+ import { fork } from 'node:child_process';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ test('R52 caller disconnect retains one durable execution, status, attempts and terminal receipt',async()=>{
14
+ const f=await fixture();
15
+ let server;
16
+ try{
17
+ const script=path.join(f.project,'disconnect.mjs');
18
+ await writeFile(script,"import {appendFileSync} from 'node:fs';appendFileSync('disconnect-runs.txt',process.pid+'\\n');await new Promise(r=>setTimeout(r,750));process.stdout.write('survived-disconnect');");
19
+ const submission={submissionId:'caller-disconnect',steps:[{step_id:'gate',kind:'command',root_id:'project',command_id:'node_script',script_path:'disconnect.mjs',script_sha256:await sha256File(script),timeout_ms:10000}]};
20
+ const task=await f.tasks.submit(submission);
21
+ server=fork(fileURLToPath(new URL('./helpers/coding-disconnect-server.mjs',import.meta.url)),
22
+ [f.policyPath,f.keys.privateKeyPath,f.keys.publicKeyPath],{stdio:['ignore','ignore','ignore','ipc'],windowsHide:true,execArgv:[]});
23
+ const closed=new Promise(resolve=>server.once('exit',resolve));
24
+ server.send({taskId:task.task_id});
25
+ const deadline=Date.now()+10000;
26
+ let started=false;
27
+ while(Date.now()<deadline){
28
+ try{await readFile(path.join(f.project,'disconnect-runs.txt'));started=true;break;}catch{}
29
+ await new Promise(r=>setTimeout(r,20));
30
+ }
31
+ assert.ok(started,'independent runtime did not start');
32
+ server.disconnect(); // Caller transport goes away; execution owner stays live.
33
+ assert.doesNotThrow(()=>process.kill(server.pid,0));
34
+ const duplicate=await f.tasks.submit(submission);
35
+ assert.equal(duplicate.task_id,task.task_id);
36
+ assert.equal(duplicate.status,'existing');
37
+ assert.equal((await f.tasks.status({taskId:task.task_id})).steps[0].attempts,1);
38
+ await closed;
39
+ assert.equal(server.exitCode,0);
40
+ const recovered=createCodingTaskRuntime({codingRuntime:f.runtime,signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
41
+ const status=await recovered.status({taskId:task.task_id});
42
+ assert.equal(status.state,'succeeded');
43
+ assert.equal(status.steps[0].attempts,1);
44
+ assert.equal((await recovered.verify({taskId:task.task_id})).ok,true);
45
+ assert.equal((await recovered.run({taskId:task.task_id})).status,'terminal');
46
+ assert.equal((await readFile(path.join(f.project,'disconnect-runs.txt'),'utf8')).trim().split(/\r?\n/).length,1);
47
+ }finally{if(server?.exitCode===null){server.kill();}await rm(f.root,{recursive:true,force:true});}
48
+ });
49
+
50
+ test('R52 cancelling a running durable command stops its owned descendants before terminal state',async()=>{
51
+ const f=await fixture();
52
+ let running;
53
+ try{
54
+ const script=path.join(f.project,'cancel-tree.mjs');
55
+ await writeFile(script,"import {spawn} from 'node:child_process'; import {writeFileSync} from 'node:fs'; const child=spawn(process.execPath,['-e',\"setTimeout(()=>require('node:fs').writeFileSync('escaped.txt','escaped'),4000)\"],{stdio:'inherit'}); writeFileSync('started.txt',String(child.pid)); setTimeout(()=>{},4000);");
56
+ const submission={submissionId:'cancel-running-tree',steps:[{step_id:'gate',kind:'command',root_id:'project',command_id:'node_script',script_path:'cancel-tree.mjs',script_sha256:await sha256File(script),timeout_ms:10000}]};
57
+ const task=await f.tasks.submit(submission);
58
+ running=f.tasks.run({taskId:task.task_id});
59
+ let descendant;
60
+ const deadline=Date.now()+3000;
61
+ while(Date.now()<deadline){
62
+ try{descendant=Number(await readFile(path.join(f.project,'started.txt'),'utf8'));break;}catch{}
63
+ await new Promise(r=>setTimeout(r,20));
64
+ }
65
+ assert.ok(descendant,'fixture did not start descendant');
66
+ const cancelledAt=Date.now();
67
+ await f.tasks.cancel({taskId:task.task_id,reason:'owned-tree-test'});
68
+ const result=await running;
69
+ assert.equal(result.status,'cancelled');
70
+ assert.ok(Date.now()-cancelledAt<2000,'cancellation waited for command to finish');
71
+ assert.throws(()=>process.kill(descendant,0),'owned descendant is still alive after terminal cancellation');
72
+ await assert.rejects(readFile(path.join(f.project,'escaped.txt')),e=>e.code==='ENOENT');
73
+ const status=await f.tasks.status({taskId:task.task_id});
74
+ assert.equal(status.steps[0].attempts,1);
75
+ const executionReceipt=await f.runtime.receiptVerify({receiptId:status.steps[0].receipt_id});
76
+ assert.equal(executionReceipt.ok,true,'cancelled execution must retain its signed terminal observation');
77
+ }finally{await running?.catch(()=>{});await rm(f.root,{recursive:true,force:true});}
78
+ });
10
79
 
11
80
  function sha256Bytes(bytes) { return createHash('sha256').update(bytes).digest('hex'); }
12
81
  async function sha256File(file) { return sha256Bytes(await readFile(file)); }
@@ -39,7 +108,7 @@ async function fixture({ armed = true } = {}) {
39
108
  }
40
109
  const runtime = createCodingRuntime({ policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath });
41
110
  const tasks = createCodingTaskRuntime({ codingRuntime: runtime, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath });
42
- return { root, project, policy, runtime, tasks, keys };
111
+ return { root, project, policyPath, policy, runtime, tasks, keys };
43
112
  }
44
113
  test('coding plane edits files under lease and verifies signed action receipts', async () => {
45
114
  const f = await fixture();
@@ -6,7 +6,7 @@ import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
6
6
  import os from 'node:os';
7
7
  import path from 'node:path';
8
8
 
9
- const oracleRoot='C:\\Users\\DEADBYTE\\.deadbyte-mcp\\evidence\\commander-biff-oracle';
9
+ const oracleRoot=process.env.DEADBYTE_BIFF_TEST_ORACLE||'C:\\Users\\DEADBYTE\\.deadbyte-mcp\\evidence\\commander-biff-oracle';
10
10
  const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
11
11
 
12
12
  test('pinned reference rejects a real BIFF8 workbook and writes OOXML bytes under .xls',async()=>{
@@ -0,0 +1,11 @@
1
+ import { loadCodingPolicy,createCodingRuntime } from '../../src/coding-plane.mjs';
2
+ import { createCodingTaskRuntime } from '../../src/coding-task-runtime.mjs';
3
+ const [policyPath,signingKeyPath,verifyKeyPath]=process.argv.slice(2);
4
+ const policy=await loadCodingPolicy(policyPath);
5
+ const codingRuntime=createCodingRuntime({policy,signingKeyPath,verifyKeyPath});
6
+ const tasks=createCodingTaskRuntime({codingRuntime,signingKeyPath,verifyKeyPath});
7
+ process.once('message',async({taskId})=>{
8
+ try{await tasks.run({taskId});process.exitCode=0;}
9
+ catch(error){console.error(error);process.exitCode=1;}
10
+ finally{if(process.connected)process.disconnect();}
11
+ });
@@ -34,15 +34,16 @@ test('R44 detached finalizer accepts only exact signed-R43-parent feature gate e
34
34
  assert.throws(()=>validateWindowsGateEvidence({...valid(),features:{...features(),skill_integrity:false}},manifest,parent),/skill_integrity/);
35
35
  });
36
36
 
37
- test('R44 detached finalizer isolates Windows PowerShell modules from the parent runtime',()=>{
37
+ test('R44 detached finalizer prioritizes native Windows modules and preserves custom paths',()=>{
38
38
  const env=windowsPowerShellEnvironment({
39
39
  SystemRoot:'C:\\Windows',USERPROFILE:'C:\\Users\\operator',ProgramFiles:'C:\\Program Files',
40
40
  PSModulePath:'C:\\poisoned-runtime-modules'
41
41
  });
42
42
  assert.equal(env.PSModulePath,[
43
+ 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules',
43
44
  'C:\\Users\\operator\\Documents\\WindowsPowerShell\\Modules',
44
45
  'C:\\Program Files\\WindowsPowerShell\\Modules',
45
- 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules'
46
+ 'C:\\poisoned-runtime-modules'
46
47
  ].join(';'));
47
- assert.doesNotMatch(env.PSModulePath,/poisoned-runtime-modules/);
48
+ assert.equal(env.PSModulePath.split(';')[0],'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules');
48
49
  });
@@ -4,9 +4,13 @@ import test from 'node:test';
4
4
 
5
5
  const read = path => readFile(new URL(`../${path}`, import.meta.url), 'utf8');
6
6
 
7
- test('R51 canonical Windows gate is successor-bound and preserves R50/R49 historical gates', async () => {
8
- const [pkgText, gate, evidence, historicalR50, historicalR49, deferred] = await Promise.all([
7
+ test('R53 canonical Windows gate is successor-bound and preserves R52/R51/R50/R49 historical gates', async () => {
8
+ const [pkgText, gate, evidence, historicalR52, historicalEvidenceR52, historicalR51, historicalEvidenceR51, historicalR50, historicalR49, deferred] = await Promise.all([
9
9
  read('package.json'),
10
+ read('scripts/gate-windows-r53.ps1'),
11
+ read('scripts/windows-gate-evidence-r53.mjs'),
12
+ read('scripts/gate-windows-r52.ps1'),
13
+ read('scripts/windows-gate-evidence-r52.mjs'),
10
14
  read('scripts/gate-windows-r51.ps1'),
11
15
  read('scripts/windows-gate-evidence-r51.mjs'),
12
16
  read('scripts/gate-windows-r50.ps1'),
@@ -14,16 +18,22 @@ test('R51 canonical Windows gate is successor-bound and preserves R50/R49 histor
14
18
  read('scripts/deferred-slot-operation-r46.mjs')
15
19
  ]);
16
20
  const pkg = JSON.parse(pkgText);
17
- assert.match(pkg.scripts['gate:windows'], /gate-windows-r51\.ps1$/);
18
- assert.match(gate, /DEADBYTE 0\.15\.28 \/ R51 WINDOWS GATE: PASS/);
19
- assert.match(gate, /r51-package-boundary/);
20
- assert.match(gate, /r51-focused-contracts/);
21
+ assert.match(pkg.scripts['gate:windows'], /gate-windows-r53\.ps1$/);
22
+ assert.match(gate, /DEADBYTE 0\.15\.30 \/ R53 WINDOWS GATE: PASS/);
23
+ assert.match(gate, /r53-package-boundary/);
24
+ assert.match(gate, /r53-focused-contracts/);
21
25
  assert.match(gate, /r49-remote-control-donors\.test\.mjs/);
22
26
  assert.match(gate, /desktop-runtime-r40\.test\.mjs/);
23
27
  assert.match(gate, /manifest-final-verify/);
24
- assert.match(evidence, /deadbyte\.r51-windows-gate\.v1/);
25
- assert.match(evidence, /pkg\.version === '0\.15\.28'/);
26
- assert.match(evidence, /release\.train === 'R51'/);
28
+ assert.match(evidence, /deadbyte\.r53-windows-gate\.v1/);
29
+ assert.match(evidence, /pkg\.version === '0\.15\.30'/);
30
+ assert.match(evidence, /release\.train === 'R53'/);
31
+ assert.match(evidence, /release\.parent\.version === '0\.15\.29'/);
32
+ assert.match(evidence, /four_hour_soak_completed: false/);
33
+ assert.match(historicalR52, /DEADBYTE 0\.15\.29 \/ R52 WINDOWS GATE: PASS/);
34
+ assert.match(historicalEvidenceR52, /deadbyte\.r52-windows-gate\.v1/);
35
+ assert.match(historicalR51, /DEADBYTE 0\.15\.28 \/ R51 WINDOWS GATE: PASS/);
36
+ assert.match(historicalEvidenceR51, /deadbyte\.r51-windows-gate\.v1/);
27
37
  assert.match(historicalR50, /DEADBYTE 0\.15\.27 \/ R50 WINDOWS GATE: PASS/);
28
38
  assert.match(historicalR49, /DEADBYTE 0\.15\.26 \/ R49 WINDOWS GATE: PASS/);
29
39
  assert.match(deferred, /const expectedVersion=packageIdentity\.version/);
@@ -1,31 +1,34 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
3
4
  import { readFile } from 'node:fs/promises';
4
5
 
5
6
  const root=new URL('../',import.meta.url);
6
- const read=rel=>readFile(new URL(rel,root),'utf8');
7
- const json=rel=>read(rel).then(JSON.parse);
7
+ const read=rel=>readFile(new URL(rel,root));
8
+ const json=rel=>read(rel).then(bytes=>JSON.parse(bytes.toString('utf8')));
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
8
10
 
9
- test('R51 release identity is coherent across package lock source metadata SBOM and gate',async()=>{
10
- const [pkg,lock,release,provenance,matrix,sbom,versionSource]=await Promise.all([
11
- 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')
11
+ test('R51 historical release metadata remains byte-frozen at published 0.15.28',async()=>{
12
+ const [releaseBytes,provenanceBytes,matrixBytes]=await Promise.all([
13
+ read('docs/R51-RELEASE.json'),
14
+ read('docs/R51-PROVENANCE.json'),
15
+ read('docs/R51-TEST-MATRIX.json')
12
16
  ]);
13
- assert.equal(pkg.version,'0.15.28');
14
- assert.equal(lock.version,pkg.version);
15
- assert.equal(lock.packages[''].version,pkg.version);
16
- assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.28'/);
17
- assert.equal(release.version,pkg.version);
17
+ assert.equal(sha256(releaseBytes),'b89d0eaf4f07d704d2170782f10e4647daec55b5c5bdc70473865c0dae88650b');
18
+ assert.equal(sha256(provenanceBytes),'78e6798e92c2d9134b2e66557b2de7863367124efbcecdd5c91012bfed19a48f');
19
+ assert.equal(sha256(matrixBytes),'eb4d8047fe4ead69a73b3d89f2d443b6fdd88bd39bd8455b80ecaf512cb49abf');
20
+ const release=JSON.parse(releaseBytes.toString('utf8'));
21
+ const provenance=JSON.parse(provenanceBytes.toString('utf8'));
22
+ const matrix=JSON.parse(matrixBytes.toString('utf8'));
23
+ assert.equal(release.version,'0.15.28');
18
24
  assert.equal(release.train,'R51');
19
- assert.equal(provenance.version,pkg.version);
20
- assert.equal(matrix.version,pkg.version);
25
+ assert.equal(provenance.version,'0.15.28');
26
+ assert.equal(matrix.version,'0.15.28');
21
27
  assert.equal(matrix.train,'R51');
22
- assert.equal(sbom.metadata.component.version,pkg.version);
23
- assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r51:0.15.28');
24
- assert.match(pkg.scripts['gate:windows'],/gate-windows-r51\.ps1/);
25
28
  });
26
29
 
27
- test('R51 lineage binds exact immutable published 0.15.27 parent',async()=>{
28
- const [release,provenance]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json')]);
30
+ test('R51 historical lineage binds exact 0.15.27 parent and 0.15.27 source predecessor',async()=>{
31
+ const [release,provenance]=await Promise.all([json('docs/R51-RELEASE.json'),json('docs/R51-PROVENANCE.json')]);
29
32
  const expected={
30
33
  release_id:'v0.15.27-1b1dbe24b45ad06a',
31
34
  manifest_sha256:'1b1dbe24b45ad06a62771d15f510662952f3d7c3e5aa08053c6c61511c523e47',
@@ -38,11 +41,10 @@ test('R51 lineage binds exact immutable published 0.15.27 parent',async()=>{
38
41
  assert.equal(provenance.parent_release_id,expected.release_id);
39
42
  assert.equal(provenance.parent_manifest_sha256,expected.manifest_sha256);
40
43
  assert.equal(provenance.parent_generation_manifest_sha256,expected.generation_manifest_sha256);
41
- assert.equal(provenance.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
42
44
  });
43
45
 
44
- test('R51 release advertises only implemented coherence single-flight authority and livelog hardening',async()=>{
45
- const release=await json('RELEASE.json');
46
+ test('R51 historical capability claims remain frozen',async()=>{
47
+ const release=await json('docs/R51-RELEASE.json');
46
48
  for(const key of [
47
49
  'runtime_package_auto_coherence',
48
50
  'coding_command_singleflight',
@@ -0,0 +1,65 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ async function runRevoke(t,prelude){
7
+ if(process.platform!=='win32'){t.skip('Windows controller behavior');return;}
8
+ const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
9
+ const begin=source.indexOf('function Disarm-Host(');
10
+ const end=source.indexOf('function Show-HostStatus(',begin);
11
+ assert.ok(begin>=0&&end>begin);
12
+ // Execute the maintained function, not a copied implementation. Simulate a
13
+ // filesystem that refuses deletion while Test-Path still sees the grant.
14
+ const script=`$ErrorActionPreference='Stop'
15
+ function Get-HostPolicyMetadata($Cfg) { return @{ArmFile='fixture-host-grant'} }
16
+ ${prelude}
17
+ ${source.slice(begin,end)}
18
+ try { Disarm-Host @{}; exit 0 } catch { Write-Output $_.Exception.Message; exit 7 }
19
+ `;
20
+ return spawnSync('C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
21
+ ['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(script,'utf16le').toString('base64')],
22
+ {encoding:'utf8',windowsHide:true,timeout:15000});
23
+ }
24
+
25
+ test('R52 controller never reports successful Host revoke while grant removal failed',async t=>{
26
+ const result=await runRevoke(t,`
27
+ function Remove-Item { param($LiteralPath,[switch]$Force,$ErrorAction) if($ErrorAction -eq 'Stop'){throw 'fixture access denied'} }
28
+ function Test-Path { param($LiteralPath) return $true }
29
+ `);
30
+ if(!result)return;
31
+ assert.equal(result.status,7,`Host revoke falsely succeeded: ${result.stdout} ${result.stderr}`);
32
+ assert.doesNotMatch(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
33
+ });
34
+
35
+ test('R52 controller rejects a grant that remains after nominally successful removal',async t=>{
36
+ const result=await runRevoke(t,`
37
+ function Remove-Item { param($LiteralPath,[switch]$Force,$ErrorAction) }
38
+ function Test-Path { param($LiteralPath) return $true }
39
+ `);
40
+ if(!result)return;
41
+ assert.equal(result.status,7);
42
+ assert.match(result.stdout,/grant remains present/);
43
+ assert.doesNotMatch(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
44
+ });
45
+
46
+ test('R52 controller revocation is idempotent when no grant exists',async t=>{
47
+ const result=await runRevoke(t,`
48
+ function Remove-Item { throw 'must not remove absent grant' }
49
+ function Test-Path { param($LiteralPath) return $false }
50
+ `);
51
+ if(!result)return;
52
+ assert.equal(result.status,0);
53
+ assert.match(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
54
+ });
55
+
56
+ test('R52 controller reports disarmed only after observing successful removal',async t=>{
57
+ const result=await runRevoke(t,`
58
+ $script:present=$true
59
+ function Remove-Item { param($LiteralPath,[switch]$Force,$ErrorAction) $script:present=$false }
60
+ function Test-Path { param($LiteralPath) return $script:present }
61
+ `);
62
+ if(!result)return;
63
+ assert.equal(result.status,0);
64
+ assert.match(result.stdout,/PRIVILEGED HOST PLANE DISARMED/);
65
+ });