deadbyte-mcp 0.15.36 → 0.15.37

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 (56) hide show
  1. package/MANIFEST.SHA256 +55 -27
  2. package/PROVENANCE.json +114 -87
  3. package/README.txt +12 -12
  4. package/RELEASE.json +214 -187
  5. package/SBOM.cdx.json +10 -25
  6. package/TEST-MATRIX.json +297 -258
  7. package/bridge/package.json +1 -1
  8. package/bridge/src/bridge-health-r59.mjs +4 -3
  9. package/bridge/src/bridge.mjs +71 -14
  10. package/bridge/src/canonical-json-r60.mjs +13 -0
  11. package/bridge/src/reconnect-delay-r60.mjs +31 -0
  12. package/bridge/src/remote-call-claims-r60.mjs +157 -0
  13. package/bridge/src/remote-dispatch-r60.mjs +75 -0
  14. package/controller/deadbyte-controller.ps1 +6 -1
  15. package/controller/deadbyte-process-policy.json +4 -4
  16. package/docs/R59-PROVENANCE.json +87 -0
  17. package/docs/R59-RELEASE.json +187 -0
  18. package/docs/R59-SBOM.cdx.json +68 -0
  19. package/docs/R59-TEST-MATRIX.json +258 -0
  20. package/docs/R59-bridge-package.json +13 -0
  21. package/docs/R59-package-lock.json +184 -0
  22. package/docs/R59-package.json +106 -0
  23. package/docs/R59-version.mjs +6 -0
  24. package/docs/R60-TASK-CONTRACT.md +22 -0
  25. package/package.json +5 -3
  26. package/scripts/full-regression-r60.mjs +32 -0
  27. package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
  28. package/src/capability-snapshot.mjs +6 -2
  29. package/src/deadbyte-cli.mjs +80 -34
  30. package/src/remote-authority.mjs +56 -0
  31. package/src/remote-enforcement-probe-r60.mjs +17 -0
  32. package/src/remote-live-log.mjs +19 -2
  33. package/src/remote-log-decode-r60.mjs +32 -0
  34. package/src/remote-mcp-machine-smoke-client.mjs +19 -1
  35. package/src/remote-session-guardian.mjs +20 -3
  36. package/src/tool-audit.mjs +5 -0
  37. package/src/version.mjs +1 -1
  38. package/test/r48-release-contract.test.mjs +1 -1
  39. package/test/r51-runtime-coherence.test.mjs +2 -2
  40. package/test/r58-cleanup-retention.test.mjs +9 -3
  41. package/test/r58-concurrent-recovery.test.mjs +9 -3
  42. package/test/r58-controller-live-local.test.mjs +22 -3
  43. package/test/r58-stale-owner.test.mjs +2 -2
  44. package/test/r59-release-contract.test.mjs +6 -6
  45. package/test/r60-authority-coherence.test.mjs +36 -0
  46. package/test/r60-authority-enforcement.test.mjs +28 -0
  47. package/test/r60-bridge-dispatch.test.mjs +119 -0
  48. package/test/r60-clock-discipline.test.mjs +15 -0
  49. package/test/r60-live-timeline.test.mjs +29 -0
  50. package/test/r60-reconnect-errors.test.mjs +21 -0
  51. package/test/r60-reconnect-jitter.test.mjs +18 -0
  52. package/test/r60-release-contract.test.mjs +81 -0
  53. package/test/r60-remote-call-recovery.test.mjs +170 -0
  54. package/test/r60-windows-log-decode.test.mjs +25 -0
  55. package/test/remote-live-log-r37.test.mjs +1 -1
  56. package/test/remote-session-cli.test.mjs +200 -43
