deadbyte-mcp 0.15.36 → 0.15.38

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 (71) hide show
  1. package/MANIFEST.SHA256 +70 -32
  2. package/PROVENANCE.json +127 -87
  3. package/README.txt +12 -12
  4. package/RELEASE.json +226 -187
  5. package/SBOM.cdx.json +10 -25
  6. package/TEST-MATRIX.json +305 -258
  7. package/bridge/package.json +1 -1
  8. package/bridge/src/bridge-health-r59.mjs +4 -3
  9. package/bridge/src/bridge.mjs +75 -18
  10. package/bridge/src/canonical-json-r60.mjs +13 -0
  11. package/bridge/src/r40-governor.mjs +109 -19
  12. package/bridge/src/reconnect-delay-r60.mjs +31 -0
  13. package/bridge/src/remote-call-claims-r60.mjs +157 -0
  14. package/bridge/src/remote-dispatch-r60.mjs +75 -0
  15. package/controller/deadbyte-controller.ps1 +6 -1
  16. package/controller/deadbyte-process-policy.json +4 -4
  17. package/docs/R59-PROVENANCE.json +87 -0
  18. package/docs/R59-RELEASE.json +187 -0
  19. package/docs/R59-SBOM.cdx.json +68 -0
  20. package/docs/R59-TEST-MATRIX.json +258 -0
  21. package/docs/R59-bridge-package.json +13 -0
  22. package/docs/R59-package-lock.json +184 -0
  23. package/docs/R59-package.json +106 -0
  24. package/docs/R59-version.mjs +6 -0
  25. package/docs/R60-TASK-CONTRACT.md +22 -0
  26. package/docs/R61-TASK-CONTRACT.md +11 -0
  27. package/docs/r61-reference-evidence.md +18 -0
  28. package/package.json +6 -3
  29. package/scripts/full-regression-r60.mjs +32 -0
  30. package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
  31. package/scripts/gate-windows-r61-prepublish.ps1 +45 -0
  32. package/scripts/npm-artifact-identity-r61.mjs +22 -0
  33. package/scripts/npm-release-r48.mjs +21 -5
  34. package/scripts/r61-mixed-lane-benchmark.mjs +98 -0
  35. package/src/capability-snapshot.mjs +6 -2
  36. package/src/deadbyte-cli.mjs +89 -40
  37. package/src/remote-authority.mjs +56 -0
  38. package/src/remote-enforcement-probe-r60.mjs +17 -0
  39. package/src/remote-live-log.mjs +47 -7
  40. package/src/remote-log-decode-r60.mjs +32 -0
  41. package/src/remote-mcp-machine-smoke-client.mjs +19 -1
  42. package/src/remote-session-guardian.mjs +20 -3
  43. package/src/runtime-update.mjs +13 -1
  44. package/src/tool-audit.mjs +5 -0
  45. package/src/version.mjs +1 -1
  46. package/test/bridge-runtime-r59.test.mjs +5 -5
  47. package/test/commander-schema-differential-r48.test.mjs +1 -1
  48. package/test/r48-release-contract.test.mjs +1 -1
  49. package/test/r51-runtime-coherence.test.mjs +2 -2
  50. package/test/r58-cleanup-retention.test.mjs +9 -3
  51. package/test/r58-concurrent-recovery.test.mjs +9 -3
  52. package/test/r58-controller-live-local.test.mjs +22 -3
  53. package/test/r58-stale-owner.test.mjs +2 -2
  54. package/test/r59-release-contract.test.mjs +6 -6
  55. package/test/r60-authority-coherence.test.mjs +36 -0
  56. package/test/r60-authority-enforcement.test.mjs +28 -0
  57. package/test/r60-bridge-dispatch.test.mjs +119 -0
  58. package/test/r60-clock-discipline.test.mjs +15 -0
  59. package/test/r60-live-timeline.test.mjs +29 -0
  60. package/test/r60-reconnect-errors.test.mjs +21 -0
  61. package/test/r60-reconnect-jitter.test.mjs +18 -0
  62. package/test/r60-release-contract.test.mjs +82 -0
  63. package/test/r60-remote-call-recovery.test.mjs +170 -0
  64. package/test/r60-windows-log-decode.test.mjs +25 -0
  65. package/test/r61-control-lane.test.mjs +154 -0
  66. package/test/r61-live-timeline-bounds.test.mjs +31 -0
  67. package/test/r61-npm-artifact-identity.test.mjs +17 -0
  68. package/test/r61-release-bridge-binding.test.mjs +32 -0
  69. package/test/r61-release-contract.test.mjs +44 -0
  70. package/test/remote-live-log-r37.test.mjs +24 -7
  71. package/test/remote-session-cli.test.mjs +200 -43
@@ -20,6 +20,8 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
20
20
  import { resolveStateRoot } from './state-root.mjs';
21
21
  import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
22
22
  import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
