deadbyte-mcp 0.11.0 → 0.11.2

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 (76) hide show
  1. package/MANIFEST.SHA256 +75 -59
  2. package/README.txt +7 -7
  3. package/bin/WORKER-REGISTRY.txt +3 -3
  4. package/bin/appcontainer-stage.exe +0 -0
  5. package/bin/appcontainer-stage.obj +0 -0
  6. package/bin/bootstrap-advapi32.exe +0 -0
  7. package/bin/bootstrap-advapi32.obj +0 -0
  8. package/bin/bootstrap-exitcode.exe +0 -0
  9. package/bin/bootstrap-exitcode.obj +0 -0
  10. package/bin/bootstrap-kernel32.exe +0 -0
  11. package/bin/bootstrap-kernel32.obj +0 -0
  12. package/bin/child-control-probe.exe +0 -0
  13. package/bin/child-control-probe.obj +0 -0
  14. package/bin/child-control-stage.exe +0 -0
  15. package/bin/child-control-stage.obj +0 -0
  16. package/bin/contained-reverse-worker.exe +0 -0
  17. package/bin/contained-reverse-worker.obj +0 -0
  18. package/bin/contained-transform-worker.exe +0 -0
  19. package/bin/contained-transform-worker.obj +0 -0
  20. package/bin/containment-probe.exe +0 -0
  21. package/bin/containment-probe.obj +0 -0
  22. package/bin/deadbyte-contain.exe +0 -0
  23. package/bin/deadbyte-contain.obj +0 -0
  24. package/bin/deadbyte-exec.exe +0 -0
  25. package/bin/deadbyte-exec.obj +0 -0
  26. package/bin/deadbyte-process-host.exe +0 -0
  27. package/bin/deadbyte-process-host.obj +0 -0
  28. package/bin/deadbyte-tunnel-host.exe +0 -0
  29. package/bin/deadbyte-tunnel-host.obj +0 -0
  30. package/controller/README.TXT +1 -1
  31. package/controller/deadbyte-controller.ps1 +4 -4
  32. package/controller/deadbyte-process-policy.json +1 -1
  33. package/docs/ARCHITECTURE.md +1 -1
  34. package/package.json +6 -6
  35. package/scripts/build-containment.ps1 +15 -15
  36. package/scripts/deferred-slot-operation-r37-0110.mjs +124 -0
  37. package/scripts/deferred-slot-operation-r38.mjs +124 -0
  38. package/scripts/deferred-slot-operation.mjs +1 -1
  39. package/scripts/final-closure-r37-0110.mjs +113 -0
  40. package/scripts/final-closure-r38.mjs +113 -0
  41. package/scripts/final-closure-verify-r37-0110.mjs +208 -0
  42. package/scripts/final-closure-verify-r38.mjs +208 -0
  43. package/scripts/final-closure-verify.mjs +16 -16
  44. package/scripts/final-closure.mjs +8 -8
  45. package/scripts/gate-windows-r37-0110.ps1 +42 -0
  46. package/scripts/gate-windows-r38.ps1 +42 -0
  47. package/scripts/gate-windows.ps1 +3 -3
  48. package/scripts/release-parity-r37-0110.mjs +200 -0
  49. package/scripts/release-parity-r38.mjs +200 -0
  50. package/scripts/release-parity-tests-r38.ps1 +9 -0
  51. package/scripts/release-parity-tests.ps1 +1 -1
  52. package/scripts/release-parity.mjs +14 -14
  53. package/scripts/windows-gate-evidence-r37-0110.mjs +52 -0
  54. package/scripts/windows-gate-evidence-r38.mjs +52 -0
  55. package/scripts/windows-gate-evidence.mjs +4 -4
  56. package/src/deadbyte-cli.mjs +58 -23
  57. package/src/release-generation.mjs +18 -1
  58. package/src/remote-live-log.mjs +23 -1
  59. package/src/remote-result-journal.mjs +23 -4
  60. package/src/version.mjs +1 -1
  61. package/test/cli-entrypoint.test.mjs +9 -0
  62. package/test/containment.test.mjs +18 -0
  63. package/test/controller.test.mjs +11 -0
  64. package/test/core.test.mjs +1 -1
  65. package/test/production-docs.test.mjs +3 -3
  66. package/test/r33-closeout-regression.test.mjs +8 -8
  67. package/test/r33-finalization.test.mjs +14 -14
  68. package/test/r36-release-identity.test.mjs +4 -4
  69. package/test/r37-0110-history.test.mjs +20 -0
  70. package/test/r37-finalization.test.mjs +14 -14
  71. package/test/r37-remote-tool-call-timeline.test.mjs +94 -0
  72. package/test/r38-finalization.test.mjs +74 -0
  73. package/test/release-generation.test.mjs +13 -3
  74. package/test/release-version.test.mjs +45 -33
  75. package/test/remote-live-log-r37.test.mjs +5 -2
  76. package/test/remote-session-cli.test.mjs +66 -25
