deadbyte-mcp 0.15.2 → 0.15.4
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 +66 -41
- package/PROVENANCE.json +3 -3
- package/RELEASE.json +3 -3
- package/package.json +1 -1
- package/scripts/autonomous-build.ps1 +1 -0
- package/scripts/autonomous-tests.ps1 +1 -0
- package/scripts/check-agent-backend-r46.mjs +77 -0
- package/scripts/check-atomic-resource-r46.mjs +84 -0
- package/scripts/check-autonomous-race-r46.mjs +160 -0
- package/scripts/check-coding-task-deadline-r46.mjs +59 -0
- package/scripts/check-deadline-cancellation-r46.mjs +49 -0
- package/scripts/check-process-deadline-r46.mjs +52 -0
- package/scripts/check-r46-background.mjs +5 -0
- package/scripts/extract-r46-failures.mjs +5 -0
- package/scripts/full-regression-r46.mjs +20 -12
- package/scripts/gate-windows-r46.ps1 +2 -0
- package/scripts/inspect-r46-active.mjs +2 -0
- package/scripts/r47-core-failures.mjs +1 -0
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/run-r46-full-regression-background.mjs +7 -0
- package/scripts/run-r46-windows-gate-background.mjs +6 -0
- package/scripts/run-r46-windows-gate-background.ps1 +10 -0
- package/scripts/windows-gate-evidence-r46.mjs +4 -3
- package/scripts/workload-contract-regression-r47.mjs +14 -0
- package/scripts/workload-stream-regression.mjs +16 -0
- package/scripts/workload-substrate-r47-regression.mjs +12 -0
- package/src/agent-backend-r44.mjs +75 -10
- package/src/atomic-mutation.mjs +55 -0
- package/src/autonomous-mcp-tools.mjs +4 -3
- package/src/autonomous-output-contracts.mjs +16 -3
- package/src/autonomous-runtime.mjs +83 -30
- package/src/coding-mcp-tools.mjs +6 -6
- package/src/coding-output-contracts.mjs +20 -5
- package/src/coding-plane.mjs +3 -24
- package/src/coding-search.mjs +52 -15
- package/src/coding-task-runtime.mjs +41 -6
- package/src/compact-gateway-mcp-tools.mjs +3 -3
- package/src/deadline-cancellation.mjs +83 -0
- package/src/fuzzy-search-core.mjs +117 -0
- package/src/fuzzy-search-worker.mjs +8 -0
- package/src/fuzzy-search.mjs +34 -0
- package/src/machine-fs-mcp-tools.mjs +1 -1
- package/src/machine-fs-output-contracts.mjs +1 -1
- package/src/machine-fs-smoke-client.mjs +1 -1
- package/src/machine-fs.mjs +10 -29
- package/src/mcp-server.mjs +6 -0
- package/src/observation-smoke-client.mjs +1 -1
- package/src/process-mcp-tools.mjs +11 -3
- package/src/process-output-contracts.mjs +23 -0
- package/src/process-runtime.mjs +54 -5
- package/src/process-smoke-client.mjs +1 -1
- package/src/resource-identity.mjs +59 -0
- package/src/runtime-update.mjs +526 -162
- package/src/workload-substrate.mjs +87 -0
- package/test/coding-runtime.test.mjs +12 -0
- package/test/coding-search-r36.test.mjs +7 -0
- package/test/full-max-catalog-r46.test.mjs +2 -2
- package/test/fuzzy-search-r46.test.mjs +54 -0
- package/test/helpers/autonomous-r34-fixture.mjs +10 -1
- package/test/machine-fs-mcp-tools.test.mjs +2 -0
- package/test/machine-fs-runtime.test.mjs +6 -0
- package/test/process-mcp-tools.test.mjs +5 -2
- package/test/process-runtime.test.mjs +18 -1
- package/test/r45-finalization.test.mjs +1 -1
- package/test/release-version.test.mjs +1 -1
- package/test/runtime-self-update.test.mjs +118 -1
- package/test/workload-substrate.test.mjs +22 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
2
|
+
|
|
3
|
+
function boundedInt(value,name,min,max){
|
|
4
|
+
if(!Number.isInteger(value)||value<min||value>max) throw new Error(`${name} must be in [${min}, ${max}]`);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createSequenceStream({streamId,generation=1,maxItems=10000,maxBytes=8*1024*1024}={}){
|
|
8
|
+
if(typeof streamId!=='string'||streamId.length<1) throw new Error('streamId required');
|
|
9
|
+
boundedInt(generation,'generation',1,Number.MAX_SAFE_INTEGER);
|
|
10
|
+
boundedInt(maxItems,'maxItems',1,1_000_000);
|
|
11
|
+
boundedInt(maxBytes,'maxBytes',1,1024*1024*1024);
|
|
12
|
+
const items=[];
|
|
13
|
+
let nextSeq=1;
|
|
14
|
+
let retainedBytes=0;
|
|
15
|
+
let logicalBytes=0;
|
|
16
|
+
let droppedItems=0;
|
|
17
|
+
let droppedBytes=0;
|
|
18
|
+
let status='open';
|
|
19
|
+
|
|
20
|
+
const sizeOf=(value,explicitBytes=null)=>{
|
|
21
|
+
if(Number.isInteger(explicitBytes)&&explicitBytes>=0) return explicitBytes;
|
|
22
|
+
return Buffer.byteLength(JSON.stringify(value),'utf8');
|
|
23
|
+
};
|
|
24
|
+
const coverage=()=>({
|
|
25
|
+
logical_first_seq:1,
|
|
26
|
+
logical_last_seq:nextSeq-1,
|
|
27
|
+
retained_first_seq:items[0]?.seq??nextSeq,
|
|
28
|
+
retained_last_seq:items.at(-1)?.seq??0,
|
|
29
|
+
complete:droppedItems===0,
|
|
30
|
+
truncated:droppedItems>0,
|
|
31
|
+
missing_ranges:droppedItems>0?[{from:1,to:items[0]?.seq-1??0}]:[],
|
|
32
|
+
logical_item_count:(nextSeq-1),
|
|
33
|
+
retained_item_count:items.length,
|
|
34
|
+
logical_bytes:logicalBytes,
|
|
35
|
+
retained_bytes:retainedBytes,
|
|
36
|
+
dropped_items:droppedItems,
|
|
37
|
+
dropped_bytes:droppedBytes,
|
|
38
|
+
status
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function append(value,{producer='default',channel=null,sizeBytes=null}={}){
|
|
42
|
+
if(status!=='open') throw new Error('stream is not open');
|
|
43
|
+
const item={seq:nextSeq++,producer,channel,value,_sizeBytes:Number.isInteger(sizeBytes)&&sizeBytes>=0 ? sizeBytes : null};
|
|
44
|
+
const bytes=item._sizeBytes ?? sizeOf(item);
|
|
45
|
+
item._sizeBytes=bytes;
|
|
46
|
+
items.push(item); retainedBytes+=bytes; logicalBytes+=bytes;
|
|
47
|
+
while(items.length>maxItems||retainedBytes>maxBytes){
|
|
48
|
+
const evicted=items.shift();
|
|
49
|
+
const evictedBytes=evicted._sizeBytes ?? sizeOf(evicted);
|
|
50
|
+
retainedBytes-=evictedBytes;
|
|
51
|
+
droppedItems+=1; droppedBytes+=evictedBytes;
|
|
52
|
+
}
|
|
53
|
+
return item;
|
|
54
|
+
}
|
|
55
|
+
function read(fromSeq=1,limit=100){
|
|
56
|
+
boundedInt(fromSeq,'fromSeq',1,Number.MAX_SAFE_INTEGER);
|
|
57
|
+
boundedInt(limit,'limit',1,10000);
|
|
58
|
+
const c=coverage();
|
|
59
|
+
if(fromSeq<c.retained_first_seq) return {items:[],coverage:c,missing:true,from_seq:fromSeq};
|
|
60
|
+
const out=items.filter(item=>item.seq>=fromSeq).slice(0,limit);
|
|
61
|
+
return {items:out,coverage:c,missing:false,from_seq:fromSeq,next_seq:out.length?out.at(-1).seq+1:fromSeq};
|
|
62
|
+
}
|
|
63
|
+
function snapshot(){return Object.freeze({stream_id:streamId,generation,next_seq:nextSeq,logical_bytes:logicalBytes,retained_first_seq:coverage().retained_first_seq,retained_last_seq:coverage().retained_last_seq,status});}
|
|
64
|
+
function delta(previous){
|
|
65
|
+
if(!previous||previous.stream_id!==streamId||previous.generation!==generation) throw new Error('stream snapshot identity mismatch');
|
|
66
|
+
const from=previous.next_seq;
|
|
67
|
+
return read(from,10000);
|
|
68
|
+
}
|
|
69
|
+
function close(){status='closed';}
|
|
70
|
+
return Object.freeze({streamId,generation,append,read,snapshot,delta,coverage,close});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function createJournalStream({streamId,generation=1,events=[],maxItems=10000,maxBytes=8*1024*1024,producer='journal',channel='event'}={}){
|
|
74
|
+
const stream=createSequenceStream({streamId,generation,maxItems,maxBytes});
|
|
75
|
+
for(const event of events){
|
|
76
|
+
const value=event?.payload && typeof event.payload==='object'
|
|
77
|
+
? { seq:event.seq, type:event.type, payload:event.payload, event_sha256:event.event_sha256 }
|
|
78
|
+
: event;
|
|
79
|
+
stream.append(value,{producer,channel,sizeBytes:Buffer.byteLength(JSON.stringify(value),'utf8')});
|
|
80
|
+
}
|
|
81
|
+
return stream;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function assertSequenceStreamSnapshot(value){
|
|
85
|
+
if(!value||typeof value!=='object'||typeof value.stream_id!=='string'||!Number.isInteger(value.next_seq)) throw new Error('invalid stream snapshot');
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
@@ -147,6 +147,18 @@ test('durable coding task chains edit command benchmark and verifies task receip
|
|
|
147
147
|
const run = await f.tasks.run({ taskId: submitted.task_id, maxSteps: 8 });
|
|
148
148
|
assert.equal(run.status, 'completed');
|
|
149
149
|
assert.equal(run.state, 'succeeded');
|
|
150
|
+
const status = await f.tasks.status({ taskId: submitted.task_id });
|
|
151
|
+
assert.equal(status.stream_id, submitted.task_id);
|
|
152
|
+
assert.equal(status.stream_generation, 1);
|
|
153
|
+
assert.equal(status.stream_next_seq, 10);
|
|
154
|
+
assert.equal(status.stream_coverage.logical_item_count, 9);
|
|
155
|
+
assert.equal(status.stream_coverage.retained_item_count, 9);
|
|
156
|
+
assert.equal(status.stream_coverage.truncated, false);
|
|
157
|
+
const page = await f.tasks.events({ taskId: submitted.task_id, fromSeq: 4, limit: 3 });
|
|
158
|
+
assert.equal(page.stream_id, submitted.task_id);
|
|
159
|
+
assert.equal(page.events.length, 3);
|
|
160
|
+
assert.equal(page.events[0].seq, 4);
|
|
161
|
+
assert.equal(page.events[2].seq, 6);
|
|
150
162
|
const verified = await f.tasks.verify({ taskId: submitted.task_id });
|
|
151
163
|
assert.equal(verified.ok, true);
|
|
152
164
|
assert.equal(verified.action_receipts_verified, 3);
|
|
@@ -38,6 +38,13 @@ test('R36 regex search returns multiple matches per file with deterministic sign
|
|
|
38
38
|
assert.match(first.query_sha256,/^[0-9a-f]{64}$/);
|
|
39
39
|
assert.match(first.snapshot_sha256,/^[0-9a-f]{64}$/);
|
|
40
40
|
assert.match(first.page_sha256,/^[0-9a-f]{64}$/);
|
|
41
|
+
assert.equal(first.stream_id,first.query_sha256);
|
|
42
|
+
assert.equal(first.stream_generation,1);
|
|
43
|
+
assert.equal(first.stream_next_seq,6);
|
|
44
|
+
assert.equal(first.stream_coverage.logical_item_count,5);
|
|
45
|
+
assert.equal(first.stream_coverage.retained_item_count,5);
|
|
46
|
+
assert.equal(first.stream_coverage.complete,true);
|
|
47
|
+
assert.equal(first.stream_coverage.truncated,false);
|
|
41
48
|
assert.ok(first.next_cursor);
|
|
42
49
|
const second=await f.search({rootId:'core',path:'.',target:'content',mode:'regex',pattern:'foo\\d',caseSensitive:false,pageSize:2,maxResults:20,cursor:first.next_cursor});
|
|
43
50
|
assert.equal(second.matches.length,2);
|
|
@@ -76,8 +76,8 @@ test('R46 real max-runtime full stdio catalog is exactly 83 with skill and four
|
|
|
76
76
|
const listed=await client.listTools();
|
|
77
77
|
const names=listed.tools.map(t=>t.name).sort();
|
|
78
78
|
const {R45_EXPECTED_TOOLS}=await import('../src/remote-mcp-r45-parity-client.mjs');
|
|
79
|
-
const R46_EXPECTED_TOOLS=[...R45_EXPECTED_TOOLS,'process_session_write'].sort();
|
|
80
|
-
assert.equal(names.length,
|
|
79
|
+
const R46_EXPECTED_TOOLS=[...R45_EXPECTED_TOOLS,'process_session_cancel','process_session_write'].sort();
|
|
80
|
+
assert.equal(names.length,84,'expected exact full R46 catalog 84, got '+names.length+': '+names.join(','));
|
|
81
81
|
assert.deepEqual(names,R46_EXPECTED_TOOLS);
|
|
82
82
|
for(const name of ['machine_hardware_query','desktop_status','desktop_observe','desktop_target','desktop_action',
|
|
83
83
|
'host_file_read','host_file_write','host_exec','skill_catalog']) assert.ok(names.includes(name),'missing '+name);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { fuzzyIndexOf } from '../src/fuzzy-search-core.mjs';
|
|
5
|
+
import { runFuzzySearchInWorker } from '../src/fuzzy-search.mjs';
|
|
6
|
+
import { createCodingSearch } from '../src/coding-search.mjs';
|
|
7
|
+
|
|
8
|
+
const sha = text => createHash('sha256').update(text,'utf8').digest('hex');
|
|
9
|
+
const files = new Map([
|
|
10
|
+
['src/a.mjs','const deadbyte_machine_identity = true;'],
|
|
11
|
+
['src/b.mjs','const unrelated = false;']
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
function cursorCodec(){
|
|
15
|
+
return {
|
|
16
|
+
async encode(value){ return Buffer.from(JSON.stringify(value)).toString('base64url'); },
|
|
17
|
+
async decode(value){ return JSON.parse(Buffer.from(value,'base64url').toString('utf8')); }
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('R46 fuzzy core and worker agree under a hard timeout boundary', async () => {
|
|
22
|
+
const text='AAAA deadbyte_machine_identity SHA256 CATALOG';
|
|
23
|
+
const direct=fuzzyIndexOf(text,'deadbyte_machine_identiti');
|
|
24
|
+
const worker=await runFuzzySearchInWorker(text,'deadbyte_machine_identiti',3000);
|
|
25
|
+
assert.equal(worker.start,direct.start);
|
|
26
|
+
assert.equal(worker.end,direct.end);
|
|
27
|
+
assert.equal(worker.distance,direct.distance);
|
|
28
|
+
assert.ok(worker.similarity >= 0.90);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('R46 coding search fuzzy mode preserves bounded paging and file identity', async () => {
|
|
32
|
+
const search=createCodingSearch({
|
|
33
|
+
tree: async ({rootId,path,maxEntries}) => ({
|
|
34
|
+
status:'ok',root_id:rootId,path,
|
|
35
|
+
entries:[...files].map(([item])=>({path:item,type:'file',bytes:files.get(item).length})).slice(0,maxEntries),
|
|
36
|
+
truncated:false
|
|
37
|
+
}),
|
|
38
|
+
fileRead: async ({relativePath}) => {
|
|
39
|
+
const text=files.get(relativePath);
|
|
40
|
+
return {content:Buffer.from(text,'utf8').toString('base64'),bytes:Buffer.byteLength(text),sha256:sha(text)};
|
|
41
|
+
},
|
|
42
|
+
cursorCodec:cursorCodec()
|
|
43
|
+
});
|
|
44
|
+
const value=await search({
|
|
45
|
+
rootId:'core',path:'.',pattern:'deadbyte_machine_identiti',
|
|
46
|
+
target:'content',mode:'fuzzy',maxResults:10,maxFileBytes:4096,maxScanBytes:4096,pageSize:10
|
|
47
|
+
});
|
|
48
|
+
assert.equal(value.status,'ok');
|
|
49
|
+
assert.equal(value.matches.length,1);
|
|
50
|
+
assert.equal(value.matches[0].path,'src/a.mjs');
|
|
51
|
+
assert.equal(value.matches[0].distance,1);
|
|
52
|
+
assert.ok(value.matches[0].similarity >= 0.90);
|
|
53
|
+
assert.match(value.matches[0].file_sha256,/^[0-9a-f]{64}$/);
|
|
54
|
+
});
|
|
@@ -75,8 +75,17 @@ export async function createR34Fixture({authority='armed',withFailingProfile=fal
|
|
|
75
75
|
const nativeDiffRuntime = nativeDiffRuntimeFactory ? nativeDiffRuntimeFactory(baseNativeDiffRuntime) : baseNativeDiffRuntime;
|
|
76
76
|
const createRuntime = ({codingRuntimeOverride=codingRuntime,nativeDiffRuntimeOverride=nativeDiffRuntime,desktopMemoryProviderOverride=desktopMemoryProvider,memoryProviderOverride=memoryProvider,desktopRuntimeOverride=desktopRuntime}={}) => createAutonomousRuntime({policy,codingRuntime:codingRuntimeOverride,nativeDiffRuntime:nativeDiffRuntimeOverride,machineFsRuntime,
|
|
77
77
|
desktopRuntime:desktopRuntimeOverride,desktopMemoryProvider:desktopMemoryProviderOverride,memoryProvider:memoryProviderOverride,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
78
|
+
const rawRuntime=createRuntime();
|
|
79
|
+
const runtime={...rawRuntime,events:async ({goalId,fromSeq=1,limit=100})=>{
|
|
80
|
+
const page=await rawRuntime.events({goalId,fromSeq,limit});
|
|
81
|
+
const eventsDir=path.join(policy.evidence_dir,'goals',goalId,'events');
|
|
82
|
+
const names=(await (await import('node:fs/promises')).readdir(eventsDir)).filter(name=>/^[0-9]{6}\.json$/.test(name)).sort();
|
|
83
|
+
const raw=[];
|
|
84
|
+
for(const name of names.slice(fromSeq-1,fromSeq-1+limit)) raw.push(JSON.parse(await readFile(path.join(eventsDir,name),'utf8')));
|
|
85
|
+
return {...page,events:raw};
|
|
86
|
+
}};
|
|
78
87
|
return {
|
|
79
|
-
root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,machineFsRuntime,nativeDiffRuntime,createRuntime,runtime
|
|
88
|
+
root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,machineFsRuntime,nativeDiffRuntime,createRuntime,runtime,
|
|
80
89
|
initial_sha256:sha(await readFile(target)),arm,disarm,
|
|
81
90
|
cleanup:()=>rm(root,{recursive:true,force:true})
|
|
82
91
|
};
|
|
@@ -37,6 +37,8 @@ test('R31 machine filesystem schemas expose root-relative IDs/preconditions only
|
|
|
37
37
|
registerMachineFsTools(server, { runtime: runtime() });
|
|
38
38
|
const read = server.tools.get('machine_fs_read').spec.inputSchema;
|
|
39
39
|
assert.equal(read.safeParse({ root_id:'core', path:'a.txt', offset:0, max_bytes:65536, hash_file:false, timeout_ms:30000 }).success, true);
|
|
40
|
+
assert.equal(read.safeParse({ root_id:'core', path:'a.txt', offset:-4096, max_bytes:4096, hash_file:false, timeout_ms:30000 }).success, true);
|
|
41
|
+
assert.equal(read.safeParse({ root_id:'core', path:'a.txt', offset:-4194305, max_bytes:4096, hash_file:false, timeout_ms:30000 }).success, false);
|
|
40
42
|
assert.equal(read.safeParse({ root_id:'core', path:'a.txt', timeout_ms:99 }).success, false);
|
|
41
43
|
const write = server.tools.get('machine_fs_write').spec.inputSchema;
|
|
42
44
|
const move = server.tools.get('machine_fs_move').spec.inputSchema;
|
|
@@ -53,6 +53,12 @@ test('R31 machine fs supports exact mkdir/write/stat/list/range-read/edit/move/d
|
|
|
53
53
|
assert.equal(Buffer.from(read.data_base64, 'base64').toString('utf8'), 'beta');
|
|
54
54
|
assert.equal(read.next_offset, 10);
|
|
55
55
|
assert.equal(read.timeout_ms, 30000);
|
|
56
|
+
const tail = await f.runtime.read({ rootId: 'project', relativePath: 'dir/a.txt', offset: -5, maxBytes: 8 });
|
|
57
|
+
assert.equal(Buffer.from(tail.data_base64, 'base64').toString('utf8'), 'gamma');
|
|
58
|
+
assert.equal(tail.offset, 11);
|
|
59
|
+
assert.equal(tail.requested_offset, -5);
|
|
60
|
+
assert.equal(tail.next_offset, 16);
|
|
61
|
+
assert.equal(tail.eof, true);
|
|
56
62
|
const edited = await f.runtime.editExact({
|
|
57
63
|
rootId: 'project', relativePath: 'dir/a.txt', expectedSha256: written.sha256,
|
|
58
64
|
oldString: 'beta', newString: 'BETA', expectedReplacements: 1
|
|
@@ -12,7 +12,10 @@ function session(state = 'running') {
|
|
|
12
12
|
return {
|
|
13
13
|
session_id: hex('a'), profile_id: 'hold', root_id: 'core', cwd: '.', child_pid: 1234,
|
|
14
14
|
creation_filetime: '133700000000000000', state, exit_code: state === 'running' ? null : 0,
|
|
15
|
-
earliest_offset: 0, stream_head: 4, stream_sha256: hex('b'),
|
|
15
|
+
earliest_offset: 0, stream_head: 4, stream_sha256: hex('b'),
|
|
16
|
+
stream_id: hex('f'), stream_generation: 1, stream_next_seq: 2,
|
|
17
|
+
stream_coverage: { logical_first_seq: 1, logical_last_seq: 1, retained_first_seq: 1, retained_last_seq: 1, complete: true, truncated: false, missing_ranges: [], logical_item_count: 1, retained_item_count: 1, logical_bytes: 4, retained_bytes: 4, dropped_items: 0, dropped_bytes: 0, status: 'open' },
|
|
18
|
+
termination: null, deadline: null, journal_head_sha256: hex('c')
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -32,7 +35,7 @@ test('R46 registers exactly seven bounded process supervision tools', () => {
|
|
|
32
35
|
const server = new FakeServer();
|
|
33
36
|
registerProcessTools(server, { runtime: runtime() });
|
|
34
37
|
assert.deepEqual([...server.tools.keys()].sort(), [
|
|
35
|
-
'process_session_action','process_session_list','process_session_read','process_session_start','process_session_status','process_session_terminate','process_session_write'
|
|
38
|
+
'process_session_action','process_session_cancel','process_session_list','process_session_read','process_session_start','process_session_status','process_session_terminate','process_session_write'
|
|
36
39
|
]);
|
|
37
40
|
for (const [name, tool] of server.tools) {
|
|
38
41
|
assert.ok(tool.spec.inputSchema, `${name} input schema missing`);
|
|
@@ -75,7 +75,7 @@ async function fixture() {
|
|
|
75
75
|
privateKeyPath: path.join(trust, 'private.pem'), publicKeyPath: path.join(trust, 'public.pem')
|
|
76
76
|
});
|
|
77
77
|
return {
|
|
78
|
-
root, project, policy, grantFile, stateDir,
|
|
78
|
+
root, project, policy, grantFile, stateDir, keys,
|
|
79
79
|
runtime: createProcessRuntime({ policy, signingKeyPath: keys.privateKeyPath, verifyKeyPath: keys.publicKeyPath })
|
|
80
80
|
};
|
|
81
81
|
}
|
|
@@ -97,6 +97,12 @@ test('R31 runtime starts one approved profile and exposes ordered bounded output
|
|
|
97
97
|
assert.ok(started.child_pid > 0);
|
|
98
98
|
assert.match(started.creation_filetime, /^[0-9]+$/);
|
|
99
99
|
assert.equal(started.profile_id, 'echo');
|
|
100
|
+
assert.equal(started.stream_id, started.session_id);
|
|
101
|
+
assert.equal(started.stream_generation, 1);
|
|
102
|
+
assert.equal(started.stream_next_seq, 1);
|
|
103
|
+
assert.equal(started.stream_coverage.logical_item_count, 0);
|
|
104
|
+
assert.equal(started.stream_coverage.retained_item_count, 0);
|
|
105
|
+
assert.equal(started.stream_coverage.complete, true);
|
|
100
106
|
const observed = await waitFor(f.runtime, started.session_id, text => text.includes('DBR31RUNTIME:42'));
|
|
101
107
|
assert.match(observed.text, /DBR31RUNTIME:42/);
|
|
102
108
|
assert.equal(observed.page.offset, 0);
|
|
@@ -148,6 +154,17 @@ test('R46 process output retention evicts oldest bytes without losing tail ident
|
|
|
148
154
|
assert.match(tailText, /DB_RING_TAIL_MARK/);
|
|
149
155
|
const verified = await f.runtime.verifyEventJournal({ sessionId: started.session_id });
|
|
150
156
|
assert.equal(verified.ok, true);
|
|
157
|
+
const recoveredRuntime = createProcessRuntime({
|
|
158
|
+
policy: f.policy,
|
|
159
|
+
signingKeyPath: f.keys.privateKeyPath,
|
|
160
|
+
verifyKeyPath: f.keys.publicKeyPath
|
|
161
|
+
});
|
|
162
|
+
const recovered = await recoveredRuntime.status({ sessionId: started.session_id });
|
|
163
|
+
assert.equal(recovered.stream_id, null);
|
|
164
|
+
assert.equal(recovered.stream_generation, null);
|
|
165
|
+
assert.equal(recovered.stream_next_seq, null);
|
|
166
|
+
assert.equal(recovered.stream_coverage, null);
|
|
167
|
+
assert.ok(recovered.stream_head > 2 * 1024 * 1024);
|
|
151
168
|
});
|
|
152
169
|
|
|
153
170
|
test('R46 process raw input keeps persistent PowerShell state with bounded wait and tail reads', { skip: process.platform !== 'win32' }, async () => {
|
|
@@ -79,6 +79,6 @@ test('R45 release machinery binds 0.15.1, 82/43 surfaces, Host v2 bootstrap and
|
|
|
79
79
|
assert.match(finalizer,/host_inline_native/);assert.match(finalizer,/native_hardware_observation/);assert.match(finalizer,/doctor_release_truth/);
|
|
80
80
|
assert.match(evidence,/host_inline_native:true/);assert.match(evidence,/native_hardware_observation:true/);assert.match(evidence,/doctor_release_truth:true/);
|
|
81
81
|
assert.match(manifestSource,/excludedPackagePrefixes = \[[^\]]*'tmp\/'[^\]]*'\.deadbyte-machine-fs-recovery\/'[^\]]*\]/);
|
|
82
|
-
assert.match(manifestSource,/excludedPackagePaths = new Set\(\['controller\/deadbyte-host-policy\.json'\]\)/);
|
|
82
|
+
assert.match(manifestSource,/excludedPackagePaths = new Set\(\['controller\/deadbyte-host-policy\.json','controller\/deadbyte-coding-policy\.json'\]\)/);
|
|
83
83
|
assert.match(manifestSource,/excludedPackagePrefixes\.some\(prefix => rel\.startsWith\(prefix\)\)/);
|
|
84
84
|
});
|
|
@@ -50,7 +50,7 @@ test('R46 current authority agrees on V0.15.2 and preserves exact R45 parent ide
|
|
|
50
50
|
assert.match(parity,/DB_R46_REPL_VALUE=42/);
|
|
51
51
|
assert.match(parity,/raw_input_not_journaled:true/);
|
|
52
52
|
assert.match(parity,/eviction_verified:true/);
|
|
53
|
-
assert.match(windowsGate,/full_tool_count:
|
|
53
|
+
assert.match(windowsGate,/full_tool_count:84/);
|
|
54
54
|
assert.match(windowsGate,/remote_reachability_ttl:true/);
|
|
55
55
|
assert.match(windowsGate,/observation_search_terminal_ttl:true/);
|
|
56
56
|
assert.match(windowsGate,/observation_search_hard_cap:128/);
|
|
@@ -4,6 +4,7 @@ import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
7
8
|
import {
|
|
8
9
|
assertDevelopmentRootSafe,
|
|
9
10
|
legacyContext,
|
|
@@ -213,11 +214,105 @@ test('R28 updater requires a stabilization window and public MCP smoke before co
|
|
|
213
214
|
assert.match(source,/McpUrlFileSha256\s*=\s*stableMcpUrl\.sha256/);
|
|
214
215
|
assert.match(source,/mcp_url_restored/);
|
|
215
216
|
const stableIndex=source.indexOf('stabilizeRuntime');
|
|
216
|
-
const receiptIndex=source.indexOf('const
|
|
217
|
+
const receiptIndex=source.indexOf('const bodyReceipt={');
|
|
217
218
|
assert.ok(stableIndex>=0&&receiptIndex>stableIndex,'stability/public gate must happen before update receipt');
|
|
218
219
|
});
|
|
219
220
|
|
|
220
221
|
|
|
222
|
+
|
|
223
|
+
test('R47 runtime updater arms a detached supervisor before any destructive stop', async () => {
|
|
224
|
+
const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
|
|
225
|
+
const start=source.indexOf('export async function performSlotUpdate');
|
|
226
|
+
const end=source.indexOf('export async function performSlotRollback',start);
|
|
227
|
+
assert.ok(start>=0&&end>start);
|
|
228
|
+
const perform=source.slice(start,end);
|
|
229
|
+
assert.match(source,/const HANDOFF_SCHEMA='deadbyte\.r27-slot-update-handoff\.v1'/);
|
|
230
|
+
assert.match(source,/shutdown_handoff_armed/);
|
|
231
|
+
assert.match(source,/detached:true/);
|
|
232
|
+
assert.match(source,/proc\.unref\(\)/);
|
|
233
|
+
const handoffIndex=perform.indexOf('const handoff=await writeHandoff');
|
|
234
|
+
const spawnIndex=perform.indexOf('await spawnDetachedSupervisor',handoffIndex);
|
|
235
|
+
const returnIndex=perform.indexOf('return {ok:true,handed_off:true',spawnIndex);
|
|
236
|
+
assert.ok(handoffIndex>=0&&spawnIndex>handoffIndex&&returnIndex>spawnIndex);
|
|
237
|
+
assert.equal(perform.indexOf("controllerCaptured(control.ctx,'stop'"),-1);
|
|
238
|
+
assert.equal(perform.indexOf('await revokeAuthority(control.ctx'),-1);
|
|
239
|
+
assert.equal(perform.indexOf("writeState(statePath,journal,'supervisor_spawned'"),-1);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('R47 supervisor resume parser is exact and recovery scans only durable update state', () => {
|
|
243
|
+
assert.deepEqual(runtimeUpdate.parseResumeHandoffArgs(['--resume-handoff','C:/state/evidence/updates/u/handoff.json']),{
|
|
244
|
+
handoff:path.resolve('C:/state/evidence/updates/u/handoff.json')
|
|
245
|
+
});
|
|
246
|
+
assert.throws(()=>runtimeUpdate.parseResumeHandoffArgs([]),/resume-handoff is required/i);
|
|
247
|
+
assert.throws(()=>runtimeUpdate.parseResumeHandoffArgs(['--resume-handoff','x','--bad']),/unknown supervisor argument/i);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('R47 interrupted transition has explicit durable recovery phases', async () => {
|
|
251
|
+
const source=await readFile(new URL('../src/runtime-update.mjs',import.meta.url),'utf8');
|
|
252
|
+
for(const phase of ['shutdown_handoff_armed','supervisor_spawned','authority_revoked','old_runtime_stopped','workspace_ready','launcher_installed','pointer_committed','fresh_runtime_verified','authority_verified','completed']){
|
|
253
|
+
assert.match(source,new RegExp(phase));
|
|
254
|
+
}
|
|
255
|
+
assert.match(source,/recoverInterruptedSlotUpdates/);
|
|
256
|
+
assert.match(source,/phaseRank\(phase\)<phaseRank\('authority_revoked'\)/);
|
|
257
|
+
assert.match(source,/recovery observed predecessor already OFFLINE/);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
test('R47 MCP core startup invokes durable interrupted-update recovery before serving', async () => {
|
|
262
|
+
const source=await readFile(new URL('../src/mcp-server.mjs',import.meta.url),'utf8');
|
|
263
|
+
assert.match(source,/import \{ recoverInterruptedSlotUpdates \} from '\.\/runtime-update\.mjs';/);
|
|
264
|
+
assert.match(source,/await recoverInterruptedSlotUpdates\(\{ stateRoot: runtimeStateRoot \}\)/);
|
|
265
|
+
const recoveryIndex=source.indexOf('await recoverInterruptedSlotUpdates');
|
|
266
|
+
const serveIndex=source.indexOf('serveStdio(');
|
|
267
|
+
assert.ok(recoveryIndex>=0&&serveIndex>recoveryIndex,'startup recovery must run before MCP serving starts');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
test('R47 Windows detached supervisor survives termination of its parent process', { skip: process.platform !== 'win32' }, async () => {
|
|
272
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r47-detached-survival-'));
|
|
273
|
+
const parentPath=path.join(root,'parent.mjs');
|
|
274
|
+
const survivorPath=path.join(root,'survivor.mjs');
|
|
275
|
+
const pidPath=path.join(root,'survivor.pid');
|
|
276
|
+
await writeFile(survivorPath,[
|
|
277
|
+
"import { writeFile } from 'node:fs/promises';",
|
|
278
|
+
"const pidPath=process.argv[2];",
|
|
279
|
+
"await writeFile(pidPath,String(process.pid),'ascii');",
|
|
280
|
+
"setInterval(()=>{},1000);"
|
|
281
|
+
].join('\n'));
|
|
282
|
+
await writeFile(parentPath,[
|
|
283
|
+
"import { spawn } from 'node:child_process';",
|
|
284
|
+
"const survivor=spawn(process.execPath,[process.argv[2],process.argv[3]],{detached:true,windowsHide:true,stdio:'ignore'});",
|
|
285
|
+
"survivor.unref();",
|
|
286
|
+
"setInterval(()=>{},1000);"
|
|
287
|
+
].join('\n'));
|
|
288
|
+
const parent=spawn(process.execPath,[parentPath,survivorPath,pidPath],{windowsHide:true,stdio:'ignore'});
|
|
289
|
+
try{
|
|
290
|
+
const deadline=Date.now()+5000;
|
|
291
|
+
let survivorPid=0;
|
|
292
|
+
while(Date.now()<deadline){
|
|
293
|
+
try{
|
|
294
|
+
survivorPid=Number((await readFile(pidPath,'ascii')).trim());
|
|
295
|
+
if(Number.isInteger(survivorPid)&&survivorPid>0) break;
|
|
296
|
+
}catch{}
|
|
297
|
+
await new Promise(resolve=>setTimeout(resolve,50));
|
|
298
|
+
}
|
|
299
|
+
assert.ok(survivorPid>0,'detached survivor did not report PID');
|
|
300
|
+
parent.kill('SIGKILL');
|
|
301
|
+
await new Promise((resolve,reject)=>{
|
|
302
|
+
const timer=setTimeout(()=>reject(new Error('parent did not terminate after SIGKILL')),5000);
|
|
303
|
+
parent.once('exit',()=>{clearTimeout(timer);resolve();});
|
|
304
|
+
parent.once('error',error=>{clearTimeout(timer);reject(error);});
|
|
305
|
+
});
|
|
306
|
+
await new Promise(resolve=>setTimeout(resolve,350));
|
|
307
|
+
assert.doesNotThrow(()=>process.kill(survivorPid,0),'detached supervisor was terminated with its parent');
|
|
308
|
+
} finally {
|
|
309
|
+
const textPid=(await readFile(pidPath,'ascii').catch(()=>'' )).trim();
|
|
310
|
+
const cleanupPid=Number(textPid);
|
|
311
|
+
if(Number.isInteger(cleanupPid)&&cleanupPid>0){try{process.kill(cleanupPid,'SIGKILL');}catch{}}
|
|
312
|
+
try{process.kill(parent.pid,'SIGKILL');}catch{}
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
221
316
|
test('R29 slot-update CLI requires explicit hash-bound host repins and offline recovery opt-in', () => {
|
|
222
317
|
const base=['--stage','stage','--development-root','dev','--expected-version','0.8.5','--expected-manifest',hex('a')];
|
|
223
318
|
const parsed=parseUpdateArgs([...base,'--host-command-pin',`powershell_script=${hex('b')}`,'--host-command-pin',`cmd_script=${hex('c')}`,'--recover-offline-predecessor']);
|
|
@@ -286,6 +381,15 @@ test('R31 slot-update CLI binds process-policy bootstrap and machine-FS enable e
|
|
|
286
381
|
assert.equal(parsed.enableMachineFs,true);
|
|
287
382
|
});
|
|
288
383
|
|
|
384
|
+
test('R46 slot-update CLI binds coding policy bootstrap as one exact pair', () => {
|
|
385
|
+
const base=['--stage','stage','--development-root','dev','--expected-version','0.15.2','--expected-manifest',hex('a')];
|
|
386
|
+
assert.throws(()=>parseUpdateArgs([...base,'--coding-policy','C:/candidate/coding.json']),/coding policy.*pair/i);
|
|
387
|
+
assert.throws(()=>parseUpdateArgs([...base,'--coding-policy-sha256',hex('b')]),/coding policy.*pair/i);
|
|
388
|
+
const parsed=parseUpdateArgs([...base,'--coding-policy','C:/candidate/coding.json','--coding-policy-sha256',hex('b')]);
|
|
389
|
+
assert.equal(parsed.codingPolicyBootstrap.path,path.resolve('C:/candidate/coding.json'));
|
|
390
|
+
assert.equal(parsed.codingPolicyBootstrap.sha256,hex('b'));
|
|
391
|
+
});
|
|
392
|
+
|
|
289
393
|
test('R40 slot-update CLI binds Desktop policy as one exact pair and enables runtime only explicitly', () => {
|
|
290
394
|
const base=['--stage','stage','--development-root','dev','--expected-version','0.12.0','--expected-manifest',hex('a')];
|
|
291
395
|
assert.throws(()=>parseUpdateArgs([...base,'--desktop-policy','C:/candidate/desktop.json']),/desktop policy.*pair/i);
|
|
@@ -299,6 +403,19 @@ test('R40 slot-update CLI binds Desktop policy as one exact pair and enables run
|
|
|
299
403
|
assert.throws(()=>parseUpdateArgs([...base,'--enable-desktop','--enable-desktop']),/duplicate self-update argument/i);
|
|
300
404
|
});
|
|
301
405
|
|
|
406
|
+
test('R46 coding policy bootstrap accepts only exact pinned enabled policy bytes',async()=>{
|
|
407
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r46-coding-bootstrap-'));
|
|
408
|
+
const file=path.join(root,'coding.json');
|
|
409
|
+
const policy={schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:'C:/old/grant.json',evidence_dir:'C:/old/evidence',roots:{core:{path:'C:/core',read:true,write:true,exec:true}},commands:{powershell_script:{executable:'C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe',executable_sha256:hex('1')}}};
|
|
410
|
+
const bytes=Buffer.from(JSON.stringify(policy)+'\n','utf8');await writeFile(file,bytes);
|
|
411
|
+
assert.equal(typeof runtimeUpdate.prepareCodingPolicyBootstrap,'function');
|
|
412
|
+
const accepted=await runtimeUpdate.prepareCodingPolicyBootstrap({path:file,sha256:sha(bytes)});
|
|
413
|
+
assert.equal(accepted.sha256,sha(bytes));assert.deepEqual(accepted.policy,policy);
|
|
414
|
+
await assert.rejects(runtimeUpdate.prepareCodingPolicyBootstrap({path:file,sha256:hex('f')}),/coding policy.*SHA-256 mismatch/i);
|
|
415
|
+
const bad=path.join(root,'bad.json');await writeFile(bad,JSON.stringify({...policy,enabled:false}));
|
|
416
|
+
assert.rejects(runtimeUpdate.prepareCodingPolicyBootstrap({path:bad,sha256:sha(await readFile(bad))}),/coding policy schema/i);
|
|
417
|
+
});
|
|
418
|
+
|
|
302
419
|
test('R40 Desktop policy bootstrap accepts only exact pinned enabled policy bytes',async()=>{
|
|
303
420
|
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-bootstrap-'));
|
|
304
421
|
const file=path.join(root,'desktop.json');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { createSequenceStream, assertSequenceStreamSnapshot } from '../src/workload-substrate.mjs';
|
|
3
|
+
|
|
4
|
+
const stream=createSequenceStream({streamId:'test',maxItems:2,maxBytes:1_000_000});
|
|
5
|
+
const a=stream.append({text:'a'},{producer:'p',channel:'stdout'});
|
|
6
|
+
const snap=stream.snapshot();
|
|
7
|
+
const b=stream.append({text:'b'},{producer:'p',channel:'stdout'});
|
|
8
|
+
assert.equal(stream.delta(snap).items[0].seq,b.seq);
|
|
9
|
+
stream.append({text:'c'},{producer:'p',channel:'stderr'});
|
|
10
|
+
const coverage=stream.coverage();
|
|
11
|
+
assert.equal(stream.snapshot().next_seq,4);
|
|
12
|
+
assert.equal(coverage.logical_item_count,3);
|
|
13
|
+
assert.equal(coverage.retained_item_count,2);
|
|
14
|
+
assert.equal(coverage.truncated,true);
|
|
15
|
+
assert.equal(coverage.complete,false);
|
|
16
|
+
assert.equal(stream.read(1,10).missing,true);
|
|
17
|
+
assert.equal(stream.read(2,10).items.length,2);
|
|
18
|
+
assert.equal(coverage.missing_ranges[0].from,1);
|
|
19
|
+
assert.equal(assertSequenceStreamSnapshot(snap).stream_id,'test');
|
|
20
|
+
stream.close();
|
|
21
|
+
assert.throws(()=>stream.append({text:'d'}),/not open/);
|
|
22
|
+
console.log(JSON.stringify({status:'pass',stream_id:stream.streamId,logical_last_seq:coverage.logical_last_seq,retained_first_seq:coverage.retained_first_seq,truncated:coverage.truncated}));
|