23
+ import { AUTHORITY_PLANES, authorityVector, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
24
+ import { decodeRemoteLogBytes, boundedRemoteDiagnostic, safeRemoteLogPrefixLength } from './remote-log-decode-r60.mjs';
23
25
 
24
26
  const REMOTE_COMMANDS=new Set([
25
27
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
@@ -27,17 +29,9 @@ const REMOTE_COMMANDS=new Set([
27
29
  'armdesktop','disarmdesktop','desktopstatus',
28
30
  'armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
29
31
  ]);
30
- const SESSION_ARM=['armprocess','armcoding','armautonomous'];
31
32
  const REMOTE_STATUS_TIMEOUT_MS=30000;
32
33
  const REMOTE_START_TIMEOUT_MS=150000;
33
- const SESSION_STATUS=[
34
- ['processstatus',/^Process plane\s*:\s*ARMED\b/m],
35
- ['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
36
- ['autonomousstatus',/^Autonomous\s*:\s*ARMED\b/m],
37
- ['desktopstatus',/^Desktop plane\s*:\s*DISARMED\b/m],
38
- ['hoststatus',/^Host plane\s*:\s*DISARMED\b/m]
39
- ];
40
- const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
34
+ const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
41
35
  const SUPERVISOR_COMMANDS=Object.freeze({
42
36
  start:'supervisorstart',stop:'supervisorstop',status:'supervisorstatus',recover:'supervisorrecover'
43
37
  });
@@ -55,6 +49,9 @@ function printHelp(){
55
49
  ` npx -y ${exact} doctor # verify current package/release truth`,
56
50
  ` npx -y ${exact} mcp # stdio MCP server for local clients`,
57
51
  ` npx -y ${exact} remote # unified foreground device session + live timeline`,
52
+ ` npx -y ${exact} remote --desktop # session with Desktop authority; Host remains disarmed`,
53
+ ` npx -y ${exact} remote --host # session with Host authority; Desktop remains disarmed`,
54
+ ` npx -y ${exact} remote --full # explicit five-plane foreground session`,
58
55
  ` npx -y ${exact} remote --resume <cursor> # replay missed results, then reconnect`,
59
56
  ` npx -y ${exact} remote --trace # show trace/span identity without payload detail`,
60
57
  ` npx -y ${exact} remote --filter <expr> # presentation filter: tool/category/status/trace`,
@@ -75,8 +72,7 @@ async function remoteContext(){
75
72
  return {root,launcher,powershell};
76
73
  }
77
74
  function boundedSingleLine(value,maxChars=2048){
78
- const line=String(value??'').replace(/[\r\n\t]+/g,' ').replace(/\s{2,}/g,' ').trim();
79
- return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
75
+ return boundedRemoteDiagnostic(value,maxChars);
80
76
  }
81
77
  async function readActiveRuntimeBinding(root){
82
78
  const file=path.join(root,'active-release.json');
@@ -158,11 +154,16 @@ async function readTailBounded(file,maxBytes=4096){
158
154
  if(length===0) return '';
159
155
  const buffer=Buffer.alloc(length);
160
156
  const handle=await open(file,'r');
161
- try{await handle.read(buffer,0,length,info.size-length);}finally{await handle.close();}
162
- return boundedSingleLine(buffer.toString('utf8'),maxBytes);
157
+ let encoding=null;
158
+ try{
159
+ const header=Buffer.alloc(2);await handle.read(header,0,2,0);
160
+ if(header[0]===0xff&&header[1]===0xfe)encoding='utf16le';
161
+ await handle.read(buffer,0,length,info.size-length);
162
+ }finally{await handle.close();}
163
+ return boundedSingleLine(decodeRemoteLogBytes(file,buffer,{encoding}),maxBytes);
163
164
  }catch(error){if(error?.code==='ENOENT') return '';throw error;}
164
165
  }
165
- function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null}={}){
166
+ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null,discardOutput=false,redirectOutput=null,json=false,probeMode=null}={}){
166
167
  if(capture&&discardOutput) throw new Error('invokeController cannot capture and discard output together');
167
168
  if(capture&&redirectOutput) throw new Error('invokeController cannot capture and redirect output together');
168
169
  if(discardOutput&&redirectOutput) throw new Error('invokeController cannot discard and redirect output together');
@@ -175,9 +176,10 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
175
176
  stderrFd=openSync(redirectOutput.stderr_path,'w');
176
177
  stdio=['ignore',stdoutFd,stderrFd];
177
178
  }else stdio=capture?['ignore','pipe','pipe']:'inherit';
178
- const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,env:windowsPowerShellEnvironment()};
179
+ const options={stdio,encoding:capture?'utf8':undefined,windowsHide:false,shell:false,
180
+ env:windowsPowerShellEnvironment(probeMode?{...process.env,DEADBYTE_REMOTE_PROBE_MODE:probeMode}:process.env)};
179
181
  if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
180
- result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
182
+ result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command,...(json?['-Json']:[])],options);
181
183
  }finally{
182
184
  if(stdoutFd!==null) closeSync(stdoutFd);
183
185
  if(stderrFd!==null) closeSync(stderrFd);
@@ -193,6 +195,10 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
193
195
  if(status!==0&&!allowFailure) throw new Error(`remote controller '${command}' failed exit=${status}`);
194
196
  return {status,stdout,stderr};
195
197
  }
198
+ function observedAuthority(ctx){
199
+ const result=invokeController(ctx,'status',{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS,json:true});
200
+ return parseAuthorityObservation(result.stdout);
201
+ }
196
202
  function controllerRuntimeReady(text){
197
203
  const value=String(text||'');
198
204
  return /^DEADBYTE MCP\s*=\s*ONLINE\s*$/m.test(value)&&
@@ -220,12 +226,12 @@ function parseAuditLogLine(file,line){
220
226
  if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
221
227
  try{return JSON.parse(line);}catch{return null;}
222
228
  }
223
- function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,verbose=false,displayMode='unicode'}={}){
229
+ function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,sourceSequence=null,verbose=false,displayMode='unicode'}={}){
224
230
  if(!line) return null;
225
231
  if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))){
226
232
  const event=auditEvent??parseAuditLogLine(file,line);
227
233
  if(!event) return null;
228
- return activityTracker?activityTracker.observe(event,{sequence}):formatCompactAuditEvent(event);
234
+ return activityTracker?activityTracker.observe(event,{sequence,sourceSequence}):formatCompactAuditEvent(event);
229
235
  }
