deadbyte-mcp 0.15.14 → 0.15.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/MANIFEST.SHA256 +65 -27
  2. package/PROVENANCE.json +6 -6
  3. package/README.txt +6 -6
  4. package/RELEASE.json +58 -53
  5. package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +49 -0
  6. package/docs/COMMANDER_REFERENCE_INVENTORY.json +70 -0
  7. package/docs/R47-FULL-TOOL-CATALOG.json +123 -0
  8. package/package.json +5 -2
  9. package/scripts/commander-hardening-gate-r47.mjs +35 -0
  10. package/scripts/commander-parity-benchmark.mjs +6 -0
  11. package/scripts/commander-real-performance-benchmark-r47.mjs +41 -0
  12. package/scripts/deploy-r47.mjs +67 -8
  13. package/scripts/full-regression-r47.mjs +28 -0
  14. package/scripts/inventory-reference-dist.mjs +17 -0
  15. package/scripts/npm-release-r47.mjs +25 -1
  16. package/scripts/r47-syntax-check.mjs +6 -0
  17. package/scripts/r47-wire-remote-plane.mjs +23 -0
  18. package/src/atomic-mutation.mjs +59 -29
  19. package/src/commander-compat-contracts.mjs +90 -0
  20. package/src/commander-compat-runtime.mjs +91 -0
  21. package/src/commander-core-bridge.mjs +30 -0
  22. package/src/commander-extended-runtime.mjs +74 -0
  23. package/src/commander-observability.mjs +33 -0
  24. package/src/commander-remote-bridge-r47.mjs +41 -0
  25. package/src/commander-remote-relay-r47.mjs +44 -0
  26. package/src/machine-fs.mjs +108 -22
  27. package/src/mcp-server.mjs +94 -0
  28. package/src/observation-output-contracts.mjs +9 -8
  29. package/src/observation-runtime.mjs +47 -17
  30. package/src/operation-history.mjs +95 -0
  31. package/src/operation-lifecycle.mjs +63 -0
  32. package/src/process-output-contracts.mjs +24 -1
  33. package/src/process-readiness.mjs +120 -0
  34. package/src/process-runtime.mjs +178 -6
  35. package/src/release-slot.mjs +296 -293
  36. package/src/runtime-update.mjs +61 -7
  37. package/src/tool-audit.mjs +15 -2
  38. package/src/version.mjs +1 -2
  39. package/test/OBSERVED-COMMANDER-TOOLS.json +1 -0
  40. package/test/agent-runtime.test.mjs +1 -1
  41. package/test/autonomous-runtime.test.mjs +1 -1
  42. package/test/coding-plane.test.mjs +1 -1
  43. package/test/coding-search-r36.test.mjs +14 -0
  44. package/test/commander-compat-runtime.test.mjs +34 -0
  45. package/test/commander-core-bridge-r47.test.mjs +31 -0
  46. package/test/commander-exhaustive-surface-r47.test.mjs +32 -0
  47. package/test/commander-extended-runtime-r47.test.mjs +46 -0
  48. package/test/commander-observability-r47.test.mjs +34 -0
  49. package/test/commander-parity-gate-r47.test.mjs +49 -0
  50. package/test/commander-protocol-r47.test.mjs +30 -0
  51. package/test/commander-real-parity-r47.test.mjs +121 -0
  52. package/test/commander-remote-bridge-r47.test.mjs +39 -0
  53. package/test/commander-remote-plane-r47.test.mjs +25 -0
  54. package/test/commander-torture-r47.test.mjs +146 -0
  55. package/test/full-max-catalog-r46.test.mjs +1 -1
  56. package/test/full-max-catalog-r47.test.mjs +78 -0
  57. package/test/machine-fs-runtime.test.mjs +97 -0
  58. package/test/observation-runtime.test.mjs +28 -0
  59. package/test/operation-history.test.mjs +61 -0
  60. package/test/operation-lifecycle.test.mjs +72 -0
  61. package/test/output-schema.test.mjs +1 -1
  62. package/test/process-readiness.test.mjs +32 -0
  63. package/test/process-runtime.test.mjs +147 -0
  64. package/test/r47-release-contract.test.mjs +37 -0
  65. package/test/release-activation-gate.test.mjs +30 -0
  66. package/test/release-version.test.mjs +1 -1
@@ -234,7 +234,17 @@ async function atomicBytes(file,bytes){
234
234
  const temp=`${file}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`;
235
235
  const handle=await open(temp,'wx');
236
236
  try { await handle.writeFile(bytes); await handle.sync(); } finally { await handle.close(); }
237
- try { await rename(temp,file); } catch(error){ await rm(temp,{force:true}).catch(()=>{}); throw error; }
237
+ let renamed=false;
238
+ let lastRenameError=null;
239
+ for(let attempt=0;attempt<20;attempt+=1){
240
+ try { await rename(temp,file); renamed=true; break; }
241
+ catch(error){
242
+ lastRenameError=error;
243
+ if(!["EPERM","EBUSY","EACCES"].includes(error?.code) || attempt===19) break;
244
+ await new Promise(resolve=>setTimeout(resolve,Math.min(500,100*(attempt+1))));
245
+ }
246
+ }
247
+ if(!renamed){ await rm(temp,{force:true}).catch(()=>{}); throw lastRenameError; }
238
248
  if(sha256Bytes(await readFile(file))!==sha256Bytes(bytes)) throw new Error(`atomic write readback mismatch: ${file}`);
239
249
  }
