deadbyte-mcp 0.9.0 → 0.11.0

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 (136) hide show
  1. package/MANIFEST.SHA256 +134 -92
  2. package/README.txt +19 -15
  3. package/bench/fixtures/corpus.json +32 -13
  4. package/bench/fixtures/r36/case-map.json +8 -0
  5. package/bin/WORKER-REGISTRY.txt +3 -3
  6. package/bin/appcontainer-stage.exe +0 -0
  7. package/bin/appcontainer-stage.obj +0 -0
  8. package/bin/bootstrap-advapi32.exe +0 -0
  9. package/bin/bootstrap-advapi32.obj +0 -0
  10. package/bin/bootstrap-exitcode.exe +0 -0
  11. package/bin/bootstrap-exitcode.obj +0 -0
  12. package/bin/bootstrap-kernel32.exe +0 -0
  13. package/bin/bootstrap-kernel32.obj +0 -0
  14. package/bin/child-control-probe.exe +0 -0
  15. package/bin/child-control-probe.obj +0 -0
  16. package/bin/child-control-stage.exe +0 -0
  17. package/bin/child-control-stage.obj +0 -0
  18. package/bin/contained-reverse-worker.exe +0 -0
  19. package/bin/contained-reverse-worker.obj +0 -0
  20. package/bin/contained-transform-worker.exe +0 -0
  21. package/bin/contained-transform-worker.obj +0 -0
  22. package/bin/containment-probe.exe +0 -0
  23. package/bin/containment-probe.obj +0 -0
  24. package/bin/deadbyte-contain.exe +0 -0
  25. package/bin/deadbyte-contain.obj +0 -0
  26. package/bin/deadbyte-exec.exe +0 -0
  27. package/bin/deadbyte-exec.obj +0 -0
  28. package/bin/deadbyte-process-host.exe +0 -0
  29. package/bin/deadbyte-process-host.obj +0 -0
  30. package/bin/deadbyte-tunnel-host.exe +0 -0
  31. package/bin/deadbyte-tunnel-host.obj +0 -0
  32. package/controller/README.TXT +8 -6
  33. package/controller/deadbyte-controller.ps1 +48 -2
  34. package/controller/deadbyte-process-policy.json +5 -5
  35. package/docs/ARCHITECTURE.md +1 -1
  36. package/docs/R36-CAPABILITY-EVIDENCE.md +43 -0
  37. package/docs/superpowers/plans/2026-09-18-r36-zero-loss-autonomy.md +739 -0
  38. package/docs/superpowers/specs/2026-09-18-r36-zero-loss-autonomy-design.md +640 -0
  39. package/package.json +1 -1
  40. package/proof/CONTAINMENT-BUILD.txt +6 -6
  41. package/scripts/deferred-slot-operation.mjs +53 -15
  42. package/scripts/final-closure-r36.mjs +113 -0
  43. package/scripts/final-closure-verify-r36.mjs +208 -0
  44. package/scripts/final-closure-verify.mjs +88 -39
  45. package/scripts/final-closure.mjs +63 -56
  46. package/scripts/gate-windows-r36.ps1 +42 -0
  47. package/scripts/gate-windows.ps1 +3 -3
  48. package/scripts/release-parity-r36.mjs +200 -0
  49. package/scripts/release-parity-tests.ps1 +9 -0
  50. package/scripts/release-parity.mjs +149 -139
  51. package/scripts/windows-gate-evidence-r36.mjs +52 -0
  52. package/scripts/windows-gate-evidence.mjs +5 -5
  53. package/src/agent-memory-plane.mjs +98 -0
  54. package/src/autonomous-context-engine.mjs +2 -1
  55. package/src/autonomous-mcp-tools.mjs +5 -1
  56. package/src/autonomous-output-contracts.mjs +9 -6
  57. package/src/autonomous-planner-contracts.mjs +36 -1
  58. package/src/autonomous-planner.mjs +25 -3
  59. package/src/autonomous-runtime.mjs +337 -43
  60. package/src/autonomous-timeout-recovery.mjs +27 -0
  61. package/src/capability-benchmark-runner.mjs +86 -1
  62. package/src/capability-benchmark.mjs +45 -0
  63. package/src/capability-ledger.mjs +158 -0
  64. package/src/coding-mcp-tools.mjs +31 -17
  65. package/src/coding-output-contracts.mjs +48 -10
  66. package/src/coding-plane.mjs +45 -9
  67. package/src/coding-search.mjs +130 -47
  68. package/src/compact-gateway-mcp-tools.mjs +128 -0
  69. package/src/compact-gateway-output-contracts.mjs +16 -0
  70. package/src/deadbyte-cli.mjs +62 -78
  71. package/src/effect-ledger.mjs +47 -0
  72. package/src/machine-fs-smoke-client.mjs +1 -1
  73. package/src/machine-fs.mjs +51 -8
  74. package/src/mcp-server.mjs +32 -9
  75. package/src/native-diff-v2.mjs +337 -0
  76. package/src/native-diff.mjs +50 -2
  77. package/src/observation-lease.mjs +32 -0
  78. package/src/observation-smoke-client.mjs +1 -1
  79. package/src/operator-surface.mjs +7 -1
  80. package/src/process-runtime.mjs +98 -11
  81. package/src/process-smoke-client.mjs +11 -3
  82. package/src/r36-compact-disarmed-probe.mjs +50 -0
  83. package/src/r36-full-surface-probe.mjs +56 -0
  84. package/src/remote-live-log.mjs +135 -0
  85. package/src/remote-mcp-autonomous-smoke-client.mjs +22 -12
  86. package/src/remote-mcp-coding-smoke-client.mjs +1 -1
  87. package/src/remote-mcp-r36-parity-client.mjs +175 -0
  88. package/src/remote-mcp-smoke-client.mjs +112 -56
  89. package/src/runtime-update.mjs +51 -1
  90. package/src/search-cursor.mjs +45 -0
  91. package/src/semantic-code.mjs +72 -5
  92. package/src/semantic-lsp-backend.mjs +39 -3
  93. package/src/version.mjs +1 -1
  94. package/src/worker-scheduler.mjs +53 -0
  95. package/test/autonomous-capability-r36.test.mjs +151 -0
  96. package/test/autonomous-output-contract.test.mjs +1 -1
  97. package/test/autonomous-runtime.test.mjs +2 -2
  98. package/test/capability-benchmark-r36.test.mjs +107 -0
  99. package/test/capability-benchmark-semantic-r36.test.mjs +52 -0
  100. package/test/capability-claims-r36.test.mjs +23 -0
  101. package/test/capability-ledger-r36.test.mjs +102 -0
  102. package/test/coding-catalog-consistency.test.mjs +2 -2
  103. package/test/coding-plane.test.mjs +2 -2
  104. package/test/coding-runtime.test.mjs +16 -0
  105. package/test/coding-search-r36.test.mjs +90 -0
  106. package/test/compact-catalog-r36.test.mjs +37 -0
  107. package/test/compact-gateway-r36.test.mjs +66 -0
  108. package/test/contained.test.mjs +1 -1
  109. package/test/core.test.mjs +1 -1
  110. package/test/deferred-slot-operation.test.mjs +9 -0
  111. package/test/fixtures/lsp-framed-server.mjs +2 -0
  112. package/test/helpers/autonomous-r34-fixture.mjs +5 -3
  113. package/test/multi-file-read.test.mjs +3 -0
  114. package/test/native-diff-topology-r36.test.mjs +105 -0
  115. package/test/package-boundary.test.mjs +6 -0
  116. package/test/process-longrun-r36.test.mjs +82 -0
  117. package/test/process-release-gate.test.mjs +9 -0
  118. package/test/production-docs.test.mjs +5 -5
  119. package/test/prompts-resources.test.mjs +7 -1
  120. package/test/r33-closeout-regression.test.mjs +4 -3
  121. package/test/r33-finalization.test.mjs +14 -9
  122. package/test/r36-finalization.test.mjs +104 -0
  123. package/test/r36-release-identity.test.mjs +45 -0
  124. package/test/r37-agent-fabric.test.mjs +92 -0
  125. package/test/r37-autonomous-timeout-recovery.test.mjs +60 -0
  126. package/test/r37-finalization.test.mjs +74 -0
  127. package/test/release-version.test.mjs +42 -33
  128. package/test/remote-live-log-r36.test.mjs +37 -0
  129. package/test/remote-live-log-r37.test.mjs +47 -0
  130. package/test/remote-live-log-recovery-r36.test.mjs +41 -0
  131. package/test/remote-r24-catalog.test.mjs +13 -0
  132. package/test/remote-session-cli.test.mjs +53 -66
  133. package/test/runtime-self-update.test.mjs +58 -1
  134. package/test/semantic-lsp-backend.test.mjs +9 -0
  135. package/test/semantic-r36-rename.test.mjs +82 -0
  136. package/deadbyte-mcp-0.8.9.tgz +0 -0