230
236
  return formatRuntimeDiagnosticLine(file,line,{verbose,displayMode});
231
237
  }
@@ -278,13 +284,23 @@ async function replayRemoteResults(root,cursor,{displayMode='unicode'}={}){
278
284
  async function readAppend(file,offset){
279
285
  let info;try{info=await stat(file);}catch(error){if(error?.code==='ENOENT') return {offset:0,text:''};throw error;}
280
286
  let start=offset;
281
- if(info.size<start) start=0;
287
+ const reset=info.size<start;
288
+ if(reset) start=0;
282
289
  if(info.size===start) return {offset:start,text:''};
283
- const length=info.size-start;
284
- const buffer=Buffer.alloc(length);
285
290
  const handle=await open(file,'r');
286
- try{await handle.read(buffer,0,length,start);}finally{await handle.close();}
287
- return {offset:info.size,text:buffer.toString('utf8')};
291
+ let encoding=null,buffer,bytesRead=0;
292
+ try{
293
+ const header=Buffer.alloc(2);await handle.read(header,0,2,0);
294
+ if(header[0]===0xff&&header[1]===0xfe&&/\.log$/i.test(file)){
295
+ encoding='utf16le';if(start%2!==0)start=Math.max(0,start-1);
296
+ }
297
+ const length=Math.min(1024*1024,info.size-start);
298
+ if(length===0)return {offset:start,text:''};
299
+ buffer=Buffer.alloc(length);
300
+ ({bytesRead}=await handle.read(buffer,0,length,start));
301
+ }finally{await handle.close();}
302
+ const complete=safeRemoteLogPrefixLength(buffer.subarray(0,bytesRead),encoding??'utf8');
303
+ return {offset:start+complete,text:decodeRemoteLogBytes(file,buffer.subarray(0,complete),{encoding}),reset};
288
304
  }
289
305
  async function snapshotRemoteLogOffsets(root){
290
306
  const offsets=new Map();
@@ -312,8 +328,9 @@ function timelineFilterEvent(timeline){
312
328
  async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTracker=null,timelineFilter=null,timelineWriter=null,verbose=false,displayMode='unicode'}={}){
313
329
  process.stdout.write(verbose
314
330
  ? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
315
- : '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
331
+ : '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. START/DONE retained; heartbeat bursts folded; Ctrl+C disconnects and revokes authority.\n');
316
332
  const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
333
+ const partialLines=new Map();
317
334
  let liveFiles=[...activeOffsets.keys()];
318
335
  let nextDiscoveryAt=0;
319
336
  while(!signal.aborted){
@@ -326,7 +343,13 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
326
343
  const prior=activeOffsets.has(file)?activeOffsets.get(file):0;
327
344
  const next=await readAppend(file,prior);
328
345
  activeOffsets.set(file,next.offset);
329
- for(const line of next.text.split(/\r?\n/)){
346
+ if(next.reset)partialLines.delete(file);
347
+ if(!next.text)continue;
348
+ const lines=((partialLines.get(file)??'')+next.text).split(/\r?\n/);
349
+ partialLines.set(file,lines.pop()??'');
350
+ if(Buffer.byteLength(partialLines.get(file),'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
351
+ for(const line of lines){
352
+ if(Buffer.byteLength(line,'utf8')>1024*1024) throw new Error(`remote log line exceeds bounded decode limit: ${path.basename(file)}`);
330
353
  const auditEvent=parseAuditLogLine(file,line);
331
354
  const timeline=auditTimelineRecord(file,line,auditEvent);
332
355
  let recorded=null;
@@ -335,7 +358,8 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
335
358
  : await journal.recordTerminal(timeline);
336
359
  if(recorded?.duplicate) continue;
337
360
  const accepted=timelineFilter&&timeline?timelineFilter.accept(timelineFilterEvent(timeline)):true;
338
- const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,verbose,displayMode});
361
+ const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,
362
+ sourceSequence:Number.isSafeInteger(auditEvent?.seq)?auditEvent.seq:null,verbose,displayMode});
339
363
  if(!formatted||!accepted) continue;
340
364
  const output=Array.isArray(formatted)?formatted:[formatted];
341
365
  for(const item of output) if(item){
@@ -363,6 +387,15 @@ async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
363
387
  catch(error){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
364
388
  }
365
389
  if(failed.length) throw new Error(`remote session cleanup failed; ownership marker retained: ${failed.join(',')}`);
390
+ let snapshot;
391
+ try{snapshot=assertAuthorityVector(observedAuthority(ctx),Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,false])));}
392
+ catch(error){throw new Error(`remote session cleanup verification failed; ownership marker retained: ${String(error?.message||error)}`);}
393
+ if(snapshot.state==='ONLINE'){
394
+ try{invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:'revoked'});}
395
+ catch(error){throw new Error(`remote session enforcement cleanup verification failed; ownership marker retained: ${String(error?.message||error)}`);}
396
+ }
397
+ const parked=invokeController(ctx,'park',{allowFailure:true,capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
398
+ if(parked.status!==0) throw new Error(`remote session park failed exit=${parked.status}; ownership marker retained`);
366
399
  }
367
400
  const GUARDIAN_SCRIPT=fileURLToPath(new URL('./remote-session-guardian.mjs',import.meta.url));
368
401
  function remoteSessionMarker(root){return path.join(root,'runtime','remote-session.json');}
@@ -454,7 +487,7 @@ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
454
487
  const same=current?.schema==='deadbyte.remote-session.v2'&&current.owner_pid===marker.owner_pid&&current.session_nonce===marker.session_nonce;
455
488
  if(same) await rm(remoteSessionMarker(ctx.root),{force:true});
456
489
  }
