deadbyte-mcp 0.15.68 → 0.15.69
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 +81 -38
- package/PROVENANCE.json +35 -23
- package/README.txt +44 -31
- package/RELEASE.json +30 -17
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +25 -2
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +65 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +97 -0
- package/docs/R186-OWNER-CONSENT-HANDOFF.txt +87 -0
- package/package.json +1 -1
- package/scripts/install-r186-owner-trust.ps1 +59 -0
- package/scripts/r175-focused-runner.mjs +38 -0
- package/scripts/r176-log-preview.mjs +18 -0
- package/scripts/r177-owner-trust-inventory.ps1 +36 -0
- package/scripts/r178-full-regression.mjs +21 -0
- package/scripts/r178-npm-ci.mjs +10 -0
- package/scripts/r178-npm-preflight.mjs +16 -0
- package/scripts/r178-release-gates-readonly.mjs +9 -0
- package/scripts/r178-shard-regression.mjs +25 -0
- package/scripts/r179-predecessor-registry.mjs +24 -0
- package/scripts/r179-protected-inventory.ps1 +21 -0
- package/scripts/r186-focused-runner.mjs +22 -0
- package/scripts/r186-owner-anchor-inspect.mjs +6 -0
- package/scripts/r186-owner-consent-check.mjs +86 -0
- package/scripts/r186-owner-grant-cli.mjs +45 -0
- package/scripts/r186-owner-key-local.mjs +22 -0
- package/scripts/r186-owner-review-export.mjs +34 -0
- package/src/deadbyte-cli.mjs +69 -56
- package/src/r174-owner-webauthn.mjs +176 -0
- package/src/r175-foreground-boundary.mjs +28 -0
- package/src/r186-owner-anchor.mjs +12 -0
- package/src/r186-owner-consent-preflight.mjs +63 -0
- package/src/r186-owner-grant-broker.mjs +96 -0
- package/src/r186-owner-local-signing.mjs +25 -0
- package/src/r186-owner-request.mjs +40 -0
- package/src/r186-owner-review-export.mjs +49 -0
- package/src/remote-live-log.mjs +81 -5
- package/src/remote-session-guardian.mjs +23 -0
- package/src/version.mjs +1 -1
- package/test/helpers/r175-active-pointer.mjs +28 -0
- package/test/helpers/release-lock.mjs +12 -0
- package/test/r129-sdk-security-baseline.test.mjs +2 -1
- package/test/r130-release-security-contract.test.mjs +11 -10
- package/test/r132-release-identity.test.mjs +12 -11
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r155-default-live-log.test.mjs +17 -16
- package/test/r160-package-decision-manifest.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +4 -5
- package/test/r163-isolated-slot-rollback-restore.test.mjs +6 -6
- package/test/r165-live-cli-skew.test.mjs +6 -6
- package/test/r167-release-contract.test.mjs +15 -13
- package/test/r168-protected-entrypoint.test.mjs +5 -2
- package/test/r168-release-contract.test.mjs +4 -3
- package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
- package/test/r172-release-contract.test.mjs +7 -7
- package/test/r173-release-contract.test.mjs +18 -17
- package/test/r174-owner-webauthn.test.mjs +144 -0
- package/test/r175-cli-flags.test.mjs +23 -0
- package/test/r175-foreground-boundary.test.mjs +87 -0
- package/test/r175-guardian-owner-boundary.test.mjs +56 -0
- package/test/r176-pretty-live-log.test.mjs +54 -0
- package/test/r177-windows-absolute-live-path.test.mjs +37 -0
- package/test/r179-absolute-path-live-log.test.mjs +40 -0
- package/test/r179-release-contract.test.mjs +62 -0
- package/test/r186-controller-owner-grant.test.mjs +20 -0
- package/test/r186-owner-consent-preflight.test.mjs +98 -0
- package/test/r186-owner-enrollment.test.mjs +20 -0
- package/test/r186-owner-grant-broker.test.mjs +85 -0
- package/test/r186-owner-local-signing.test.mjs +30 -0
- package/test/r186-owner-request.test.mjs +28 -0
- package/test/r186-owner-review-export.test.mjs +52 -0
- package/test/r48-release-contract.test.mjs +2 -1
- package/test/r51-runtime-coherence.test.mjs +4 -2
- package/test/r58-cleanup-retention.test.mjs +5 -12
- package/test/r58-concurrent-recovery.test.mjs +5 -1
- package/test/r58-stale-owner.test.mjs +9 -3
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/remote-live-log-r37.test.mjs +2 -2
- package/test/remote-live-log-recovery-r36.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +92 -126
- package/node_modules/which/CHANGELOG.md +0 -166
package/src/remote-live-log.mjs
CHANGED
|
@@ -11,7 +11,7 @@ const LIFECYCLE=Object.freeze({
|
|
|
11
11
|
session_new:'[SESSION] New foreground session',
|
|
12
12
|
session_restored:'♻️ [SESSION] Previous result stream replayed',
|
|
13
13
|
ready:'✅ [READY] DEADBYTE MCP online',
|
|
14
|
-
cleanup:'
|
|
14
|
+
cleanup:'[SESSION] Closing observation session...'
|
|
15
15
|
});
|
|
16
16
|
const LIFECYCLE_ASCII=Object.freeze({
|
|
17
17
|
device_start:'[DEVICE] Starting DEADBYTE MCP...',
|
|
@@ -20,7 +20,7 @@ const LIFECYCLE_ASCII=Object.freeze({
|
|
|
20
20
|
session_new:'[SESSION] New foreground session',
|
|
21
21
|
session_restored:'[SESSION] Previous result stream replayed',
|
|
22
22
|
ready:'[READY] DEADBYTE MCP online',
|
|
23
|
-
cleanup:'[
|
|
23
|
+
cleanup:'[SESSION] Closing observation session...'
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
function safeAtom(value){
|
|
@@ -104,13 +104,33 @@ function rootedPath(args,key='path',rootPaths={}){
|
|
|
104
104
|
if(base){
|
|
105
105
|
const flavor=/^(?:[A-Za-z]:[\\/]|\\\\)/.test(base)?path.win32:path.posix;
|
|
106
106
|
const absolute=p==='.'?flavor.resolve(base):flavor.resolve(base,p);
|
|
107
|
+
const relative=flavor.relative(flavor.resolve(base),absolute);
|
|
108
|
+
// A display-only resolver must not misleadingly mark traversal as in-root.
|
|
109
|
+
if(relative==='..'||relative.startsWith('..'+flavor.sep)||flavor.isAbsolute(relative))
|
|
110
|
+
return '[outside root='+root+'] '+p;
|
|
107
111
|
return absolute+' [root='+root+']';
|
|
108
112
|
}
|
|
113
|
+
// We do not invent drive letters when the trusted runtime policy has
|
|
114
|
+
// not supplied the actual root mapping.
|
|
109
115
|
return root+':'+p;
|
|
110
116
|
}
|
|
111
117
|
if(root) return 'root='+root;
|
|
112
118
|
return p?'path='+p:'';
|
|
113
119
|
}
|
|
120
|
+
function detailedDisplayPaths(args,rootPaths){
|
|
121
|
+
const safe=sanitizeAuditArgs(args??{});
|
|
122
|
+
const paths=[];
|
|
123
|
+
const entry=(key,label)=>{
|
|
124
|
+
if(!safePathAtom(safe?.[key]))return;
|
|
125
|
+
const value=rootedPath(safe,key,rootPaths)+(safe.root_id&&!safePathAtom(rootPaths?.[safe.root_id])?' [unresolved root]':'');
|
|
126
|
+
if(value)paths.push((label?label+': ':'')+value);
|
|
127
|
+
};
|
|
128
|
+
if(safe?.source_path&&safe?.destination_path){
|
|
129
|
+
entry('source_path','FROM');entry('destination_path','TO');
|
|
130
|
+
}else if(safe?.path)entry('path','');
|
|
131
|
+
else if(safe?.script_path)entry('script_path','SCRIPT');
|
|
132
|
+
return paths;
|
|
133
|
+
}
|
|
114
134
|
function displayToolName(tool,args){
|
|
115
135
|
const op=safeAtom(args?.op);
|
|
116
136
|
if(op&&['machine_fs','coding_inspect','process_session','desktop_control','machine_observe'].includes(tool)) return tool+'.'+op;
|
|
@@ -399,10 +419,12 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
|
|
|
399
419
|
|
|
400
420
|
export function defaultForegroundTimelineOptions({verbose=false}={}){
|
|
401
421
|
if(typeof verbose!=='boolean')throw new Error('default live timeline verbosity invalid');
|
|
402
|
-
|
|
422
|
+
// R176 default: readable emoji timeline with compact heartbeat groups.
|
|
423
|
+
// Commander/verbose presentation remains explicitly selectable by callers.
|
|
424
|
+
return Object.freeze({commanderStyle:false,foldHeartbeats:!verbose,prettyStyle:!verbose});
|
|
403
425
|
}
|
|
404
426
|
|
|
405
|
-
export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,foldHeartbeats=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000,commanderStyle=false}={}){
|
|
427
|
+
export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,foldHeartbeats=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000,commanderStyle=false,prettyStyle=false}={}){
|
|
406
428
|
if(typeof clock!=='function') throw new Error('live timeline clock must be a function');
|
|
407
429
|
if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('remote display mode invalid');
|
|
408
430
|
if(!Number.isInteger(heartbeatBurstLimit)||heartbeatBurstLimit<2||heartbeatBurstLimit>100) throw new Error('heartbeatBurstLimit invalid');
|
|
@@ -410,6 +432,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
410
432
|
if(!rootPaths||typeof rootPaths!=='object'||Array.isArray(rootPaths)) throw new Error('live timeline rootPaths invalid');
|
|
411
433
|
if(typeof trace!=='boolean') throw new Error('live timeline trace invalid');
|
|
412
434
|
if(typeof commanderStyle!=='boolean') throw new Error('live timeline commanderStyle invalid');
|
|
435
|
+
if(typeof prettyStyle!=='boolean') throw new Error('live timeline prettyStyle invalid');
|
|
413
436
|
if(typeof foldHeartbeats!=='boolean') throw new Error('live timeline foldHeartbeats invalid');
|
|
414
437
|
if(!Number.isInteger(slowCallMs)||slowCallMs<100||slowCallMs>600000) throw new Error('live timeline slowCallMs invalid');
|
|
415
438
|
const pending=new Map();
|
|
@@ -428,6 +451,23 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
428
451
|
return visual+' '+displayClock(eventMs(event,clock))+' '+seq+'['+shortSpanId(event.request_id)+'] '+tool;
|
|
429
452
|
}
|
|
430
453
|
function traceSuffix(meta){return trace&&meta?' '+traceText(meta,displayMode):'';}
|
|
454
|
+
function prettyLine(event,tool,tag,detail='',glyph=null,displayPaths=[]){
|
|
455
|
+
const time=displayClock(eventMs(event,clock)).slice(0,8);
|
|
456
|
+
const title=tool.length>30?tool.slice(0,29)+'…':tool;
|
|
457
|
+
// Spaces avoid incorrect console-cell widths for double-width emoji.
|
|
458
|
+
const icon=glyph??iconForTool(tool,displayMode);
|
|
459
|
+
const leader=displayMode==='ascii'?' | ':' │ ';
|
|
460
|
+
const shortId=shortSpanId(event.request_id);
|
|
461
|
+
const label=title.padEnd(31,' ');
|
|
462
|
+
const headline=time+' '+icon+' '+tag.padEnd(8,' ')+' '+label+
|
|
463
|
+
leader+'['+shortId+']'+(detail?' '+clipped(detail,220):'');
|
|
464
|
+
if(!displayPaths.length)return remoteDisplayText(headline,displayMode);
|
|
465
|
+
const pathIcon=displayMode==='ascii'?'PATH: ':'📁 ';
|
|
466
|
+
// Paths are not shortened to an ambiguous basename: retain the Windows
|
|
467
|
+
// drive, directory tree and policy root. Respect the bounded input limit.
|
|
468
|
+
const descriptions=displayPaths.map(p=>' '+pathIcon+p);
|
|
469
|
+
return remoteDisplayText([headline,...descriptions].join('\n'),displayMode);
|
|
470
|
+
}
|
|
431
471
|
function resultLine(event,span,sequence){
|
|
432
472
|
const outcome=outcomeClass(event);
|
|
433
473
|
const error=outcome==='error';
|
|
@@ -458,6 +498,16 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
458
498
|
const prefixIcon=unknown?(displayMode==='ascii'?'WARN':'⚠️'):null;
|
|
459
499
|
const evidence=receipt?' '+(displayMode==='ascii'?'RECEIPT':'🧾 receipt')+(displayMode==='ascii'?'=':'=')+receipt:'';
|
|
460
500
|
const reasonSep=displayMode==='ascii'?' - ':' — ';
|
|
501
|
+
if(prettyStyle){
|
|
502
|
+
const tag=unknown?'UNKNOWN':pendingResult?'PENDING':error?(rawStatus==='denied'?'DENIED':'FAIL'):'PASS';
|
|
503
|
+
const glyph=displayMode==='ascii'?(unknown?'!':error?'X':pendingResult?'~':'+'):
|
|
504
|
+
unknown?'⚠️':error?'❌':pendingResult?'⏳':'✅';
|
|
505
|
+
const pathList=span?.display_paths??[];
|
|
506
|
+
const nonPathSummary=pathList.length?summary.replace(pathList[0],'').trim():summary;
|
|
507
|
+
const detail=[duration,trace&&span?.trace_meta?traceText(span.trace_meta,displayMode):'',nonPathSummary,statusDetail.trim(),receipt?'receipt='+receipt:'',
|
|
508
|
+
reason?reason:''].filter(Boolean).join(' | ');
|
|
509
|
+
return prettyLine(event,displayTool,tag,detail,glyph,pathList);
|
|
510
|
+
}
|
|
461
511
|
if(commanderStyle){
|
|
462
512
|
const tag=unknown?'OUTCOME_UNKNOWN':pendingResult?'pending':error?'failed':'completed';
|
|
463
513
|
const state=rawStatus?' status='+rawStatus:'';
|
|
@@ -475,6 +525,11 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
475
525
|
const endClock=displayClock(value.last_ms);
|
|
476
526
|
const range=value.first_ms===value.last_ms?endClock:startClock.slice(0,8)+'–'+endClock.slice(0,8);
|
|
477
527
|
if(value.count>1) foldedHeartbeatLines+=value.count-1;
|
|
528
|
+
if(prettyStyle){
|
|
529
|
+
return prettyLine({request_id:'polling',created_at_utc:new Date(value.last_ms).toISOString()},
|
|
530
|
+
value.tool,'FOLDED','×'+value.count+' avg '+formatDuration(avg),
|
|
531
|
+
displayMode==='ascii'?'H':'📡');
|
|
532
|
+
}
|
|
478
533
|
const marker=displayMode==='ascii'?'OK':'✓';
|
|
479
534
|
return remoteDisplayText(iconForTool(value.tool,displayMode)+' '+range+' '+seq+value.tool+' ×'+value.count+' avg '+formatDuration(avg)+' '+marker,displayMode);
|
|
480
535
|
}
|
|
@@ -512,12 +567,13 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
512
567
|
const args=event.args??{};
|
|
513
568
|
const displayTool=displayToolName(tool,args);
|
|
514
569
|
const summary=summarizeLiveToolArgs(displayTool,args,{rootPaths});
|
|
570
|
+
const displayPaths=prettyStyle?detailedDisplayPaths(args,rootPaths):[];
|
|
515
571
|
const traceMeta=traceIdentity(event,args);
|
|
516
572
|
if(pending.size>=MAX_PRESENTATION_IDENTITIES){
|
|
517
573
|
pendingOverflow++;
|
|
518
574
|
return pendingOverflow===1?remoteDisplayText('LIVELOG pending span cap reached; full audit remains on disk',displayMode):null;
|
|
519
575
|
}
|
|
520
|
-
pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,trace_meta:traceMeta,started_at_ms:clock()});
|
|
576
|
+
pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,display_paths:displayPaths,trace_meta:traceMeta,started_at_ms:clock()});
|
|
521
577
|
peakInFlight=Math.max(peakInFlight,pending.size);
|
|
522
578
|
if(commanderStyle){
|
|
523
579
|
const safeArgs=sanitizeAuditArgs(args);
|
|
@@ -527,6 +583,9 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
527
583
|
}
|
|
528
584
|
if(verbose) return formatRemoteToolCall({...event,tool:displayTool},{clock,displayMode})+traceSuffix(traceMeta);
|
|
529
585
|
if(visibleStart(displayTool)){
|
|
586
|
+
if(prettyStyle) return prettyLine(event,displayTool,'START',
|
|
587
|
+
[trace?traceText(traceMeta,displayMode):'',displayPaths.length?summary.replace(displayPaths[0],'').trim():summary].filter(Boolean).join(' | '),
|
|
588
|
+
null,displayPaths);
|
|
530
589
|
const startMarker=displayMode==='ascii'?'START':'▶ START';
|
|
531
590
|
return remoteDisplayText(eventPrefix(event,sequence,{displayTool})+traceSuffix(traceMeta)+' '+startMarker+(summary?' '+summary:''),displayMode);
|
|
532
591
|
}
|
|
@@ -606,6 +665,23 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
606
665
|
const p50=sorted.length?(sorted.length%2?sorted[(sorted.length-1)/2]:(sorted[sorted.length/2-1]+sorted[sorted.length/2])/2):null;
|
|
607
666
|
const p95=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.95)-1)]:null;
|
|
608
667
|
const p99=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.99)-1)]:null;
|
|
668
|
+
if(prettyStyle){
|
|
669
|
+
const ascii=displayMode==='ascii';
|
|
670
|
+
const lines=[
|
|
671
|
+
(ascii?'[SUMMARY]':'📊 SESSION')+' '+resultCount+' calls | '+okCount+' ok | '+
|
|
672
|
+
errorCount+(errorCount===1?' error | ':' errors | ')+unknownCount+' unknown'+(pendingCount?' | '+pendingCount+' pending':''),
|
|
673
|
+
(ascii?' work:':' 🛠 work:')+' '+mutationCount+' mutations | peak '+peakInFlight+' in-flight'+
|
|
674
|
+
(foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeats':''),
|
|
675
|
+
(ascii?' timing:':' ⏱ timing:')+' p50 '+(p50===null?'n/a':formatDuration(p50))+
|
|
676
|
+
' | p95 '+(p95===null?'n/a':formatDuration(p95))+
|
|
677
|
+
' | p99 '+(p99===null?'n/a':formatDuration(p99))+
|
|
678
|
+
(slowest?' | slowest '+slowest.tool+' '+formatDuration(slowest.duration_ms):''),
|
|
679
|
+
(ascii?' top:':' 🔝 top:')+(top?' '+top:' none')
|
|
680
|
+
];
|
|
681
|
+
if(pendingOverflow)lines.push((ascii?' WARNING:':' ⚠️ WARNING:')+
|
|
682
|
+
' '+pendingOverflow+' untracked presentation spans; inspect signed audit');
|
|
683
|
+
return remoteDisplayText(lines.join('\n'),displayMode);
|
|
684
|
+
}
|
|
609
685
|
return remoteDisplayText((displayMode==='ascii'?'[SUMMARY]':'📊 [SUMMARY]')+' '+resultCount+' calls | '+okCount+' ok | '+
|
|
610
686
|
errorCount+' error'+(errorCount===1?'':'s')+' | '+unknownCount+' unknown'+(pendingCount?' | '+pendingCount+' pending':'')+' | '+mutationCount+' mutation'+(mutationCount===1?'':'s')+
|
|
611
687
|
' | tool time '+(formatDuration(totalDurationMs)||'0ms')+
|
|
@@ -8,6 +8,7 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
|
8
8
|
import { resolveStateRoot } from './state-root.mjs';
|
|
9
9
|
import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
|
|
10
10
|
import { readRemoteMarkerSnapshot, deletePinnedRemoteMarker } from './foreground-marker-cas-win32.mjs';
|
|
11
|
+
import {assertObserveOnlyMarker} from './r175-foreground-boundary.mjs';
|
|
11
12
|
|
|
12
13
|
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
|
|
13
14
|
const TRUSTED_WINDOWS_ROOT='C:\\Windows';
|
|
@@ -68,6 +69,9 @@ async function main(){
|
|
|
68
69
|
const ownerPid=Number(args['owner-pid']);
|
|
69
70
|
const nonce=String(args['session-nonce']||'');
|
|
70
71
|
const ipcOwner=args['owner-channel']==='ipc';
|
|
72
|
+
const observerGuard=args['authority-scope']==='observe-only';
|
|
73
|
+
if(args['authority-scope']!==undefined&&!observerGuard)
|
|
74
|
+
throw new Error('R175 guardian authority scope invalid');
|
|
71
75
|
if(ipcOwner&&typeof process.send!=='function')throw new Error('guardian owner IPC channel missing');
|
|
72
76
|
if(!Number.isInteger(ownerPid)||ownerPid<1) throw new Error('guardian owner pid invalid');
|
|
73
77
|
if(ipcOwner&&process.ppid!==ownerPid)throw new Error('guardian IPC parent does not match declared owner');
|
|
@@ -85,6 +89,25 @@ async function main(){
|
|
|
85
89
|
continue;
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
// R179: a missing command-line flag is never permission to downgrade an
|
|
93
|
+
// observation-only marker to legacy broad authority revocation.
|
|
94
|
+
if(assertObserveOnlyMarker(marker)&&!observerGuard)
|
|
95
|
+
throw new Error('R179_OBSERVER_GUARDIAN_SCOPE_DOWNGRADE: owner grants retained');
|
|
96
|
+
if(observerGuard){
|
|
97
|
+
// Guardian scope is inherited from the parent process command line.
|
|
98
|
+
// Marker mismatch can never degrade into legacy unconditional revoke.
|
|
99
|
+
if(!assertObserveOnlyMarker(marker))
|
|
100
|
+
throw new Error('R175 observer guardian marker invalid; no grants touched');
|
|
101
|
+
// Observation-only sessions own the foreground marker, never the
|
|
102
|
+
// process/coding/host/desktop/autonomous grants or runtime.
|
|
103
|
+
const current=await readRemoteMarkerSnapshot(markerPath);
|
|
104
|
+
if(!current)return;
|
|
105
|
+
if(!markerMatches(current.marker,{ownerPid,nonce})||!assertObserveOnlyMarker(current.marker))return;
|
|
106
|
+
if(deletePinnedRemoteMarker(markerPath,current.sha256)!=='REMOVED')
|
|
107
|
+
throw new Error('R175 guardian monitor marker CAS failed; marker retained');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
88
111
|
const failed=[];
|
|
89
112
|
for(const command of CLEANUP){
|
|
90
113
|
const latest=await readMarker(markerPath);
|
package/src/version.mjs
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {mkdir,readFile,writeFile} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const sha256=value=>createHash('sha256').update(value).digest('hex');
|
|
6
|
+
|
|
7
|
+
export async function installExactActivePointer({stateRoot,packageRoot,launcherPath}){
|
|
8
|
+
const manifestBytes=await readFile(path.join(packageRoot,'MANIFEST.SHA256'));
|
|
9
|
+
const manifestSha256=sha256(manifestBytes);
|
|
10
|
+
const pkg=JSON.parse(await readFile(path.join(packageRoot,'package.json'),'utf8'));
|
|
11
|
+
const generationRoot=path.join(stateRoot,'generations','test-current');
|
|
12
|
+
const configPath=path.join(generationRoot,'controller-config.psd1');
|
|
13
|
+
await mkdir(generationRoot,{recursive:true});
|
|
14
|
+
await writeFile(configPath,"@{ Label = 'test-current' }\r\n",'utf8');
|
|
15
|
+
const pointer={
|
|
16
|
+
schema:'deadbyte.active-release.v1',
|
|
17
|
+
release_id:`v${pkg.version}-${manifestSha256.slice(0,16)}`,
|
|
18
|
+
version:pkg.version,
|
|
19
|
+
manifest_sha256:manifestSha256,
|
|
20
|
+
release_root:path.resolve(packageRoot),
|
|
21
|
+
generation_root:generationRoot,
|
|
22
|
+
generation_manifest_sha256:sha256(await readFile(configPath)),
|
|
23
|
+
launcher_sha256:sha256(await readFile(launcherPath)),
|
|
24
|
+
config_path:configPath
|
|
25
|
+
};
|
|
26
|
+
await writeFile(path.join(stateRoot,'active-release.json'),`${JSON.stringify(pointer)}\n`,'utf8');
|
|
27
|
+
return pointer;
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {readFile} from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
|
|
5
|
+
export async function readReleaseLock(root){
|
|
6
|
+
const rootPath=root instanceof URL?fileURLToPath(root):root;
|
|
7
|
+
for(const relative of ['package-lock.json','docs/R184-package-lock.json']){
|
|
8
|
+
try{return JSON.parse(await readFile(path.join(rootPath,relative),'utf8'));}
|
|
9
|
+
catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
10
|
+
}
|
|
11
|
+
throw new Error('R184_RELEASE_LOCK_EVIDENCE_MISSING');
|
|
12
|
+
}
|
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import {readFile} from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {readReleaseLock} from './helpers/release-lock.mjs';
|
|
6
7
|
|
|
7
8
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
8
9
|
async function json(file){return JSON.parse(await readFile(path.join(root,file),'utf8'));}
|
|
@@ -29,7 +30,7 @@ test('R129 shipped client and server SDK exclude all recorded 2.1.0 security adv
|
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
test('R129 lockfile resolves security-fixed SDK packages without prerelease versions',async()=>{
|
|
32
|
-
const lock=await
|
|
33
|
+
const lock=await readReleaseLock(root);
|
|
33
34
|
const pins=['@modelcontextprotocol/client','@modelcontextprotocol/server'];
|
|
34
35
|
for(const dep of pins){
|
|
35
36
|
const version=lock.packages?.['node_modules/'+dep]?.version;
|
|
@@ -4,23 +4,24 @@ import {readFile} from 'node:fs/promises';
|
|
|
4
4
|
import {fileURLToPath} from 'node:url';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import {DEADBYTE_VERSION} from '../src/version.mjs';
|
|
7
|
+
import {readReleaseLock} from './helpers/release-lock.mjs';
|
|
7
8
|
|
|
8
9
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
10
|
const read=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
|
|
10
|
-
test('current successor retains the R130 security baseline with truthful
|
|
11
|
+
test('current successor retains the R130 security baseline with truthful R179 identity',async()=>{
|
|
11
12
|
const [pkg,release,provenance,bom,lock,matrix]=await Promise.all([
|
|
12
13
|
read('package.json'),read('RELEASE.json'),read('PROVENANCE.json'),
|
|
13
|
-
read('SBOM.cdx.json'),
|
|
14
|
+
read('SBOM.cdx.json'),readReleaseLock(root),read('TEST-MATRIX.json')]);
|
|
14
15
|
assert.equal(pkg.name,'deadbyte-mcp');
|
|
15
16
|
for(const actual of [pkg.version,release.version,provenance.version,matrix.version,bom.metadata.component.version,lock.version,lock.packages[''].version,DEADBYTE_VERSION])
|
|
16
|
-
assert.equal(actual,'0.15.
|
|
17
|
-
assert.equal(release.train,'
|
|
18
|
-
assert.equal(matrix.train,'
|
|
19
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
20
|
-
assert.equal(release.source_predecessor.train,'
|
|
21
|
-
assert.match(release.source_predecessor.source_path,/0\.15\.
|
|
22
|
-
assert.equal(release.source_predecessor.manifest_status,'
|
|
23
|
-
assert.equal(release.runtime_predecessor.version,'0.15.
|
|
17
|
+
assert.equal(actual,'0.15.69');
|
|
18
|
+
assert.equal(release.train,'R179');
|
|
19
|
+
assert.equal(matrix.train,'R179');
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.68');
|
|
21
|
+
assert.equal(release.source_predecessor.train,'R173');
|
|
22
|
+
assert.match(release.source_predecessor.source_path,/deadbyte-mcp-0\.15\.68\.tgz/i);
|
|
23
|
+
assert.equal(release.source_predecessor.manifest_status,'ACTIVE_PRODUCTION_MANIFEST_VERIFIED');
|
|
24
|
+
assert.equal(release.runtime_predecessor.version,'0.15.68');
|
|
24
25
|
assert.equal(provenance.r160.source_predecessor_manifest_status,'STALE_REJECTED');
|
|
25
26
|
assert.equal(pkg.dependencies['@modelcontextprotocol/client'],'2.3.1');
|
|
26
27
|
assert.equal(pkg.dependencies['@modelcontextprotocol/server'],'2.3.1');
|
|
@@ -4,28 +4,29 @@ import {readFile} from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
import {DEADBYTE_VERSION} from '../src/version.mjs';
|
|
7
|
+
import {readReleaseLock} from './helpers/release-lock.mjs';
|
|
7
8
|
|
|
8
9
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
10
|
const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
|
|
10
11
|
|
|
11
|
-
test('
|
|
12
|
+
test('R179 package identity preserves R132 evidence and published R173 source predecessor',async()=>{
|
|
12
13
|
const [pkg,lock,release,prov,bom,matrix]=await Promise.all([
|
|
13
|
-
read('package.json'),
|
|
14
|
+
read('package.json'),readReleaseLock(root),read('RELEASE.json'),
|
|
14
15
|
read('PROVENANCE.json'),read('SBOM.cdx.json'),read('TEST-MATRIX.json')]);
|
|
15
16
|
for(const value of [pkg.version,lock.version,lock.packages[''].version,release.version,prov.version,bom.metadata.component.version,matrix.version,DEADBYTE_VERSION]){
|
|
16
17
|
assert.equal(value,pkg.version);
|
|
17
18
|
}
|
|
18
|
-
assert.equal(release.train,'
|
|
19
|
-
assert.equal(matrix.train,'
|
|
20
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
21
|
-
assert.equal(release.source_predecessor.train,'
|
|
22
|
-
assert.match(release.source_predecessor.source_path,/0\.15\.
|
|
19
|
+
assert.equal(release.train,'R179');
|
|
20
|
+
assert.equal(matrix.train,'R179');
|
|
21
|
+
assert.equal(release.source_predecessor.version,'0.15.68');
|
|
22
|
+
assert.equal(release.source_predecessor.train,'R173');
|
|
23
|
+
assert.match(release.source_predecessor.source_path,/deadbyte-mcp-0\.15\.68\.tgz/i);
|
|
23
24
|
assert.equal(prov.r160.source_predecessor_manifest_status,'STALE_REJECTED');
|
|
24
25
|
assert.equal(release.r165.published_source_manifest_sha256,'4adf7d94682fbb71c4a91eb3147ecc48076bbc5d79cb67fcb875cde6a1bb4805');
|
|
25
26
|
assert.equal(release.r165.published_source_tarball_sha256,'812e97609b7c51353ed25f2202129737e43bb4fbe0778c3adec43ec065352641');
|
|
26
|
-
assert.equal(release.source_predecessor.tarball_sha256,'
|
|
27
|
-
assert.equal(release.
|
|
28
|
-
assert.equal(release.runtime_predecessor.version,'0.15.
|
|
27
|
+
assert.equal(release.source_predecessor.tarball_sha256,'1a7f1fe05a06178b8d772a140be6e0470790b743cef690c3f8f8165cc61075bf');
|
|
28
|
+
assert.equal(release.r179.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
|
|
29
|
+
assert.equal(release.runtime_predecessor.version,'0.15.68');
|
|
29
30
|
assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
|
|
30
31
|
assert.equal(prov.runtime_predecessor.manifest_sha256,release.runtime_predecessor.manifest_sha256);
|
|
31
32
|
assert.equal(prov.runtime_predecessor.generation_manifest_sha256,release.runtime_predecessor.generation_manifest_sha256);
|
|
@@ -36,7 +37,7 @@ test('R173 package identity preserves R132 evidence and published R172 source pr
|
|
|
36
37
|
});
|
|
37
38
|
|
|
38
39
|
test('R132 shipped SBOM matches the actual audited bundled MCP SDK',async()=>{
|
|
39
|
-
const [pkg,bom,lock]=await Promise.all([read('package.json'),read('SBOM.cdx.json'),
|
|
40
|
+
const [pkg,bom,lock]=await Promise.all([read('package.json'),read('SBOM.cdx.json'),readReleaseLock(root)]);
|
|
40
41
|
for(const name of ['@modelcontextprotocol/client','@modelcontextprotocol/server','@modelcontextprotocol/core']){
|
|
41
42
|
const entry=bom.components.find(v=>v.name===name);
|
|
42
43
|
assert.ok(entry,'missing SBOM component: '+name);
|
|
@@ -26,7 +26,7 @@ test('R153 historical verifier refuses current successor package identity',async
|
|
|
26
26
|
const state={pkg:JSON.parse(await readFile(path.join(root,'package.json'),'utf8')),
|
|
27
27
|
release:JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8')),
|
|
28
28
|
bridge:JSON.parse(await readFile(path.join(root,'bridge/package.json'),'utf8'))};
|
|
29
|
-
assert.equal(state.pkg.version,'0.15.
|
|
29
|
+
assert.equal(state.pkg.version,'0.15.69');
|
|
30
30
|
assert.throws(()=>verifyR153Lineage(state),/package version/);
|
|
31
31
|
await assert.rejects(verifyR153IdentityTree(root),/package version/);
|
|
32
32
|
});
|
|
@@ -14,25 +14,25 @@ function defaultTracker(){
|
|
|
14
14
|
return live.createLiveTimelineTracker({clock:now,...live.defaultForegroundTimelineOptions({verbose:false})});
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
test('
|
|
17
|
+
test('R176 default folds harmless status polling',()=>{
|
|
18
18
|
const tracker=defaultTracker();
|
|
19
19
|
const start=tracker.observe({event_type:'tool.request',request_id:'abc012345678',tool:'machine_status',args:{}},{sequence:1});
|
|
20
|
-
assert.
|
|
21
|
-
assert.match(start,/machine_status/);
|
|
20
|
+
assert.equal(start,null);
|
|
22
21
|
const done=tracker.observe({event_type:'tool.result',request_id:'abc012345678',tool:'machine_status',outcome:{status:'ok',duration_ms:3}},{sequence:2});
|
|
23
|
-
assert.
|
|
24
|
-
assert.match(
|
|
22
|
+
assert.equal(done,null);
|
|
23
|
+
assert.match(tracker.reconcileInterrupted().join('\n'),/machine_status.*×1/);
|
|
25
24
|
});
|
|
26
25
|
|
|
27
|
-
test('
|
|
26
|
+
test('R176 status bursts collapse without losing event totals',()=>{
|
|
28
27
|
const tracker=defaultTracker();
|
|
29
28
|
for(let n=0;n<3;n++){
|
|
30
29
|
const id='repeat-'+n;
|
|
31
|
-
assert.
|
|
32
|
-
assert.
|
|
30
|
+
assert.equal(tracker.observe({event_type:'tool.request',request_id:id,tool:'machine_ping',args:{}},{sequence:n*2+1}),null);
|
|
31
|
+
assert.equal(tracker.observe({event_type:'tool.result',request_id:id,tool:'machine_ping',outcome:{status:'ok',duration_ms:1}},{sequence:n*2+2}),null);
|
|
33
32
|
}
|
|
33
|
+
assert.match(tracker.reconcileInterrupted().join('\n'),/machine_ping.*×3/);
|
|
34
34
|
assert.match(tracker.summary(),/3 calls \| 3 ok/);
|
|
35
|
-
assert.
|
|
35
|
+
assert.match(tracker.summary(),/folded 2 heartbeat/);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
test('R155 ordinary remote keeps credentials and payload opaque by default',()=>{
|
|
@@ -42,7 +42,7 @@ test('R155 ordinary remote keeps credentials and payload opaque by default',()=>
|
|
|
42
42
|
command_id:'node_script',path:'C:\\Safe\\test.js',
|
|
43
43
|
metadata:{token:'Bearer should-not-appear'}
|
|
44
44
|
}},{sequence:1});
|
|
45
|
-
assert.match(line,/
|
|
45
|
+
assert.match(line,/START/);
|
|
46
46
|
assert.match(line,/node_script/);
|
|
47
47
|
assert.doesNotMatch(line,/sk-secret-test|PRIVATE SOURCE CONTENT|should-not-appear/);
|
|
48
48
|
});
|
|
@@ -51,10 +51,10 @@ test('R155 ordinary remote prints DENIED and UNKNOWN as failures rather than com
|
|
|
51
51
|
const tracker=defaultTracker();
|
|
52
52
|
tracker.observe({event_type:'tool.request',request_id:'one',tool:'host_file_read',args:{path:'a.txt'}},{sequence:1});
|
|
53
53
|
const fail=tracker.observe({event_type:'tool.result',request_id:'one',tool:'host_file_read',outcome:{status:'denied',is_error:true,reason:'arm file missing'}},{sequence:2});
|
|
54
|
-
assert.match(fail,/
|
|
54
|
+
assert.match(fail,/DENIED/);assert.match(fail,/arm file missing/);
|
|
55
55
|
tracker.observe({event_type:'tool.request',request_id:'two',tool:'coding_file_write',args:{path:'x.txt'}},{sequence:3});
|
|
56
56
|
const unknown=tracker.observe({event_type:'tool.result',request_id:'two',tool:'coding_file_write',outcome:{status:'interrupted',side_effect_state:'unknown'}},{sequence:4});
|
|
57
|
-
assert.match(unknown,/
|
|
57
|
+
assert.match(unknown,/UNKNOWN/);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
test('R155 ordinary remote has no new CLI live-log mode and wired default live behavior',()=>{
|
|
@@ -66,16 +66,17 @@ test('R155 ordinary remote has no new CLI live-log mode and wired default live b
|
|
|
66
66
|
assert.match(source,/defaultForegroundTimelineOptions\(\{verbose\}\)/);
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
test('
|
|
69
|
+
test('R176 same snapshot suppresses repeated capability messages',()=>{
|
|
70
70
|
const tracker=defaultTracker();
|
|
71
71
|
const snapshot='a'.repeat(64);
|
|
72
72
|
for(let n=0;n<2;n++){
|
|
73
73
|
const id='cap-'+n;
|
|
74
|
-
assert.
|
|
74
|
+
assert.equal(tracker.observe({event_type:'tool.request',request_id:id,tool:'capability_manifest',args:{}},{sequence:n*2+1}),null);
|
|
75
75
|
const done=tracker.observe({event_type:'tool.result',request_id:id,tool:'capability_manifest',outcome:{status:'ok',snapshot_id:snapshot,state_epoch:1,duration_ms:2}},{sequence:n*2+2});
|
|
76
|
-
assert.match(done,/
|
|
76
|
+
if(n===0) assert.match(done,/PASS/);
|
|
77
|
+
else assert.equal(done,null);
|
|
77
78
|
}
|
|
78
|
-
assert.match(tracker.summary(),/2 calls
|
|
79
|
+
assert.match(tracker.summary(),/2 calls \| 2 ok/);
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
test('R155 runtime-only stream reset does not claim audit corruption, audit reset remains severe',()=>{
|
|
@@ -59,7 +59,7 @@ test('R160 startup decision manifest rejects a newly required recovery module om
|
|
|
59
59
|
test('R160 remote startup verifies decision bytes before evidence recovery and foreground claim',async()=>{
|
|
60
60
|
const source=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
61
61
|
const verifyAt=source.indexOf('await verifyPackageDecisionManifest({root:CURRENT_PACKAGE_ROOT,expectedVersion:DEADBYTE_VERSION})');
|
|
62
|
-
const recoverAt=source.indexOf('
|
|
62
|
+
const recoverAt=source.indexOf('await recoverRemoteSession(ctx,{displayMode})');
|
|
63
63
|
const claimAt=source.indexOf('marker=await claimRemoteSession(ctx)');
|
|
64
64
|
assert.ok(verifyAt>0&&verifyAt<recoverAt&&recoverAt<claimAt,
|
|
65
65
|
'package decision manifest must be verified before recovery and claim');
|
|
@@ -8,12 +8,11 @@ const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
|
|
|
8
8
|
|
|
9
9
|
test('R160 historical contract remains distinct from the current package identity',async()=>{
|
|
10
10
|
const [release,provenance]=await Promise.all([read('RELEASE.json'),read('PROVENANCE.json')]);
|
|
11
|
-
assert.equal(release.runtime_predecessor.version,'0.15.
|
|
12
|
-
assert.equal(release.runtime_predecessor.release_id,'v0.15.
|
|
13
|
-
assert.equal(release.runtime_predecessor.manifest_sha256,'
|
|
14
|
-
assert.equal(release.runtime_predecessor.generation_manifest_sha256,'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4');
|
|
15
|
-
assert.equal(release.r160.source_predecessor_train,'R159');
|
|
11
|
+
assert.equal(release.runtime_predecessor.version,'0.15.68');
|
|
12
|
+
assert.equal(release.runtime_predecessor.release_id,'v0.15.68-ff29dbb2fbb89862');
|
|
13
|
+
assert.equal(release.runtime_predecessor.manifest_sha256,'ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885');
|
|
16
14
|
assert.equal(release.r160.runtime_predecessor_version,'0.15.61');
|
|
15
|
+
assert.equal(release.r160.source_predecessor_train,'R159');
|
|
17
16
|
assert.equal(release.r160.public_tool_delta,0);
|
|
18
17
|
assert.equal(release.r160.authority_scope_delta,0);
|
|
19
18
|
assert.equal(release.r160.production_activation,'NOT_RUN');
|
|
@@ -8,9 +8,9 @@ import {fileURLToPath} from 'node:url';
|
|
|
8
8
|
import {atomicWriteActivePointer,materializeReleaseSlot,parseActivePointer,parseManifest,renderStableLauncherPs1,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
9
9
|
|
|
10
10
|
const candidate=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
11
|
-
const version='0.15.
|
|
12
|
-
const parent=path.join(os.homedir(),'.deadbyte-mcp','releases','v0.15.
|
|
13
|
-
const parentDigest='
|
|
11
|
+
const version='0.15.69';
|
|
12
|
+
const parent=path.join(os.homedir(),'.deadbyte-mcp','releases','v0.15.68-ff29dbb2fbb89862');
|
|
13
|
+
const parentDigest='ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885';
|
|
14
14
|
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
15
15
|
async function generation(state,label){
|
|
16
16
|
const root=path.join(state,'generations',label);
|
|
@@ -21,7 +21,7 @@ async function generation(state,label){
|
|
|
21
21
|
await writeFile(path.join(root,'GENERATION.SHA256'),manifest,{flag:'wx'});
|
|
22
22
|
return {root,manifest_sha256:sha(manifest),config_path:path.join(root,'controller-config.psd1')};
|
|
23
23
|
}
|
|
24
|
-
test('
|
|
24
|
+
test('R179 isolated immutable slot activates, rolls back, and restores 0.15.69 without authority or production mutation',async()=>{
|
|
25
25
|
const stateBase=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r163-isolated-'));
|
|
26
26
|
const state=path.join(stateBase,'state');
|
|
27
27
|
try{
|
|
@@ -31,7 +31,7 @@ test('R173 isolated immutable slot activates, rolls back, and restores 0.15.68 w
|
|
|
31
31
|
const nextManifest=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
32
32
|
assert.equal(sha(parentManifest),parentDigest);
|
|
33
33
|
const nextDigest=sha(nextManifest);
|
|
34
|
-
const parentSlot=await materializeReleaseSlot({source:parent,stateRoot:state,version:'0.15.
|
|
34
|
+
const parentSlot=await materializeReleaseSlot({source:parent,stateRoot:state,version:'0.15.68',manifestSha256:parentDigest});
|
|
35
35
|
const nextSlot=await materializeReleaseSlot({source:candidate,stateRoot:state,version,manifestSha256:nextDigest});
|
|
36
36
|
const pGen=await generation(state,'r165-parent');
|
|
37
37
|
const nGen=await generation(state,'r165-next');
|
|
@@ -43,7 +43,7 @@ test('R173 isolated immutable slot activates, rolls back, and restores 0.15.68 w
|
|
|
43
43
|
const activated=await atomicWriteActivePointer(pointerPath,ptr(nextSlot,version,nGen));
|
|
44
44
|
assert.equal(activated.release_id,nextSlot.release_id);
|
|
45
45
|
await verifyManifestTreeExact(nextSlot.release_root,parseManifest(nextManifest.toString('utf8')));
|
|
46
|
-
const rolled=await atomicWriteActivePointer(pointerPath,ptr(parentSlot,'0.15.
|
|
46
|
+
const rolled=await atomicWriteActivePointer(pointerPath,ptr(parentSlot,'0.15.68',pGen));
|
|
47
47
|
assert.equal(rolled.release_id,parentSlot.release_id);
|
|
48
48
|
assert.equal(rolled.manifest_sha256,parentDigest);
|
|
49
49
|
await verifyManifestTreeExact(parentSlot.release_root,parseManifest(parentManifest.toString('utf8')));
|
|
@@ -17,7 +17,7 @@ const hashMarker=c=>c.repeat(64);
|
|
|
17
17
|
async function fixture(t,kind){
|
|
18
18
|
assert.equal(typeof envRoot,'string'); assert.ok(path.isAbsolute(envRoot));
|
|
19
19
|
assert.match(sha(await readFile(path.join(envRoot,'MANIFEST.SHA256'))),/^[0-9a-f]{64}$/);
|
|
20
|
-
assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.
|
|
20
|
+
assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.69');
|
|
21
21
|
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r164-skew-e2e-'));
|
|
22
22
|
t.after(async()=>{await rm(dir,{recursive:true,force:true,maxRetries:10,retryDelay:150});});
|
|
23
23
|
const state=path.join(dir,'state'),bin=path.join(state,'bin');
|
|
@@ -65,26 +65,26 @@ async function run(fx){
|
|
|
65
65
|
assert.doesNotMatch(trace,/\b(?:start|restart|armprocess|armcoding|armautonomous|deploy)\b/i,'unexpected runtime operation');
|
|
66
66
|
return {output:(result.stderr||'')+'\n'+(result.stdout||''),trace};
|
|
67
67
|
}
|
|
68
|
-
gate('
|
|
68
|
+
gate('R175 executable CLI blocks foreground observation on version skew before touching history',async t=>{
|
|
69
69
|
const fx=await fixture(t,'completed');
|
|
70
70
|
const r=await run(fx);
|
|
71
|
-
assert.match(r.output,/
|
|
71
|
+
assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
|
|
72
72
|
assert.doesNotMatch(r.output,/R159 ambiguous multiple completed/i);
|
|
73
73
|
assert.equal(r.trace,'');
|
|
74
74
|
assert.doesNotMatch(r.trace,/disarmdesktop/);
|
|
75
75
|
});
|
|
76
76
|
gate('R165 executable CLI rejects pending current reconciliation before upgrade',async t=>{
|
|
77
77
|
const fx=await fixture(t,'pending'),r=await run(fx);
|
|
78
|
-
assert.match(r.output,/
|
|
78
|
+
assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
|
|
79
79
|
assert.doesNotMatch(r.trace,/disarmhost/);
|
|
80
80
|
});
|
|
81
81
|
gate('R165 executable CLI rejects tampered terminal before upgrade',async t=>{
|
|
82
82
|
const fx=await fixture(t,'tampered'),r=await run(fx);
|
|
83
|
-
assert.match(r.output,/
|
|
83
|
+
assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
|
|
84
84
|
assert.doesNotMatch(r.trace,/disarmhost/);
|
|
85
85
|
});
|
|
86
86
|
gate('R165 executable CLI keeps ambiguous history fail-closed when predecessor is not ONLINE',async t=>{
|
|
87
87
|
const fx=await fixture(t,'offline'),r=await run(fx);
|
|
88
|
-
assert.match(r.output,/
|
|
88
|
+
assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
|
|
89
89
|
assert.doesNotMatch(r.trace,/disarmhost/);
|
|
90
90
|
});
|