@@ -0,0 +1,102 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ CAPABILITY_LEDGER_SCHEMA,
5
+ capabilityProfileFromEnv,
6
+ createCapabilityLedger,
7
+ assertCapabilityClosure
8
+ } from '../src/capability-ledger.mjs';
9
+
10
+ const entry = overrides => ({
11
+ id:'coding.search',
12
+ full_tool:'coding_search',
13
+ compact_tool:'coding_inspect',
14
+ compact_op:'search',
15
+ runtime_flag:'DEADBYTE_CODING_RUNTIME',
16
+ authority:'coding',
17
+ mutating:false,
18
+ receipt_class:'observation',
19
+ production:true,
20
+ host_only:false,
21
+ ...overrides
22
+ });
23
+
24
+ const snapshot = names => ({
25
+ schema:'deadbyte.tool-surface.v1',
26
+ tools:names.map(name=>({name,input_sha256:'0'.repeat(64),output_sha256:'1'.repeat(64)})),
27
+ surface_sha256:'2'.repeat(64)
28
+ });
29
+
30
+ test('R36 capability ledger rejects duplicate ids', () => {
31
+ assert.throws(() => createCapabilityLedger([
32
+ entry({}),
33
+ entry({full_tool:'other_tool'})
34
+ ]), /duplicate capability id/i);
35
+ });
36
+
37
+ test('R36 capability ledger validates authority and canonicalizes identity', () => {
38
+ assert.throws(() => createCapabilityLedger([entry({authority:'root'})]), /authority/i);
39
+ const a=createCapabilityLedger([
40
+ entry({id:'process.read',full_tool:'process_session_read',compact_tool:'process_session',compact_op:'read',runtime_flag:'DEADBYTE_PROCESS_RUNTIME',authority:'process'}),
41
+ entry({})
42
+ ]);
43
+ const b=createCapabilityLedger([
44
+ entry({}),
45
+ entry({id:'process.read',full_tool:'process_session_read',compact_tool:'process_session',compact_op:'read',runtime_flag:'DEADBYTE_PROCESS_RUNTIME',authority:'process'})
46
+ ]);
47
+ assert.equal(a.schema,CAPABILITY_LEDGER_SCHEMA);
48
+ assert.equal(a.ledger_sha256,b.ledger_sha256);
49
+ assert.deepEqual(a.entries.map(x=>x.id),['coding.search','process.read']);
50
+ assert.ok(Object.isFrozen(a));
51
+ assert.ok(Object.isFrozen(a.entries));
52
+ });
53
+
54
+ test('R36 surface profile defaults full and accepts only full or compact', () => {
55
+ assert.equal(capabilityProfileFromEnv({}),'full');
56
+ assert.equal(capabilityProfileFromEnv({DEADBYTE_CAPABILITY_PROFILE:'full'}),'full');
57
+ assert.equal(capabilityProfileFromEnv({DEADBYTE_CAPABILITY_PROFILE:'compact'}),'compact');
58
+ assert.throws(() => capabilityProfileFromEnv({DEADBYTE_CAPABILITY_PROFILE:'wide'}), /full or compact/i);
59
+ });
60
+
61
+ test('R36 compact closure covers every production non-host capability', () => {
62
+ const ledger=createCapabilityLedger([
63
+ entry({}),
64
+ entry({id:'host.exec',full_tool:'host_exec',compact_tool:null,compact_op:null,runtime_flag:'DEADBYTE_PRIVILEGED_POLICY',authority:'host',mutating:true,receipt_class:'host',production:false,host_only:true})
65
+ ]);
66
+ assert.equal(assertCapabilityClosure({
67
+ ledger,
68
+ fullToolSnapshot:snapshot(['coding_search','host_exec']),
69
+ compactToolSnapshot:snapshot(['coding_inspect'])
70
+ }),true);
71
+ });
72
+
73
+ test('R36 closure fails when a production route is lost or advertised tool is absent', () => {
74
+ const missingCompact=createCapabilityLedger([entry({compact_tool:null,compact_op:null})]);
75
+ assert.throws(() => assertCapabilityClosure({
76
+ ledger:missingCompact,
77
+ fullToolSnapshot:snapshot(['coding_search']),
78
+ compactToolSnapshot:snapshot([])
79
+ }), /compact route/i);
80
+
81
+ const ledger=createCapabilityLedger([entry({})]);
82
+ assert.throws(() => assertCapabilityClosure({
83
+ ledger,
84
+ fullToolSnapshot:snapshot([]),
85
+ compactToolSnapshot:snapshot(['coding_inspect'])
86
+ }), /full tool/i);
87
+ assert.throws(() => assertCapabilityClosure({
88
+ ledger,
89
+ fullToolSnapshot:snapshot(['coding_search']),
90
+ compactToolSnapshot:snapshot([])
91
+ }), /compact tool/i);
92
+ });
93
+
94
+ test('R36 compact surface hard-fails above the observed 42-tool ceiling', () => {
95
+ const ledger=createCapabilityLedger([entry({})]);
96
+ const tooMany=Array.from({length:43},(_,i)=>i===0?'coding_inspect':`extra_${i}`);
97
+ assert.throws(() => assertCapabilityClosure({
98
+ ledger,
99
+ fullToolSnapshot:snapshot(['coding_search']),
100
+ compactToolSnapshot:snapshot(tooMany)
101
+ }), /42/i);
102
+ });
@@ -8,7 +8,7 @@ const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
8
8
  const expected=[
9
9
  'coding_benchmark_run','coding_command_run','coding_file_read','coding_file_write','coding_files_read',
10
10
  'coding_receipt_verify','coding_search','coding_session_status','coding_stat','coding_symbol_definition',
11
- 'coding_symbol_outline','coding_symbol_references','coding_symbol_replace','coding_task_approve','coding_task_cancel',
11
+ 'coding_symbol_diagnostics','coding_symbol_implementations','coding_symbol_outline','coding_symbol_references','coding_symbol_rename_preview','coding_symbol_replace','coding_task_approve','coding_task_cancel',
12
12
  'coding_task_events','coding_task_run','coding_task_status','coding_task_submit','coding_task_verify','coding_text_replace','coding_tree'
13
13
  ].sort();