@@ -0,0 +1,52 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdir, open, readFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const here=path.dirname(fileURLToPath(import.meta.url));
8
+ const root=path.resolve(here,'..');
9
+ const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ const marker='DEADBYTE V0.11.2 / R38 WINDOWS BATCH GATE: PASS';
11
+ function insist(ok,message){if(!ok) throw new Error(message);}
12
+
13
+ export async function writeWindowsGateEvidence({
14
+ candidateRoot=root,
15
+ stateRoot=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp')),
16
+ clock=()=>new Date()
17
+ }={}){
18
+ const candidate=path.resolve(candidateRoot);
19
+ const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
20
+ const manifestSha256=sha256(manifestBytes);
21
+ const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
22
+ insist(pkg.version==='0.11.2',`Windows gate evidence requires V0.11.2, found ${pkg.version}`);
23
+ const completed=clock();
24
+ insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
25
+ const evidence={
26
+ schema:'deadbyte.r38-windows-gate.v1',status:'passed',exit_code:0,version:'0.11.2',
27
+ marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256
28
+ };
29
+ const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
30
+ const dir=path.join(path.resolve(stateRoot),'evidence','release');
31
+ await mkdir(dir,{recursive:true});
32
+ const file=path.join(dir,`r38-windows-gate-${manifestSha256.slice(0,16)}.json`);
33
+ try{
34
+ const handle=await open(file,'wx',0o600);
35
+ try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
36
+ return {status:'written',path:file,sha256:sha256(bytes),manifest_sha256:manifestSha256};
37
+ }catch(error){
38
+ if(error?.code!=='EEXIST') throw error;
39
+ const existingBytes=await readFile(file);
40
+ const existing=JSON.parse(existingBytes.toString('utf8'));
41
+ insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing Windows gate evidence invalid');
42
+ insist(existing.version==='0.11.2'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
43
+ return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256};
44
+ }
45
+ }
46
+
47
+ const invoked=process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url);
48
+ if(invoked){
49
+ writeWindowsGateEvidence()
50
+ .then(result=>console.log(JSON.stringify(result)))
51
+ .catch(error=>{console.error(error instanceof Error?error.stack:String(error));process.exitCode=1;});
52
+ }
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
7
7
  const here=path.dirname(fileURLToPath(import.meta.url));
8
8
  const root=path.resolve(here,'..');
9
9
  const sha256=bytes=>createHash('sha256').update(bytes).digest('hex');
10
- const marker='DEADBYTE V0.11.0 / R37 WINDOWS BATCH GATE: PASS';
10
+ const marker='DEADBYTE V0.11.1 / R37 WINDOWS BATCH GATE: PASS';
11
11
  function insist(ok,message){if(!ok) throw new Error(message);}
12
12
 