@@ -0,0 +1,170 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm, writeFile, readFile } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { openRemoteCallClaims } from '../bridge/src/remote-call-claims-r60.mjs';
7
+ import {canonicalJson as bridgeCanonicalJson} from '../bridge/src/canonical-json-r60.mjs';
8
+ import {canonicalJson as coreCanonicalJson} from '../src/canonical-json.mjs';
9
+
10
+ const binding={device_id:'device-1',session_id:'session-1',request_id:'call-1',tool:'coding_file_write',
11
+ arguments:{root_id:'fixture',path:'a.txt',content:'A'},expires_at_utc:'2030-01-01T00:00:00.000Z',
12
+ release_id:'v0.15.37-test',generation_manifest_sha256:'a'.repeat(64),machine_id:'d'.repeat(64),online_epoch:'c'.repeat(64),classification:'mutating'};
13
+
14
+ async function withStore(fn){
15
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r60-claims-'));
16
+ try{await fn(openRemoteCallClaims({root:dir,clock:()=>Date.parse('2029-01-01T00:00:00.000Z')}));}
17
+ finally{await rm(dir,{recursive:true,force:true});}
18
+ }
19
+
20
+ test('Bridge and Core canonical argument encoding remain byte-identical',()=>{
21
+ const value={z:[{b:2,a:1}],a:{thai:'ทดสอบ',x:null}};
22
+ assert.equal(bridgeCanonicalJson(value),coreCanonicalJson(value));
23
+ });
24
+
25
+ test('claim refuses missing machine/epoch/classification identity before execution',async()=>withStore(async store=>{
26
+ await assert.rejects(store.claim({...binding,machine_id:undefined}),/machine_id/);
27
+ await assert.rejects(store.claim({...binding,online_epoch:undefined}),/online epoch/);
28
+ await assert.rejects(store.claim({...binding,classification:undefined}),/classification/);
29
+ }));
30
+
31
+ test('duplicate request identity with same canonical payload never receives a second execution claim',async()=>withStore(async store=>{
32
+ const first=await store.claim(binding);
33
+ assert.equal(first.owner,true);
34
+ const second=await store.claim({...binding,arguments:{content:'A',path:'a.txt',root_id:'fixture'}});
35
+ assert.equal(second.owner,false);
36
+ assert.equal(second.state,'CLAIMED');
37
+ await store.dispatch(first.claim_id);
38
+ const uncertain=await store.claim(binding);
39
+ assert.equal(uncertain.owner,false);
40
+ assert.equal(uncertain.state,'EXECUTING');
41
+ assert.equal(uncertain.side_effect_state,'may_have_started');
42
+ }));
43
+
44
+ test('changed payload and expired request fail closed',async()=>withStore(async store=>{
45
+ await store.claim(binding);
46
+ await assert.rejects(store.claim({...binding,arguments:{...binding.arguments,content:'B'}}),/request_id_conflict/);
47
+ await assert.rejects(store.claim({...binding,request_id:'expired',expires_at_utc:'2020-01-01T00:00:00.000Z'}),/expired/);
48
+ }));
49
+
50
+ test('same JSON-RPC request id in a distinct session is not a duplicate',async()=>withStore(async store=>{
51
+ const first=await store.claim(binding);
52
+ const second=await store.claim({...binding,session_id:'session-2'});
53
+ assert.equal(first.owner,true);
54
+ assert.equal(second.owner,true);
55
+ assert.notEqual(first.claim_id,second.claim_id);
56
+ }));
57
+
58
+ test('an already claimed call cannot acquire a fresh deadline after its original expiry',async()=>{
59
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r60-expiry-'));
60
+ let now=Date.parse('2029-01-01T00:00:00.000Z');
61
+ try{
62
+ const store=openRemoteCallClaims({root:dir,clock:()=>now});
63
+ await store.claim({...binding,expires_at_utc:'2029-01-01T00:00:01.000Z'});
64
+ now+=2000;
65
+ await assert.rejects(store.claim({...binding,expires_at_utc:'2029-01-02T00:00:00.000Z'}),/expired/);
66
+ }finally{await rm(dir,{recursive:true,force:true});}
67
+ });
68
+
69
+ test('stable idempotency key survives transport session and JSON-RPC id changes',async()=>withStore(async store=>{
70
+ const first=await store.claim({...binding,idempotency_key:'goal-write-1'});
71
+ await store.dispatch(first.claim_id);
72
+ await store.terminal(first.claim_id,{status:'ok',receipt_sha256:'b'.repeat(64)});
73
+ const replay=await store.claim({...binding,idempotency_key:'goal-write-1',session_id:'session-2',request_id:'call-99'});
74
+ assert.equal(replay.owner,false);
75
+ assert.equal(replay.state,'TERMINAL');
76
+ assert.equal(replay.claim_id,first.claim_id);
77
+ assert.deepEqual(replay.result,{status:'ok',receipt_sha256:'b'.repeat(64)});
78
+ await assert.rejects(store.claim({...binding,idempotency_key:'goal-write-1',session_id:'session-3',request_id:'call-100',arguments:{...binding.arguments,content:'B'}}),/idempotency_key_conflict/);
79
+ }));
80
+
81
+ test('same JSON-RPC id with a different stable key is a distinct logical mutation',async()=>withStore(async store=>{
82
+ const first=await store.claim({...binding,idempotency_key:'goal-write-1'});
83
+ const second=await store.claim({...binding,idempotency_key:'goal-write-2'});
84
+ assert.equal(first.owner,true);
85
+ assert.equal(second.owner,true);
86
+ assert.notEqual(first.claim_id,second.claim_id);
87
+ }));
88
+
89
+ test('concurrent duplicate claims have one owner and terminal result is replayable without dispatch',async()=>withStore(async store=>{
90
+ const claims=await Promise.all(Array.from({length:12},()=>store.claim(binding)));
91
+ assert.equal(claims.filter(item=>item.owner).length,1);
92
+ const id=claims[0].claim_id;
93
+ await store.dispatch(id);
94
+ await assert.rejects(store.dispatch(id),/already dispatched/);
95
+ await store.terminal(id,{status:'ok',receipt_sha256:'b'.repeat(64)});
96
+ const replay=await store.claim(binding);
97
+ assert.equal(replay.owner,false);
98
+ assert.equal(replay.state,'TERMINAL');
99
+ assert.deepEqual(replay.result,{status:'ok',receipt_sha256:'b'.repeat(64)});
100
+ }));
101
+
102
+ test('corrupted terminal bytes fail closed instead of replaying altered success',async()=>{
103
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r60-corrupt-'));
104
+ try{
105
+ const store=openRemoteCallClaims({root:dir,clock:()=>Date.parse('2029-01-01T00:00:00.000Z')});
106
+ const first=await store.claim(binding);
107
+ await store.dispatch(first.claim_id);
108
+ await store.terminal(first.claim_id,{status:'ok',receipt_sha256:'b'.repeat(64)});
109
+ const file=path.join(dir,first.claim_id.slice(0,2),`${first.claim_id}.terminal.json`);
110
+ const original=await readFile(file,'utf8');
111
+ await writeFile(file,original.replace('"status":"ok"','"status":"forged"'));
112
+ await assert.rejects(store.claim(binding),/terminal.*hash|corrupt/i);
113
+ }finally{await rm(dir,{recursive:true,force:true});}
114
+ });
115
+
116
+ test('corrupted required claim record fails closed before any duplicate dispatch',async()=>{
117
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r60-corrupt-'));
118
+ try{
119
+ const store=openRemoteCallClaims({root:dir,clock:()=>Date.parse('2029-01-01T00:00:00.000Z')});
120
+ const first=await store.claim(binding);
121
+ const file=path.join(dir,first.claim_id.slice(0,2),`${first.claim_id}.claim.json`);
122
+ const original=await readFile(file,'utf8');
123
+ await writeFile(file,original.replace('"tool":"coding_file_write"','"tool":"host_exec"'));
124
+ await assert.rejects(store.claim(binding),/claim.*hash|corrupt/i);
125
+ }finally{await rm(dir,{recursive:true,force:true});}
126
+ });
127
+
128
+ test('startup recovery marks only post-dispatch mutations interrupted and leaves pre-effect claims recoverable',async()=>withStore(async store=>{
129
+ const pending=await store.claim({...binding,request_id:'pre-effect'});
130
+ const uncertain=await store.claim({...binding,request_id:'post-dispatch'});
131
+ await store.dispatch(uncertain.claim_id);
132
+ const batch=await store.recover({limit:100});
133
+ assert.equal(batch.records.length,2);
134
+ assert.equal((await store.state(pending.claim_id)).state,'CLAIMED');
135
+ const after=await store.state(uncertain.claim_id);
136
+ assert.equal(after.state,'INTERRUPTED');
137
+ assert.equal(after.side_effect_state,'unknown');
138
+ }));
139
+
140
+
141
+ test('recovery inventory excludes terminal and expired claims while retaining eligible pre-effect work',async()=>{
142
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r60-recovery-filter-'));
143
+ let now=Date.parse('2029-01-01T00:00:00.000Z');
144
+ try{
145
+ const store=openRemoteCallClaims({root:dir,clock:()=>now});
146
+ const terminal=await store.claim({...binding,request_id:'terminal-history',expires_at_utc:'2030-01-01T00:00:00.000Z'});
147
+ await store.dispatch(terminal.claim_id);
148
+ await store.terminal(terminal.claim_id,{status:'ok',receipt_sha256:'b'.repeat(64)});
149
+ const expired=await store.claim({...binding,request_id:'expired-history',expires_at_utc:'2029-01-01T00:00:01.000Z'});
150
+ const pending=await store.claim({...binding,request_id:'eligible-pending',expires_at_utc:'2030-01-01T00:00:00.000Z'});
151
+ now+=2000;
152
+ const batch=await store.recover({limit:100});
153
+ const ids=new Set(batch.records.map(item=>item.claim_id));
154
+ assert.equal(ids.has(terminal.claim_id),false,'terminal history is not recovery work');
155
+ assert.equal(ids.has(expired.claim_id),false,'expired work must not consume recovery budget');
156
+ assert.equal(ids.has(pending.claim_id),true,'eligible pre-effect claim must remain recoverable');
157
+ }finally{await rm(dir,{recursive:true,force:true});}
158
+ });
159
+
160
+
161
+ test('stable idempotency key cannot replay execution evidence across machine online epoch rotation',async()=>withStore(async store=>{
162
+ const keyed={...binding,idempotency_key:'epoch-bound-write'};
163
+ const first=await store.claim(keyed);
164
+ await store.dispatch(first.claim_id);
165
+ await store.terminal(first.claim_id,{status:'ok',receipt_sha256:'b'.repeat(64)});
166
+ await assert.rejects(
167
+ store.claim({...keyed,session_id:'session-after-revoke',request_id:'retry-after-revoke',online_epoch:'e'.repeat(64)}),
168
+ /epoch|identity drift/i
169
+ );
170
+ }));
@@ -0,0 +1,25 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { decodeRemoteLogBytes, boundedRemoteDiagnostic, safeRemoteLogPrefixLength } from '../src/remote-log-decode-r60.mjs';
4
+
5
+ test('known Windows log source decodes UTF-16LE BOM without corrupting text',()=>{
6
+ const bytes=Buffer.concat([Buffer.from([0xff,0xfe]),Buffer.from('[TOOL] ✓ host_file_read\r\n','utf16le')]);
7
+ assert.equal(decodeRemoteLogBytes('controller-start.stdout.log',bytes),'[TOOL] ✓ host_file_read\r\n');
8
+ assert.notEqual(decodeRemoteLogBytes('segment-00000001.jsonl',bytes),'[TOOL] ✓ host_file_read\r\n');
9
+ });
10
+
11
+ test('diagnostic preview removes NUL/control injection and remains bounded',()=>{
12
+ const value=boundedRemoteDiagnostic('ok\0secret\r\n'+('x'.repeat(3000)),128);
13
+ assert.ok(!value.includes('\0'));
14
+ assert.ok(value.length<=131);
15
+ assert.equal(value.split('\n').length,1);
16
+ });
17
+
18
+ test('bounded log pages do not split UTF-8 or UTF-16 characters',()=>{
19
+ const utf8=Buffer.from('A😀','utf8');
20
+ assert.equal(safeRemoteLogPrefixLength(utf8.subarray(0,4),'utf8'),1);
21
+ assert.equal(safeRemoteLogPrefixLength(utf8,'utf8'),5);
22
+ const utf16=Buffer.from('A😀','utf16le');
23
+ assert.equal(safeRemoteLogPrefixLength(utf16.subarray(0,4),'utf16le'),2);
24
+ assert.equal(safeRemoteLogPrefixLength(utf16,'utf16le'),6);
25
+ });
@@ -33,7 +33,7 @@ test('R40 long or mutating call gets start line and terminal error carries sanit
33
33
 
34
34
  test('R40 heartbeat burst folds repeated status calls instead of printing every round-trip',()=>{
35
35
  let now=1000;
36
- const tracker=createLiveTimelineTracker({clock:()=>now,heartbeatBurstLimit:5,heartbeatWindowMs:3000});
36
+ const tracker=createLiveTimelineTracker({clock:()=>now,foldHeartbeats:true,heartbeatBurstLimit:5,heartbeatWindowMs:3000});
37
37
  for(let i=0;i<5;i++){
38
38
  const id='ping-'+String(i).padStart(12,'0');
39
39
  tracker.observe({event_type:'tool.request',tool:'machine_ping',request_id:id,args:{nonce:'mcpbench-'+i}},{sequence:30+i*2});
@@ -24,7 +24,12 @@ async function fixture({failCommand='',statusOnline=false}={}){
24
24
  await mkdir(bin,{recursive:true});
25
25
  const launcher=path.join(bin,'DEADBYTE-MCP.ps1');
26
26
  const ps1=`param([string]$Command)\n$ErrorActionPreference='Stop'\nfor($i=0;$i -lt 20;$i++){ try { [IO.File]::AppendAllText($env:DEADBYTE_REMOTE_TEST_TRACE,(\"$Command\" + [Environment]::NewLine),[Text.Encoding]::UTF8); break } catch { if($i -eq 19){ throw }; Start-Sleep -Milliseconds 10 } }\nif ($env:DEADBYTE_REMOTE_TEST_FAIL -and $Command -eq $env:DEADBYTE_REMOTE_TEST_FAIL) { $runtime=Join-Path $env:DEADBYTE_STATE_ROOT 'runtime'; New-Item -ItemType Directory -Path $runtime -Force | Out-Null; $failure=@{ schema='deadbyte.controller-start-failure.v1'; command=$Command; failed_at_utc='2026-09-26T00:00:00.000Z'; message='fixture controller evidence detail' } | ConvertTo-Json -Compress; [IO.File]::WriteAllText((Join-Path $runtime 'controller-start-failure.json'),$failure,[Text.Encoding]::UTF8); Write-Error \"forced $Command failure\"; exit 9 }\nswitch ($Command) {\n 'start' { if ($env:DEADBYTE_REMOTE_TEST_SPAWN_CHILD -eq '1') { $node=(Get-Command node.exe).Source; $spawned=Start-Process -FilePath $node -ArgumentList @('-e','setTimeout(()=>process.exit(0),10000)') -PassThru -WindowStyle Hidden -RedirectStandardOutput $env:DEADBYTE_REMOTE_TEST_SPAWN_STDOUT -RedirectStandardError $env:DEADBYTE_REMOTE_TEST_SPAWN_STDERR; if ($env:DEADBYTE_REMOTE_TEST_SPAWN_PID) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_SPAWN_PID,[string]$spawned.Id,[Text.Encoding]::UTF8) } }; if ($env:DEADBYTE_REMOTE_TEST_STARTED) { [IO.File]::WriteAllText($env:DEADBYTE_REMOTE_TEST_STARTED,'1',[Text.Encoding]::UTF8) }; Write-Host 'DEADBYTE MCP = ONLINE' }\n 'status' { if ($env:DEADBYTE_REMOTE_TEST_STATUS_ONLINE -eq '1' -or ($env:DEADBYTE_REMOTE_TEST_STARTED -and (Test-Path -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED))) { Write-Host 'DEADBYTE MCP = ONLINE'; Write-Host 'Bridge PID : 1001 [verified]'; Write-Host 'Cloud PID : 1002 [verified]'; Write-Host 'Local ready : True'; Write-Host 'Loop supervisor: RUNNING [verified]' } else { Write-Host 'DEADBYTE MCP = OFFLINE' } }\n 'processstatus' { Write-Host 'Process plane : ARMED [standing-grant]' }\n 'codingstatus' { Write-Host 'Coding plane : ARMED [standing-grant]' }\n 'autonomousstatus' { Write-Host 'Autonomous : ARMED [standing-grant]' }\n 'desktopstatus' { Write-Host 'Desktop plane : DISARMED [disarmed]' }\n 'hoststatus' { Write-Host 'Host plane : DISARMED [disarmed]' }\n 'park' { if ($env:DEADBYTE_REMOTE_TEST_STARTED) { Remove-Item -LiteralPath $env:DEADBYTE_REMOTE_TEST_STARTED -Force -ErrorAction SilentlyContinue }; Write-Host 'DEADBYTE MCP = PARKED' }\n default { Write-Host \"$Command OK\" }\n}\nexit 0\n`;
27
- await writeFile(launcher,ps1,'utf8');
27
+ const structured=`function PlaneArmed([string]$name,[bool]$default) { $base=Join-Path $env:DEADBYTE_STATE_ROOT ('runtime\\' + $name); if (Test-Path -LiteralPath ($base + '.disarmed')) { return $false }; if (Test-Path -LiteralPath ($base + '.armed')) { return $true }; return $default }
28
+ if ($Command -eq 'status' -and $Json) { $names=@('process','coding','autonomous','desktop','host'); $authority=@{}; foreach($name in $names){ $initial=($name -in @('process','coding','autonomous')) -or (($name -eq 'host') -and ($env:DEADBYTE_REMOTE_TEST_HOST_ARMED -eq '1')) -or (($name -eq 'desktop') -and ($env:DEADBYTE_REMOTE_TEST_DESKTOP_ARMED -eq '1')); $armed=PlaneArmed $name $initial; if($name -eq 'host' -and (($env:DEADBYTE_REMOTE_TEST_JSON_HOST_LIE_ALWAYS -eq '1') -or (($env:DEADBYTE_REMOTE_TEST_JSON_HOST_LIE -eq '1') -and (Test-Path -LiteralPath (Join-Path $env:DEADBYTE_STATE_ROOT 'runtime\\host.disarm-attempt'))))){$armed=$false}; $authority[$name]=@{ configured=$true; armed=$armed; reason=$(if($armed){'standing-grant'}else{'disarmed'}) } }; @{schema='deadbyte.controller-observation.v1';state='ONLINE';local_ready=$true;core_root='fixture';bridge=@{alive=$true;reason='verified'};core=@{alive=$true;reason='verified'};cloud=@{alive=$true;reason='verified'};supervisor=@{running=$true};authority=$authority} | ConvertTo-Json -Depth 5 -Compress; exit 0 }
29
+ if ($Command -eq 'machinetest' -and $env:DEADBYTE_REMOTE_PROBE_MODE) { $hostArmed=PlaneArmed 'host' ($env:DEADBYTE_REMOTE_TEST_HOST_ARMED -eq '1'); $desktopArmed=PlaneArmed 'desktop' ($env:DEADBYTE_REMOTE_TEST_DESKTOP_ARMED -eq '1'); $wantHost=$env:DEADBYTE_REMOTE_PROBE_MODE -in @('host','full'); $wantDesktop=$env:DEADBYTE_REMOTE_PROBE_MODE -in @('desktop','full'); if($hostArmed -ne $wantHost -or $desktopArmed -ne $wantDesktop){ Write-Error 'remote enforcement probe mismatch'; exit 9 } }
30
+ if ($Command -match '^(arm|disarm)(process|coding|autonomous|desktop|host)$') { $name=$Matches[2]; $base=Join-Path $env:DEADBYTE_STATE_ROOT ('runtime\\' + $name); New-Item -ItemType Directory -Path (Split-Path $base) -Force | Out-Null; if ($Matches[1] -eq 'arm') { [IO.File]::WriteAllText(($base+'.armed'),'1'); Remove-Item -LiteralPath ($base+'.disarmed') -Force -ErrorAction SilentlyContinue } elseif ($env:DEADBYTE_REMOTE_TEST_DISARM_NOOP -ne $name) { [IO.File]::WriteAllText(($base+'.disarmed'),'1'); Remove-Item -LiteralPath ($base+'.armed') -Force -ErrorAction SilentlyContinue } else { [IO.File]::WriteAllText(($base+'.disarm-attempt'),'1') } }
31
+ switch ($Command) {`;
32
+ await writeFile(launcher,ps1.replace('param([string]$Command)','param([string]$Command,[switch]$Json)').replace('switch ($Command) {',()=>structured),'utf8');
28
33
  return {dir,state,trace,env:{...process.env,DEADBYTE_STATE_ROOT:state,DEADBYTE_REMOTE_TEST_TRACE:trace,DEADBYTE_REMOTE_TEST_FAIL:failCommand,DEADBYTE_REMOTE_TEST_STATUS_ONLINE:statusOnline?'1':'0',DEADBYTE_REMOTE_TEST_STARTED:startedMarker,DEADBYTE_REMOTE_TEST_SPAWN_STDOUT:spawnedStdout,DEADBYTE_REMOTE_TEST_SPAWN_STDERR:spawnedStderr,DEADBYTE_REMOTE_TEST_SPAWN_PID:spawnedPid},spawnedPid};
29
34
  }
30
35
 
@@ -76,18 +81,15 @@ winTest('bare remote reuses a fully verified ONLINE runtime without invoking blo
76
81
  try{
77
82
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
78
83
  const output=await collect(child);
79
- const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'),{timeoutMs:8000});
80
- assert.deepEqual(ready.slice(0,9),[
81
- 'status','armprocess','armcoding','armautonomous',
82
- 'processstatus','codingstatus','autonomousstatus','desktopstatus','hoststatus'
83
- ]);
84
+ const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=3,{timeoutMs:8000});
85
+ assert.deepEqual(ready.slice(0,6),['status','status','armprocess','armcoding','armautonomous','status'],output.stderr);
84
86
  assert.doesNotMatch(ready.join(','),/(^|,)start(,|$)/);
85
87
  assert.equal(child.exitCode,null,'remote session must remain foreground after reusing ONLINE runtime');
86
88
  assert.match(output.stdout,/Runtime already ONLINE; reusing verified bridge\/cloud\/supervisor/);
87
89
  const authDeadline=Date.now()+3000;
88
- while(Date.now()<authDeadline&&!/\[AUTH\] ARMED: process, coding, autonomous \| desktop, host DISARMED/.test(output.stdout)) await sleep(50);
90
+ while(Date.now()<authDeadline&&!/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/.test(output.stdout)) await sleep(50);
89
91
  assert.match(output.stdout,/\[DISPLAY\] ASCII fallback: stdout_not_tty/);
90
- assert.match(output.stdout,/\[AUTH\] ARMED: process, coding, autonomous \| desktop, host DISARMED/);
92
+ assert.match(output.stdout,/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/);
91
93
  child.kill('SIGINT');
92
94
  await output.closed;
93
95
  }finally{
@@ -96,6 +98,141 @@ winTest('bare remote reuses a fully verified ONLINE runtime without invoking blo
96
98
  }
97
99
  });
98
100
 
101
+ winTest('bounded remote disarms a stale standing Host grant before READY',async()=>{
102
+ const fx=await fixture({statusOnline:true});
103
+ fx.env.DEADBYTE_REMOTE_TEST_HOST_ARMED='1';
104
+ let child=null;
105
+ try{
106
+ child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
107
+ const output=await collect(child);
108
+ const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=3,{timeoutMs:8000});
109
+ assert.ok(commands.indexOf('disarmhost')>commands.indexOf('status'));
110
+ const deadline=Date.now()+3000;
111
+ while(Date.now()<deadline&&!/\[READY\]/.test(output.stdout)) await sleep(50);
112
+ assert.match(output.stdout,/\[READY\]/,output.stderr);
113
+ child.stdin.end();
114
+ assert.equal((await output.closed).code,0,output.stderr);
115
+ }finally{
116
+ if(child&&child.exitCode===null) child.kill('SIGKILL');
117
+ await rm(fx.dir,{recursive:true,force:true});
118
+ }
119
+ });
120
+
121
+ winTest('remote --full verifies all five planes before READY and revokes them on disconnect',async()=>{
122
+ const fx=await fixture({statusOnline:true});
123
+ let child=null;
124
+ try{
125
+ child=spawn(process.execPath,[cliPath,'remote','--full'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
126
+ const output=await collect(child);
127
+ const deadline=Date.now()+8000;
128
+ while(Date.now()<deadline&&!/\[READY\]/.test(output.stdout)) await sleep(50);
129
+ assert.match(output.stdout,/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop ARMED \| host ARMED/,output.stderr);
130
+ assert.match(output.stdout,/\[READY\]/);
131
+ const before=await traceCommands(fx.trace);
132
+ assert.ok(before.includes('armdesktop')&&before.includes('armhost'));
133
+ child.stdin.end();
134
+ assert.equal((await output.closed).code,0,output.stderr);
135
+ const after=await traceCommands(fx.trace);
136
+ assert.deepEqual(after.slice(-8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
137
+ }finally{
138
+ if(child?.exitCode===null) child.kill('SIGKILL');
139
+ await rm(fx.dir,{recursive:true,force:true});
140
+ }
141
+ });
142
+
143
+ for(const [flag,armedPlane,disarmedPlane] of [['--desktop','desktop','host'],['--host','host','desktop']]){
144
+ winTest(`remote ${flag} arms only the requested extra plane`,async()=>{
145
+ const fx=await fixture({statusOnline:true});
146
+ let child=null;
147
+ try{
148
+ child=spawn(process.execPath,[cliPath,'remote',flag],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
149
+ const output=await collect(child);
150
+ const deadline=Date.now()+8000;
151
+ while(Date.now()<deadline&&!new RegExp(`${armedPlane} ARMED`).test(output.stdout)) await sleep(50);
152
+ assert.match(output.stdout,/\[READY\]/,output.stderr);
153
+ assert.match(output.stdout,new RegExp(`${armedPlane} ARMED`));
154
+ assert.match(output.stdout,new RegExp(`${disarmedPlane} DISARMED`));
155
+ const commands=await traceCommands(fx.trace);
156
+ assert.ok(commands.includes(`arm${armedPlane}`));
157
+ assert.ok(!commands.includes(`arm${disarmedPlane}`));
158
+ child.stdin.end();
159
+ assert.equal((await output.closed).code,0,output.stderr);
160
+ }finally{
161
+ if(child?.exitCode===null) child.kill('SIGKILL');
162
+ await rm(fx.dir,{recursive:true,force:true});
163
+ }
164
+ });
165
+ }
166
+
167
+ winTest('conflicting remote authority modes fail before controller mutation',async()=>{
168
+ const fx=await fixture({statusOnline:true});
169
+ try{
170
+ const child=spawn(process.execPath,[cliPath,'remote','--host','--desktop'],{env:fx.env,stdio:['ignore','pipe','pipe'],windowsHide:true});
171
+ const output=await collect(child);
172
+ assert.notEqual((await output.closed).code,0);
173
+ assert.match(output.stderr,/mutually exclusive/);
174
+ assert.deepEqual(await traceCommands(fx.trace),[]);
175
+ }finally{await rm(fx.dir,{recursive:true,force:true});}
176
+ });
177
+
178
+ winTest('remote refuses READY when public signed MCP execution probe fails',async()=>{
179
+ const fx=await fixture({statusOnline:true,failCommand:'machinetest'});
180
+ let child=null;
181
+ try{
182
+ child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
183
+ const output=await collect(child);
184
+ const ended=await Promise.race([output.closed,sleep(6500).then(()=>({timeout:true}))]);
185
+ assert.equal(ended.timeout,undefined,'remote did not fail closed after execution probe failure');
186
+ assert.notEqual(ended.code,0);
187
+ assert.doesNotMatch(output.stdout,/\[READY\]|\[OK\] ONLINE/);
188
+ assert.match(output.stderr,/machinetest|execution probe/i);
189
+ }finally{
190
+ if(child?.exitCode===null) child.kill('SIGKILL');
191
+ await rm(fx.dir,{recursive:true,force:true});
192
+ }
193
+ });
194
+
195
+ winTest('remote refuses READY when controller snapshot lies about Host enforcement',async()=>{
196
+ const fx=await fixture({statusOnline:true});
197
+ fx.env.DEADBYTE_REMOTE_TEST_HOST_ARMED='1';
198
+ fx.env.DEADBYTE_REMOTE_TEST_JSON_HOST_LIE_ALWAYS='1';
199
+ let child=null;
200
+ try{
201
+ child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
202
+ const output=await collect(child);
203
+ const ended=await Promise.race([output.closed,sleep(6500).then(()=>({timeout:true}))]);
204
+ assert.equal(ended.timeout,undefined,'remote did not reject split authority truth');
205
+ assert.notEqual(ended.code,0);
206
+ assert.doesNotMatch(output.stdout,/\[READY\]|\[OK\] ONLINE/);
207
+ assert.match(output.stderr,/machinetest|enforcement/i);
208
+ }finally{
209
+ if(child?.exitCode===null) child.kill('SIGKILL');
210
+ await rm(fx.dir,{recursive:true,force:true});
211
+ }
212
+ });
213
+
214
+ winTest('foreground cleanup retains ownership when status lies about a no-op Host revoke',async()=>{
215
+ const fx=await fixture({statusOnline:true});
216
+ fx.env.DEADBYTE_REMOTE_TEST_DISARM_NOOP='host';
217
+ fx.env.DEADBYTE_REMOTE_TEST_JSON_HOST_LIE='1';
218
+ let child=null;
219
+ try{
220
+ child=spawn(process.execPath,[cliPath,'remote','--full'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
221
+ const output=await collect(child);
222
+ const deadline=Date.now()+8000;
223
+ while(Date.now()<deadline&&!/\[READY\]/.test(output.stdout)) await sleep(50);
224
+ assert.match(output.stdout,/\[READY\]/,output.stderr);
225
+ child.stdin.end();
226
+ assert.notEqual((await output.closed).code,0,'cleanup must fail closed');
227
+ assert.match(output.stderr,/cleanup.*verification|enforcement/i);
228
+ assert.equal(JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8')).schema,'deadbyte.remote-session.v2');
229
+ assert.ok(!(await traceCommands(fx.trace)).includes('park'));
230
+ }finally{
231
+ if(child?.exitCode===null) child.kill('SIGKILL');
232
+ await rm(fx.dir,{recursive:true,force:true});
233
+ }
234
+ });
235
+
99
236
  winTest('bare remote PARKED startup does not wait for a long-lived child holding inherited pipe handles',async()=>{
100
237
  const fx=await fixture();
101
238
  fx.env.DEADBYTE_REMOTE_TEST_SPAWN_CHILD='1';
@@ -104,15 +241,14 @@ winTest('bare remote PARKED startup does not wait for a long-lived child holding
104
241
  const startedAt=Date.now();
105
242
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
106
243
  const output=await collect(child);
107
- const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'),{timeoutMs:8000});
244
+ const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4,{timeoutMs:8000});
108
245
  const elapsed=Date.now()-startedAt;
109
- assert.deepEqual(ready.slice(0,11),[
110
- 'status','start','status','armprocess','armcoding','armautonomous',
111
- 'processstatus','codingstatus','autonomousstatus','desktopstatus','hoststatus'
112
- ]);
246
+ assert.deepEqual(ready.slice(0,8),['status','start','status','status','armprocess','armcoding','armautonomous','status']);
113
247
  assert.ok(elapsed<7000,`PARKED startup waited on inherited child pipe handles: ${elapsed}ms`);
114
248
  assert.equal(child.exitCode,null,'remote session must remain foreground after PARKED startup');
115
249
  assert.match(output.stdout,/\[BOOT\] Starting bridge\/runtime\.\.\./);
250
+ const readyDeadline=Date.now()+3000;
251
+ while(Date.now()<readyDeadline&&!/\[OK\] ONLINE/.test(output.stdout)) await sleep(50);
116
252
  assert.match(output.stdout,/\[OK\] ONLINE/);
117
253
  child.stdin.end();
118
254
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
@@ -132,14 +268,11 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
132
268
  try{
133
269
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
134
270
  const output=await collect(child);
135
- const ready=await waitFor(fx.trace,commands=>commands.slice(0,10).join(',')==='status,start,status,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
136
- assert.deepEqual(ready.slice(0,11),[
137
- 'status','start','status','armprocess','armcoding','armautonomous',
138
- 'processstatus','codingstatus','autonomousstatus','desktopstatus','hoststatus'
139
- ]);
271
+ const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4);
272
+ assert.deepEqual(ready.slice(0,8),['status','start','status','status','armprocess','armcoding','armautonomous','status']);
140
273
  assert.equal(child.exitCode,null,'remote session must remain foreground after becoming ready');
141
274
  const tailDeadline=Date.now()+3000;
142
- while(Date.now()<tailDeadline&&!/\[AUTH\] ARMED: process, coding, autonomous \| desktop, host DISARMED/.test(output.stdout)) await sleep(50);
275
+ while(Date.now()<tailDeadline&&!/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/.test(output.stdout)) await sleep(50);
143
276
  assert.match(output.stdout,/\[DISPLAY\] ASCII fallback: stdout_not_tty/);
144
277
  assert.match(output.stdout,/\[DEVICE\] Starting DEADBYTE MCP/);
145
278
  assert.match(output.stdout,/\[BRIDGE\] Local bridge connected/);
@@ -148,7 +281,7 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
148
281
  assert.match(output.stdout,/\[BOOT\] Starting local \+ remote runtime/);
149
282
  assert.match(output.stdout,/\[OK\] ONLINE/);
150
283
  assert.match(output.stdout,/\[READY\] DEADBYTE MCP online/);
151
- assert.match(output.stdout,/\[AUTH\] ARMED: process, coding, autonomous \| desktop, host DISARMED/);
284
+ assert.match(output.stdout,/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/);
152
285
  assert.match(output.stdout,/\[LOG\] Human live timeline\. Transport IN\/OUT hidden; full replay\/audit detail remains on disk\./);
153
286
  assert.doesNotMatch(output.stdout,/Process plane\s*:/);
154
287
  assert.doesNotMatch(output.stdout,/Coding plane\s*:/);
@@ -194,7 +327,7 @@ winTest('bare remote uses one unified live timeline, keeps authority semantics,
194
327
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
195
328
  assert.equal(ended.timeout,undefined,'remote session did not exit after foreground input closed');
196
329
  const commands=await traceCommands(fx.trace);
197
- assert.deepEqual(commands.slice(-6),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park']);
330
+ assert.deepEqual(commands.slice(-8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
198
331
  assert.match(output.stdout,/^DEADBYTE MCP$/m);
199
332
  assert.match(output.stdout,/Human live timeline/i);
200
333
  assert.match(output.stdout,/\[AUTH\] Revoking session authority/);
@@ -212,7 +345,7 @@ winTest('remote --resume replays sanitized tool-call and terminal result events
212
345
  try{
213
346
  first=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
214
347
  const firstOutput=await collect(first);
215
- await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
348
+ await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4);
216
349
  const liveDeadline=Date.now()+3000;
217
350
  while(Date.now()<liveDeadline&&!/\[LOG\] Human live timeline/.test(firstOutput.stdout)) await sleep(50);
218
351
  const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
@@ -275,7 +408,7 @@ winTest('remote --verbose enables sanitized request detail while preserving fore
275
408
  try{
276
409
  child=spawn(process.execPath,[cliPath,'remote','--verbose'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
277
410
  const output=await collect(child);
278
- await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
411
+ await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4);
279
412
  assert.equal(child.exitCode,null,'verbose remote remains a foreground session');
280
413
  assert.match(output.stdout,/\[LOG\] Verbose live timeline\. Full sanitized request detail enabled/);
281
414
  const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
@@ -293,7 +426,7 @@ winTest('remote --verbose enables sanitized request detail while preserving fore
293
426
  assert.equal(ended.timeout,undefined);
294
427
  assert.equal(ended.code,0,output.stderr);
295
428
  const commands=await traceCommands(fx.trace);
296
- assert.deepEqual(commands.slice(-6),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park']);
429
+ assert.deepEqual(commands.slice(-8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
297
430
  }finally{
298
431
  if(child?.exitCode===null) child.kill('SIGKILL');
299
432
  await rm(fx.dir,{recursive:true,force:true});
@@ -306,11 +439,8 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
306
439
  try{
307
440
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
308
441
  const output=await collect(child);
309
- const ready=await waitFor(fx.trace,commands=>commands.slice(0,10).join(',')==='status,start,status,armprocess,armcoding,armautonomous,processstatus,codingstatus,autonomousstatus,hoststatus');
310
- assert.deepEqual(ready.slice(0,11),[
311
- 'status','start','status','armprocess','armcoding','armautonomous',
312
- 'processstatus','codingstatus','autonomousstatus','desktopstatus','hoststatus'
313
- ]);
442
+ const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4);
443
+ assert.deepEqual(ready.slice(0,8),['status','start','status','status','armprocess','armcoding','armautonomous','status']);
314
444
  const markerPath=path.join(fx.state,'runtime','remote-session.json');
315
445
  const markerDeadline=Date.now()+3000;
316
446
  let marker=null;
@@ -327,8 +457,8 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
327
457
  assert.equal(killed,true,'failed to hard-kill remote owner');
328
458
  await Promise.race([output.closed,sleep(3000)]);
329
459
 
330
- const cleaned=await waitFor(fx.trace,commands=>commands.slice(-6).join(',')==='disarmautonomous,disarmcoding,disarmprocess,disarmhost,park',{timeoutMs:10000});
331
- assert.deepEqual(cleaned.slice(-6),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park']);
460
+ const cleaned=await waitFor(fx.trace,commands=>commands.slice(-8).join(',')==='disarmautonomous,disarmcoding,disarmprocess,disarmdesktop,disarmhost,status,machinetest,park',{timeoutMs:10000});
461
+ assert.deepEqual(cleaned.slice(-8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
332
462
  const markerGoneDeadline=Date.now()+3000;
333
463
  let markerGone=false;
334
464
  while(Date.now()<markerGoneDeadline){
@@ -342,6 +472,30 @@ winTest('detached guardian revokes authority, parks, and removes the session mar
342
472
  }
343
473
  });
344
474
 
475
+ winTest('guardian retains ownership marker when disarm exits zero but Host remains armed',async()=>{
476
+ const fx=await fixture({statusOnline:true});
477
+ fx.env.DEADBYTE_REMOTE_TEST_DISARM_NOOP='host';
478
+ let child=null;
479
+ try{
480
+ child=spawn(process.execPath,[cliPath,'remote','--full'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
481
+ const output=await collect(child);
482
+ const deadline=Date.now()+8000;
483
+ while(Date.now()<deadline&&!/\[READY\]/.test(output.stdout)) await sleep(50);
484
+ assert.match(output.stdout,/\[READY\]/,output.stderr);
485
+ child.kill('SIGKILL');
486
+ await output.closed;
487
+ await waitFor(fx.trace,commands=>commands.includes('disarmhost'),{timeoutMs:8000});
488
+ await sleep(750);
489
+ const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
490
+ assert.equal(marker.schema,'deadbyte.remote-session.v2');
491
+ const commands=await traceCommands(fx.trace);
492
+ assert.ok(!commands.includes('park'),'guardian must not park after unverified revocation');
493
+ }finally{
494
+ if(child?.exitCode===null) child.kill('SIGKILL');
495
+ await rm(fx.dir,{recursive:true,force:true});
496
+ }
497
+ });
498
+
345
499
  winTest('bare remote rolls back authority and parks if one plane fails to arm',async()=>{
346
500
  const fx=await fixture({failCommand:'armcoding'});
347
501
  try{
@@ -351,8 +505,8 @@ winTest('bare remote rolls back authority and parks if one plane fails to arm',a
351
505
  assert.equal(ended.timeout,undefined,'failed startup did not terminate');
352
506
  assert.notEqual(ended.code,0);
353
507
  const commands=await traceCommands(fx.trace);
354
- assert.deepEqual(commands.slice(0,5),['status','start','status','armprocess','armcoding']);
355
- assert.deepEqual(commands.slice(-6),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park']);
508
+ assert.deepEqual(commands.slice(0,6),['status','start','status','status','armprocess','armcoding']);
509
+ assert.deepEqual(commands.slice(-8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
356
510
  assert.doesNotMatch(output.stdout,/Live logs/i);
357
511
  }finally{await rm(fx.dir,{recursive:true,force:true});}
358
512
  });
@@ -366,10 +520,10 @@ winTest('bare remote recovers a stale crashed session before arming a new one',a
366
520
  await writeFile(path.join(runtime,'remote-session.json'),JSON.stringify({schema:'deadbyte.remote-session.v1',pid:2147483000,started_at_utc:'2026-01-01T00:00:00.000Z'}),'utf8');
367
521
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
368
522
  const output=await collect(child);
369
- const ready=await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
370
- assert.deepEqual(ready.slice(0,6),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park']);
371
- assert.equal(ready[6],'status');
372
- assert.equal(ready[7],'start');
523
+ const ready=await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=5);
524
+ assert.deepEqual(ready.slice(0,8),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','status','machinetest','park']);
525
+ assert.equal(ready[8],'status');
526
+ assert.equal(ready[9],'start');
373
527
  assert.match(output.stdout,/\[RECOVERY\] Stale remote session pid=2147483000; revoking authority before restart\./);
374
528
  child.stdin.end();
375
529
  const ended=await Promise.race([output.closed,sleep(6000).then(()=>({timeout:true}))]);
@@ -387,7 +541,7 @@ winTest('remote warning lines use deterministic ASCII fallback on piped Windows
387
541
  try{
388
542
  child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
389
543
  const output=await collect(child);
390
- await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
544
+ await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4);
391
545
  const markerPath=path.join(fx.state,'runtime','remote-session.json');
392
546
  await writeFile(markerPath,'{invalid-json\n','utf8');
393
547
  child.stdin.end();
@@ -408,8 +562,8 @@ winTest('R58 concurrent cold remote claims have one winner and no loser cleanup'
408
562
  const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
409
563
  children.push(child);outputs.push(await collect(child));
410
564
  }
411
- const commands=await waitFor(fx.trace,items=>items.includes('hoststatus'));
412
- assert.ok(commands.includes('hoststatus'),outputs.map(o=>o.stderr).join('\n'));
565
+ const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=3);
566
+ assert.ok(commands.filter(x=>x==='status').length>=3,outputs.map(o=>o.stderr).join('\n'));
413
567
  const deadline=Date.now()+5000;
414
568
  while(Date.now()<deadline&&children.filter(c=>c.exitCode===null).length>1)await sleep(50);
415
569
  const live=children.filter(c=>c.exitCode===null);
@@ -432,8 +586,11 @@ winTest('R58 duplicate remote verifies the real live owner and leaves its author
432
586
  try{
433
587
  first=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
434
588
  const firstOutput=await collect(first);
435
- const commands=await waitFor(fx.trace,items=>items.includes('hoststatus'));
436
- assert.ok(commands.includes('hoststatus'),firstOutput.stderr);
589
+ const commands=await waitFor(fx.trace,items=>items.filter(x=>x==='status').length>=3);
590
+ assert.ok(commands.filter(x=>x==='status').length>=3,firstOutput.stderr);
591
+ const ownerReadyDeadline=Date.now()+3000;
592
+ while(Date.now()<ownerReadyDeadline&&!/\[READY\]/.test(firstOutput.stdout)) await sleep(50);
593
+ assert.match(firstOutput.stdout,/\[READY\]/,firstOutput.stderr);
437
594
  const before=await traceCommands(fx.trace);
438
595
  const markerPath=path.join(fx.state,'runtime','remote-session.json');
439
596
  const marker=JSON.parse(await readFile(markerPath,'utf8'));
@@ -529,7 +686,7 @@ winTest('remote --trace --filter selects presentation without dropping journaled
529
686
  env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true
530
687
  });
531
688
  const output=await collect(child);
532
- await waitFor(fx.trace,commands=>commands.includes('hoststatus'));
689
+ await waitFor(fx.trace,commands=>commands.filter(x=>x==='status').length>=4);
533
690
  const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
534
691
  const auditDir=path.join(fx.state,'audit');await mkdir(auditDir,{recursive:true});
535
692
  const audit=[