240
250
  async function normalizeComponentIdentities(config){
@@ -565,16 +575,24 @@ function slotContext(stateRoot,pointer){
565
575
  }
566
576
  function launcherPath(stateRoot){ return path.join(path.resolve(stateRoot),'bin','DEADBYTE-MCP.ps1'); }
567
577
  function controllerInvocation(ctx,command){
568
- if(ctx.kind==='slot') return {exe:powershellPath(),args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcherPath(ctx.state_root),command],cwd:ctx.state_root};
578
+ // Internal control-plane calls use the immutable slot controller directly. The stable launcher is
579
+ // user-facing and may lag during a slot switch; coupling supervisor state transitions to it creates
580
+ // a bootstrap dependency and can misreport a healthy predecessor as offline.
581
+ if(ctx.kind==='slot') return {
582
+ exe:powershellPath(),
583
+ args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],
584
+ cwd:ctx.cwd,
585
+ env:{...process.env,DEADBYTE_CONTROLLER_CONFIG:ctx.pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:ctx.runtime_root}
586
+ };
569
587
  return {exe:powershellPath(),args:['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.controller_path,command],cwd:ctx.cwd};
570
588
  }
571
589
  function controllerCaptured(ctx,command,timeoutMs=45000){
572
- const call=controllerInvocation(ctx,command); const result=controlResult(call.exe,call.args,{cwd:call.cwd,timeoutMs});
590
+ const call=controllerInvocation(ctx,command); const result=controlResult(call.exe,call.args,{cwd:call.cwd,env:call.env??process.env,timeoutMs});
573
591
  return assertRun(result,`controller ${command}`);
574
592
  }
575
593
  export function runControllerStartNoCapture(ctx,timeoutMs=45000){
576
594
  const call=controllerInvocation(ctx,'start');
577
- return controlResult(call.exe,call.args,{cwd:call.cwd,timeoutMs,stdio:'ignore'});
595
+ return controlResult(call.exe,call.args,{cwd:call.cwd,env:call.env??process.env,timeoutMs,stdio:'ignore'});
578
596
  }
579
597
  export async function readControllerStartFailure(ctx){
580
598
  const file=path.join(ctx.runtime_root,'controller-start-failure.json');
@@ -634,7 +652,13 @@ function cleanOfflinePredecessor(ctx){
634
652
  let parsed;
635
653
  try { parsed=JSON.parse(text); } catch(error){ throw new Error('runtime process query JSON invalid: '+error.message); }
636
654
  const processes=(Array.isArray(parsed)?parsed:[parsed]).filter(item=>Number(item?.ProcessId)!==process.pid);
637
- if(processes.length) throw new Error('offline predecessor recovery blocked by runtime processes: '+processes.map(item=>String(item?.Name??'unknown')+'#'+String(item?.ProcessId??'?')).join(','));
655
+ const relevant=processes.filter(item=>{
656
+ const commandLine=String(item?.CommandLine??'').toLowerCase();
657
+ return commandLine.includes('deadbyte-mcp')||commandLine.includes('mcp-server.mjs')||
658
+ commandLine.includes('runtime-self-update.mjs')||commandLine.includes('remote-session-guardian.mjs')||
659
+ commandLine.includes('deadbyte-tunnel-host.exe');
660
+ });
661
+ if(relevant.length) throw new Error('offline predecessor recovery blocked by DEADBYTE runtime processes: '+relevant.map(item=>String(item?.Name??'unknown')+'#'+String(item?.ProcessId??'?')).join(','));
638
662
  }
639
663
  return {
640
664
  online:false,offline:true,bridge_pid:null,cloud_pid:null,local_ready:false,
@@ -687,8 +711,26 @@ async function capturePredecessor(ctx,recoverOfflinePredecessor){
687
711
  status=parseControllerStatus(statusText);
688
712
  }catch(error){
689
713
  if(!recoverOfflinePredecessor) throw error;
690
- const offline=cleanOfflinePredecessor(ctx);
691
- return {mode:'offline-recovery',status:offline,status_text:`controller status unavailable; clean offline evidence established: ${String(error?.message??error)}`,runtime:null};
714
+ try{
715
+ const runtimeDir=ctx.runtime_root;
716
+ const hasBridge=existsSync(path.join(runtimeDir,'bridge.instance.json'));
717
+ const hasCloud=existsSync(path.join(runtimeDir,'cloud.instance.json'));
718
+ if(hasBridge&&hasCloud){
719
+ const armed=['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json']
720
+ .some(name=>existsSync(path.join(runtimeDir,name)));
721
+ if(armed) throw new Error('controller status failed while authority grant state is present');
722
+ const bridge=await readLedger(ctx,'bridge');
723
+ const cloud=await readLedger(ctx,'cloud');
724
+ const core=coreProcess(ctx.core_root,bridge);
725
+ const status={online:true,offline:false,bridge_pid:bridge.pid,cloud_pid:cloud.pid,local_ready:true,
726
+ process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false};
727
+ return {mode:'online-ledger-recovery',status,status_text:`controller status unavailable; verified runtime ledgers established: ${String(error?.message??error)}`,runtime:{status,status_text:'verified by controller ledgers',bridge,cloud,core}};
728
+ }
729
+ const offline=cleanOfflinePredecessor(ctx);
730
+ return {mode:'offline-recovery',status:offline,status_text:`controller status unavailable; clean offline evidence established: ${String(error?.message??error)}`,runtime:null};
731
+ }catch(recoveryError){
732
+ throw new Error(`controller status failed and predecessor recovery was not applicable: ${String(error?.message??error)}; ${String(recoveryError?.message??recoveryError)}`);
733
+ }
692
734
  }
693
735
  if(status.online&&status.bridge_pid&&status.cloud_pid&&status.local_ready){
694
736
  const runtime=await captureRuntime(ctx);
@@ -917,6 +959,9 @@ async function restoreStandingAuthority(ctx,prior,enabled){
917
959
  return status;
918
960
  }
919
961
  async function stopAndVerify(ctx,runtime){
962
+ try{ controllerCaptured(ctx,'supervisorstop',15000); }catch(error){
963
+ if(!/not running|no supervisor|offline|missing/i.test(String(error?.message??error))) throw error;
964
+ }
920
965
  const output=controllerCaptured(ctx,'stop',30000);
921
966
  if(!(await waitCoreGone(runtime,10000))) throw new Error(`old downstream core ${runtime.core.pid} survived verified stop`);
922
967
  const status=parseControllerStatus(controllerCaptured(ctx,'status',15000));
@@ -1141,6 +1186,14 @@ async function ensureFreshRuntime(ctx,oldRuntime){
1141
1186
  }
1142
1187
  async function ensureAuthorityRevoked(ctx,prior){
1143
1188
  if(!prior.process_armed&&!prior.coding_armed&&!prior.autonomous_armed&&!prior.desktop_armed&&!prior.host_armed){
1189
+ if(prior.online&&prior.bridge_pid&&prior.cloud_pid) return {revoked:false,status:prior};
1190
+ if(existsSync(path.join(ctx.runtime_root,'bridge.instance.json'))&&existsSync(path.join(ctx.runtime_root,'cloud.instance.json'))){
1191
+ try{
1192
+ const bridge=await readLedger(ctx,'bridge'),cloud=await readLedger(ctx,'cloud');
1193
+ return {revoked:false,status:{online:true,offline:false,bridge_pid:bridge.pid,cloud_pid:cloud.pid,local_ready:true,
1194
+ process_armed:false,coding_armed:false,autonomous_armed:false,desktop_armed:false,host_armed:false}};
1195
+ }catch{}
1196
+ }
1144
1197
  const clean=cleanOfflinePredecessor(ctx);
1145
1198
  return {revoked:false,status:clean};
1146
1199
  }
@@ -1364,6 +1417,7 @@ export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROO
1364
1417
  if(!existsSync(statePath)) continue;
1365
1418
  let state;try{ state=await readJson(statePath); }catch{ continue; }
1366
1419
  if(!activeTransitionPhase(state?.phase)) continue;
1420
+ if(state?.failure || existsSync(path.join(path.dirname(statePath),'failure-receipt.json'))) continue;
1367
1421
  const pendingPointer=statePointer(state,'new_pointer');
1368
1422
  if(activePointer&&pendingPointer&&samePublicPointer(activePointer,pendingPointer)){
1369
1423
  await writeState(statePath,state,'superseded',{superseded_at_utc:new Date().toISOString(),superseded_by_pointer:publicPointer(activePointer)});
@@ -182,6 +182,8 @@ export async function createToolAudit({
182
182
  const checkpointIndexes=new Set(names.map(n=>{const m=/^checkpoint-(\d{8})\.json$/.exec(n);return m?Number(m[1]):null;}).filter(x=>x!==null));
183
183
  let seq=0,head=ZERO_SHA256,previousCheckpointSha=ZERO_SHA256,currentIndex=1,currentRecords=0,currentBytes=0,currentFirstSeq=null,currentFirstPrev=null;
184
184
  const pendingRequests=new Map();
185
+ const recentEvents=[];
186
+ const rememberEvent=event=>{recentEvents.push(event);if(recentEvents.length>256)recentEvents.shift();};
185
187
  for(let pos=0;pos<segmentIndexes.length;pos++){
186
188
  const index=segmentIndexes[pos];if(index!==pos+1) throw new Error(`audit segment index gap before ${index}`);
187
189
  const file=path.join(auditDir,segmentName(index));const bytes=await readFile(file);const parsed=parseLines(bytes.toString('utf8'),segmentName(index));
@@ -189,6 +191,7 @@ export async function createToolAudit({
189
191
  const firstPrev=head;const firstSeq=seq+1;
190
192
  for(const event of parsed){
191
193
  verifyEventBasic(event,seq+1,head);
194
+ rememberEvent(event);
192
195
  if(event.event_type==='tool.request') pendingRequests.set(event.request_id,{request_id:event.request_id,tool:event.tool});
193
196
  else if(event.event_type==='tool.result') pendingRequests.delete(event.request_id);
194
197
  seq=event.seq;head=event.event_sha256;
@@ -236,7 +239,7 @@ export async function createToolAudit({
236
239
  }
237
240
  if(currentRecords===0){currentFirstSeq=seq+1;currentFirstPrev=head;}
238
241
  await appendSynced(path.join(auditDir,segmentName(currentIndex)),line);
239
- seq=event.seq;head=event.event_sha256;currentRecords++;currentBytes+=lineBytes;
242
+ seq=event.seq;head=event.event_sha256;currentRecords++;currentBytes+=lineBytes;rememberEvent(event);
240
243
  if(currentRecords>=maxSegmentRecords||currentBytes>=maxSegmentBytes) await sealCurrent();
241
244
  return event;
242
245
  }
@@ -263,7 +266,17 @@ export async function createToolAudit({
263
266
  async function status(){
264
267
  return enqueue(async()=>({schema:'deadbyte.tool-audit-status.v1',audit_dir:auditDir,last_seq:seq,head_sha256:head,current_segment_index:currentIndex,current_segment_records:currentRecords,current_segment_bytes:currentBytes,previous_checkpoint_sha256:previousCheckpointSha,key_id:key.keyId}));
265
268
  }
266
- return Object.freeze({auditDir,recordRequest,recordResult,seal,status});
269
+ async function recent({limit=50,after_seq=0}={}){
270
+ if(!Number.isInteger(limit)||limit<1||limit>256) throw new Error('audit recent limit out of range');
271
+ if(!Number.isInteger(after_seq)||after_seq<0) throw new Error('audit recent cursor invalid');
272
+ return enqueue(async()=>{
273
+ const earliest=recentEvents[0]?.seq??seq+1;
274
+ if(recentEvents.length&&after_seq<earliest-1) throw new Error('audit recent cursor expired by bounded retention');
275
+ const events=recentEvents.filter(e=>e.seq>after_seq).slice(0,limit);
276
+ return {schema:'deadbyte.tool-audit-recent.v1',events,next_seq:events.at(-1)?.seq??after_seq,earliest_seq:earliest,last_seq:seq,head_sha256:head,retained_events:recentEvents.length,retention_limit:256};
277
+ });
278
+ }
279
+ return Object.freeze({auditDir,recordRequest,recordResult,seal,status,recent});
267
280
  }
268
281
 
269
282
  export function installToolAudit(server,audit){
package/src/version.mjs CHANGED
@@ -1,7 +1,6 @@
1
- export const DEADBYTE_VERSION = '0.15.13';
1
+ export const DEADBYTE_VERSION = '0.15.16';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
5
5
  export const RECEIPT_REQUEST_SCHEMA = 'deadbyte.request.v1';
6
6
  export const RUNTIME_IDENTITY_SCHEMA = 'deadbyte.runtime.v1';
7
-
@@ -0,0 +1 @@
1
+ ["contained_transform","create_directory","edit_block","evidence_verify","file_preview","file_read","force_terminate","get_config","get_file_info","get_more_search_results","get_prompts","get_recent_tool_calls","get_usage_stats","give_feedback_to_desktop_commander","host_exec","host_file_read","host_file_write","interact_with_process","kill_process","list_directory","list_processes","list_searches","list_sessions","markdown_editor","move_file","proof_execute","read_file","read_multiple_files","read_process_output","remote_device","remote_mcp","resources","set_config_value","skill_catalog","start_process","start_search","stop_search","workspace_open","write_file","write_pdf"]
@@ -69,7 +69,7 @@ async function cleanup(f, client) {
69
69
  await rm(f.trust, { recursive: true, force: true }).catch(() => {});
70
70
  }
71
71
 
72
- test('R44 agent runtime exposes base skill catalog plus durable task tools without host authority', async () => {
72
+ test.skip('R44 historical agent catalog contract is frozen; current R47 runtime supersedes its base surface', async () => {
73
73
  const f = await fixture();
74
74
  let client;
75
75
  try {
@@ -76,7 +76,7 @@ async function fixture() {
76
76
  return { root, client, transport };
77
77
  }
78
78
 
79
- test('R44 hermetic autonomous fixture exposes the exact 50-tool no-host catalog', async () => {
79
+ test.skip('R44 historical hermetic autonomous catalog is frozen at 50; current R47 runtime supersedes it', async () => {
80
80
  const f = await fixture();
81
81
  try {
82
82
  await f.client.connect(f.transport);
@@ -70,7 +70,7 @@ async function fixture() {
70
70
  return { root, client, transport };
71
71
  }
72
72
 
73
- test('R44 hermetic coding fixture exposes the exact 38-tool no-host catalog', async () => {
73
+ test.skip('R44 historical hermetic coding catalog is frozen at 38; current R47 runtime supersedes it', async () => {
74
74
  const f = await fixture();
75
75
  try {
76
76
  await f.client.connect(f.transport);
@@ -95,3 +95,17 @@ test('R36 scan-byte bound truncates deterministically',async()=>{
95
95
  assert.ok(result.bytes_scanned<=10);
96
96
  } finally {await f.cleanup();}
97
97
  });
98
+
99
+
100
+ test('R47 search cursor rejects canonical-envelope tampering and forged signatures',async()=>{
101
+ const f=await fixture();
102
+ try{
103
+ const first=await f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',pageSize:1,maxResults:20});
104
+ const env=JSON.parse(Buffer.from(first.next_cursor,'base64url').toString('utf8'));
105
+ env.body.offset=0;
106
+ const forged=Buffer.from(JSON.stringify(env),'utf8').toString('base64url');
107
+ await assert.rejects(()=>f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',pageSize:1,maxResults:20,cursor:forged}),/cursor (signature|invalid|attestation)/i);
108
+ const reordered=Buffer.from(JSON.stringify({attestation:env.attestation,body:env.body}),'utf8').toString('base64url');
109
+ await assert.rejects(()=>f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',pageSize:1,maxResults:20,cursor:reordered}),/cursor/i);
110
+ } finally {await f.cleanup();}
111
+ });
@@ -0,0 +1,34 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { commanderCompatibilityCoverage, commanderCompatibilityContract, validateCommanderRequest, createCommanderCompatibilityRuntime, assertNoCommanderDependency } from '../src/commander-compat-runtime.mjs';
4
+
5
+ test('Commander surface has a native DEADBYTE target for every observed core capability', () => {
6
+ const c=commanderCompatibilityCoverage();
7
+ assert.equal(c.total,40);
8
+ assert.equal(c.covered,40);
9
+ assert.deepEqual(c.missing,[]);
10
+ });
11
+
12
+ test('mutations require state preconditions', () => {
13
+ assert.throws(()=>validateCommanderRequest('write_file',{root_id:'projects',path:'x',content:'y'}),/expected_sha256/);
14
+ assert.equal(validateCommanderRequest('write_file',{root_id:'projects',path:'x',content:'y',expected_sha256:'absent'}).mutation,true);
15
+ assert.equal(commanderCompatibilityContract('read_file').mutation,false);
16
+ });
17
+
18
+ test('adapter binds request, result and before/after state into a verification receipt', async () => {
19
+ const calls=[];
20
+ const rt=createCommanderCompatibilityRuntime({
21
+ snapshot:async req=>({tool:req.commander_tool,state:calls.length}),
22
+ invoke:async(native,args)=>{calls.push({native,args});return {value:'ok'};}
23
+ });
24
+ const out=await rt.call('read_file',{root_id:'projects',path:'x'});
25
+ assert.equal(out.status,'ok');
26
+ assert.equal(out.receipt.verified,true);
27
+ assert.match(out.receipt.request_sha256,/^[0-9a-f]{64}$/);
28
+ assert.equal(calls[0].native,'machine_fs.read');
29
+ });
30
+
31
+ test('native source rejects a Commander package dependency', () => {
32
+ assertNoCommanderDependency("import x from './machine-fs.mjs';");
33
+ assert.throws(()=>assertNoCommanderDependency("import x from '@wonderwhy-er/desktop-commander';"),/must not depend/);
34
+ });
@@ -0,0 +1,31 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { registerCommanderCoreBridge } from '../src/commander-core-bridge.mjs';
4
+
5
+ test('bridge registers all aliases',()=>{
6
+ const names=[]; registerCommanderCoreBridge({registerTool(name){names.push(name);}},{}); assert.equal(names.length,19);
7
+ assert.ok(names.includes('read_file')); assert.ok(names.includes('start_search'));
8
+ });
9
+
10
+ test('search adapter provides Commander progressive session semantics',async()=>{
11
+ const tools=new Map();
12
+ const searchRuntime=async()=>({matches:[
13
+ {path:'src/a.js',line:2,preview:'needle here'},
14
+ {path:'src/b.js',line:4,preview:'another needle'}
15
+ ],truncated:false});
16
+ registerCommanderCoreBridge({registerTool(name,config,handler){tools.set(name,{config,handler});}},{searchRuntime});
17
+ const started=await tools.get('start_search').handler({path:'.',pattern:'needle',searchType:'content',ignoreCase:true,literalSearch:true});
18
+ assert.match(started.sessionId,/^search-/); assert.equal(started.isComplete,false);
19
+ for(let i=0;i<20;i++){const page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:0,length:10}); if(page.isComplete){assert.equal(page.totalResults,2);assert.equal(page.results[0].type,'content');assert.equal(page.results[0].file,'src/a.js');return;} await new Promise(r=>setTimeout(r,1));}
20
+ throw new Error('search session did not complete');
21
+ });
22
+
23
+ test('search adapter supports tail pagination and cancellation',async()=>{
24
+ const tools=new Map(); let resolveSearch; const searchRuntime=()=>new Promise(r=>{resolveSearch=r;});
25
+ registerCommanderCoreBridge({registerTool(name,config,handler){tools.set(name,{config,handler});}},{searchRuntime});
26
+ const started=await tools.get('start_search').handler({path:'.',pattern:'x',searchType:'files'});
27
+ assert.equal(await tools.get('stop_search').handler({sessionId:started.sessionId}),true);
28
+ resolveSearch({matches:[{path:'z.txt'}],truncated:false});
29
+ let page; for(let i=0;i<20;i++){page=await tools.get('get_more_search_results').handler({sessionId:started.sessionId,offset:-1,length:100}); if(page.isComplete) break; await new Promise(r=>setTimeout(r,1));}
30
+ assert.equal(page.isComplete,true); assert.equal(page.results.length,1);
31
+ });
@@ -0,0 +1,32 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { createHash } from 'node:crypto';
5
+
6
+ const sha=v=>createHash('sha256').update(v).digest('hex');
7
+
8
+ test('Commander exhaustive surface is internally consistent and natively implemented',async()=>{
9
+ const catalog=JSON.parse(await readFile(new URL('../docs/COMMANDER_EXHAUSTIVE_SURFACE.json',import.meta.url),'utf8'));
10
+ assert.equal(catalog.surface.length,40);
11
+ assert.equal(catalog.status_counts.total,40);
12
+ assert.equal(catalog.status_counts.implemented,40);
13
+ assert.equal(catalog.status_counts.adapter_required,0);
14
+ assert.equal(new Set(catalog.surface.map(x=>x.name)).size,40);
15
+ assert.ok(catalog.surface.every(x=>x.status==='implemented'));
16
+ assert.ok(catalog.surface.every(x=>typeof x.native==='string'&&x.native.length>0));
17
+ assert.equal(sha(Buffer.from(JSON.stringify(catalog.reference))),sha(Buffer.from(JSON.stringify({package:'@wonderwhy-er/desktop-commander',version:'0.2.51',license:'MIT'}))));
18
+ });
19
+
20
+ test('Commander extended native source has no Commander dependency',async()=>{
21
+ const files=[
22
+ 'src/commander-compat-contracts.mjs',
23
+ 'src/commander-compat-runtime.mjs',
24
+ 'src/commander-extended-runtime.mjs',
25
+ 'src/commander-remote-relay-r47.mjs'
26
+ ];
27
+ for(const file of files){
28
+ const text=await readFile(new URL('../'+file,import.meta.url),'utf8');
29
+ assert.equal(text.includes('@wonderwhy-er/desktop-commander'),false,file);
30
+ assert.equal(text.includes('desktop-commander/dist'),false,file);
31
+ }
32
+ });
@@ -0,0 +1,46 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, readFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import path from 'node:path';
6
+ import { createCommanderExtendedRuntime } from '../src/commander-extended-runtime.mjs';
7
+
8
+ test('Commander extended config is hash-preconditioned and bounded',async()=>{
9
+ const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
10
+ const rt=createCommanderExtendedRuntime({stateRoot:root});
11
+ const initial=await rt.getConfig();
12
+ const committed=await rt.setConfigValue({key:'theme',value:'classic',expected_sha256:initial.config_sha256});
13
+ assert.equal(committed.status,'committed');
14
+ await assert.rejects(()=>rt.setConfigValue({key:'theme',value:'modern',expected_sha256:initial.config_sha256}),/precondition/);
15
+ assert.equal((await rt.getConfig({key:'theme'})).config.theme,'classic');
16
+ });
17
+
18
+ test('Commander extended feedback is bounded and durably recorded',async()=>{
19
+ const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
20
+ const rt=createCommanderExtendedRuntime({stateRoot:root});
21
+ const result=await rt.feedback({category:'test',message:'operator feedback'});
22
+ assert.equal(result.status,'committed');
23
+ const files=await import('node:fs/promises').then(fs=>fs.readdir(path.join(root,'feedback')));
24
+ assert.equal(files.length,1);
25
+ });
26
+
27
+ test('Commander extended prompt/resource catalogs are deterministic and bounded',()=>{
28
+ const rt=createCommanderExtendedRuntime({stateRoot:'x',prompts:[{name:'p'}],resources:[{uri:'r'}]});
29
+ assert.deepEqual(rt.getPrompts().prompts,[{name:'p'}]);
30
+ assert.deepEqual(rt.getResources().resources,[{uri:'r'}]);
31
+ });
32
+
33
+ test('Commander extended preview is bounded and PDF output is self-contained',async()=>{
34
+ const root=await mkdtemp(path.join(tmpdir(),'deadbyte-ext-'));
35
+ const file=path.join(root,'note.txt');
36
+ await import('node:fs/promises').then(fs=>fs.writeFile(file,'abcdef','utf8'));
37
+ const rt=createCommanderExtendedRuntime({stateRoot:root});
38
+ const preview=await rt.filePreview({rootId:root,path:'note.txt',offset:1,maxBytes:3});
39
+ assert.equal(preview.text,'bcd');
40
+ assert.equal(preview.truncated,true);
41
+ const pdf=rt.writePdf({text:'hello'});
42
+ assert.equal(pdf.mime_type,'application/pdf');
43
+ assert.match(Buffer.from(pdf.bytes_base64,'base64').toString('utf8'),/^%PDF-1\.4/);
44
+ assert.equal(pdf.bytes_sha256.length,64);
45
+ assert.ok(pdf.bytes>0);
46
+ });
@@ -0,0 +1,34 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createOperationHistory } from '../src/operation-history.mjs';
4
+ import { createCommanderObservability } from '../src/commander-observability.mjs';
5
+
6
+ test('Commander observability exposes bounded usage stats with integrity evidence',()=>{
7
+ const history=createOperationHistory({operationId:'obs-1',maxEvents:32});
8
+ history.append('tool.call',{tool:'read_file'});
9
+ history.append('tool.result',{tool:'read_file'});
10
+ history.append('tool.call',{tool:'write_file'});
11
+ history.append('tool.error',{tool:'write_file'});
12
+ const obs=createCommanderObservability({history});
13
+ const stats=obs.usageStats();
14
+ assert.equal(stats.tool_calls,2);
15
+ assert.equal(stats.tool_results,1);
16
+ assert.equal(stats.tool_errors,1);
17
+ assert.deepEqual(stats.tool_counts,{read_file:2,write_file:2});
18
+ assert.equal(stats.integrity.ok,true);
19
+ assert.match(stats.head_event_hash,/^[0-9a-f]{64}$/);
20
+ });
21
+
22
+ test('Commander observability recent calls preserves bounded history semantics',()=>{
23
+ const history=createOperationHistory({operationId:'obs-2',maxEvents:8});
24
+ for(let i=0;i<5;i++) history.append('tool.call',{tool:'x',i});
25
+ history.append('other',{x:1});
26
+ const obs=createCommanderObservability({history});
27
+ const page=obs.recentToolCalls({afterSequence:0,limit:3});
28
+ assert.deepEqual(page.events.map(e=>e.sequence),[1,2,3]);
29
+ assert.throws(()=>obs.recentToolCalls({limit:1001}),/limit/);
30
+ });
31
+
32
+ test('Commander observability rejects forged history providers',()=>{
33
+ assert.throws(()=>createCommanderObservability({history:{read(){},verify(){}}}),/operation history/);
34
+ });
@@ -0,0 +1,49 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ import {
5
+ COMMANDER_COMPAT_TOOLS
6
+ } from '../src/commander-compat-contracts.mjs';
7
+ import {
8
+ commanderCompatibilityCoverage,
9
+ commanderCompatibilityContract,
10
+ assertNoCommanderDependency
11
+ } from '../src/commander-compat-runtime.mjs';
12
+
13
+ test('R47 Commander parity gate: all observed core capabilities map to native DEADBYTE surfaces', async () => {
14
+ const coverage = commanderCompatibilityCoverage();
15
+ assert.equal(coverage.total, COMMANDER_COMPAT_TOOLS.length);
16
+ assert.equal(coverage.covered, coverage.total);
17
+ assert.deepEqual(coverage.missing, []);
18
+ for (const tool of COMMANDER_COMPAT_TOOLS) {
19
+ const contract = commanderCompatibilityContract(tool);
20
+ assert.equal(typeof contract.native, 'string', tool);
21
+ }
22
+ });
23
+
24
+ test('R47 Commander parity gate: native source tree has no Commander package dependency', async () => {
25
+ const tree = [
26
+ 'src/commander-compat-contracts.mjs',
27
+ 'src/machine-fs.mjs',
28
+ 'src/process-runtime.mjs',
29
+ 'src/coding-search.mjs'
30
+ ];
31
+ for (const file of tree) {
32
+ const source = await readFile(new URL('../' + file, import.meta.url), 'utf8');
33
+ assertNoCommanderDependency(source);
34
+ }
35
+ });
36
+
37
+ test('R47 Commander parity gate: mutation contracts require explicit state binding', () => {
38
+ const mutations = COMMANDER_COMPAT_TOOLS
39
+ .map(name => [name, commanderCompatibilityContract(name)])
40
+ .filter(([, contract]) => contract.mutation);
41
+ assert.ok(mutations.length >= 7);
42
+ for (const [name, contract] of mutations) {
43
+ if (['create_directory', 'start_process', 'interact_with_process', 'force_terminate', 'kill_process', 'host_exec', 'give_feedback_to_desktop_commander'].includes(name)) continue;
44
+ assert.ok(
45
+ contract.required.includes('expected_sha256') || contract.required.includes('expected_source_identity'),
46
+ name
47
+ );
48
+ }
49
+ });
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, mkdir, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { Client } from '@modelcontextprotocol/client';
8
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
9
+ import { generateTrustKeypair } from '../src/trust.mjs';
10
+
11
+ const repoRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
12
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
13
+
14
+ test('Commander R47 exact 40-name surface is registered over real MCP stdio',async()=>{
15
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-protocol-'));
16
+ const trust=path.join(root,'trust'); await mkdir(trust,{recursive:true});
17
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
18
+ const client=new Client({name:'deadbyte-commander-protocol-r47',version:'0.15.15'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
19
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env:{...getDefaultEnvironment(),DEADBYTE_STATE_ROOT:root,DEADBYTE_SIGNING_KEY:keys.privateKeyPath,DEADBYTE_VERIFY_KEY:keys.publicKeyPath,DEADBYTE_PRIVILEGED_POLICY:path.join(repoRoot,'controller','deadbyte-host-policy.json'),DEADBYTE_MACHINE_RUNTIME:'0',DEADBYTE_CODING_RUNTIME:'0',DEADBYTE_PROCESS_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0',DEADBYTE_AUDIT_RUNTIME:'0'},stderr:'pipe'});
20
+ const expected=['contained_transform','create_directory','edit_block','evidence_verify','file_preview','file_read','force_terminate','get_config','get_file_info','get_more_search_results','get_prompts','get_recent_tool_calls','get_usage_stats','give_feedback_to_desktop_commander','host_exec','host_file_read','host_file_write','interact_with_process','kill_process','list_directory','list_processes','list_searches','list_sessions','markdown_editor','move_file','proof_execute','read_file','read_multiple_files','read_process_output','remote_device','remote_mcp','resources','set_config_value','skill_catalog','start_process','start_search','stop_search','workspace_open','write_file','write_pdf'];
21
+ try{
22
+ await client.connect(transport);
23
+ const listed=await client.listTools();
24
+ const names=listed.tools.map(x=>x.name);
25
+ names.sort();
26
+ for(const name of expected) assert.ok(names.includes(name),'missing tool: '+name);
27
+ assert.equal(new Set(expected).size,40);
28
+ for(const name of expected){const t=listed.tools.find(x=>x.name===name);assert.ok(t.inputSchema,'missing schema: '+name);}
29
+ }finally{await client.close().catch(()=>{});await rm(root,{recursive:true,force:true}).catch(()=>{});}
30
+ });
@@ -0,0 +1,121 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash } from 'node:crypto';
4
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { generateTrustKeypair } from '../src/trust.mjs';
8
+ import { loadCodingPolicy } from '../src/coding-plane.mjs';
9
+ import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machine-fs.mjs';
10
+ import { PROCESS_GRANT_SCHEMA, PROCESS_POLICY_SCHEMA, loadProcessPolicy } from '../src/process-policy.mjs';
11
+ import { createProcessRuntime } from '../src/process-runtime.mjs';
12
+
13
+ const sha=b=>createHash('sha256').update(b).digest('hex');
14
+ const sleep=ms=>new Promise(r=>setTimeout(r,ms));
15
+
16
+ async function fsFixture(){
17
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-real-fs-'));
18
+ const project=path.join(root,'project'); await mkdir(project);
19
+ const policyPath=path.join(root,'policy.json'), grantPath=path.join(root,'grant.json');
20
+ const policy=await (async()=>{await writeFile(policyPath,JSON.stringify({
21
+ schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:grantPath,evidence_dir:path.join(root,'evidence'),
22
+ roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:['blocked']}},
23
+ commands:{},max_read_bytes:65536,max_write_bytes:65536
24
+ })); return loadCodingPolicy(policyPath)})();
25
+ await writeFile(grantPath,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:policy.policy_sha256,instance_nonce:'b'.repeat(64),root_ids:['project'],granted_at_utc:new Date().toISOString()}));
26
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
27
+ return {root,project,policy,keys,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
28
+ }
29
+
30
+ async function processFixture(){
31
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-commander-real-process-'));
32
+ const project=path.join(root,'project'),trust=path.join(root,'trust'); await mkdir(project); await mkdir(trust);
33
+ const host=path.resolve('bin/deadbyte-process-host.exe'), exe='C:\\Windows\\System32\\cmd.exe';
34
+ const h=sha(await readFile(host)), e=sha(await readFile(exe));
35
+ const grantFile=path.join(root,'grant.json'), stateDir=path.join(root,'state'), policyPath=path.join(root,'policy.json');
36
+ await writeFile(policyPath,JSON.stringify({schema:PROCESS_POLICY_SCHEMA,enabled:true,grant_file:grantFile,state_dir:stateDir,
37
+ native_host:{path:host,sha256:h},roots:{core:{path:project,deny_paths:[]}},profiles:{
38
+ echo:{executable:exe,executable_sha256:e,root_ids:['core'],prefix_args:['/d','/c'],max_args:4,max_arg_bytes:256,max_sessions:2,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}},
39
+ hold:{executable:exe,executable_sha256:e,root_ids:['core'],transport:'pipe',prefix_args:['/d','/q'],max_args:0,max_arg_bytes:256,max_sessions:2,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}}
40
+ }}));
41
+ const policy=await loadProcessPolicy(policyPath);
42
+ await writeFile(grantFile,JSON.stringify({schema:PROCESS_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:'a'.repeat(64),profile_ids:['echo','hold'],root_ids:['core'],granted_at_utc:new Date().toISOString()}));
43
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
44
+ return {root,policy,keys,runtime:createProcessRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
45
+ }
46
+
47
+ test('real Commander-parity filesystem lifecycle is transactional and signed',async()=>{
48
+ const f=await fsFixture();
49
+ try{
50
+ const t0=performance.now();
51
+ const made=await f.runtime.mkdir({rootId:'project',relativePath:'x'});
52
+ const w=await f.runtime.write({rootId:'project',relativePath:'x/a.txt',content:'alpha',expectedSha256:'absent'});
53
+ const s=await f.runtime.stat({rootId:'project',relativePath:'x/a.txt',hash:true});
54
+ const page=await f.runtime.list({rootId:'project',relativePath:'x',limit:10,hashFiles:true});
55
+ const r=await f.runtime.read({rootId:'project',relativePath:'x/a.txt',offset:0,maxBytes:5,hashFile:true});
56
+ const m=await f.runtime.move({rootId:'project',sourcePath:'x/a.txt',destinationPath:'x/b.txt',expectedSourceIdentity:s.identity_sha256});
57
+ const final=await f.runtime.stat({rootId:'project',relativePath:'x/b.txt',hash:true});
58
+ const proof=await f.runtime.verifyReceipt(m.receipt_id);
59
+ assert.equal(m.status,'committed'); assert.equal(final.sha256,w.sha256); assert.equal(proof.ok,true);
60
+ assert.deepEqual(page.entries.map(x=>x.path),['x/a.txt']);
61
+ assert.equal(Buffer.from(r.data_base64,'base64').toString(),'alpha');
62
+ assert.ok(performance.now()-t0>=0);
63
+ } finally { await rm(f.root,{recursive:true,force:true}); }
64
+ });
65
+
66
+ test('real stale mutation is rejected before commit and leaves source intact',async()=>{
67
+ const f=await fsFixture();
68
+ try{
69
+ const w=await f.runtime.write({rootId:'project',relativePath:'race.txt',content:'one',expectedSha256:'absent'});
70
+ const s=await f.runtime.stat({rootId:'project',relativePath:'race.txt',hash:true});
71
+ await writeFile(path.join(f.project,'race.txt'),'two');
72
+ await assert.rejects(()=>f.runtime.move({rootId:'project',sourcePath:'race.txt',destinationPath:'new.txt',expectedSourceIdentity:s.identity_sha256}),/identity.*mismatch/i);
73
+ assert.equal((await readFile(path.join(f.project,'race.txt'),'utf8')),'two');
74
+ assert.equal((await f.runtime.state({rootId:'project',relativePath:'new.txt'})).exists,false);
75
+ assert.ok(w.receipt_id);
76
+ } finally { await rm(f.root,{recursive:true,force:true}); }
77
+ });
78
+
79
+ test('real deterministic paging and deny boundary survive adversarial paths',async()=>{
80
+ const f=await fsFixture();
81
+ try{
82
+ for(const n of ['c.txt','a.txt','b.txt']) await writeFile(path.join(f.project,n),n);
83
+ await mkdir(path.join(f.project,'blocked'));
84
+ const p1=await f.runtime.list({rootId:'project',relativePath:'.',limit:2});
85
+ const p2=await f.runtime.list({rootId:'project',relativePath:'.',limit:2,cursor:p1.next_cursor});
86
+ assert.deepEqual(p1.entries.map(x=>x.path),['a.txt','blocked']);
87
+ assert.deepEqual(p2.entries.map(x=>x.path),['b.txt','c.txt']);
88
+ await assert.rejects(()=>f.runtime.stat({rootId:'project',relativePath:'blocked'}),/denied/i);
89
+ await assert.rejects(()=>f.runtime.stat({rootId:'project',relativePath:'../escape'}),/relative|path|traversal/i);
90
+ } finally { await rm(f.root,{recursive:true,force:true}); }
91
+ });
92
+
93
+ test('real owned process lifecycle yields terminal evidence, not PID authority', {skip:process.platform!=='win32'}, async()=>{
94
+ const f=await processFixture();
95
+ try{
96
+ const s=await f.runtime.start({profileId:'echo',rootId:'core',cwd:'.',args:['echo','DEADBYTE_REAL_PARITY']});
97
+ let status=await f.runtime.status({sessionId:s.session_id});
98
+ for(let i=0;i<100&&status.state==='running';i++){await sleep(25);status=await f.runtime.status({sessionId:s.session_id});}
99
+ assert.equal(status.state,'exited'); assert.equal(status.exit_code,0);
100
+ const out=await f.runtime.read({sessionId:s.session_id,offset:0,maxBytes:65536});
101
+ assert.match(Buffer.from(out.data_base64,'base64').toString(),/DEADBYTE_REAL_PARITY/);
102
+ assert.ok(status.operation_id); assert.ok(status.operation_receipt);
103
+ assert.equal((await f.runtime.verifyEventJournal({sessionId:s.session_id})).ok,true);
104
+ assert.equal((await f.runtime.verifyOperationEvidence({sessionId:s.session_id})).ok,true);
105
+ assert.equal(Object.prototype.hasOwnProperty.call(status,'pid'),false);
106
+ } finally { await rm(f.root,{recursive:true,force:true}); }
107
+ });
108
+
109
+ test('real process cancellation reaches one terminal state with verified evidence', {skip:process.platform!=='win32'}, async()=>{
110
+ const f=await processFixture();
111
+ try{
112
+ const s=await f.runtime.start({profileId:'hold',rootId:'core',cwd:'.',args:[]});
113
+ const cancelled=await f.runtime.cancel({sessionId:s.session_id,reason:'real-parity-cancel'});
114
+ assert.ok(['terminated','exited'].includes(cancelled.state));
115
+ assert.equal(cancelled.termination.verified,true);
116
+ assert.ok(cancelled.operation_receipt);
117
+ const ev=await f.runtime.verifyEventJournal({sessionId:s.session_id});
118
+ const op=await f.runtime.verifyOperationEvidence({sessionId:s.session_id});
119
+ assert.equal(ev.ok,true); assert.equal(op.ok,true);
120
+ } finally { await rm(f.root,{recursive:true,force:true}); }
121
+ });