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.
Files changed (67) hide show
  1. package/MANIFEST.SHA256 +66 -41
  2. package/PROVENANCE.json +3 -3
  3. package/RELEASE.json +3 -3
  4. package/package.json +1 -1
  5. package/scripts/autonomous-build.ps1 +1 -0
  6. package/scripts/autonomous-tests.ps1 +1 -0
  7. package/scripts/check-agent-backend-r46.mjs +77 -0
  8. package/scripts/check-atomic-resource-r46.mjs +84 -0
  9. package/scripts/check-autonomous-race-r46.mjs +160 -0
  10. package/scripts/check-coding-task-deadline-r46.mjs +59 -0
  11. package/scripts/check-deadline-cancellation-r46.mjs +49 -0
  12. package/scripts/check-process-deadline-r46.mjs +52 -0
  13. package/scripts/check-r46-background.mjs +5 -0
  14. package/scripts/extract-r46-failures.mjs +5 -0
  15. package/scripts/full-regression-r46.mjs +20 -12
  16. package/scripts/gate-windows-r46.ps1 +2 -0
  17. package/scripts/inspect-r46-active.mjs +2 -0
  18. package/scripts/r47-core-failures.mjs +1 -0
  19. package/scripts/release-manifest.mjs +1 -1
  20. package/scripts/run-r46-full-regression-background.mjs +7 -0
  21. package/scripts/run-r46-windows-gate-background.mjs +6 -0
  22. package/scripts/run-r46-windows-gate-background.ps1 +10 -0
  23. package/scripts/windows-gate-evidence-r46.mjs +4 -3
  24. package/scripts/workload-contract-regression-r47.mjs +14 -0
  25. package/scripts/workload-stream-regression.mjs +16 -0
  26. package/scripts/workload-substrate-r47-regression.mjs +12 -0
  27. package/src/agent-backend-r44.mjs +75 -10
  28. package/src/atomic-mutation.mjs +55 -0
  29. package/src/autonomous-mcp-tools.mjs +4 -3
  30. package/src/autonomous-output-contracts.mjs +16 -3
  31. package/src/autonomous-runtime.mjs +83 -30
  32. package/src/coding-mcp-tools.mjs +6 -6
  33. package/src/coding-output-contracts.mjs +20 -5
  34. package/src/coding-plane.mjs +3 -24
  35. package/src/coding-search.mjs +52 -15
  36. package/src/coding-task-runtime.mjs +41 -6
  37. package/src/compact-gateway-mcp-tools.mjs +3 -3
  38. package/src/deadline-cancellation.mjs +83 -0
  39. package/src/fuzzy-search-core.mjs +117 -0
  40. package/src/fuzzy-search-worker.mjs +8 -0
  41. package/src/fuzzy-search.mjs +34 -0
  42. package/src/machine-fs-mcp-tools.mjs +1 -1
  43. package/src/machine-fs-output-contracts.mjs +1 -1
  44. package/src/machine-fs-smoke-client.mjs +1 -1
  45. package/src/machine-fs.mjs +10 -29
  46. package/src/mcp-server.mjs +6 -0
  47. package/src/observation-smoke-client.mjs +1 -1
  48. package/src/process-mcp-tools.mjs +11 -3
  49. package/src/process-output-contracts.mjs +23 -0
  50. package/src/process-runtime.mjs +54 -5
  51. package/src/process-smoke-client.mjs +1 -1
  52. package/src/resource-identity.mjs +59 -0
  53. package/src/runtime-update.mjs +526 -162
  54. package/src/workload-substrate.mjs +87 -0
  55. package/test/coding-runtime.test.mjs +12 -0
  56. package/test/coding-search-r36.test.mjs +7 -0
  57. package/test/full-max-catalog-r46.test.mjs +2 -2
  58. package/test/fuzzy-search-r46.test.mjs +54 -0
  59. package/test/helpers/autonomous-r34-fixture.mjs +10 -1
  60. package/test/machine-fs-mcp-tools.test.mjs +2 -0
  61. package/test/machine-fs-runtime.test.mjs +6 -0
  62. package/test/process-mcp-tools.test.mjs +5 -2
  63. package/test/process-runtime.test.mjs +18 -1
  64. package/test/r45-finalization.test.mjs +1 -1
  65. package/test/release-version.test.mjs +1 -1
  66. package/test/runtime-self-update.test.mjs +118 -1
  67. 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,83,'expected exact full R46 catalog 83, got '+names.length+': '+names.join(','));
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:createRuntime(),
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'), journal_head_sha256: hex('c')
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:83/);
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 body={schema:UPDATE_SCHEMA');
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}));