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.
- package/MANIFEST.SHA256 +65 -27
- package/PROVENANCE.json +6 -6
- package/README.txt +6 -6
- package/RELEASE.json +58 -53
- package/docs/COMMANDER_EXHAUSTIVE_SURFACE.json +49 -0
- package/docs/COMMANDER_REFERENCE_INVENTORY.json +70 -0
- package/docs/R47-FULL-TOOL-CATALOG.json +123 -0
- package/package.json +5 -2
- package/scripts/commander-hardening-gate-r47.mjs +35 -0
- package/scripts/commander-parity-benchmark.mjs +6 -0
- package/scripts/commander-real-performance-benchmark-r47.mjs +41 -0
- package/scripts/deploy-r47.mjs +67 -8
- package/scripts/full-regression-r47.mjs +28 -0
- package/scripts/inventory-reference-dist.mjs +17 -0
- package/scripts/npm-release-r47.mjs +25 -1
- package/scripts/r47-syntax-check.mjs +6 -0
- package/scripts/r47-wire-remote-plane.mjs +23 -0
- package/src/atomic-mutation.mjs +59 -29
- package/src/commander-compat-contracts.mjs +90 -0
- package/src/commander-compat-runtime.mjs +91 -0
- package/src/commander-core-bridge.mjs +30 -0
- package/src/commander-extended-runtime.mjs +74 -0
- package/src/commander-observability.mjs +33 -0
- package/src/commander-remote-bridge-r47.mjs +41 -0
- package/src/commander-remote-relay-r47.mjs +44 -0
- package/src/machine-fs.mjs +108 -22
- package/src/mcp-server.mjs +94 -0
- package/src/observation-output-contracts.mjs +9 -8
- package/src/observation-runtime.mjs +47 -17
- package/src/operation-history.mjs +95 -0
- package/src/operation-lifecycle.mjs +63 -0
- package/src/process-output-contracts.mjs +24 -1
- package/src/process-readiness.mjs +120 -0
- package/src/process-runtime.mjs +178 -6
- package/src/release-slot.mjs +296 -293
- package/src/runtime-update.mjs +61 -7
- package/src/tool-audit.mjs +15 -2
- package/src/version.mjs +1 -2
- package/test/OBSERVED-COMMANDER-TOOLS.json +1 -0
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/coding-search-r36.test.mjs +14 -0
- package/test/commander-compat-runtime.test.mjs +34 -0
- package/test/commander-core-bridge-r47.test.mjs +31 -0
- package/test/commander-exhaustive-surface-r47.test.mjs +32 -0
- package/test/commander-extended-runtime-r47.test.mjs +46 -0
- package/test/commander-observability-r47.test.mjs +34 -0
- package/test/commander-parity-gate-r47.test.mjs +49 -0
- package/test/commander-protocol-r47.test.mjs +30 -0
- package/test/commander-real-parity-r47.test.mjs +121 -0
- package/test/commander-remote-bridge-r47.test.mjs +39 -0
- package/test/commander-remote-plane-r47.test.mjs +25 -0
- package/test/commander-torture-r47.test.mjs +146 -0
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/full-max-catalog-r47.test.mjs +78 -0
- package/test/machine-fs-runtime.test.mjs +97 -0
- package/test/observation-runtime.test.mjs +28 -0
- package/test/operation-history.test.mjs +61 -0
- package/test/operation-lifecycle.test.mjs +72 -0
- package/test/output-schema.test.mjs +1 -1
- package/test/process-readiness.test.mjs +32 -0
- package/test/process-runtime.test.mjs +147 -0
- package/test/r47-release-contract.test.mjs +37 -0
- package/test/release-activation-gate.test.mjs +30 -0
- package/test/release-version.test.mjs +1 -1
package/src/runtime-update.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
691
|
-
|
|
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)});
|
package/src/tool-audit.mjs
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
+
});
|