13
13
  export async function writeWindowsGateEvidence({
@@ -19,11 +19,11 @@ export async function writeWindowsGateEvidence({
19
19
  const manifestBytes=await readFile(path.join(candidate,'MANIFEST.SHA256'));
20
20
  const manifestSha256=sha256(manifestBytes);
21
21
  const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
22
- insist(pkg.version==='0.11.0',`Windows gate evidence requires V0.11.0, found ${pkg.version}`);
22
+ insist(pkg.version==='0.11.1',`Windows gate evidence requires V0.11.1, found ${pkg.version}`);
23
23
  const completed=clock();
24
24
  insist(completed instanceof Date&&!Number.isNaN(completed.getTime()),'Windows gate evidence clock invalid');
25
25
  const evidence={
26
- schema:'deadbyte.r37-windows-gate.v1',status:'passed',exit_code:0,version:'0.11.0',
26
+ schema:'deadbyte.r37-windows-gate.v1',status:'passed',exit_code:0,version:'0.11.1',
27
27
  marker,completed_at_utc:completed.toISOString(),manifest_sha256:manifestSha256
28
28
  };
29
29
  const bytes=Buffer.from(`${JSON.stringify(evidence,null,2)}\n`,'utf8');
@@ -39,7 +39,7 @@ export async function writeWindowsGateEvidence({
39
39
  const existingBytes=await readFile(file);
40
40
  const existing=JSON.parse(existingBytes.toString('utf8'));
41
41
  insist(existing.schema===evidence.schema&&existing.status==='passed'&&existing.exit_code===0,'existing Windows gate evidence invalid');
42
- insist(existing.version==='0.11.0'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
42
+ insist(existing.version==='0.11.1'&&existing.marker===marker&&existing.manifest_sha256===manifestSha256,'existing Windows gate evidence binding mismatch');
43
43
  return {status:'existing',path:file,sha256:sha256(existingBytes),manifest_sha256:manifestSha256};
44
44
  }
45
45
  }
@@ -9,14 +9,17 @@ import { fileURLToPath } from 'node:url';
9
9
  import { defaultPublicKeyPath } from './trust.mjs';
10
10
  import { verifyAuditChain } from './audit-verify.mjs';
11
11
  import { openRemoteResultJournal, parseRemoteResultCursor } from './remote-result-journal.mjs';
12
- import { createLiveTimelineTracker, formatCompactAuditEvent, formatRemoteLifecycle, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
12
+ import { DEADBYTE_VERSION } from './version.mjs';
13
+ import { createLiveTimelineTracker, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
13
14
 
14
15
  const REMOTE_COMMANDS=new Set([
15
16
  'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
16
17
  'armprocess','disarmprocess','processstatus','armhost','disarmhost','hoststatus','hosttest',
17
18
  'armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
18
19
  ]);
19
- const SESSION_START=['start','armprocess','armcoding','armautonomous'];
20
+ const SESSION_ARM=['armprocess','armcoding','armautonomous'];
21
+ const REMOTE_STATUS_TIMEOUT_MS=30000;
22
+ const REMOTE_START_TIMEOUT_MS=150000;
20
23
  const SESSION_STATUS=[
21
24
  ['processstatus',/^Process plane\s*:\s*ARMED\b/m],
22
25
  ['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
@@ -52,11 +55,16 @@ async function remoteContext(){
52
55
  const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
53
56
  return {root,launcher,powershell};
54
57
  }
55
- function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true}={}){
56
- const result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],{
58
+ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCaptured=true,timeoutMs=null}={}){
59
+ const options={
57
60
  stdio:capture?['ignore','pipe','pipe']:'inherit',encoding:capture?'utf8':undefined,windowsHide:false,shell:false
58
- });
59
- if(result.error) throw result.error;
61
+ };
62
+ if(Number.isInteger(timeoutMs)&&timeoutMs>0) options.timeout=timeoutMs;
63
+ const result=spawnSync(ctx.powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',ctx.launcher,command],options);
64
+ if(result.error){
65
+ if(result.error?.code==='ETIMEDOUT') throw new Error(`remote controller '${command}' timed out after ${timeoutMs}ms`);
66
+ throw result.error;
67
+ }
60
68
  const status=Number.isInteger(result.status)?result.status:1;
61
69
  const stdout=capture?String(result.stdout||''):'';
62
70
  const stderr=capture?String(result.stderr||''):'';
@@ -64,6 +72,14 @@ function invokeController(ctx,command,{capture=false,allowFailure=false,echoCapt
64
72
  if(status!==0&&!allowFailure) throw new Error(`remote controller '${command}' failed exit=${status}`);
65
73
  return {status,stdout,stderr};
66
74
  }
75
+ function controllerRuntimeReady(text){
76
+ const value=String(text||'');
77
+ return /^DEADBYTE MCP\s*=\s*ONLINE\s*$/m.test(value)&&
78
+ /^Bridge PID\s*:\s*\d+\s+\[verified\]\s*$/m.test(value)&&
79
+ /^Cloud PID\s*:\s*\d+\s+\[verified\]\s*$/m.test(value)&&
80
+ /^Local ready\s*:\s*True\s*$/m.test(value)&&
81
+ /^Loop supervisor:\s*RUNNING\s+\[verified\]\s*$/m.test(value);
82
+ }
67
83
  async function listLogFiles(root){
68
84
  const files=[];
69
85
  const audit=path.join(root,'audit');
@@ -88,13 +104,17 @@ function formatLogLine(file,line,{activityTracker=null}={}){
88
104
  return formatRuntimeDiagnosticLine(file,line);
89
105
  }
90
106
  function remoteResultJournalPath(root,sessionId){return path.join(root,'runtime','remote-results',`${sessionId}.json`);}
91
- function auditTerminalResult(file,line){
107
+ function auditTimelineRecord(file,line){
92
108
  if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
93
109
  let event;try{event=JSON.parse(line);}catch{return null;}
94
- if(event?.event_type!=='tool.result'||typeof event.request_id!=='string'||!event.request_id||typeof event.tool!=='string'||!event.tool) return null;
110
+ if(typeof event?.request_id!=='string'||!event.request_id||typeof event.tool!=='string'||!event.tool) return null;
95
111
  const observed=Number.isFinite(Date.parse(event.created_at_utc))?new Date(Date.parse(event.created_at_utc)).toISOString():new Date().toISOString();
96
- return {request_id:event.request_id,tool:event.tool,status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,
97
- payload_sha256:createHash('sha256').update(Buffer.from(line,'utf8')).digest('hex')};
112
+ const payload_sha256=createHash('sha256').update(Buffer.from(line,'utf8')).digest('hex');
113
+ if(event.event_type==='tool.request') return {event_type:'tool.call',request_id:event.request_id,tool:event.tool,
114
+ observed_at_utc:observed,arguments:event.args??{},payload_sha256};
115
+ if(event.event_type==='tool.result') return {event_type:'tool.result',request_id:event.request_id,tool:event.tool,
116
+ status:event.outcome?.is_error===true?'error':'ok',observed_at_utc:observed,payload_sha256};
117
+ return null;
98
118
  }
99
119
  async function replayRemoteResults(root,cursor){
100
120
  const parsed=parseRemoteResultCursor(cursor);
@@ -105,6 +125,12 @@ async function replayRemoteResults(root,cursor){
105
125
  for(;;){
106
126
  const page=await journal.replayAfter(next,{limit:200});
107
127
  for(const event of page.events){
128
+ if(event.event_type==='tool.call'){
129
+ const line=formatRemoteToolCall({event_type:'tool.request',tool:event.tool,request_id:event.request_id,
130
+ observed_at_utc:event.observed_at_utc,args:event.arguments});
131
+ process.stdout.write(`${line} | REPLAY cursor r1:${event.session_id}:${event.seq}\n`);
132
+ continue;
133
+ }
108
134
  const marker=event.status==='error'?'[ERR]':'[OK]';
109
135
  process.stdout.write(`[REPLAY] ${marker} ${event.tool} [cursor r1:${event.session_id}:${event.seq}]\n`);
110
136
  }
@@ -139,9 +165,11 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
139
165
  const next=await readAppend(file,prior);
140
166
  activeOffsets.set(file,next.offset);
141
167
  for(const line of next.text.split(/\r?\n/)){
142
- const terminal=auditTerminalResult(file,line);
168
+ const timeline=auditTimelineRecord(file,line);
143
169
  let recorded=null;
144
- if(terminal&&journal) recorded=await journal.recordTerminal(terminal);
170
+ if(timeline&&journal) recorded=timeline.event_type==='tool.call'
171
+ ? await journal.recordCall(timeline)
172
+ : await journal.recordTerminal(timeline);
145
173
  if(recorded?.duplicate) continue;
146
174
  const formatted=formatLogLine(file,line,{activityTracker});
147
175
  if(!formatted) continue;
@@ -257,18 +285,21 @@ async function runRemoteSession({resumeCursor=null}={}){
257
285
  const logOffsets=await snapshotRemoteLogOffsets(ctx.root);
258
286
  logTask=tailRemoteLogs(ctx.root,aborter.signal,{journal:resultJournal,offsets:logOffsets,activityTracker});
259
287
  process.stdout.write('[BOOT] Starting local + remote runtime...\n');
260
- const started=[];
261
- for(const command of SESSION_START){
262
- if(command==='start') process.stdout.write('[BOOT] Starting bridge/runtime...\n');
263
- else process.stdout.write(`🔐 [AUTH] Arming ${command.slice(3)}...\n`);
264
- const controllerResult=invokeController(ctx,command,{capture:true,echoCaptured:false});
265
- started.push(controllerResult);
266
- if(command==='start'){
267
- process.stdout.write(`${formatRemoteLifecycle('bridge_connected')}\n`);
268
- process.stdout.write(`${formatRemoteLifecycle('cloud_connected')}\n`);
269
- }
288
+ const preflight=invokeController(ctx,'status',{capture:true,allowFailure:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
289
+ let startText=preflight.stdout||'';
290
+ if(preflight.status===0&&controllerRuntimeReady(preflight.stdout)){
291
+ process.stdout.write('[BOOT] Runtime already ONLINE; reusing verified bridge/cloud/supervisor.\n');
292
+ }else{
293
+ process.stdout.write('[BOOT] Starting bridge/runtime...\n');
294
+ const started=invokeController(ctx,'start',{capture:true,echoCaptured:false,timeoutMs:REMOTE_START_TIMEOUT_MS});
295
+ startText=started.stdout||'';
296
+ }
297
+ process.stdout.write(`${formatRemoteLifecycle('bridge_connected')}\n`);
298
+ process.stdout.write(`${formatRemoteLifecycle('cloud_connected')}\n`);
299
+ for(const command of SESSION_ARM){
300
+ process.stdout.write(`🔐 [AUTH] Arming ${command.slice(3)}...\n`);
301
+ invokeController(ctx,command,{capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
270
302
  }
271
- const startText=started[0]?.stdout||'';
272
303
  const url=startText.match(/^URL\s*:\s*(\S+)/m)?.[1]||'';
273
304
  process.stdout.write('[OK] ONLINE\n');
274
305
  process.stdout.write(`${formatRemoteLifecycle('ready')}\n`);
@@ -320,6 +351,10 @@ async function runAudit(args){
320
351
  }
321
352
  async function main(argv){
322
353
  const [command,...rest]=argv;
354
+ if(command==='--version'||command==='-v'||command==='version'){
355
+ if(rest.length) throw new Error('version accepts no arguments');
356
+ process.stdout.write(`${DEADBYTE_VERSION}\n`);return;
357
+ }
323
358
  if(!command||command==='help'||command==='--help'||command==='-h'){printHelp();return;}
324
359
  if(command==='mcp'){
325
360
  if(rest.length) throw new Error('mcp accepts no arguments');
@@ -60,16 +60,33 @@ function rebindCodingPolicy(raw,{developmentRoot,stateRoot,parityScratchRoot}){
60
60
  policy.roots.parity_scratch={path:parityScratchRoot,read:true,write:true,exec:false,deny_paths:[]};
61
61
  return policy;
62
62
  }
63
+ async function currentWindowsGateScript(rootPath){
64
+ const packagePath=path.join(rootPath,'package.json');
65
+ const stat=await lstat(packagePath);
66
+ if(!stat.isFile()||stat.isSymbolicLink()) throw new Error('windows release gate package.json invalid');
67
+ let pkg;
68
+ try{ pkg=JSON.parse(await readFile(packagePath,'utf8')); }
69
+ catch{ throw new Error('windows release gate package.json malformed'); }
70
+ const command=String(pkg?.scripts?.['gate:windows']??'').trim();
71
+ const match=/^powershell(?:\.exe)?\s+-NoProfile\s+-ExecutionPolicy\s+Bypass\s+-File\s+(scripts\/[A-Za-z0-9._\/-]+\.ps1)$/i.exec(command);
72
+ if(!match) throw new Error('windows release gate package script invalid');
73
+ return safeRel(match[1]);
74
+ }
63
75
  async function rebindAutonomousPolicy(raw,{stateRoot,codingPolicy,codingPolicyBytes}){
64
76
  const policy=clone(raw);
65
77
  policy.lease_file=path.join(stateRoot,'runtime','autonomous-grant.json');
66
78
  policy.evidence_dir=path.join(stateRoot,'evidence','autonomous');
67
79
  policy.coding_policy_sha256=sha256(codingPolicyBytes);
68
80
  for(const [id,profile] of Object.entries(policy.profiles??{})){
69
- const rel=safeRel(String(profile.script_path).replaceAll('\\','/'));
70
81
  const rootId=String(profile.root_id??'');
71
82
  const rootPath=codingPolicy.roots?.[rootId]?.path;
72
83
  if(typeof rootPath!=='string'||!path.isAbsolute(rootPath)) throw new Error(`profile root invalid: ${id}`);
84
+ if(id==='windows-release'){
85
+ if(profile.kind!=='command'||rootId!=='core'||profile.command_id!=='powershell_script') throw new Error('windows release profile shape invalid');
86
+ profile.script_path=await currentWindowsGateScript(rootPath);
87
+ profile.description='Full Windows target release gate; runs only when finish is requested.';
88
+ }
89
+ const rel=safeRel(String(profile.script_path).replaceAll('\\','/'));
73
90
  const file=path.resolve(rootPath,...rel.split('/'));
74
91
  const boundary=path.relative(rootPath,file);
75
92
  if(boundary.startsWith(`..${path.sep}`)||path.isAbsolute(boundary)) throw new Error(`profile path escapes coding root: ${id}`);
@@ -1,3 +1,6 @@
1
+ import { canonicalJson } from './canonical-json.mjs';
2
+ import { sanitizeAuditArgs } from './tool-audit.mjs';
3
+
1
4
  const SAFE_ARG_KEYS=Object.freeze(['root_id','path','pattern','command_id','profile_id','session_id','goal_id','task_id','step_id','mode']);
2
5
  const LIFECYCLE=Object.freeze({
3
6
  device_start:'🚀 [DEVICE] Starting DEADBYTE MCP...',
@@ -30,6 +33,25 @@ function summarizeArgs(args){
30
33
  return parts.length?` ${parts.join(' ')}`:'';
31
34
  }
32
35
 
36
+ const TOOL_COLUMN_WIDTH=32;
37
+
38
+ function eventTimestamp(event,clock){
39
+ for(const value of [event?.created_at_utc,event?.observed_at_utc]){
40
+ const parsed=Date.parse(value);
41
+ if(typeof value==='string'&&Number.isFinite(parsed)) return new Date(parsed).toISOString();
42
+ }
43
+ const value=Number(clock());
44
+ return new Date(Number.isFinite(value)?value:Date.now()).toISOString();
45
+ }
46
+
47
+ export function formatRemoteToolCall(event,{clock=()=>Date.now()}={}){
48
+ if(!event||typeof event!=='object'||Array.isArray(event)||event.event_type!=='tool.request'||
49
+ typeof event.tool!=='string'||!event.tool) return null;
50
+ if(typeof clock!=='function') throw new Error('remote tool-call clock must be a function');
51
+ const args=sanitizeAuditArgs(event.args??{});
52
+ return `${eventTimestamp(event,clock)} | ${event.tool.padEnd(TOOL_COLUMN_WIDTH)} | Arguments: ${canonicalJson(args)}`;
53
+ }
54
+
33
55
  function formatDuration(ms){
34
56
  const value=Number(ms);
35
57
  if(!Number.isFinite(value)||value<0) return '';
@@ -92,7 +114,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now()}={}){
92
114
  if(event.event_type==='tool.request'&&requestId&&tool){
93
115
  if(pending.has(requestId)||completed.has(requestId)) return null;
94
116
  pending.set(requestId,{request_id:requestId,tool,started_at_ms:clock()});
95
- return `▶ [${shortSpanId(requestId)}] RUN ${tool}${summarizeArgs(event.args)}`;
117
+ return formatRemoteToolCall(event,{clock});
96
118
  }
97
119
  if(event.event_type==='tool.result'&&requestId&&tool){
98
120
  if(completed.has(requestId)) return null;
@@ -1,10 +1,12 @@
1
1
  import { lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { sanitizeAuditArgs } from './tool-audit.mjs';
3
4
 
4
5
  export const REMOTE_RESULT_JOURNAL_SCHEMA='deadbyte.remote-result-journal.v1';
5
6
  const SESSION=/^[0-9a-f]{24}$/;
6
7
  const HASH=/^[0-9a-f]{64}$/;
7
8
  const STATUS=new Set(['ok','error']);
9
+ const EVENT_TYPES=new Set(['tool.call','tool.result']);
8
10
 
9
11
  function text(value,label,max){
10
12
  if(typeof value!=='string'||value.length<1||value.length>max||value.includes('\0')) throw new Error(`${label} invalid`);
@@ -26,14 +28,22 @@ function validateEvent(raw,index=null){
26
28
  const sessionId=validateSession(raw.session_id);
27
29
  const requestId=text(raw.request_id,'remote result request_id',256);
28
30
  const tool=text(raw.tool,'remote result tool',128);
29
- if(!STATUS.has(raw.status)) throw new Error('remote result status invalid');
31
+ const eventType=raw.event_type===undefined?'tool.result':raw.event_type;
32
+ if(!EVENT_TYPES.has(eventType)) throw new Error('remote result event_type invalid');
30
33
  const observed=iso(raw.observed_at_utc,'remote result observed_at_utc');
31
34
  if(typeof raw.payload_sha256!=='string'||!HASH.test(raw.payload_sha256)) throw new Error('remote result payload_sha256 invalid');
32
35
  if(index!==null&&seq!==index) throw new Error('remote result sequence mismatch');
33
- return Object.freeze({session_id:sessionId,seq,request_id:requestId,tool,status:raw.status,
36
+ if(eventType==='tool.call'){
37
+ const rawArgs=raw.arguments??{};
38
+ if(!rawArgs||typeof rawArgs!=='object'||Array.isArray(rawArgs)) throw new Error('remote result call arguments invalid');
39
+ return Object.freeze({session_id:sessionId,seq,event_type:eventType,request_id:requestId,tool,
40
+ observed_at_utc:observed,payload_sha256:raw.payload_sha256,arguments:sanitizeAuditArgs(rawArgs)});
41
+ }
42
+ if(!STATUS.has(raw.status)) throw new Error('remote result status invalid');
43
+ return Object.freeze({session_id:sessionId,seq,event_type:eventType,request_id:requestId,tool,status:raw.status,
34
44
  observed_at_utc:observed,payload_sha256:raw.payload_sha256});
35
45
  }
36
- function identity(item){return `${item.request_id}\0${item.status}\0${item.payload_sha256}`;}
46
+ function identity(item){return `${item.event_type}\0${item.request_id}\0${item.status??''}\0${item.payload_sha256}`;}
37
47
  export function parseRemoteResultCursor(cursor){
38
48
  if(typeof cursor!=='string') throw new Error('remote result cursor invalid');
39
49
  const match=/^r1:([0-9a-f]{24}):(0|[1-9][0-9]{0,15})$/.exec(cursor);
@@ -89,8 +99,17 @@ export async function openRemoteResultJournal({filePath,sessionId,maxEntries=204
89
99
  return Object.freeze({
90
100
  file_path:file,session_id:sid,
91
101
  cursor(){return cursorFor(sid,state.next_seq-1);},
102
+ async recordCall(input){
103
+ const candidate=validateEvent({session_id:sid,seq:state.next_seq,event_type:'tool.call',...input});
104
+ const key=identity(candidate);
105
+ const existing=state.events.find(event=>identity(event)===key);
106
+ if(existing) return {duplicate:true,event:existing,cursor:cursorFor(sid,state.next_seq-1)};
107
+ const event=candidate;
108
+ state.events.push(event);state.next_seq+=1;await persist();
109
+ return {duplicate:false,event,cursor:cursorFor(sid,event.seq)};
110
+ },
92
111
  async recordTerminal(input){
93
- const candidate=validateEvent({session_id:sid,seq:state.next_seq,...input});
112
+ const candidate=validateEvent({session_id:sid,seq:state.next_seq,event_type:'tool.result',...input});
94
113
  const key=identity(candidate);
95
114
  const existing=state.events.find(event=>identity(event)===key);
96
115
  if(existing) return {duplicate:true,event:existing,cursor:cursorFor(sid,state.next_seq-1)};
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.11.0';
1
+ export const DEADBYTE_VERSION = '0.11.2';
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';
@@ -1,6 +1,7 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { readFile } from 'node:fs/promises';
4
+ import { spawnSync } from 'node:child_process';
4
5
  import path from 'node:path';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
@@ -16,6 +17,14 @@ test('package exposes npx-first deadbyte-mcp bin and is registry-publishable',as
16
17
  assert.equal(pkg.publishConfig?.access,'public');
17
18
  });
18
19
 
20
+ test('npx CLI reports the canonical package version through --version, -v and version',()=>{
21
+ for(const arg of ['--version','-v','version']){
22
+ const result=spawnSync(process.execPath,[cliPath,arg],{cwd:root,encoding:'utf8',windowsHide:true});
23
+ assert.equal(result.status,0,result.stderr);
24
+ assert.equal(result.stdout.trim(),'0.11.2');
25
+ }
26
+ });
27
+
19
28
  test('npx CLI remote path invokes stable PS1 directly and never routes through CMD',async()=>{
20
29
  const source=await readFile(cliPath,'utf8');
21
30
  assert.match(source,/^#!\/usr\/bin\/env node/m);
@@ -230,6 +230,24 @@ test('native build script targets Windows 10 and preserves MSVC diagnostics', as
230
230
  assert.doesNotMatch(source, /\$VsWhere = Join-Path \$\{env:ProgramFiles\(x86\)\}/);
231
231
  });
232
232
 
233
+ test('native build is reproducible at both MSVC object and linker boundaries', async () => {
234
+ const buildScript = fileURLToPath(new URL('../scripts/build-containment.ps1', import.meta.url));
235
+ const source = await readFile(buildScript, 'utf8');
236
+ const lines = source.split(/\r?\n/);
237
+ const commonLine = lines.find(line => line.startsWith('$Common ='));
238
+ const bootstrapLine = lines.find(line => line.startsWith('$BootstrapCommon ='));
239
+ assert.ok(commonLine?.includes('/Brepro'), 'common compiler flags must include /Brepro');
240
+ assert.ok(bootstrapLine?.includes('/Brepro'), 'bootstrap compiler flags must include /Brepro');
241
+ for (const name of [
242
+ 'Launcher','Probe','BootstrapKernel','BootstrapAdvapi','StageLauncher','BootstrapExit',
243
+ 'ChildControlStage','ChildControlProbe','ContainedLauncher','ContainedWorker',
244
+ 'ContainedReverseWorker','TunnelHost','ProcessHost'
245
+ ]) {
246
+ const line = lines.find(candidate => candidate.startsWith(`$${name}Command =`));
247
+ assert.ok(line?.includes('/link /Brepro /INCREMENTAL:NO'), `${name} linker flags must be reproducible and non-incremental`);
248
+ }
249
+ });
250
+
233
251
 
234
252
 
235
253
  test('native build command composition is Windows PowerShell 5.1 parser-safe', async () => {
@@ -215,3 +215,14 @@ test('R34 supervisor lifecycle never arms privileged planes', async () => {
215
215
  assert.match(block,/Disarm-Process/);
216
216
  assert.match(block,/Disarm-Host/);
217
217
  });
218
+
219
+ test('supervisor recovery never assigns PowerShell automatic variable $Host', async () => {
220
+ const source=await readFile(controller,'utf8');
221
+ const start=source.indexOf('function Recover-AutonomousSupervisor');
222
+ const end=source.indexOf('function Show-State',start);
223
+ assert.ok(start>=0&&end>start,'supervisor recovery block missing');
224
+ const block=source.slice(start,end);
225
+ assert.doesNotMatch(block,/\$host\s*=/i,'PowerShell $Host is read-only and names are case-insensitive');
226
+ assert.match(block,/\$hostStatus\s*=\s*Get-HostArmStatus\s+\$Cfg/);
227
+ assert.match(block,/Host=\$hostStatus/);
228
+ });
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
105
105
  assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
106
106
  assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
107
107
  assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
108
- assert.equal(result.receipt.runtime.package_version, '0.11.0');
108
+ assert.equal(result.receipt.runtime.package_version, '0.11.2');
109
109
  assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
110
110
  assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
111
111
  assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);
@@ -9,9 +9,9 @@ const readme=()=>readFile(path.join(root,'README.txt'),'utf8');
9
9
 
10
10
  test('production README documents one-command stdio configs for major MCP clients',async()=>{
11
11
  const text=await readme();
12
- assert.match(text,/npx -y deadbyte-mcp@0\.11\.0 mcp/);
12
+ assert.match(text,/npx -y deadbyte-mcp@0\.11\.2 mcp/);
13
13
  assert.match(text,/CLAUDE DESKTOP/);
14
- assert.match(text,/"mcpServers"[\s\S]*?"command"\s*:\s*"npx"[\s\S]*?"-y"[\s\S]*?"deadbyte-mcp@0\.11\.0"[\s\S]*?"mcp"/);
14
+ assert.match(text,/"mcpServers"[\s\S]*?"command"\s*:\s*"npx"[\s\S]*?"-y"[\s\S]*?"deadbyte-mcp@0\.11\.2"[\s\S]*?"mcp"/);
15
15
  assert.match(text,/CURSOR/);
16
16
  assert.match(text,/\.cursor\/mcp\.json/);
17
17
  assert.match(text,/VS CODE/);
@@ -20,7 +20,7 @@ test('production README documents one-command stdio configs for major MCP client
20
20
  assert.match(text,/CODEX/);
21
21
  assert.match(text,/\[mcp_servers\.deadbyte\]/);
22
22
  assert.match(text,/command\s*=\s*"npx"/);
23
- assert.match(text,/args\s*=\s*\["-y",\s*"deadbyte-mcp@0\.11\.0",\s*"mcp"\]/);
23
+ assert.match(text,/args\s*=\s*\["-y",\s*"deadbyte-mcp@0\.11\.2",\s*"mcp"\]/);
24
24
  });
25
25
 
26
26
  test('production README records tracer bullet, semantic scope, remote UX, safety and document extension',async()=>{
@@ -5,7 +5,7 @@ import { mkdtemp, mkdir, readFile, rm, writeFile, readdir } from 'node:fs/promis
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { spawn } from 'node:child_process';
8
- import * as parity from '../scripts/release-parity.mjs';
8
+ import * as parity from '../scripts/release-parity-r38.mjs';
9
9
  import { createToolAudit } from '../src/tool-audit.mjs';
10
10
  import { verifyAuditChain } from '../src/audit-verify.mjs';
11
11
  import { generateTrustKeypair } from '../src/trust.mjs';
@@ -86,12 +86,12 @@ test('audit restart reconciles an orphan request with an explicit interrupted re
86
86
  }finally{await rm(stateRoot,{recursive:true,force:true});}
87
87
  });
88
88
 
89
- test('Windows R37 gate emits manifest-bound machine-readable evidence outside the candidate tree', async()=>{
89
+ test('Windows R38 gate emits manifest-bound machine-readable evidence outside the candidate tree', async()=>{
90
90
  const stateRoot=await temp('deadbyte-r33-windows-evidence-');
91
91
  try{
92
- const gate=await readFile(path.join(root,'scripts','gate-windows.ps1'),'utf8');
93
- assert.match(gate,/windows-gate-evidence\.mjs/,'gate-windows.ps1 must invoke the evidence writer');
94
- const result=await run(process.execPath,[path.join(root,'scripts','windows-gate-evidence.mjs')],{
92
+ const gate=await readFile(path.join(root,'scripts','gate-windows-r38.ps1'),'utf8');
93
+ assert.match(gate,/windows-gate-evidence-r38\.mjs/,'gate-windows-r38.ps1 must invoke the evidence writer');
94
+ const result=await run(process.execPath,[path.join(root,'scripts','windows-gate-evidence-r38.mjs')],{
95
95
  cwd:root,env:{...process.env,DEADBYTE_STATE_ROOT:stateRoot}
96
96
  });
97
97
  assert.equal(result.code,0,result.stderr||result.stdout);
@@ -99,12 +99,12 @@ test('Windows R37 gate emits manifest-bound machine-readable evidence outside th
99
99
  assert.equal(summary.status,'written');
100
100
  const evidence=JSON.parse(await readFile(summary.path,'utf8'));
101
101
  const manifestSha=sha256(await readFile(path.join(root,'MANIFEST.SHA256')));
102
- assert.equal(evidence.schema,'deadbyte.r37-windows-gate.v1');
102
+ assert.equal(evidence.schema,'deadbyte.r38-windows-gate.v1');
103
103
  assert.equal(evidence.status,'passed');
104
104
  assert.equal(evidence.exit_code,0);
105
- assert.equal(evidence.version,'0.11.0');
105
+ assert.equal(evidence.version,'0.11.2');
106
106
  assert.equal(evidence.manifest_sha256,manifestSha);
107
- assert.equal(evidence.marker,'DEADBYTE V0.11.0 / R37 WINDOWS BATCH GATE: PASS');
107
+ assert.equal(evidence.marker,'DEADBYTE V0.11.2 / R38 WINDOWS BATCH GATE: PASS');
108
108
  assert.ok(summary.path.startsWith(path.join(stateRoot,'evidence','release')));
109
109
  }finally{await rm(stateRoot,{recursive:true,force:true});}
110
110
  });
@@ -2,8 +2,8 @@ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { readFile } from 'node:fs/promises';
4
4
  import { R34_EXPECTED_TOOLS } from '../src/remote-mcp-r34-parity-client.mjs';
5
- import { validateParityEvidence } from '../scripts/release-parity.mjs';
6
- import { verifyFinalClosureObject } from '../scripts/final-closure-verify.mjs';
5
+ import { validateParityEvidence } from '../scripts/release-parity-r38.mjs';
6
+ import { verifyFinalClosureObject } from '../scripts/final-closure-verify-r38.mjs';
7
7
 
8
8
  const root = new URL('../', import.meta.url);
9
9
  const text = rel => readFile(new URL(rel, root), 'utf8');
@@ -19,28 +19,28 @@ test('R34 public parity catalog is exact, unique, and includes production coding
19
19
 
20
20
  test('release parity and final verifier are package entry points', async () => {
21
21
  const pkg = JSON.parse(await text('package.json'));
22
- assert.equal(pkg.scripts['release:parity'], 'node scripts/release-parity.mjs');
23
- assert.equal(pkg.scripts['release:final:write'], 'node scripts/final-closure.mjs');
24
- assert.equal(pkg.scripts['release:final:verify'], 'node scripts/final-closure-verify.mjs');
22
+ assert.equal(pkg.scripts['release:parity'], 'node scripts/release-parity-r38.mjs');
23
+ assert.equal(pkg.scripts['release:final:write'], 'node scripts/final-closure-r38.mjs');
24
+ assert.equal(pkg.scripts['release:final:verify'], 'node scripts/final-closure-verify-r38.mjs');
25
25
  assert.equal(typeof validateParityEvidence, 'function');
26
26
  assert.equal(typeof verifyFinalClosureObject, 'function');
27
27
  });
28
28
 
29
- test('R37 final closure verifier is implementation-independent from production helpers', async () => {
30
- const verifier = await text('scripts/final-closure-verify.mjs');
31
- const writer = await text('scripts/final-closure.mjs');
29
+ test('R38 final closure verifier is implementation-independent from production helpers', async () => {
30
+ const verifier = await text('scripts/final-closure-verify-r38.mjs');
31
+ const writer = await text('scripts/final-closure-r38.mjs');
32
32
  assert.doesNotMatch(verifier, /from ['"]\.\.\/src\//);
33
33
  assert.match(verifier, /DEADBYTE-R27-SLOT-UPDATE-V1/);
34
34
  assert.match(verifier, /DEADBYTE-MACHINE-PING-V1/);
35
- assert.match(verifier, /DEADBYTE-R37-FINAL-CLOSURE-V1/);
36
- assert.match(writer, /pointer\.version==='0\.11\.0'/);
37
- assert.match(verifier, /envelope\.body\.version==='0\.11\.0'/);
38
- assert.match(verifier, /release_label==='R37 FINAL MACHINE BASELINE'/);
39
- assert.match(verifier, /8d1e5f09745e764796b60e0a8a0168be7334ed4bf834fad5974a80eba295b4e8/);
35
+ assert.match(verifier, /DEADBYTE-R38-FINAL-CLOSURE-V1/);
36
+ assert.match(writer, /pointer\.version==='0\.11\.2'/);
37
+ assert.match(verifier, /envelope\.body\.version==='0\.11\.2'/);
38
+ assert.match(verifier, /release_label==='R38 FINAL MACHINE BASELINE'/);
39
+ assert.match(verifier, /220fac89dd82cdcdddb82cd7d0b71b768f84a5c87d57ebaca0f5383f33d97a76/);
40
40
  });
41
41
 
42
42
  test('final closure verifier canonicalizes traversal order before exact-tree comparison', async () => {
43
- const verifier = await text('scripts/final-closure-verify.mjs');
43
+ const verifier = await text('scripts/final-closure-verify-r38.mjs');
44
44
  assert.match(verifier, /const actual=\(await walk\(root\)\)\.filter\(rel=>rel!==manifestName\)\.sort\(\(a,b\)=>a\.localeCompare\(b,'en'\)\);/);
45
45
  assert.match(verifier, /const expected=\[\.\.\.entries\.keys\(\)\]\.sort\(\(a,b\)=>a\.localeCompare\(b,'en'\)\);/);
46
46
  });
@@ -10,10 +10,10 @@ const R36_RELEASE_ID='v0.10.0-8d1e5f09745e7647';
10
10
  const R36_MANIFEST='8d1e5f09745e764796b60e0a8a0168be7334ed4bf834fad5974a80eba295b4e8';
11
11
 
12
12
  test('sealed R36 historical proof scripts preserve 0.10.0 / R36 and exact R35 predecessor',async()=>{
13
- const [gate,parity,closure,closureVerify,windowsGate,currentParity]=await Promise.all([
13
+ const [gate,parity,closure,closureVerify,windowsGate,r37_0110Parity]=await Promise.all([
14
14
  text('scripts/gate-windows-r36.ps1'),text('scripts/release-parity-r36.mjs'),
15
15
  text('scripts/final-closure-r36.mjs'),text('scripts/final-closure-verify-r36.mjs'),
16
- text('scripts/windows-gate-evidence-r36.mjs'),text('scripts/release-parity.mjs')
16
+ text('scripts/windows-gate-evidence-r36.mjs'),text('scripts/release-parity-r37-0110.mjs')
17
17
  ]);
18
18
  assert.match(gate,/DEADBYTE V0\.10\.0 \/ R36 WINDOWS BATCH GATE: PASS/);
19
19
  assert.match(parity,/deadbyte\.r36-parity\.v1/);
@@ -23,8 +23,8 @@ test('sealed R36 historical proof scripts preserve 0.10.0 / R36 and exact R35 pr
23
23
  assert.match(closure,/version:'0\.10\.0'/);
24
24
  assert.match(closureVerify,/DEADBYTE-R36-FINAL-CLOSURE-V1/);
25
25
  assert.match(windowsGate,/deadbyte\.r36-windows-gate\.v1/);
26
- assert.match(currentParity,new RegExp(R36_RELEASE_ID.replaceAll('.','\\.')));
27
- assert.match(currentParity,new RegExp(R36_MANIFEST));
26
+ assert.match(r37_0110Parity,new RegExp(R36_RELEASE_ID.replaceAll('.','\\.')));
27
+ assert.match(r37_0110Parity,new RegExp(R36_MANIFEST));
28
28
  });
29
29
 
30
30
  test('current R37 controller still binds production remote to compact profile and restores parent environment',async()=>{
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { readFile } from 'node:fs/promises';
4
+ const root=new URL('../',import.meta.url);
5
+ const text=rel=>readFile(new URL(rel,root),'utf8');
6
+
7
+ test('sealed public 0.11.0 release scripts remain preserved as historical R37 authority',async()=>{
8
+ const [gate,evidence,parity,closure,verify,deferred]=await Promise.all([
9
+ text('scripts/gate-windows-r37-0110.ps1'),text('scripts/windows-gate-evidence-r37-0110.mjs'),
10
+ text('scripts/release-parity-r37-0110.mjs'),text('scripts/final-closure-r37-0110.mjs'),
11
+ text('scripts/final-closure-verify-r37-0110.mjs'),text('scripts/deferred-slot-operation-r37-0110.mjs')
12
+ ]);
13
+ assert.match(gate,/V0\.11\.0 \/ R37/);
14
+ assert.match(evidence,/version:'0\.11\.0'/);
15
+ assert.match(parity,/v0\.10\.0-8d1e5f09745e7647/);
16
+ assert.match(parity,/candidate\?\.version==='0\.11\.0'/);
17
+ assert.match(closure,/version:'0\.11\.0'/);
18
+ assert.match(verify,/value\?\.version==='0\.11\.0'/);
19
+ assert.match(deferred,/--expected-version','0\.11\.0'/);
20
+ });