457
- async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression=''}={}){
490
+ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default'}={}){
458
491
  const display=prepareRemoteConsole();
459
492
  const displayMode=display.mode;
460
493
  const ctx=await remoteContext();
@@ -480,9 +513,9 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
480
513
  filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
481
514
  });
482
515
  const rootPaths=await readLiveRootPaths(ctx.root);
483
- const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace});
516
+ const activityTracker=createLiveTimelineTracker({verbose,displayMode,rootPaths,trace,foldHeartbeats:!verbose});
484
517
  const timelineFilter=filterExpression?createLiveTimelineFilter(filterExpression):null;
485
- const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite:!verbose});
518
+ const timelineWriter=createLiveTimelineWriter({stream:process.stdout,displayMode,rewrite:false});
486
519
  const aborter=new AbortController();
487
520
  const abort=()=>aborter.abort();
488
521
  const watchStdin=!process.stdin.isTTY;
@@ -524,21 +557,31 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
524
557
  }
525
558
  process.stdout.write(`${formatRemoteLifecycle('bridge_connected',{displayMode})}\n`);
526
559
  process.stdout.write(`${formatRemoteLifecycle('cloud_connected',{displayMode})}\n`);
527
- for(const command of SESSION_ARM){
560
+ const desired=authorityVector(mode);
561
+ const priorAuthority=observedAuthority(ctx);
562
+ for(const plane of AUTHORITY_PLANES.filter(name=>!desired[name]&&priorAuthority.authority[name].armed)){
563
+ const command=`disarm${plane}`;
564
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Revoking unwanted ${plane} authority...\n`,displayMode);
565
+ invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
566
+ }
567
+ for(const plane of AUTHORITY_PLANES.filter(name=>desired[name])){
568
+ const command=`arm${plane}`;
528
569
  writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Arming ${command.slice(3)}...\n`,displayMode);
529
570
  invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
530
571
  }
531
572
  const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
573
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying exact ${mode} authority vector...\n`,displayMode);
574
+ const current=await readActiveRuntimeBinding(ctx.root);
575
+ const authority=assertAuthorityVector(assertRuntimeReadyObservation(observedAuthority(ctx),{
576
+ coreRoot:current?.release_root??null
577
+ }),desired);
578
+ process.stdout.write('[BOOT] Verifying public signed MCP execution path...\n');
579
+ invokeController(ctx,'machinetest',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS,probeMode:mode});
580
+ ready=true;
581
+ writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ${authorityBanner(authority)}\n`,displayMode);
532
582
  process.stdout.write('[OK] ONLINE\n');
533
583
  process.stdout.write(`${formatRemoteLifecycle('ready',{displayMode})}\n`);
534
584
  if(url) writeRemote(process.stdout,`URL: ${url}\n`,displayMode);
