deadbyte-mcp 0.15.37 → 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 +30 -20
- package/PROVENANCE.json +28 -15
- package/README.txt +12 -12
- package/RELEASE.json +26 -14
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +23 -15
- package/bridge/package.json +1 -1
- package/bridge/src/bridge.mjs +4 -4
- package/bridge/src/r40-governor.mjs +109 -19
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R61-TASK-CONTRACT.md +11 -0
- package/docs/r61-reference-evidence.md +18 -0
- package/package.json +4 -3
- 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/deadbyte-cli.mjs +9 -6
- package/src/remote-live-log.mjs +28 -5
- package/src/runtime-update.mjs +13 -1
- 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/r60-release-contract.test.mjs +4 -3
- 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 +23 -6
- package/test/remote-session-cli.test.mjs +1 -1
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}={}){
|
|
@@ -330,10 +335,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
330
335
|
if(!Number.isInteger(slowCallMs)||slowCallMs<100||slowCallMs>600000) throw new Error('live timeline slowCallMs invalid');
|
|
331
336
|
const pending=new Map();
|
|
332
337
|
const completed=new Set();
|
|
338
|
+
const MAX_PRESENTATION_IDENTITIES=4096;
|
|
333
339
|
const toolCounts=new Map();
|
|
334
340
|
const durationSamples=[];
|
|
335
341
|
let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
|
|
336
342
|
let lastCapability=null,suppressedCapability=0,suppressedCapabilityReported=false;
|
|
343
|
+
let lastSourceSequence=0,pendingOverflow=0;
|
|
337
344
|
|
|
338
345
|
function eventPrefix(event,sequence,{displayTool=null,icon=null}={}){
|
|
339
346
|
const seq=Number.isSafeInteger(sequence)&&sequence>0?'#'+sequence+' ':'';
|
|
@@ -362,10 +369,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
362
369
|
error?(displayMode==='ascii'?'ERR':'✗'):
|
|
363
370
|
['write','edit','run'].includes(category)?(displayMode==='ascii'?'SUCCEEDED':'✅ SUCCEEDED'):
|
|
364
371
|
(displayMode==='ascii'?'OK':'✓');
|
|
372
|
+
const rawStatus=safeAtom(event.outcome?.status);
|
|
373
|
+
const statusDetail=rawStatus&&!['ok','error'].includes(rawStatus)?' status='+rawStatus:'';
|
|
365
374
|
const prefixIcon=unknown?(displayMode==='ascii'?'WARN':'⚠️'):null;
|
|
366
375
|
const evidence=receipt?' '+(displayMode==='ascii'?'RECEIPT':'🧾 receipt')+(displayMode==='ascii'?'=':'=')+receipt:'';
|
|
367
376
|
const reasonSep=displayMode==='ascii'?' - ':' — ';
|
|
368
|
-
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);
|
|
369
378
|
}
|
|
370
379
|
function burstLine(value){
|
|
371
380
|
if(!value) return null;
|
|
@@ -386,7 +395,8 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
386
395
|
function addStats(event,span){
|
|
387
396
|
resultCount++;
|
|
388
397
|
const displayTool=span?.display_tool||event.tool;
|
|
389
|
-
toolCounts.
|
|
398
|
+
const countKey=toolCounts.has(displayTool)||toolCounts.size<256?displayTool:'[other]';
|
|
399
|
+
toolCounts.set(countKey,(toolCounts.get(countKey)??0)+1);
|
|
390
400
|
const outcome=outcomeClass(event);
|
|
391
401
|
if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else okCount++;
|
|
392
402
|
if(['write','edit','run'].includes(categoryForTool(displayTool))) mutationCount++;
|
|
@@ -397,8 +407,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
397
407
|
if(!slowest||duration>slowest.duration_ms) slowest={tool:displayTool,duration_ms:duration};
|
|
398
408
|
}
|
|
399
409
|
}
|
|
400
|
-
function observe(event,{sequence=null}={}){
|
|
410
|
+
function observe(event,{sequence=null,sourceSequence=null}={}){
|
|
401
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
|
+
}
|
|
402
416
|
const requestId=typeof event.request_id==='string'&&event.request_id?event.request_id:null;
|
|
403
417
|
const tool=typeof event.tool==='string'&&event.tool?event.tool:null;
|
|
404
418
|
if(event.event_type==='tool.request'&&requestId&&tool){
|
|
@@ -407,10 +421,17 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
407
421
|
const displayTool=displayToolName(tool,args);
|
|
408
422
|
const summary=summarizeLiveToolArgs(displayTool,args,{rootPaths});
|
|
409
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
|
+
}
|
|
410
428
|
pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,trace_meta:traceMeta,started_at_ms:clock()});
|
|
411
429
|
peakInFlight=Math.max(peakInFlight,pending.size);
|
|
412
430
|
if(verbose) return formatRemoteToolCall({...event,tool:displayTool},{clock,displayMode})+traceSuffix(traceMeta);
|
|
413
|
-
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
|
+
}
|
|
414
435
|
return null;
|
|
415
436
|
}
|
|
416
437
|
if(event.event_type==='tool.result'&&requestId&&tool){
|
|
@@ -418,6 +439,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
418
439
|
const span=pending.get(requestId)??null;
|
|
419
440
|
pending.delete(requestId);
|
|
420
441
|
completed.add(requestId);
|
|
442
|
+
if(completed.size>MAX_PRESENTATION_IDENTITIES)completed.delete(completed.values().next().value);
|
|
421
443
|
addStats(event,span);
|
|
422
444
|
const error=event.outcome?.is_error===true;
|
|
423
445
|
const nowMs=eventMs(event,clock);
|
|
@@ -487,6 +509,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
487
509
|
}
|
|
488
510
|
return Object.freeze({
|
|
489
511
|
observe,
|
|
512
|
+
retention(){return Object.freeze({pending:pending.size,completed:completed.size,pending_overflow:pendingOverflow,last_source_sequence:lastSourceSequence});},
|
|
490
513
|
pending(){return [...pending.values()].map(({request_id,tool})=>({request_id,tool}));},
|
|
491
514
|
reconcileInterrupted,
|
|
492
515
|
summary
|
package/src/runtime-update.mjs
CHANGED
|
@@ -394,6 +394,18 @@ export async function prepareBridgeSpec(config,bootstrap){
|
|
|
394
394
|
return next;
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
export async function prepareReleaseBridgeSpec(config,bootstrap,releaseRoot){
|
|
398
|
+
if(!bootstrap) return structuredClone(config);
|
|
399
|
+
if(typeof releaseRoot!=='string'||!path.isAbsolute(releaseRoot))throw new Error('immutable release root required for Bridge binding');
|
|
400
|
+
// Verify the requested development input, then bind runtime paths only to
|
|
401
|
+
// the manifest-verified immutable slot produced from that same candidate.
|
|
402
|
+
const verifiedSource=await prepareBridgeSpec(config,bootstrap);
|
|
403
|
+
return prepareBridgeSpec(verifiedSource,{
|
|
404
|
+
root:path.join(path.resolve(releaseRoot),'bridge'),
|
|
405
|
+
scriptSha256:verifiedSource.Bridge.ScriptSha256
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
397
409
|
export async function prepareAutonomousSupervisorSpec(config,{releaseRoot,enable=false}={}){
|
|
398
410
|
const next=structuredClone(config);
|
|
399
411
|
if(!enable) return next;
|
|
@@ -1682,7 +1694,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1682
1694
|
if(!control.desktopPolicy) throw new Error('DesktopRuntimeEnabled requires pinned desktop policy bootstrap or existing policy');
|
|
1683
1695
|
control.config.DesktopRuntimeEnabled=true;
|
|
1684
1696
|
}
|
|
1685
|
-
control.config=await
|
|
1697
|
+
control.config=await prepareReleaseBridgeSpec(control.config,bridgeBootstrap,slot.release_root);
|
|
1686
1698
|
control.config=await prepareNamedTunnelCloudSpec(control.config,{releaseRoot:slot.release_root,bootstrap:cloudTransport});
|
|
1687
1699
|
control.config=await prepareAutonomousSupervisorSpec(control.config,{releaseRoot:slot.release_root,enable:enableAutonomousSupervisor});
|
|
1688
1700
|
authorityPreparation=await prepareAuthorityPolicies({hostPolicy:control.hostPolicy,codingPolicy:control.codingPolicy},{commandPins:hostCommandPins});
|
package/src/version.mjs
CHANGED
|
@@ -12,21 +12,21 @@ const limits={
|
|
|
12
12
|
|
|
13
13
|
test('R59 bridge governor removes cancelled queued work and drains without replay',async()=>{
|
|
14
14
|
const governor=createBridgeGovernor({limits});
|
|
15
|
-
const first=await governor.acquire('
|
|
15
|
+
const first=await governor.acquire('coding_file_read');
|
|
16
16
|
const abort=new AbortController();
|
|
17
|
-
const queued=governor.acquire('
|
|
17
|
+
const queued=governor.acquire('coding_file_read',{signal:abort.signal});
|
|
18
18
|
abort.abort(new Error('caller disconnected'));
|
|
19
19
|
await assert.rejects(queued,/caller disconnected|aborted/);
|
|
20
20
|
assert.equal(governor.snapshot().normal.queued,0);
|
|
21
21
|
assert.equal(governor.snapshot().normal.cancelled,1);
|
|
22
|
-
const queuedDuringRecovery=governor.acquire('
|
|
22
|
+
const queuedDuringRecovery=governor.acquire('coding_file_read');
|
|
23
23
|
assert.equal(governor.reconcile('core generation changed'),1);
|
|
24
24
|
await assert.rejects(queuedDuringRecovery,/core generation changed/);
|
|
25
25
|
first.release();
|
|
26
|
-
const after=await governor.acquire('
|
|
26
|
+
const after=await governor.acquire('coding_file_read');
|
|
27
27
|
after.release();
|
|
28
28
|
const drain=governor.close('bridge reconciling');
|
|
29
|
-
await assert.rejects(governor.acquire('
|
|
29
|
+
await assert.rejects(governor.acquire('coding_file_read'),/closed/);
|
|
30
30
|
await drain;
|
|
31
31
|
assert.equal(governor.snapshot().normal.active,0);
|
|
32
32
|
});
|
|
@@ -5,7 +5,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
5
5
|
import {createCommanderWireAdapter} from '../src/commander-wire-adapter-r47.mjs';
|
|
6
6
|
import {registerCommanderExtendedBridge} from '../src/commander-extended-bridge-r47.mjs';
|
|
7
7
|
|
|
8
|
-
const referencePath='D:\\Projects\\
|
|
8
|
+
const referencePath='D:\\Projects\\deadbyte-mcp\\references\\desktop-commander\\dist\\tools\\schemas.js';
|
|
9
9
|
const expectedHash='f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140';
|
|
10
10
|
async function referenceSchemas(){
|
|
11
11
|
let source=await readFile(referencePath,'utf8');
|
|
@@ -4,6 +4,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
4
4
|
|
|
5
5
|
const read=rel=>readFile(new URL('../'+rel,import.meta.url),'utf8');
|
|
6
6
|
const json=rel=>read(rel).then(raw=>JSON.parse(raw.replace(/^\uFEFF/,'')));
|
|
7
|
+
const currentPackage=await json('package.json');
|
|
7
8
|
|
|
8
9
|
const PARENT=Object.freeze({
|
|
9
10
|
version:'0.15.36',
|
|
@@ -12,7 +13,7 @@ const PARENT=Object.freeze({
|
|
|
12
13
|
generation_manifest_sha256:'6134f9f26b680e13ed545da4ab5f3020f411ea2569062fb9c154c8a7e036c9dc'
|
|
13
14
|
});
|
|
14
15
|
|
|
15
|
-
test('R60 identity is coherent and binds the exact production-accepted 0.15.36 predecessor',async()=>{
|
|
16
|
+
test('R60 identity is coherent and binds the exact production-accepted 0.15.36 predecessor',{skip:currentPackage.version!=='0.15.37'},async()=>{
|
|
16
17
|
const [pkg,lock,release,prov,matrix,sbom,version,bridgePkg,contract]=await Promise.all([
|
|
17
18
|
json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),
|
|
18
19
|
json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),json('bridge/package.json'),
|
|
@@ -46,7 +47,7 @@ test('R60 identity is coherent and binds the exact production-accepted 0.15.36 p
|
|
|
46
47
|
assert.match(contract,/0\.15\.37/);
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
test('R60 release contract exposes the proven remote truth semantics without widening dependencies',async()=>{
|
|
50
|
+
test('R60 release contract exposes the proven remote truth semantics without widening dependencies',{skip:currentPackage.version!=='0.15.37'},async()=>{
|
|
50
51
|
const [pkg,release,matrix]=await Promise.all([json('package.json'),json('RELEASE.json'),json('TEST-MATRIX.json')]);
|
|
51
52
|
assert.equal(Object.keys(pkg.dependencies??{}).length,4);
|
|
52
53
|
assert.equal(release.r60_remote_hardening.authority_truth_coherence,true);
|
|
@@ -69,7 +70,7 @@ test('R60 release contract exposes the proven remote truth semantics without wid
|
|
|
69
70
|
assert.equal(matrix.observed_pre_release_evidence.r60_package.status,'PASS_VERIFY_ONLY_PRE_FINAL_SEAL');
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
test('R60 package scripts bind the new focused, full-regression and Windows-gate entrypoints',async()=>{
|
|
73
|
+
test('R60 package scripts bind the new focused, full-regression and Windows-gate entrypoints',{skip:currentPackage.version!=='0.15.37'},async()=>{
|
|
73
74
|
const [pkg,gate]=await Promise.all([json('package.json'),read('scripts/gate-windows-r60-prepublish.ps1')]);
|
|
74
75
|
assert.match(pkg.scripts['r60:focused:test'],/r60-authority-coherence\.test\.mjs/);
|
|
75
76
|
assert.match(pkg.scripts['r60:focused:test'],/r60-remote-call-recovery\.test\.mjs/);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createBridgeGovernor,bridgeToolClass} from '../bridge/src/r40-governor.mjs';
|
|
4
|
+
|
|
5
|
+
test('control MCP execution is admitted while unrelated bulk work saturates',async()=>{
|
|
6
|
+
const governor=createBridgeGovernor();
|
|
7
|
+
const bulk=[];
|
|
8
|
+
let control=null;
|
|
9
|
+
try{
|
|
10
|
+
for(let i=0;i<8;i++)bulk.push(await governor.acquire('coding_search'));
|
|
11
|
+
const abort=new AbortController();
|
|
12
|
+
const pending=governor.acquire('machine_ping',{signal:abort.signal});
|
|
13
|
+
const observed=await Promise.race([
|
|
14
|
+
pending.then(ticket=>({kind:'admitted',ticket})),
|
|
15
|
+
new Promise(resolve=>setTimeout(()=>resolve({kind:'blocked'}),25))
|
|
16
|
+
]);
|
|
17
|
+
if(observed.kind==='admitted')control=observed.ticket;
|
|
18
|
+
else abort.abort(new Error('control blocked behind bulk'));
|
|
19
|
+
assert.equal(observed.kind,'admitted','control plane must not queue behind bulk');
|
|
20
|
+
assert.equal(governor.snapshot().control.active,1);
|
|
21
|
+
}finally{
|
|
22
|
+
control?.release();
|
|
23
|
+
for(const ticket of bulk)ticket.release();
|
|
24
|
+
await governor.close();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('compact routes use argument operation and never promote bulk to control',()=>{
|
|
29
|
+
assert.equal(bridgeToolClass('machine_observe',{op:'machine_status'}),'control');
|
|
30
|
+
assert.equal(bridgeToolClass('machine_observe',{op:'search_page'}),'bulk');
|
|
31
|
+
assert.equal(bridgeToolClass('process_session',{op:'read'}),'bulk');
|
|
32
|
+
assert.equal(bridgeToolClass('desktop_control',{op:'action'}),'desktop_action');
|
|
33
|
+
assert.equal(bridgeToolClass('coding_file_write',{root_id:'scratch'}),'normal');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('control queue saturates, cancels and drains without admitting cancelled work',async()=>{
|
|
37
|
+
const governor=createBridgeGovernor({limits:{control:{max_active:1,max_queue:1,queue_timeout_ms:100}}});
|
|
38
|
+
const first=await governor.acquire('machine_ping');
|
|
39
|
+
const abort=new AbortController();
|
|
40
|
+
const queued=governor.acquire('machine_status',{signal:abort.signal});
|
|
41
|
+
await assert.rejects(governor.acquire('machine_ping'),/queue full/);
|
|
42
|
+
assert.equal(governor.snapshot().control.queued,1);
|
|
43
|
+
abort.abort(new Error('client cancelled'));
|
|
44
|
+
await assert.rejects(queued,/client cancelled/);
|
|
45
|
+
assert.equal(governor.snapshot().control.queued,0);
|
|
46
|
+
const timed=governor.acquire('machine_status');
|
|
47
|
+
await assert.rejects(timed,/queue timeout/);
|
|
48
|
+
assert.equal(governor.snapshot().control.timed_out,1);
|
|
49
|
+
first.release();
|
|
50
|
+
const after=await governor.acquire('machine_status');
|
|
51
|
+
after.release();
|
|
52
|
+
await governor.close();
|
|
53
|
+
assert.equal(governor.snapshot().control.active,0);
|
|
54
|
+
});
|
|
55
|
+
test('mixed normal and bulk lanes share one bounded downstream budget while control retains a reserved slot',async()=>{
|
|
56
|
+
const governor=createBridgeGovernor();
|
|
57
|
+
const work=[];
|
|
58
|
+
let control=null;
|
|
59
|
+
let queuedTicket=null;
|
|
60
|
+
try{
|
|
61
|
+
for(let i=0;i<4;i++)work.push(await governor.acquire('coding_search'));
|
|
62
|
+
for(let i=0;i<4;i++)work.push(await governor.acquire('coding_file_read'));
|
|
63
|
+
|
|
64
|
+
const saturated=governor.snapshot();
|
|
65
|
+
assert.equal(saturated.shared.active,8);
|
|
66
|
+
assert.equal(saturated.shared.non_control_active,8);
|
|
67
|
+
assert.equal(saturated.shared.max_active,9);
|
|
68
|
+
assert.equal(saturated.shared.max_non_control_active,8);
|
|
69
|
+
|
|
70
|
+
const pendingWork=governor.acquire('coding_file_read');
|
|
71
|
+
const observedWork=await Promise.race([
|
|
72
|
+
pendingWork.then(ticket=>({kind:'admitted',ticket})),
|
|
73
|
+
new Promise(resolve=>setTimeout(()=>resolve({kind:'blocked'}),25))
|
|
74
|
+
]);
|
|
75
|
+
assert.equal(observedWork.kind,'blocked','ninth non-control call must respect shared budget');
|
|
76
|
+
|
|
77
|
+
control=await governor.acquire('machine_ping');
|
|
78
|
+
assert.equal(governor.snapshot().shared.active,9,'control uses reserved downstream slot');
|
|
79
|
+
assert.equal(governor.snapshot().control.active,1);
|
|
80
|
+
control.release();
|
|
81
|
+
control=null;
|
|
82
|
+
|
|
83
|
+
const released=work.shift();
|
|
84
|
+
released.release();
|
|
85
|
+
|
|
86
|
+
queuedTicket=await pendingWork;
|
|
87
|
+
assert.equal(governor.snapshot().shared.non_control_active,8);
|
|
88
|
+
}finally{
|
|
89
|
+
control?.release();
|
|
90
|
+
queuedTicket?.release();
|
|
91
|
+
for(const ticket of work)ticket.release();
|
|
92
|
+
await governor.close();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const final=governor.snapshot();
|
|
96
|
+
assert.equal(final.shared.active,0);
|
|
97
|
+
assert.equal(final.shared.non_control_active,0);
|
|
98
|
+
});
|
|
99
|
+
test('oldest admissible waiter is not starved by control backlog',async()=>{
|
|
100
|
+
const governor=createBridgeGovernor({
|
|
101
|
+
shared:{max_active:3,max_non_control_active:2}
|
|
102
|
+
});
|
|
103
|
+
const held=[];
|
|
104
|
+
const resolved=[];
|
|
105
|
+
const normalAbort=new AbortController();
|
|
106
|
+
const controlAbort=new AbortController();
|
|
107
|
+
let normalPending=null;
|
|
108
|
+
let controlPending=null;
|
|
109
|
+
|
|
110
|
+
try{
|
|
111
|
+
held.push(await governor.acquire('coding_search'));
|
|
112
|
+
held.push(await governor.acquire('coding_search'));
|
|
113
|
+
held.push(await governor.acquire('machine_ping'));
|
|
114
|
+
|
|
115
|
+
normalPending=governor.acquire(
|
|
116
|
+
'coding_file_read',
|
|
117
|
+
{signal:normalAbort.signal}
|
|
118
|
+
).then(ticket=>{
|
|
119
|
+
resolved.push(ticket);
|
|
120
|
+
return ticket;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
controlPending=governor.acquire(
|
|
124
|
+
'machine_status',
|
|
125
|
+
{signal:controlAbort.signal}
|
|
126
|
+
).then(ticket=>{
|
|
127
|
+
resolved.push(ticket);
|
|
128
|
+
return ticket;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const releasedBulk=held.shift();
|
|
132
|
+
releasedBulk.release();
|
|
133
|
+
|
|
134
|
+
await new Promise(resolve=>setImmediate(resolve));
|
|
135
|
+
|
|
136
|
+
assert.equal(
|
|
137
|
+
governor.snapshot().normal.active,
|
|
138
|
+
1,
|
|
139
|
+
'older non-control waiter must win the newly available admissible slot'
|
|
140
|
+
);
|
|
141
|
+
}finally{
|
|
142
|
+
normalAbort.abort(new Error('test cleanup'));
|
|
143
|
+
controlAbort.abort(new Error('test cleanup'));
|
|
144
|
+
|
|
145
|
+
await Promise.allSettled(
|
|
146
|
+
[normalPending,controlPending].filter(Boolean)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
for(const ticket of resolved)ticket.release();
|
|
150
|
+
for(const ticket of held)ticket.release();
|
|
151
|
+
|
|
152
|
+
await governor.close();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createLiveTimelineTracker,createLiveTimelineFilter} from '../src/remote-live-log.mjs';
|
|
4
|
+
|
|
5
|
+
test('long LIVELOG session retains bounded presentation identities and suppresses old audit replay',()=>{
|
|
6
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000});
|
|
7
|
+
let sourceSequence=0;
|
|
8
|
+
for(let i=0;i<12000;i++){
|
|
9
|
+
const id=`bounded-${i}`;
|
|
10
|
+
tracker.observe({event_type:'tool.request',tool:'machine_status',request_id:id,args:{}},
|
|
11
|
+
{sourceSequence:++sourceSequence});
|
|
12
|
+
tracker.observe({event_type:'tool.result',tool:'machine_status',request_id:id,
|
|
13
|
+
outcome:{is_error:false,duration_ms:1}},{sourceSequence:++sourceSequence});
|
|
14
|
+
}
|
|
15
|
+
const stats=tracker.retention();
|
|
16
|
+
assert.ok(stats.completed<=4096,JSON.stringify(stats));
|
|
17
|
+
assert.equal(stats.pending,0);
|
|
18
|
+
const replay=tracker.observe({event_type:'tool.result',tool:'machine_status',request_id:'bounded-0',
|
|
19
|
+
outcome:{is_error:false,duration_ms:1}},{sourceSequence:2});
|
|
20
|
+
assert.equal(replay,null,'old source sequence cannot reappear as a duplicate terminal line');
|
|
21
|
+
assert.match(tracker.summary(),/12000 calls \| 12000 ok/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('filtered long session retains bounded request presentation state',()=>{
|
|
25
|
+
const filter=createLiveTimelineFilter('status=error,trace=goal-r61');
|
|
26
|
+
for(let i=0;i<12000;i++)filter.accept({event_type:'tool.request',tool:'coding_file_write',request_id:`pending-${i}`,
|
|
27
|
+
args:{goal_id:'goal-r61',root_id:'scratch',path:'x.txt'}});
|
|
28
|
+
assert.ok(filter.retention().pending<=4096);
|
|
29
|
+
const latest=filter.accept({event_type:'tool.result',tool:'coding_file_write',request_id:'pending-11999',outcome:{is_error:true}});
|
|
30
|
+
assert.equal(latest,true);
|
|
31
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {verifyPublishedTarballIdentity} from '../scripts/npm-artifact-identity-r61.mjs';
|
|
5
|
+
|
|
6
|
+
const bytes=Buffer.from('deadbyte-r61-exact-tarball-fixture');
|
|
7
|
+
const sha=(name,value)=>createHash(name).update(value).digest(name==='sha512'?'base64':'hex');
|
|
8
|
+
const published={version:'0.15.38',dist:{integrity:'sha512-'+sha('sha512',bytes),shasum:sha('sha1',bytes),tarball:'https://registry.npmjs.org/deadbyte-mcp/-/deadbyte-mcp-0.15.38.tgz'}};
|
|
9
|
+
|
|
10
|
+
test('R61 npm round trip requires exact registry bytes, metadata, version and latest tag',()=>{
|
|
11
|
+
const accepted=verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:Buffer.from(bytes),published,tags:{latest:'0.15.38'}});
|
|
12
|
+
assert.equal(accepted.tarball_sha256,sha('sha256',bytes));
|
|
13
|
+
assert.equal(accepted.npm_integrity,published.dist.integrity);
|
|
14
|
+
assert.throws(()=>verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:Buffer.from('different'),published,tags:{latest:'0.15.38'}}),/registry tarball bytes differ/);
|
|
15
|
+
assert.throws(()=>verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:bytes,published:{...published,dist:{...published.dist,integrity:'sha512-false'}},tags:{latest:'0.15.38'}}),/registry metadata/);
|
|
16
|
+
assert.throws(()=>verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:bytes,published,tags:{latest:'0.15.37'}}),/latest tag/);
|
|
17
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {prepareReleaseBridgeSpec} from '../src/runtime-update.mjs';
|
|
8
|
+
|
|
9
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
|
|
11
|
+
test('R61 update binds Bridge script and local dependencies to immutable release slot',async()=>{
|
|
12
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'r61-bridge-slot-'));
|
|
13
|
+
const source=path.join(root,'source','bridge'),slot=path.join(root,'releases','v0.15.38-test'),releaseBridge=path.join(slot,'bridge');
|
|
14
|
+
const script='import "./dep.mjs";\n';
|
|
15
|
+
try{
|
|
16
|
+
for(const bridge of [source,releaseBridge]){
|
|
17
|
+
await mkdir(path.join(bridge,'src'),{recursive:true});
|
|
18
|
+
await writeFile(path.join(bridge,'package.json'),'{"name":"deadbyte-chatgpt-bridge","version":"0.15.38"}\n');
|
|
19
|
+
await writeFile(path.join(bridge,'src','bridge.mjs'),script);
|
|
20
|
+
await writeFile(path.join(bridge,'src','dep.mjs'),'export const bound=true;\n');
|
|
21
|
+
}
|
|
22
|
+
const config={Bridge:{FilePath:process.execPath,FileSha256:sha(await readFile(process.execPath)),
|
|
23
|
+
Arguments:'src\\bridge.mjs',WorkingDirectory:source},Cloud:{}};
|
|
24
|
+
const result=await prepareReleaseBridgeSpec(config,{root:source,scriptSha256:sha(script)},slot);
|
|
25
|
+
assert.equal(result.Bridge.WorkingDirectory,releaseBridge);
|
|
26
|
+
assert.equal(result.Bridge.ScriptPath,path.join(releaseBridge,'src','bridge.mjs'));
|
|
27
|
+
assert.ok(result.Bridge.Dependencies.length>=1);
|
|
28
|
+
for(const dependency of result.Bridge.Dependencies)assert.ok(dependency.Path.startsWith(path.join(releaseBridge,'src')+path.sep));
|
|
29
|
+
await writeFile(path.join(releaseBridge,'src','bridge.mjs'),'changed\n');
|
|
30
|
+
await assert.rejects(prepareReleaseBridgeSpec(config,{root:source,scriptSha256:sha(script)},slot),/bridge script SHA-256 mismatch/);
|
|
31
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
32
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
const read=rel=>readFile(new URL('../'+rel,import.meta.url),'utf8');
|
|
6
|
+
const json=rel=>read(rel).then(raw=>JSON.parse(raw.replace(/^\uFEFF/,'')));
|
|
7
|
+
const PARENT={
|
|
8
|
+
release_id:'v0.15.37-1cfecd7889419da7',
|
|
9
|
+
manifest_sha256:'1cfecd7889419da7800814e3a2a4ffa93d48dfdb0c344a5b7b91fe8d093230aa',
|
|
10
|
+
generation_manifest_sha256:'54842021c75f01bf7ecbd5b10ab8a482ecf107765b45c46f9d39b12bab65eac4'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
test('R61 identity binds exact sealed 0.15.37 predecessor without a public surface expansion',async()=>{
|
|
14
|
+
const [pkg,lock,bridge,release,provenance,matrix,sbom,version,gate]=await Promise.all([
|
|
15
|
+
json('package.json'),json('package-lock.json'),json('bridge/package.json'),json('RELEASE.json'),
|
|
16
|
+
json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),
|
|
17
|
+
read('scripts/gate-windows-r61-prepublish.ps1')
|
|
18
|
+
]);
|
|
19
|
+
assert.equal(pkg.version,'0.15.38');
|
|
20
|
+
assert.equal(lock.version,pkg.version);
|
|
21
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
22
|
+
assert.equal(bridge.version,pkg.version);
|
|
23
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.15\.38'/);
|
|
24
|
+
assert.equal(release.version,pkg.version);
|
|
25
|
+
assert.equal(release.train,'R61');
|
|
26
|
+
assert.equal(release.parent.version,'0.15.37');
|
|
27
|
+
for(const [key,value] of Object.entries(PARENT)){
|
|
28
|
+
assert.equal(release.parent[key],value);
|
|
29
|
+
assert.equal(release.runtime_predecessor[key],value);
|
|
30
|
+
assert.equal(provenance.runtime_predecessor[key],value);
|
|
31
|
+
}
|
|
32
|
+
assert.equal(provenance.version,pkg.version);
|
|
33
|
+
assert.equal(matrix.version,pkg.version);
|
|
34
|
+
assert.equal(matrix.train,'R61');
|
|
35
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
36
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r61:0.15.38');
|
|
37
|
+
assert.equal(Object.keys(pkg.dependencies??{}).length,4);
|
|
38
|
+
assert.equal(release.r61_multi_workload.public_surface_tool_count,43);
|
|
39
|
+
assert.equal(release.r61_multi_workload.new_authority_scopes,0);
|
|
40
|
+
assert.equal(release.r61_multi_workload.bridge_bound_to_immutable_slot,true);
|
|
41
|
+
assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r61-prepublish.ps1');
|
|
42
|
+
assert.match(gate,/v0\.15\.37-1cfecd7889419da7/);
|
|
43
|
+
assert.doesNotMatch(gate,/v0\.15\.36-090d9db3e0679c0d/);
|
|
44
|
+
});
|
|
@@ -22,7 +22,7 @@ test('R40 long or mutating call gets start line and terminal error carries sanit
|
|
|
22
22
|
const tracker=createLiveTimelineTracker({clock:()=>now});
|
|
23
23
|
const start=tracker.observe({event_type:'tool.request',tool:'coding_command_run',request_id:'request-23456789',
|
|
24
24
|
ts:'1970-01-01T00:00:01.000Z',args:{root_id:'core',command_id:'node_script',script_path:'scripts/check.mjs'}},{sequence:20});
|
|
25
|
-
assert.match(start,/^🏃 07:00:01\.000 #20 \[request-\] coding_command_run core node_script scripts\/check\.mjs
|
|
25
|
+
assert.match(start,/^🏃 07:00:01\.000 #20 \[request-\] coding_command_run ▶ START core node_script scripts\/check\.mjs$/);
|
|
26
26
|
now=1900;
|
|
27
27
|
const done=tracker.observe({event_type:'tool.result',tool:'coding_command_run',request_id:'request-23456789',
|
|
28
28
|
ts:'1970-01-01T00:00:01.900Z',outcome:{is_error:true,duration_ms:900,reason:'script exited 1: ENOENT scripts/missing.mjs'}},{sequence:21});
|
|
@@ -72,9 +72,9 @@ test('R40 CLI exposes explicit verbose mode while keeping default live mode comp
|
|
|
72
72
|
const {readFile}=await import('node:fs/promises');
|
|
73
73
|
const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
74
74
|
assert.match(src,/readLiveRootPaths\(ctx\.root\)/);
|
|
75
|
-
assert.match(src,/createLiveTimelineTracker\(\{verbose,displayMode,rootPaths,trace\}\)/);
|
|
75
|
+
assert.match(src,/createLiveTimelineTracker\(\{verbose,displayMode,rootPaths,trace,foldHeartbeats:!verbose\}\)/);
|
|
76
76
|
assert.match(src,/createLiveTimelineFilter\(filterExpression\)/);
|
|
77
|
-
assert.match(src,/createLiveTimelineWriter\(\{stream:process\.stdout,displayMode,rewrite
|
|
77
|
+
assert.match(src,/createLiveTimelineWriter\(\{stream:process\.stdout,displayMode,rewrite:false\}\)/);
|
|
78
78
|
assert.match(src,/arg==='--verbose'\|\|arg==='-v'/);
|
|
79
79
|
assert.match(src,/arg==='--trace'/);
|
|
80
80
|
assert.match(src,/arg==='--filter'/);
|
|
@@ -100,7 +100,7 @@ test('R40 interrupted side effect is explicit failed-after-unknown-effect instea
|
|
|
100
100
|
args:{root_id:'core',path:'x.txt',content:'x'}},{sequence:70});
|
|
101
101
|
const line=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'unknown-12345678',
|
|
102
102
|
outcome:{status:'interrupted',is_error:true,duration_ms:0,side_effect_state:'unknown',recovery:{reason:'runtime_restart'}}},{sequence:71});
|
|
103
|
-
assert.match(line,/FAILED_AFTER_UNKNOWN_EFFECT — runtime_restart$/);
|
|
103
|
+
assert.match(line,/FAILED_AFTER_UNKNOWN_EFFECT status=interrupted — runtime_restart$/);
|
|
104
104
|
assert.doesNotMatch(line,/\s[✓✗]\s/);
|
|
105
105
|
});
|
|
106
106
|
|
|
@@ -124,7 +124,7 @@ test('visual opcode projection resolves multiplexed machine_fs operation and exp
|
|
|
124
124
|
now=1007;
|
|
125
125
|
const done=tracker.observe({event_type:'tool.result',tool:'machine_fs',request_id:'machinefs-12345678',
|
|
126
126
|
ts:'1970-01-01T00:00:01.007Z',outcome:{status:'committed',is_error:false,duration_ms:7,receipt_sha256s:[receipt]}},{sequence:81});
|
|
127
|
-
assert.match(done,/^📝 .*machine_fs\.write .*7ms ✅ SUCCEEDED 🧾 receipt=11111111…$/);
|
|
127
|
+
assert.match(done,/^📝 .*machine_fs\.write .*7ms ✅ SUCCEEDED status=committed 🧾 receipt=11111111…$/);
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
test('visual opcode projection reserves warning semantics for explicitly unknown side effects',()=>{
|
|
@@ -133,7 +133,7 @@ test('visual opcode projection reserves warning semantics for explicitly unknown
|
|
|
133
133
|
args:{root_id:'core',command_id:'powershell_inline',mode:'inline'}},{sequence:90});
|
|
134
134
|
const done=tracker.observe({event_type:'tool.result',tool:'host_exec',request_id:'unknown-host-1234',
|
|
135
135
|
outcome:{status:'interrupted',is_error:true,duration_ms:4,side_effect_state:'unknown',recovery:{reason:'runtime_restart'}}},{sequence:91});
|
|
136
|
-
assert.match(done,/^⚠️ .*host_exec .*4ms FAILED_AFTER_UNKNOWN_EFFECT — runtime_restart$/);
|
|
136
|
+
assert.match(done,/^⚠️ .*host_exec .*4ms FAILED_AFTER_UNKNOWN_EFFECT status=interrupted — runtime_restart$/);
|
|
137
137
|
assert.doesNotMatch(done,/❌|🚫/);
|
|
138
138
|
});
|
|
139
139
|
|
|
@@ -218,3 +218,20 @@ test('R51 live timeline marks slow calls and reports peak in-flight concurrency'
|
|
|
218
218
|
tracker.observe({event_type:'tool.result',tool:'coding_file_read',request_id:'slow-b-12345678',outcome:{is_error:false,duration_ms:5}});
|
|
219
219
|
assert.match(tracker.summary(),/peak in-flight 2/);
|
|
220
220
|
});
|
|
221
|
+
|
|
222
|
+
test('human mutation span labels START and preserves terminal audit status',()=>{
|
|
223
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000});
|
|
224
|
+
const start=tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'detail-1234567890',
|
|
225
|
+
args:{root_id:'core',path:'x.txt',content:'x'}},{sequence:120});
|
|
226
|
+
assert.match(start,/coding_file_write ▶ START core:x\.txt \(1B\)$/);
|
|
227
|
+
const done=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'detail-1234567890',
|
|
228
|
+
outcome:{status:'committed',is_error:false,duration_ms:3,receipt_sha256s:['a'.repeat(64)]}},{sequence:121});
|
|
229
|
+
assert.match(done,/coding_file_write .*3ms ✅ SUCCEEDED status=committed 🧾 receipt=aaaaaaaa…$/);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test('foreground human log keeps START and DONE separate and folds heartbeat noise',async()=>{
|
|
233
|
+
const {readFile}=await import('node:fs/promises');
|
|
234
|
+
const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
235
|
+
assert.match(src,/createLiveTimelineTracker\(\{verbose,displayMode,rootPaths,trace,foldHeartbeats:!verbose\}\)/);
|
|
236
|
+
assert.match(src,/createLiveTimelineWriter\(\{stream:process\.stdout,displayMode,rewrite:false\}\)/);
|
|
237
|
+
});
|
|
@@ -148,7 +148,7 @@ for(const [flag,armedPlane,disarmedPlane] of [['--desktop','desktop','host'],['-
|
|
|
148
148
|
child=spawn(process.execPath,[cliPath,'remote',flag],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
149
149
|
const output=await collect(child);
|
|
150
150
|
const deadline=Date.now()+8000;
|
|
151
|
-
while(Date.now()<deadline
|
|
151
|
+
while(Date.now()<deadline&&!/\[READY\]/.test(output.stdout)) await sleep(50);
|
|
152
152
|
assert.match(output.stdout,/\[READY\]/,output.stderr);
|
|
153
153
|
assert.match(output.stdout,new RegExp(`${armedPlane} ARMED`));
|
|
154
154
|
assert.match(output.stdout,new RegExp(`${disarmedPlane} DISARMED`));
|