14
14
  function extract(source){
@@ -17,7 +17,7 @@ function extract(source){
17
17
  return [...m[1].matchAll(/'([^']+)'/g)].map(x=>x[1]).sort();
18
18
  }
19
19
  for(const rel of ['src/machine-fs-smoke-client.mjs','src/observation-smoke-client.mjs','src/remote-mcp-coding-smoke-client.mjs','src/remote-mcp-smoke-client.mjs']){
20
- test(`${rel} uses the exact R34 22-tool coding catalog`,async()=>{
20
+ test(`${rel} uses the exact R36 25-tool coding catalog`,async()=>{
21
21
  const source=await readFile(path.join(root,...rel.split('/')),'utf8');
22
22
  assert.deepEqual(extract(source),expected);
23
23
  });
@@ -15,7 +15,7 @@ const serverPath = path.join(repoRoot, 'src', 'mcp-server.mjs');
15
15
  const workspaceRoot = path.join(repoRoot, 'workspaces');
16
16
  const codingTools = [
17
17
  'coding_benchmark_run', 'coding_command_run', 'coding_file_read', 'coding_file_write', 'coding_files_read',
18
- 'coding_receipt_verify', 'coding_search', 'coding_session_status', 'coding_stat', 'coding_symbol_definition', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_replace', 'coding_task_approve', 'coding_task_cancel',
18
+ 'coding_receipt_verify', 'coding_search', 'coding_session_status', 'coding_stat', 'coding_symbol_definition', 'coding_symbol_diagnostics', 'coding_symbol_implementations', 'coding_symbol_outline', 'coding_symbol_references', 'coding_symbol_rename_preview', 'coding_symbol_replace', 'coding_task_approve', 'coding_task_cancel',
19
19
  'coding_task_events', 'coding_task_run', 'coding_task_status', 'coding_task_submit',
20
20
  'coding_task_verify', 'coding_text_replace', 'coding_tree'
21
21
  ];
@@ -69,7 +69,7 @@ async function fixture() {
69
69
  return { root, client, transport };
70
70
  }
71
71
 
72
- test('R34 hermetic coding fixture exposes the exact 34-tool no-host catalog', async () => {
72
+ test('R36 hermetic coding fixture exposes the exact 37-tool no-host catalog', async () => {
73
73
  const f = await fixture();
74
74
  try {
75
75
  await f.client.connect(f.transport);
@@ -56,6 +56,22 @@ test('coding plane edits files under lease and verifies signed action receipts',
56
56
  } finally { await rm(f.root, { recursive: true, force: true }); }
57
57
  });
58
58
 
59
+ test('R36 coding stat exposes mutation-relevant observation identity', async () => {
60
+ const f = await fixture();
61
+ try {
62
+ await writeFile(path.join(f.project,'stat.txt'),'stat-body\n');
63
+ const value=await f.runtime.stat({rootId:'project',relativePath:'stat.txt',hash:true});
64
+ assert.equal(value.status,'ok');
65
+ assert.equal(value.normalized_path,'stat.txt');
66
+ assert.equal(value.type,'file');
67
+ assert.equal(value.reparse_state,'none');
68
+ assert.match(value.sha256,/^[0-9a-f]{64}$/);
69
+ assert.match(value.object_identity_sha256,/^[0-9a-f]{64}$/);
70
+ assert.match(value.observation_sha256,/^[0-9a-f]{64}$/);
71
+ assert.equal(value.policy_root_id,'project');
72
+ } finally { await rm(f.root, { recursive: true, force: true }); }
73
+ });
74
+
59
75
  test('coding plane rejects mutation when lease is absent or precondition is stale', async () => {
60
76
  const f = await fixture({ armed: false });
61
77
  try {
@@ -0,0 +1,90 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm } from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { generateTrustKeypair } from '../src/trust.mjs';
7
+ import { createSearchCursorCodec } from '../src/search-cursor.mjs';
8
+ import { createCodingSearch } from '../src/coding-search.mjs';
9
+
10
+ async function fixture(){
11
+ const temp=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r36-search-'));
12
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(temp,'private.pem'),publicKeyPath:path.join(temp,'public.pem')});
13
+ const state=new Map([
14
+ ['src/a.txt',Buffer.from('foo1 foo2\nbar foo3\n','utf8')],
15
+ ['src/b.txt',Buffer.from('other FOO4 and foo5\n','utf8')],
16
+ ['src/foo-file.js',Buffer.from('nothing here\n','utf8')],
17
+ ['src/binary.bin',Buffer.from([0xff,0xfe,0x00,0x61])]
18
+ ]);
19
+ let reads=0;
20
+ const tree=async()=>({status:'ok',root_id:'core',path:'.',truncated:false,entries:[...state].map(([p,b])=>({path:p,type:'file',bytes:b.length}))});
21
+ const fileRead=async({relativePath,encoding})=>{
22
+ reads+=1;
23
+ const bytes=state.get(relativePath);
24
+ if(!bytes) throw new Error('missing');
25
+ return {status:'ok',root_id:'core',path:relativePath,encoding,bytes:bytes.length,sha256:(await import('node:crypto')).createHash('sha256').update(bytes).digest('hex'),content:encoding==='base64'?bytes.toString('base64'):bytes.toString('utf8')};
26
+ };
27
+ const cursorCodec=createSearchCursorCodec({signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
28
+ const search=createCodingSearch({tree,fileRead,cursorCodec});
29
+ return {temp,state,search,get reads(){return reads;},cleanup:()=>rm(temp,{recursive:true,force:true})};
30
+ }
31
+
32
+ test('R36 regex search returns multiple matches per file with deterministic signed paging',async()=>{
33
+ const f=await fixture();
34
+ try{
35
+ const first=await f.search({rootId:'core',path:'.',target:'content',mode:'regex',pattern:'foo\\d',caseSensitive:false,pageSize:2,maxResults:20});
36
+ assert.equal(first.matches.length,2);
37
+ assert.deepEqual(first.matches.map(x=>[x.path,x.line,x.column]),[['src/a.txt',1,1],['src/a.txt',1,6]]);
38
+ assert.match(first.query_sha256,/^[0-9a-f]{64}$/);
39
+ assert.match(first.snapshot_sha256,/^[0-9a-f]{64}$/);
40
+ assert.match(first.page_sha256,/^[0-9a-f]{64}$/);
41
+ assert.ok(first.next_cursor);
42
+ 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
+ assert.equal(second.matches.length,2);
44
+ assert.deepEqual(second.matches.map(x=>x.path),['src/a.txt','src/b.txt']);
45
+ assert.notEqual(second.page_sha256,first.page_sha256);
46
+ } finally {await f.cleanup();}
47
+ });
48
+
49
+ test('R36 path search does not decode file content and is deterministic',async()=>{
50
+ const f=await fixture();
51
+ try{
52
+ const result=await f.search({rootId:'core',target:'path',mode:'literal',pattern:'foo-file',pageSize:10,maxResults:20});
53
+ assert.equal(result.matches.length,1);
54
+ assert.equal(result.matches[0].path,'src/foo-file.js');
55
+ assert.equal(result.matches[0].line,null);
56
+ assert.equal(result.bytes_scanned,0);
57
+ assert.equal(f.reads,0);
58
+ } finally {await f.cleanup();}
59
+ });
60
+
61
+ test('R36 search cursor is query-bound and stale snapshot fails closed',async()=>{
62
+ const f=await fixture();
63
+ try{
64
+ const first=await f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',pageSize:1,maxResults:20});
65
+ await assert.rejects(()=>f.search({rootId:'core',target:'content',mode:'literal',pattern:'bar',pageSize:1,maxResults:20,cursor:first.next_cursor}),/cursor.*query/i);
66
+ f.state.set('src/a.txt',Buffer.from('foo1 changed same workflow\n','utf8'));
67
+ await assert.rejects(()=>f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',pageSize:1,maxResults:20,cursor:first.next_cursor}),/cursor.*stale|snapshot/i);
68
+ } finally {await f.cleanup();}
69
+ });
70
+
71
+ test('R36 invalid regex fails before repository file reads and binary is classified',async()=>{
72
+ const f=await fixture();
73
+ try{
74
+ await assert.rejects(()=>f.search({rootId:'core',target:'content',mode:'regex',pattern:'[unterminated'}),/regex/i);
75
+ assert.equal(f.reads,0);
76
+ const result=await f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',pageSize:20,maxResults:20});
77
+ assert.equal(result.binary_files_skipped,1);
78
+ assert.ok(result.matches.length>=5);
79
+ } finally {await f.cleanup();}
80
+ });
81
+
82
+ test('R36 scan-byte bound truncates deterministically',async()=>{
83
+ const f=await fixture();
84
+ try{
85
+ const result=await f.search({rootId:'core',target:'content',mode:'literal',pattern:'foo',maxScanBytes:10,pageSize:20,maxResults:20});
86
+ assert.equal(result.truncated,true);
87
+ assert.equal(result.truncation_reason,'scan_byte_limit');
88
+ assert.ok(result.bytes_scanned<=10);
89
+ } finally {await f.cleanup();}
90
+ });
@@ -0,0 +1,37 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Client } from '@modelcontextprotocol/client';
4
+ import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
5
+ import { fileURLToPath } from 'node:url';
6
+ import path from 'node:path';
7
+
8
+ const here=path.dirname(fileURLToPath(import.meta.url));
9
+ const repoRoot=path.resolve(here,'..');
10
+ const serverPath=path.join(repoRoot,'src','mcp-server.mjs');
11
+ const gatewayNames=['capability_manifest','coding_inspect','machine_fs','machine_observe','process_session'];
12
+
13
+ test('R36 real compact stdio catalog stays <=42 and exposes all gateways',async()=>{
14
+ const client=new Client({name:'deadbyte-r36-compact-test',version:'0.10.0-dev'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
15
+ const env={
16
+ ...getDefaultEnvironment(),
17
+ DEADBYTE_CAPABILITY_PROFILE:'compact',
18
+ DEADBYTE_AUDIT_RUNTIME:'0',DEADBYTE_MACHINE_RUNTIME:'0',DEADBYTE_AGENT_RUNTIME:'0',
19
+ DEADBYTE_PROCESS_RUNTIME:'0',DEADBYTE_CODING_RUNTIME:'0',DEADBYTE_MACHINE_FS_RUNTIME:'0',
20
+ DEADBYTE_OBSERVATION_RUNTIME:'0',DEADBYTE_AUTONOMOUS_RUNTIME:'0'
21
+ };
22
+ const transport=new StdioClientTransport({command:process.execPath,args:[serverPath],cwd:repoRoot,env,stderr:'pipe'});
23
+ try{
24
+ await client.connect(transport);
25
+ const listed=await client.listTools();
26
+ const names=listed.tools.map(tool=>tool.name).sort();
27
+ assert.ok(names.length<=42,`compact tool count ${names.length} exceeds 42`);
28
+ for(const name of gatewayNames) assert.ok(names.includes(name),`missing ${name}`);
29
+ for(const legacy of ['workspace_open','file_read','proof_execute','contained_transform']) assert.equal(names.includes(legacy),false,`legacy full-only tool leaked: ${legacy}`);
30
+ const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
31
+ assert.equal(manifest.isError,false);
32
+ assert.equal(manifest.structuredContent.status,'ok');
33
+ assert.equal(manifest.structuredContent.result.profile,'compact');
34
+ assert.match(manifest.structuredContent.result.ledger_sha256,/^[0-9a-f]{64}$/);
35
+ assert.ok(manifest.structuredContent.result.capabilities.length>40);
36
+ } finally {await client.close().catch(()=>{});}
37
+ });
@@ -0,0 +1,66 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {
4
+ codingInspectInputSchema,
5
+ machineFsGatewayInputSchema,
6
+ processSessionGatewayInputSchema,
7
+ machineObserveInputSchema,
8
+ registerCompactGatewayTools
9
+ } from '../src/compact-gateway-mcp-tools.mjs';
10
+
11
+ const fakeServer=()=>{
12
+ const tools=new Map();
13
+ return {
14
+ tools,
15
+ registerTool(name,definition,handler){
16
+ if(tools.has(name)) throw new Error(`duplicate ${name}`);
17
+ tools.set(name,{definition,handler});
18
+ }
19
+ };
20
+ };
21
+
22
+ test('R36 compact gateway schemas reject generic arbitrary dispatch',()=>{
23
+ assert.equal(codingInspectInputSchema.safeParse({op:'call',tool_name:'host_exec',args:{}}).success,false);
24
+ assert.equal(machineFsGatewayInputSchema.safeParse({op:'exec',command:'whoami'}).success,false);
25
+ assert.equal(processSessionGatewayInputSchema.safeParse({op:'stdin',session_id:'0'.repeat(64),input:'raw'}).success,false);
26
+ assert.equal(machineObserveInputSchema.safeParse({op:'revoke',reason:'no'}).success,false);
27
+ });
28
+
29
+ test('R36 compact schemas preserve representative preconditions',()=>{
30
+ assert.equal(machineFsGatewayInputSchema.safeParse({
31
+ op:'move',root_id:'core',source_path:'a.txt',destination_path:'b.txt',expected_source_identity:'a'.repeat(64)
32
+ }).success,true);
33
+ assert.equal(machineFsGatewayInputSchema.safeParse({
34
+ op:'move',root_id:'core',source_path:'a.txt',destination_path:'b.txt'
35
+ }).success,false);
36
+ assert.equal(processSessionGatewayInputSchema.safeParse({op:'action',session_id:'b'.repeat(64),action_id:'ping'}).success,true);
37
+ });
38
+
39
+ test('R36 registers exactly five compact gateway names',()=>{
40
+ const server=fakeServer();
41
+ registerCompactGatewayTools(server,{
42
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
43
+ codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null
44
+ });
45
+ assert.deepEqual([...server.tools.keys()].sort(),[
46
+ 'capability_manifest','coding_inspect','machine_fs','machine_observe','process_session'
47
+ ]);
48
+ });
49
+
50
+ test('R36 coding_inspect gateway calls runtime directly and never MCP-recurses',async()=>{
51
+ const calls=[];
52
+ const codingRuntime={
53
+ stat:async args=>{calls.push(['stat',args]);return {status:'ok',root_id:args.rootId,path:args.relativePath,type:'file'};}
54
+ };
55
+ const server=fakeServer();
56
+ registerCompactGatewayTools(server,{
57
+ profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'0'.repeat(64)},
58
+ codingRuntime,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null
59
+ });
60
+ const tool=server.tools.get('coding_inspect');
61
+ const result=await tool.handler({op:'stat',root_id:'core',path:'src/a.mjs',hash:false});
62
+ assert.deepEqual(calls,[['stat',{rootId:'core',relativePath:'src/a.mjs',hash:false}]]);
63
+ assert.equal(result.structuredContent.status,'ok');
64
+ assert.equal(result.structuredContent.op,'stat');
65
+ assert.equal(result.structuredContent.result.path,'src/a.mjs');
66
+ });
@@ -364,7 +364,7 @@ test('contained execution source has a compile-time worker registry and no user
364
364
  assert.match(server, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
365
365
  assert.doesNotMatch(server, /executable:\s*z\./);
366
366
  assert.doesNotMatch(executor, /--worker/);
367
- assert.doesNotMatch(server, /command_run|shell_execute|powershell_run/);
367
+ assert.doesNotMatch(server, /registerTool\(\s*['"](?:command_run|shell_execute|powershell_run)['"]/);
368
368
  });
369
369
 
370
370
  test('contained launcher imports by stdin/profile copy and never mounts a host workspace', async () => {
@@ -105,7 +105,7 @@ test('proof execution creates deterministic output and signed verifiable receipt
105
105
  assert.equal(result.receipt.request.schema, 'deadbyte.request.v1');
106
106
  assert.match(result.receipt.request.nonce, /^[0-9a-f]{64}$/);
107
107
  assert.match(result.receipt.request.sha256, /^[0-9a-f]{64}$/);
108
- assert.equal(result.receipt.runtime.package_version, '0.9.0');
108
+ assert.equal(result.receipt.runtime.package_version, '0.11.0');
109
109
  assert.match(result.receipt.runtime.mcp_ingress_adapter, /^(?:none|tunnel-stdio-probe-compat-v1)$/);
110
110
  assert.match(result.receipt.runtime.mcp_ingress_adapter_sha256, /^[0-9a-f]{64}$/);
111
111
  assert.match(result.receipt.runtime.mcp_ingress_mode_source_sha256, /^[0-9a-f]{64}$/);
@@ -12,6 +12,15 @@ test('R34 deferred slot operation preserves supervisor bootstrap across rollback
12
12
  assert.match(source,/verifySlotUpdateEnvelope/);
13
13
  assert.match(source,/runSlotUpdateCli\(args,\{stateRoot\}\)/);
14
14
  assert.match(source,/--enable-autonomous-supervisor/);
15
+ assert.match(source,/loadPowerShellDataFile/);
16
+ assert.match(source,/active-release\.json/);
17
+ assert.match(source,/Bridge\.WorkingDirectory/);
18
+ assert.match(source,/src['"],['"]bridge\.mjs/);
19
+ assert.match(source,/--bridge-root/);
20
+ assert.match(source,/--bridge-script-sha256/);
21
+ assert.match(source,/explicitBridgeRoot/);
22
+ assert.match(source,/explicitBridgeSha256/);
23
+ assert.match(source,/bridge override requires exact root \+ SHA pair/i);
15
24
  assert.match(source,/detached:true/);
16
25
  assert.match(source,/rdc_operation_count:0/);
17
26
  assert.doesNotMatch(source,/shell:\s*true/);
@@ -18,6 +18,8 @@ function respond(msg){
18
18
  if(msg.method==='textDocument/documentSymbol') { if(documentSymbolNullCount>0){documentSymbolNullCount-=1;return send({jsonrpc:'2.0',id:msg.id,result:null});} return send({jsonrpc:'2.0',id:msg.id,result:[{name:'alpha',kind:12,range:{start:{line:0,character:0},end:{line:0,character:26}},selectionRange:{start:{line:0,character:16},end:{line:0,character:21}},children:[]}]}); }
19
19
  if(msg.method==='textDocument/definition') { if(!Number.isInteger(msg.params?.position?.character)) return send({jsonrpc:'2.0',id:msg.id,error:{code:-32602,message:'position.character required'}}); if(!ready())return send({jsonrpc:'2.0',id:msg.id,result:null}); if(requiredOpenCount>0&&openedUris.length<requiredOpenCount)return send({jsonrpc:'2.0',id:msg.id,result:null}); const targetUri=requiredOpenCount>0?(openedUris.find(uri=>uri!==msg.params.textDocument.uri)??msg.params.textDocument.uri):msg.params.textDocument.uri; return send({jsonrpc:'2.0',id:msg.id,result:{uri:targetUri,range:{start:{line:0,character:16},end:{line:0,character:21}}}}); }
20
20
  if(msg.method==='textDocument/references') { if(!Number.isInteger(msg.params?.position?.character)) return send({jsonrpc:'2.0',id:msg.id,error:{code:-32602,message:'position.character required'}}); if(referencesEmptyCount>0){referencesEmptyCount-=1;return send({jsonrpc:'2.0',id:msg.id,result:[]});} if(requiredOpenCount>0&&openedUris.length<requiredOpenCount)return send({jsonrpc:'2.0',id:msg.id,result:[]}); const uris=requiredOpenCount>0?openedUris:[msg.params.textDocument.uri]; return send({jsonrpc:'2.0',id:msg.id,result:uris.map(uri=>({uri,range:{start:{line:0,character:16},end:{line:0,character:21}}}))}); }
21
+ if(msg.method==='textDocument/implementation') { if(msg.params?.position?.character===999) return send({jsonrpc:'2.0',id:msg.id,error:{code:-32601,message:'method not found'}}); const uri=openedUris.find(x=>x!==msg.params.textDocument.uri)??msg.params.textDocument.uri; return send({jsonrpc:'2.0',id:msg.id,result:[{uri,range:{start:{line:0,character:16},end:{line:0,character:21}}}]}); }
22
+ if(msg.method==='textDocument/rename') { if(msg.params?.newName==='UNSUPPORTED') return send({jsonrpc:'2.0',id:msg.id,error:{code:-32601,message:'method not found'}}); const changes={}; for(const uri of (openedUris.length?openedUris:[msg.params.textDocument.uri])) changes[uri]=[{range:{start:{line:0,character:16},end:{line:0,character:21}},newText:msg.params.newName}]; return send({jsonrpc:'2.0',id:msg.id,result:{changes}}); }
21
23
  if(msg.method==='textDocument/diagnostic') return send({jsonrpc:'2.0',id:msg.id,result:{kind:'full',items:[{range:{start:{line:0,character:0},end:{line:0,character:5}},severity:1,code:'fixture',message:'fixture diagnostic'}]}});
22
24
  if(msg.method==='never') return;
23
25
  if(msg.method==='oversize') return send({jsonrpc:'2.0',id:msg.id,result:{blob:'x'.repeat(65536)}});
@@ -4,6 +4,7 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { generateTrustKeypair } from '../../src/trust.mjs';
6
6
  import { createCodingRuntime, loadCodingPolicy } from '../../src/coding-plane.mjs';
7
+ import { createMachineFsRuntime } from '../../src/machine-fs.mjs';
7
8
  import { loadAutonomousPolicy } from '../../src/autonomous-policy.mjs';
8
9
  import { createNativeDiffRuntime } from '../../src/native-diff.mjs';
9
10
  import { createAutonomousRuntime } from '../../src/autonomous-runtime.mjs';
@@ -69,12 +70,13 @@ export async function createR34Fixture({authority='armed',withFailingProfile=fal
69
70
  if(authority==='armed') await arm();
70
71
  const baseCodingRuntime = createCodingRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
71
72
  const codingRuntime = codingRuntimeFactory ? codingRuntimeFactory(baseCodingRuntime) : baseCodingRuntime;
72
- const baseNativeDiffRuntime = createNativeDiffRuntime({policy,codingRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
73
+ const machineFsRuntime = createMachineFsRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
74
+ const baseNativeDiffRuntime = createNativeDiffRuntime({policy,codingRuntime,machineFsRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
73
75
  const nativeDiffRuntime = nativeDiffRuntimeFactory ? nativeDiffRuntimeFactory(baseNativeDiffRuntime) : baseNativeDiffRuntime;
74
- const createRuntime = ({codingRuntimeOverride=codingRuntime,nativeDiffRuntimeOverride=nativeDiffRuntime}={}) => createAutonomousRuntime({policy,codingRuntime:codingRuntimeOverride,nativeDiffRuntime:nativeDiffRuntimeOverride,
76
+ const createRuntime = ({codingRuntimeOverride=codingRuntime,nativeDiffRuntimeOverride=nativeDiffRuntime}={}) => createAutonomousRuntime({policy,codingRuntime:codingRuntimeOverride,nativeDiffRuntime:nativeDiffRuntimeOverride,machineFsRuntime,
75
77
  signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
76
78
  return {
77
- root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,nativeDiffRuntime,createRuntime,runtime:createRuntime(),
79
+ root,project,target,keys,codingPolicy,policy,baseCodingRuntime,codingRuntime,machineFsRuntime,nativeDiffRuntime,createRuntime,runtime:createRuntime(),
78
80
  initial_sha256:sha(await readFile(target)),arm,disarm,
79
81
  cleanup:()=>rm(root,{recursive:true,force:true})
80
82
  };
@@ -35,6 +35,9 @@ test('coding_files_read reads bounded mixed encodings in one MCP call and fails
35
35
  assert.ok((await f.client.listTools()).tools.some(t=>t.name==='coding_files_read'));
36
36
  const result=structured(await f.client.callTool({name:'coding_files_read',arguments:{root_id:'project',files:[{path:'a.txt',encoding:'utf8'},{path:'b.txt',encoding:'utf8'},{path:'c.bin',encoding:'base64'}],max_total_bytes:64}}));
37
37
  assert.equal(result.status,'ok'); assert.equal(result.files.length,3); assert.equal(result.total_bytes,15); assert.equal(result.files[0].content,'alpha\n'); assert.equal(result.files[2].content,Buffer.from([0,1,2,255]).toString('base64'));
38
+ assert.match(result.observation_sha256,/^[0-9a-f]{64}$/);
39
+ const repeat=structured(await f.client.callTool({name:'coding_files_read',arguments:{root_id:'project',files:[{path:'a.txt',encoding:'utf8'},{path:'b.txt',encoding:'utf8'},{path:'c.bin',encoding:'base64'}],max_total_bytes:64}}));
40
+ assert.equal(repeat.observation_sha256,result.observation_sha256);
38
41
  const denied=await f.client.callTool({name:'coding_files_read',arguments:{root_id:'project',files:[{path:'a.txt',encoding:'utf8'},{path:'.git/secret',encoding:'utf8'}],max_total_bytes:64}});
39
42
  assert.equal(denied.isError,true); assert.equal(denied.structuredContent.status,'failed'); assert.match(denied.structuredContent.error,/denied by policy/);
40
43
  } finally {await f.client.close().catch(()=>{}); await rm(f.root,{recursive:true,force:true}).catch(()=>{});}
@@ -0,0 +1,105 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createHash, randomBytes } from 'node:crypto';
4
+ import { access, mkdtemp, mkdir, readFile, rm, symlink, 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 { createCodingRuntime, loadCodingPolicy } from '../src/coding-plane.mjs';
9
+ import { createMachineFsRuntime } from '../src/machine-fs.mjs';
10
+ import { loadAutonomousPolicy } from '../src/autonomous-policy.mjs';
11
+ import { createNativeDiffRuntime, NATIVE_DIFF_V2_SCHEMA } from '../src/native-diff.mjs';
12
+
13
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
14
+ const exists=file=>access(file).then(()=>true,()=>false);
15
+ async function fixture(){
16
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r36-topology-'));
17
+ const project=path.join(root,'project'); const trust=path.join(root,'trust');
18
+ await mkdir(project); await mkdir(trust);
19
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
20
+ const codingPath=path.join(root,'coding-policy.json');
21
+ await writeFile(codingPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:path.join(root,'coding-lease.json'),evidence_dir:path.join(root,'coding-evidence'),roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:[]}},commands:{},max_read_bytes:524288,max_write_bytes:2097152}));
22
+ const codingPolicy=await loadCodingPolicy(codingPath);
23
+ await writeFile(codingPolicy.lease_file,JSON.stringify({schema:'deadbyte.coding-lease.v1',policy_sha256:codingPolicy.policy_sha256,instance_nonce:randomBytes(32).toString('hex'),root_ids:['project'],armed_at_utc:new Date().toISOString(),expires_at_utc:new Date(Date.now()+3600000).toISOString()}));
24
+ const autoPath=path.join(root,'autonomous-policy.json');
25
+ await writeFile(autoPath,JSON.stringify({schema:'deadbyte.autonomous-policy.v1',enabled:true,lease_file:path.join(root,'auto-lease.json'),evidence_dir:path.join(root,'auto-evidence'),root_ids:['project'],coding_policy_sha256:codingPolicy.policy_sha256,planner:{backend:'client_sampling',max_tokens:2048},profiles:{},limits:{max_files_per_diff:12,max_diff_bytes:1048576}}));
26
+ const policy=await loadAutonomousPolicy(autoPath,codingPolicy);
27
+ await writeFile(policy.lease_file,JSON.stringify({schema:'deadbyte.autonomous-lease.v1',policy_sha256:policy.policy_sha256,instance_nonce:randomBytes(32).toString('hex'),root_ids:['project'],armed_at_utc:new Date().toISOString(),expires_at_utc:new Date(Date.now()+1800000).toISOString()}));
28
+ const codingRuntime=createCodingRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
29
+ const machineFsRuntime=createMachineFsRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
30
+ const nativeDiff=createNativeDiffRuntime({policy,codingRuntime,machineFsRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
31
+ return {root,project,keys,policy,codingRuntime,machineFsRuntime,nativeDiff};
32
+ }
33
+
34
+ test('R36 Native Diff V2 commits mkdir write move and independently verifies mixed receipts',async()=>{
35
+ const f=await fixture(); try{
36
+ await writeFile(path.join(f.project,'seed.txt'),'seed\n');
37
+ const seed=await f.machineFsRuntime.stat({rootId:'project',relativePath:'seed.txt',hash:true});
38
+ const preview=await f.nativeDiff.preview({rootId:'project',title:'topology commit',operations:[
39
+ {kind:'mkdir',path:'dst'},
40
+ {kind:'write',path:'dst/new.txt',expected_sha256:'absent',encoding:'utf8',content:'new\n'},
41
+ {kind:'move',source_path:'seed.txt',destination_path:'dst/seed.txt',expected_source_identity:seed.identity_sha256}
42
+ ]});
43
+ assert.equal(preview.schema,NATIVE_DIFF_V2_SCHEMA); assert.equal(preview.operation_count,3);
44
+ const applied=await f.nativeDiff.apply({diffId:preview.diff_id});
45
+ assert.equal(applied.status,'committed');
46
+ assert.equal(await readFile(path.join(f.project,'dst','new.txt'),'utf8'),'new\n');
47
+ assert.equal(await readFile(path.join(f.project,'dst','seed.txt'),'utf8'),'seed\n');
48
+ assert.equal(await exists(path.join(f.project,'seed.txt')),false);
49
+ const verified=await f.nativeDiff.verify({diffId:preview.diff_id});
50
+ assert.equal(verified.ok,true); assert.equal(verified.current_state_matches,true);
51
+ assert.equal(verified.operation_receipts_verified,3);
52
+ } finally {await rm(f.root,{recursive:true,force:true});}
53
+ });
54
+
55
+ test('R36 Native Diff V2 rolls back mkdir/write when later topology operation fails',async()=>{
56
+ const f=await fixture(); try{
57
+ await writeFile(path.join(f.project,'seed.txt'),'seed\n');
58
+ const seed=await f.machineFsRuntime.stat({rootId:'project',relativePath:'seed.txt',hash:true});
59
+ const preview=await f.nativeDiff.preview({rootId:'project',title:'topology rollback',operations:[
60
+ {kind:'mkdir',path:'dst'},
61
+ {kind:'write',path:'dst/temp.txt',expected_sha256:'absent',encoding:'utf8',content:'temporary\n'},
62
+ {kind:'move',source_path:'seed.txt',destination_path:'dst/seed.txt',expected_source_identity:seed.identity_sha256}
63
+ ]});
64
+ const sabotaged=Object.freeze({...f.machineFsRuntime,move:async()=>{throw new Error('forced topology failure');}});
65
+ const runtime=createNativeDiffRuntime({policy:f.policy,codingRuntime:f.codingRuntime,machineFsRuntime:sabotaged,signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath});
66
+ await assert.rejects(runtime.apply({diffId:preview.diff_id}),/rolled back/i);
67
+ assert.equal(await exists(path.join(f.project,'dst')),false);
68
+ assert.equal(await readFile(path.join(f.project,'seed.txt'),'utf8'),'seed\n');
69
+ } finally {await rm(f.root,{recursive:true,force:true});}
70
+ });
71
+
72
+ test('R36 Native Diff V2 rejects stale move identity and destination collision before mutation',async()=>{
73
+ const f=await fixture(); try{
74
+ await writeFile(path.join(f.project,'a.txt'),'one'); await writeFile(path.join(f.project,'b.txt'),'occupied');
75
+ const state=await f.machineFsRuntime.stat({rootId:'project',relativePath:'a.txt',hash:true});
76
+ await writeFile(path.join(f.project,'a.txt'),'two');
77
+ await assert.rejects(f.nativeDiff.preview({rootId:'project',operations:[{kind:'move',source_path:'a.txt',destination_path:'c.txt',expected_source_identity:state.identity_sha256}]}),/identity|precondition/i);
78
+ const fresh=await f.machineFsRuntime.stat({rootId:'project',relativePath:'a.txt',hash:true});
79
+ await assert.rejects(f.nativeDiff.preview({rootId:'project',operations:[{kind:'move',source_path:'a.txt',destination_path:'b.txt',expected_source_identity:fresh.identity_sha256}]}),/destination.*absent|collision|precondition/i);
80
+ assert.equal(await readFile(path.join(f.project,'a.txt'),'utf8'),'two');
81
+ } finally {await rm(f.root,{recursive:true,force:true});}
82
+ });
83
+
84
+ test('R36 Native Diff V2 rmdir accepts only exact empty directory identity',async()=>{
85
+ const f=await fixture(); try{
86
+ await mkdir(path.join(f.project,'empty')); await mkdir(path.join(f.project,'nonempty')); await writeFile(path.join(f.project,'nonempty','x.txt'),'x');
87
+ const empty=await f.machineFsRuntime.stat({rootId:'project',relativePath:'empty'});
88
+ const nonempty=await f.machineFsRuntime.stat({rootId:'project',relativePath:'nonempty'});
89
+ await assert.rejects(f.nativeDiff.preview({rootId:'project',operations:[{kind:'rmdir',path:'nonempty',expected_source_identity:nonempty.identity_sha256}]}),/empty/i);
90
+ const preview=await f.nativeDiff.preview({rootId:'project',operations:[{kind:'rmdir',path:'empty',expected_source_identity:empty.identity_sha256}]});
91
+ assert.equal((await f.nativeDiff.apply({diffId:preview.diff_id})).status,'committed');
92
+ assert.equal(await exists(path.join(f.project,'empty')),false);
93
+ assert.equal((await f.nativeDiff.verify({diffId:preview.diff_id})).ok,true);
94
+ } finally {await rm(f.root,{recursive:true,force:true});}
95
+ });
96
+
97
+ test('R36 Native Diff V2 denies topology through reparse points',async t=>{
98
+ const f=await fixture(); try{
99
+ const outside=path.join(f.root,'outside'); await mkdir(outside);
100
+ const link=path.join(f.project,'link');
101
+ try { await symlink(outside,link,process.platform==='win32'?'junction':'dir'); }
102
+ catch(error){ if(['EPERM','EACCES','ENOSYS'].includes(error?.code)){t.skip(`symlink unavailable: ${error.code}`);return;} throw error; }
103
+ await assert.rejects(f.nativeDiff.preview({rootId:'project',operations:[{kind:'rmdir',path:'link',expected_source_identity:'0'.repeat(64)}]}),/reparse|symlink/i);
104
+ } finally {await rm(f.root,{recursive:true,force:true});}
105
+ });
@@ -2,6 +2,7 @@ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import path from 'node:path';
4
4
  import { spawnSync } from 'node:child_process';
5
+ import { readdirSync } from 'node:fs';
5
6
  import { fileURLToPath } from 'node:url';
6
7
 
7
8
  const root = path.resolve(fileURLToPath(new URL('..', import.meta.url)));
@@ -22,6 +23,11 @@ function packFiles() {
22
23
  return entry.files.map(item => item.path.replaceAll('\\', '/')).sort();
23
24
  }
24
25
 
26
+ test('R36 successor source root contains no nested npm tarball artifacts',()=>{
27
+ const tarballs=readdirSync(root,{withFileTypes:true}).filter(item=>item.isFile()&&item.name.endsWith('.tgz')).map(item=>item.name).sort();
28
+ assert.deepEqual(tarballs,[]);
29
+ });
30
+
25
31
  test('R28 package boundary contains only immutable workspace seed files', () => {
26
32
  const files = packFiles();
27
33
  const workspacePayload = files.filter(rel => rel.startsWith('workspaces/'));