535
- writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying process, coding, autonomous; desktop + host must stay DISARMED...\n`,displayMode);
536
- for(const [command,pattern] of SESSION_STATUS){
537
- const result=invokeController(ctx,command,{capture:true,echoCaptured:false});
538
- if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} state mismatch`);
539
- }
540
- ready=true;
541
- writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ARMED: process, coding, autonomous | desktop, host DISARMED\n`,displayMode);
542
585
  await logTask;
543
586
  }finally{
544
587
  const wasAborted=aborter.signal.aborted;
@@ -546,6 +589,8 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
546
589
  if(logTask) await logTask.catch(()=>{});
547
590
  timelineWriter.flush();
548
591
  for(const line of activityTracker.reconcileInterrupted('session_end')) process.stdout.write(`${line}\n`);
592
+ const filterRetention=timelineFilter?.retention();
593
+ if(filterRetention?.evicted>0) process.stdout.write(`[WARN] LIVELOG filter evicted ${filterRetention.evicted} presentation identities; full audit remains on disk.\n`);
549
594
  process.stdout.write(`${activityTracker.summary()}\n`);
550
595
  process.removeListener('SIGINT',abort);process.removeListener('SIGTERM',abort);
551
596
  if(watchStdin){process.stdin.removeListener('end',abort);process.stdin.removeListener('close',abort);process.stdin.pause();}
@@ -556,12 +601,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
556
601
  }
557
602
  }
558
603
  async function runRemote(args){
559
- let verbose=false,trace=false,filterExpression='',resumeCursor=null;
604
+ let verbose=false,trace=false,filterExpression='',resumeCursor=null,mode='default';
560
605
  const positional=[];
561
606
  for(let index=0;index<args.length;index++){
562
607
  const arg=args[index];
563
608
  if(arg==='--verbose'||arg==='-v'){verbose=true;continue;}
564
609
  if(arg==='--trace'){trace=true;continue;}
610
+ if(['--desktop','--host','--full'].includes(arg)){
611
+ if(mode!=='default') throw new Error('remote authority modes are mutually exclusive');
612
+ mode=arg.slice(2);continue;
613
+ }
565
614
  if(arg==='--filter'){
566
615
  if(filterExpression) throw new Error('remote --filter may be specified once');
567
616
  if(index+1>=args.length) throw new Error('remote --filter requires an expression');
@@ -574,8 +623,8 @@ async function runRemote(args){
574
623
  }
575
624
  positional.push(arg);
576
625
  }
577
- const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null;
578
- if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression});return;}
626
+ const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null||mode!=='default';
627
+ if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression,mode});return;}
579
628
  if(presentation) throw new Error('remote presentation flags are valid only for foreground session');
580
629
  if(positional.length>1) throw new Error('remote accepts one controller command');
581
630
  const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
@@ -0,0 +1,56 @@
1
+ import path from 'node:path';
2
+
3
+ export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
4
+
5
+ export function authorityVector(mode='default'){
6
+ if(!['default','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
7
+ return Object.freeze({
8
+ process:true,coding:true,autonomous:true,
9
+ desktop:mode==='desktop'||mode==='full',
10
+ host:mode==='host'||mode==='full'
11
+ });
12
+ }
13
+
14
+ export function parseAuthorityObservation(raw){
15
+ let value;
16
+ try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
17
+ catch{throw new Error('controller observation JSON invalid');}
18
+ if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
19
+ throw new Error('controller observation schema invalid');
20
+ const authority={};
21
+ for(const plane of AUTHORITY_PLANES){
22
+ const entry=value.authority[plane];
23
+ if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
24
+ throw new Error(`controller authority ${plane} observation invalid`);
25
+ authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128)});
26
+ }
27
+ return Object.freeze({...value,authority:Object.freeze(authority)});
28
+ }
29
+
30
+ export function assertAuthorityVector(snapshot,expected){
31
+ for(const plane of AUTHORITY_PLANES){
32
+ const entry=snapshot?.authority?.[plane];
33
+ if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
34
+ if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
35
+ if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
36
+ }
37
+ return snapshot;
38
+ }
39
+
40
+ export function authorityBanner(snapshot){
41
+ return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
42
+ }
43
+
44
+ export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
45
+ if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
46
+ for(const name of ['bridge','core','cloud']){
47
+ if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
48
+ }
49
+ if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
50
+ if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
51
+ if(coreRoot!==null){
52
+ if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
53
+ throw new Error('core root does not match active release');
54
+ }
55
+ return snapshot;
56
+ }
@@ -0,0 +1,17 @@
1
+ import { authorityVector } from './remote-authority.mjs';
2
+
3
+ export async function probeRemoteEnforcement({client,mode='default'}={}){
4
+ if(!client||typeof client.callTool!=='function')throw new Error('remote enforcement client invalid');
5
+ const expected=mode==='revoked'?{desktop:false,host:false}:authorityVector(mode);
6
+ const desktop=await client.callTool({name:'desktop_control',arguments:{op:'status'}});
7
+ const desktopValue=desktop?.structuredContent;
8
+ const desktopArmed=desktop?.isError===false&&desktopValue?.status==='ok'&&desktopValue?.result?.armed===true;
9
+ const desktopUnavailable=desktop?.isError===true&&desktopValue?.status==='unavailable';
10
+ if(expected.desktop?!desktopArmed:desktopArmed||(!desktopUnavailable&&desktopValue?.result?.armed!==false))
11
+ throw new Error('Desktop enforcement does not match requested authority');
12
+ const host=await client.callTool({name:'host_file_read',arguments:{root_id:'core',path:'package.json',encoding:'utf8'}});
13
+ const hostArmed=host?.isError===false&&host?.structuredContent?.status==='ok'&&/^[0-9a-f]{64}$/.test(String(host.structuredContent.sha256??''));
14
+ const hostDenied=host?.isError===true&&host?.structuredContent?.status==='denied';
15
+ if(expected.host?!hostArmed:!hostDenied) throw new Error('Host enforcement does not match requested authority');
16
+ return Object.freeze({desktop_armed:desktopArmed,host_armed:hostArmed});
17
+ }
@@ -275,6 +275,8 @@ function toolFilterMatch(pattern,tool){
275
275
  export function createLiveTimelineFilter(expression=''){
276
276
  const spec=parseFilterExpression(expression);
277
277
  const pending=new Map();
278
+ const MAX_FILTER_IDENTITIES=4096;
279
+ let evicted=0;
278
280
  const baseMatch=meta=>(!spec.tool||toolFilterMatch(spec.tool,meta.tool))&&(!spec.category||spec.category===meta.category)&&(!spec.trace||spec.trace===meta.trace_id);
279
281
  return Object.freeze({
280
282
  accept(event){
@@ -284,6 +286,8 @@ export function createLiveTimelineFilter(expression=''){
284
286
  if(event.event_type==='tool.request'){
285
287
  const args=event.args??{};const tool=displayToolName(rawTool,args);const trace=traceIdentity(event,args);
286
288
  const meta={tool,category:categoryForTool(tool),trace_id:trace.trace_id};
289
+ if(pending.has(requestId))pending.delete(requestId);
290
+ if(pending.size>=MAX_FILTER_IDENTITIES){pending.delete(pending.keys().next().value);evicted++;}
287
291
  pending.set(requestId,meta);
288
292
  return spec.status?false:baseMatch(meta);
289
293
  }
@@ -294,7 +298,8 @@ export function createLiveTimelineFilter(expression=''){
294
298
  }
295
299
  return false;
296
300
  },
297
- spec
301
+ spec,
302
+ retention(){return Object.freeze({pending:pending.size,evicted});}
298
303
  });
299
304
  }
300
305
  export function createLiveTimelineWriter({stream=process.stdout,displayMode='unicode',rewrite=true}={}){
@@ -319,19 +324,23 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
319
324
  return Object.freeze({write,flush,pending(){return active?{...active}:null;}});
320
325
  }
321
326
 
322
- export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000}={}){
327
+ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,foldHeartbeats=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000}={}){
323
328
  if(typeof clock!=='function') throw new Error('live timeline clock must be a function');
324
329
  if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('remote display mode invalid');
325
330
  if(!Number.isInteger(heartbeatBurstLimit)||heartbeatBurstLimit<2||heartbeatBurstLimit>100) throw new Error('heartbeatBurstLimit invalid');
326
331
  if(!Number.isInteger(heartbeatWindowMs)||heartbeatWindowMs<250||heartbeatWindowMs>60000) throw new Error('heartbeatWindowMs invalid');
327
332
  if(!rootPaths||typeof rootPaths!=='object'||Array.isArray(rootPaths)) throw new Error('live timeline rootPaths invalid');
328
333
  if(typeof trace!=='boolean') throw new Error('live timeline trace invalid');
334
+ if(typeof foldHeartbeats!=='boolean') throw new Error('live timeline foldHeartbeats invalid');
329
335
  if(!Number.isInteger(slowCallMs)||slowCallMs<100||slowCallMs>600000) throw new Error('live timeline slowCallMs invalid');
330
336
  const pending=new Map();
331
337
  const completed=new Set();
338
+ const MAX_PRESENTATION_IDENTITIES=4096;
332
339
  const toolCounts=new Map();
333
340
  const durationSamples=[];
334
341
  let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
342
+ let lastCapability=null,suppressedCapability=0,suppressedCapabilityReported=false;
343
+ let lastSourceSequence=0,pendingOverflow=0;
335
344
 
336
345
  function eventPrefix(event,sequence,{displayTool=null,icon=null}={}){
337
346
  const seq=Number.isSafeInteger(sequence)&&sequence>0?'#'+sequence+' ':'';
@@ -360,10 +369,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
360
369
  error?(displayMode==='ascii'?'ERR':'✗'):
361
370
  ['write','edit','run'].includes(category)?(displayMode==='ascii'?'SUCCEEDED':'✅ SUCCEEDED'):
362
371
  (displayMode==='ascii'?'OK':'✓');
372
+ const rawStatus=safeAtom(event.outcome?.status);
373
+ const statusDetail=rawStatus&&!['ok','error'].includes(rawStatus)?' status='+rawStatus:'';
363
374
  const prefixIcon=unknown?(displayMode==='ascii'?'WARN':'⚠️'):null;
364
375
  const evidence=receipt?' '+(displayMode==='ascii'?'RECEIPT':'🧾 receipt')+(displayMode==='ascii'?'=':'=')+receipt:'';
365
376
  const reasonSep=displayMode==='ascii'?' - ':' — ';
366
- return remoteDisplayText(eventPrefix(event,sequence,{displayTool,icon:prefixIcon})+traceSuffix(span?.trace_meta)+(summary?' '+summary:'')+(duration?' '+duration:'')+slow+' '+marker+evidence+(reason?reasonSep+reason:''),displayMode);
377
+ return remoteDisplayText(eventPrefix(event,sequence,{displayTool,icon:prefixIcon})+traceSuffix(span?.trace_meta)+(summary?' '+summary:'')+(duration?' '+duration:'')+slow+' '+marker+statusDetail+evidence+(reason?reasonSep+reason:''),displayMode);
367
378
  }
368
379
  function burstLine(value){
369
380
  if(!value) return null;
@@ -384,7 +395,8 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
384
395
  function addStats(event,span){
385
396
  resultCount++;
386
397
  const displayTool=span?.display_tool||event.tool;
387
- toolCounts.set(displayTool,(toolCounts.get(displayTool)??0)+1);
398
+ const countKey=toolCounts.has(displayTool)||toolCounts.size<256?displayTool:'[other]';
399
+ toolCounts.set(countKey,(toolCounts.get(countKey)??0)+1);
388
400
  const outcome=outcomeClass(event);
389
401
  if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else okCount++;
390
402
  if(['write','edit','run'].includes(categoryForTool(displayTool))) mutationCount++;
@@ -395,8 +407,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
395
407
  if(!slowest||duration>slowest.duration_ms) slowest={tool:displayTool,duration_ms:duration};
396
408
  }
397
409
  }
398
- function observe(event,{sequence=null}={}){
410
+ function observe(event,{sequence=null,sourceSequence=null}={}){
399
411
  if(!event||typeof event!=='object'||Array.isArray(event)) return null;
412
+ if(Number.isSafeInteger(sourceSequence)&&sourceSequence>0){
413
+ if(sourceSequence<=lastSourceSequence)return null;
414
+ lastSourceSequence=sourceSequence;
415
+ }
400
416
  const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
401
417
  const tool=typeof event.tool==='string'&&event.tool?event.tool:null;
402
418
  if(event.event_type==='tool.request'&&requestId&&tool){
@@ -405,10 +421,17 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
405
421
  const displayTool=displayToolName(tool,args);
406
422
  const summary=summarizeLiveToolArgs(displayTool,args,{rootPaths});
407
423
  const traceMeta=traceIdentity(event,args);
424
+ if(pending.size>=MAX_PRESENTATION_IDENTITIES){
425
+ pendingOverflow++;
426
+ return pendingOverflow===1?remoteDisplayText('LIVELOG pending span cap reached; full audit remains on disk',displayMode):null;
427
+ }
408
428
  pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,trace_meta:traceMeta,started_at_ms:clock()});
409
429
  peakInFlight=Math.max(peakInFlight,pending.size);
410
430
  if(verbose) return formatRemoteToolCall({...event,tool:displayTool},{clock,displayMode})+traceSuffix(traceMeta);
411
- if(visibleStart(displayTool)) return eventPrefix(event,sequence,{displayTool})+traceSuffix(traceMeta)+(summary?' '+summary:'')+' …';
431
+ if(visibleStart(displayTool)){
432
+ const startMarker=displayMode==='ascii'?'START':'▶ START';
433
+ return remoteDisplayText(eventPrefix(event,sequence,{displayTool})+traceSuffix(traceMeta)+' '+startMarker+(summary?' '+summary:''),displayMode);
434
+ }
412
435
  return null;
413
436
  }
414
437
  if(event.event_type==='tool.result'&&requestId&&tool){
@@ -416,10 +439,22 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
416
439
  const span=pending.get(requestId)??null;
417
440
  pending.delete(requestId);
418
441
  completed.add(requestId);
442
+ if(completed.size>MAX_PRESENTATION_IDENTITIES)completed.delete(completed.values().next().value);
419
443
  addStats(event,span);
420
444
  const error=event.outcome?.is_error===true;
421
445
  const nowMs=eventMs(event,clock);
422
- if(HEARTBEAT_TOOLS.has(tool)&&!error){
446
+ if(tool==='capability_manifest'&&!error){
447
+ const snapshot=event.outcome?.snapshot_id,epoch=event.outcome?.state_epoch;
448
+ if(/^[0-9a-f]{64}$/.test(String(snapshot??''))&&Number.isInteger(epoch)&&epoch>0){
449
+ const identity=`${snapshot}:${epoch}`;
450
+ if(identity===lastCapability){suppressedCapability++;return null;}
451
+ lastCapability=identity;
452
+ const prior=flushBurst();
453
+ const line=resultLine(event,span,sequence)+` snapshot=${snapshot.slice(0,8)} epoch=${epoch}`;
454
+ return prior?[prior,line]:line;
455
+ }
456
+ }
457
+ if(foldHeartbeats&&HEARTBEAT_TOOLS.has(tool)&&!error){
423
458
  const duration=Number(event.outcome?.duration_ms);
424
459
  if(burst&&burst.tool===tool&&nowMs-burst.last_ms<=heartbeatWindowMs){
425
460
  burst.count++;
@@ -449,6 +484,10 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
449
484
  const lines=[];
450
485
  const folded=flushBurst();
451
486
  if(folded) lines.push(folded);
487
+ if(suppressedCapability>0&&!suppressedCapabilityReported){
488
+ lines.push(remoteDisplayText(`capability_manifest ×${suppressedCapability} unchanged suppressed`,displayMode));
489
+ suppressedCapabilityReported=true;
490
+ }
452
491
  for(const item of pending.values()) lines.push(remoteDisplayText((displayMode==='ascii'?'[WARN]':'⚠')+' ['+shortSpanId(item.request_id)+'] OUTCOME_UNKNOWN '+item.tool+' — '+suffix+'; inspect state before retry',displayMode));
453
492
  pending.clear();
454
493
  return lines;
@@ -470,6 +509,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
470
509
  }
471
510
  return Object.freeze({
472
511
  observe,
512
+ retention(){return Object.freeze({pending:pending.size,completed:completed.size,pending_overflow:pendingOverflow,last_source_sequence:lastSourceSequence});},
473
513
  pending(){return [...pending.values()].map(({request_id,tool})=>({request_id,tool}));},
474
514
  reconcileInterrupted,
475
515
  summary
@@ -0,0 +1,32 @@
1
+ import path from 'node:path';
2
+
3
+ export function safeRemoteLogPrefixLength(bytes,encoding='utf8'){
4
+ if(!Buffer.isBuffer(bytes))throw new Error('remote log prefix bytes invalid');
5
+ if(encoding==='utf16le'){
6
+ let size=bytes.length-(bytes.length%2);
7
+ if(size>=2){const last=bytes.readUInt16LE(size-2);if(last>=0xd800&&last<=0xdbff)size-=2;}
8
+ return size;
9
+ }
10
+ if(encoding!=='utf8')throw new Error('remote log prefix encoding invalid');
11
+ let index=bytes.length-1;
12
+ while(index>=0&&(bytes[index]&0xc0)===0x80)index--;
13
+ if(index<0)return 0;
14
+ const lead=bytes[index];
15
+ const needed=lead<0x80?1:lead>=0xc2&&lead<=0xdf?2:lead>=0xe0&&lead<=0xef?3:lead>=0xf0&&lead<=0xf4?4:1;
16
+ return bytes.length-index<needed?index:bytes.length;
17
+ }
18
+
19
+ export function decodeRemoteLogBytes(file,bytes,{encoding=null}={}){
20
+ if(typeof file!=='string'||!Buffer.isBuffer(bytes))throw new Error('remote log decoder input invalid');
21
+ const knownLog=/\.log$/i.test(path.basename(file));
22
+ if(!knownLog)return bytes.toString('utf8');
23
+ const utf16=encoding==='utf16le'||(bytes.length>=2&&bytes[0]===0xff&&bytes[1]===0xfe);
24
+ if(!utf16)return bytes.toString('utf8').replace(/^\uFEFF/,'');
25
+ return bytes.toString('utf16le').replace(/^\uFEFF/,'');
26
+ }
27
+
28
+ export function boundedRemoteDiagnostic(value,maxChars=2048){
29
+ if(!Number.isInteger(maxChars)||maxChars<32||maxChars>8192)throw new Error('remote diagnostic bound invalid');
30
+ const line=String(value??'').replace(/\x1b\[[0-9;]*[A-Za-z]/g,'').replace(/[\x00-\x1f\x7f]+/g,' ').replace(/\s{2,}/g,' ').trim();
31
+ return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
32
+ }
@@ -1,11 +1,17 @@
1
1
  import { randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
2
4
  import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
3
5
  import { verifyMachinePingPublic } from './machine-identity.mjs';
6
+ import { parseActivePointer } from './release-slot.mjs';
7
+ import { probeRemoteEnforcement } from './remote-enforcement-probe-r60.mjs';
4
8
 
5
9
  const endpointText = process.argv[2];
6
10
  if (!endpointText) throw new Error('usage: node src/remote-mcp-machine-smoke-client.mjs https://host/mcp/path');
7
11
  const endpoint = new URL(endpointText);
8
12
  if (endpoint.protocol !== 'https:') throw new Error('machine live gate requires HTTPS');
13
+ const enforcementMode=process.argv[3]??null;
14
+ if(enforcementMode!==null&&!['default','desktop','host','full','revoked'].includes(enforcementMode))throw new Error('remote enforcement probe mode invalid');
9
15
 
10
16
  const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
11
17
  const client = new Client(
@@ -49,6 +55,17 @@ try {
49
55
  }
50
56
  const signatureVerified = await verifyMachinePingPublic({ ping, publicKeySpkiBase64: status.public_key_spki_base64 });
51
57
  if (!signatureVerified) throw new Error('machine ping public Ed25519 verification failed');
58
+ if(enforcementMode!==null){
59
+ const stateRoot=process.env.DEADBYTE_STATE_ROOT;
60
+ if(!stateRoot||!path.isAbsolute(stateRoot))throw new Error('remote enforcement probe state root missing');
61
+ const pointer=parseActivePointer(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
62
+ for(const [actual,expected] of [
63
+ [status.release_id,pointer.release_id],
64
+ [status.release_manifest_sha256,pointer.manifest_sha256],
65
+ [status.generation_manifest_sha256,pointer.generation_manifest_sha256]
66
+ ])if(actual!==expected)throw new Error('signed machine status does not bind active release/generation');
67
+ await probeRemoteEnforcement({client,mode:enforcementMode});
68
+ }
52
69
 
53
70
  console.log(JSON.stringify({
54
71
  status: 'passed',
@@ -59,7 +76,8 @@ try {
59
76
  release_id: status.release_id,
60
77
  release_manifest_sha256: status.release_manifest_sha256,
61
78
  generation_manifest_sha256: status.generation_manifest_sha256,
62
- signature_verified: true
79
+ signature_verified: true,
80
+ ...(enforcementMode?{enforcement_mode:enforcementMode,enforcement_verified:true}:{})
63
81
  }));
64
82
  } finally {
65
83
  await client.close().catch(() => {});
@@ -6,8 +6,9 @@ import path from 'node:path';
6
6
  import { setTimeout as sleep } from 'node:timers/promises';
7
7
  import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
8
  import { resolveStateRoot } from './state-root.mjs';
9
+ import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
9
10
 
10
- const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
11
+ const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
11
12
  // This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
12
13
  // poll. A reused PID cannot keep a new foreground guardian alive.
13
14
  let ownerDisconnected=false;
@@ -42,12 +43,23 @@ function markerMatches(marker,{ownerPid,nonce}){
42
43
  marker?.owner_pid===ownerPid&&
43
44
  marker?.session_nonce===nonce;
44
45
  }
45
- function invokeCleanup(powershell,launcher,command,stateRoot){
46
+ function invokeCleanup(powershell,launcher,command,stateRoot,probeMode=null){
46
47
  const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{
47
- stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
48
+ stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot,
49
+ ...(probeMode?{DEADBYTE_REMOTE_PROBE_MODE:probeMode}:{})}),timeout:probeMode?150000:30000
48
50
  });
49
51
  return !result.error&&result.status===0;
50
52
  }
53
+ function verifyRevoked(powershell,launcher,stateRoot){
54
+ const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'status','-Json'],{
55
+ stdio:['ignore','pipe','pipe'],encoding:'utf8',windowsHide:true,shell:false,
56
+ env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
57
+ });
58
+ if(result.error||result.status!==0) return null;
59
+ try{
60
+ return assertAuthorityVector(parseAuthorityObservation(result.stdout),Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,false])));
61
+ }catch{return null;}
62
+ }
51
63
 
52
64
  async function main(){
53
65
  if(process.platform!=='win32') return;
@@ -82,6 +94,11 @@ async function main(){
82
94
  if(!invokeCleanup(powershell,launcher,command,root)) failed.push(command);
83
95
  }
84
96
  if(failed.length) throw new Error('guardian cleanup failed; ownership marker retained: '+failed.join(','));
97
+ const observed=verifyRevoked(powershell,launcher,root);
98
+ if(!observed) throw new Error('guardian authority cleanup unverified; ownership marker retained');
99
+ if(observed.state==='ONLINE'&&!invokeCleanup(powershell,launcher,'machinetest',root,'revoked'))
100
+ throw new Error('guardian runtime enforcement cleanup unverified; ownership marker retained');
101
+ if(!invokeCleanup(powershell,launcher,'park',root)) throw new Error('guardian park failed; ownership marker retained');
85
102
  const current=await readMarker(markerPath);
86
103
  if(current&&markerMatches(current,{ownerPid,nonce})) await rm(markerPath,{force:true});
87
104
  return;