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.
- package/MANIFEST.SHA256 +70 -32
- package/PROVENANCE.json +127 -87
- package/README.txt +12 -12
- package/RELEASE.json +226 -187
- package/SBOM.cdx.json +10 -25
- package/TEST-MATRIX.json +305 -258
- package/bridge/package.json +1 -1
- package/bridge/src/bridge-health-r59.mjs +4 -3
- package/bridge/src/bridge.mjs +75 -18
- package/bridge/src/canonical-json-r60.mjs +13 -0
- package/bridge/src/r40-governor.mjs +109 -19
- package/bridge/src/reconnect-delay-r60.mjs +31 -0
- package/bridge/src/remote-call-claims-r60.mjs +157 -0
- package/bridge/src/remote-dispatch-r60.mjs +75 -0
- package/controller/deadbyte-controller.ps1 +6 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R59-PROVENANCE.json +87 -0
- package/docs/R59-RELEASE.json +187 -0
- package/docs/R59-SBOM.cdx.json +68 -0
- package/docs/R59-TEST-MATRIX.json +258 -0
- package/docs/R59-bridge-package.json +13 -0
- package/docs/R59-package-lock.json +184 -0
- package/docs/R59-package.json +106 -0
- package/docs/R59-version.mjs +6 -0
- package/docs/R60-TASK-CONTRACT.md +22 -0
- package/docs/R61-TASK-CONTRACT.md +11 -0
- package/docs/r61-reference-evidence.md +18 -0
- package/package.json +6 -3
- package/scripts/full-regression-r60.mjs +32 -0
- package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
- package/scripts/gate-windows-r61-prepublish.ps1 +45 -0
- package/scripts/npm-artifact-identity-r61.mjs +22 -0
- package/scripts/npm-release-r48.mjs +21 -5
- package/scripts/r61-mixed-lane-benchmark.mjs +98 -0
- package/src/capability-snapshot.mjs +6 -2
- package/src/deadbyte-cli.mjs +89 -40
- package/src/remote-authority.mjs +56 -0
- package/src/remote-enforcement-probe-r60.mjs +17 -0
- package/src/remote-live-log.mjs +47 -7
- package/src/remote-log-decode-r60.mjs +32 -0
- package/src/remote-mcp-machine-smoke-client.mjs +19 -1
- package/src/remote-session-guardian.mjs +20 -3
- package/src/runtime-update.mjs +13 -1
- package/src/tool-audit.mjs +5 -0
- package/src/version.mjs +1 -1
- package/test/bridge-runtime-r59.test.mjs +5 -5
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/r48-release-contract.test.mjs +1 -1
- package/test/r51-runtime-coherence.test.mjs +2 -2
- package/test/r58-cleanup-retention.test.mjs +9 -3
- package/test/r58-concurrent-recovery.test.mjs +9 -3
- package/test/r58-controller-live-local.test.mjs +22 -3
- package/test/r58-stale-owner.test.mjs +2 -2
- package/test/r59-release-contract.test.mjs +6 -6
- package/test/r60-authority-coherence.test.mjs +36 -0
- package/test/r60-authority-enforcement.test.mjs +28 -0
- package/test/r60-bridge-dispatch.test.mjs +119 -0
- package/test/r60-clock-discipline.test.mjs +15 -0
- package/test/r60-live-timeline.test.mjs +29 -0
- package/test/r60-reconnect-errors.test.mjs +21 -0
- package/test/r60-reconnect-jitter.test.mjs +18 -0
- package/test/r60-release-contract.test.mjs +82 -0
- package/test/r60-remote-call-recovery.test.mjs +170 -0
- package/test/r60-windows-log-decode.test.mjs +25 -0
- package/test/r61-control-lane.test.mjs +154 -0
- package/test/r61-live-timeline-bounds.test.mjs +31 -0
- package/test/r61-npm-artifact-identity.test.mjs +17 -0
- package/test/r61-release-bridge-binding.test.mjs +32 -0
- package/test/r61-release-contract.test.mjs +44 -0
- package/test/remote-live-log-r37.test.mjs +24 -7
- package/test/remote-session-cli.test.mjs +200 -43
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
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,
|
|
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'&¤t.owner_pid===marker.owner_pid&¤t.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
|
|
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
|
-
|
|
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
|
+
}
|
package/src/remote-live-log.mjs
CHANGED
|
@@ -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.
|
|
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))
|
|
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(
|
|
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'
|
|
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
|
|
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;
|