deadbyte-mcp 0.15.68 → 0.15.70

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 (87) hide show
  1. package/MANIFEST.SHA256 +85 -38
  2. package/PROVENANCE.json +52 -24
  3. package/README.txt +44 -31
  4. package/RELEASE.json +47 -17
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +44 -2
  7. package/bridge/package.json +1 -1
  8. package/controller/deadbyte-controller.ps1 +65 -1
  9. package/controller/deadbyte-process-policy.json +4 -4
  10. package/docs/R184-package-lock.json +97 -0
  11. package/docs/R186-OWNER-CONSENT-HANDOFF.txt +87 -0
  12. package/package.json +1 -1
  13. package/scripts/install-r186-owner-trust.ps1 +59 -0
  14. package/scripts/r175-focused-runner.mjs +38 -0
  15. package/scripts/r176-log-preview.mjs +18 -0
  16. package/scripts/r177-owner-trust-inventory.ps1 +36 -0
  17. package/scripts/r178-full-regression.mjs +21 -0
  18. package/scripts/r178-npm-ci.mjs +10 -0
  19. package/scripts/r178-npm-preflight.mjs +16 -0
  20. package/scripts/r178-release-gates-readonly.mjs +9 -0
  21. package/scripts/r178-shard-regression.mjs +25 -0
  22. package/scripts/r179-predecessor-registry.mjs +24 -0
  23. package/scripts/r179-protected-inventory.ps1 +21 -0
  24. package/scripts/r186-focused-runner.mjs +22 -0
  25. package/scripts/r186-owner-anchor-inspect.mjs +6 -0
  26. package/scripts/r186-owner-consent-check.mjs +86 -0
  27. package/scripts/r186-owner-grant-cli.mjs +45 -0
  28. package/scripts/r186-owner-key-local.mjs +22 -0
  29. package/scripts/r186-owner-review-export.mjs +34 -0
  30. package/src/deadbyte-cli.mjs +130 -56
  31. package/src/foreground-owner-recovery-r190.mjs +117 -0
  32. package/src/r174-owner-webauthn.mjs +176 -0
  33. package/src/r175-foreground-boundary.mjs +28 -0
  34. package/src/r186-owner-anchor.mjs +12 -0
  35. package/src/r186-owner-consent-preflight.mjs +63 -0
  36. package/src/r186-owner-grant-broker.mjs +96 -0
  37. package/src/r186-owner-local-signing.mjs +25 -0
  38. package/src/r186-owner-request.mjs +40 -0
  39. package/src/r186-owner-review-export.mjs +49 -0
  40. package/src/remote-live-log.mjs +81 -5
  41. package/src/remote-session-guardian.mjs +23 -0
  42. package/src/version.mjs +1 -1
  43. package/test/helpers/r175-active-pointer.mjs +28 -0
  44. package/test/helpers/release-lock.mjs +12 -0
  45. package/test/r129-sdk-security-baseline.test.mjs +2 -1
  46. package/test/r130-release-security-contract.test.mjs +11 -10
  47. package/test/r132-release-identity.test.mjs +12 -11
  48. package/test/r153-prepublish-identity.test.mjs +1 -1
  49. package/test/r155-default-live-log.test.mjs +17 -16
  50. package/test/r160-package-decision-manifest.test.mjs +1 -1
  51. package/test/r160-release-contract.test.mjs +4 -5
  52. package/test/r163-isolated-slot-rollback-restore.test.mjs +8 -8
  53. package/test/r165-live-cli-skew.test.mjs +6 -6
  54. package/test/r167-release-contract.test.mjs +15 -13
  55. package/test/r168-protected-entrypoint.test.mjs +5 -2
  56. package/test/r168-release-contract.test.mjs +4 -3
  57. package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
  58. package/test/r172-release-contract.test.mjs +7 -7
  59. package/test/r173-release-contract.test.mjs +18 -17
  60. package/test/r174-owner-webauthn.test.mjs +144 -0
  61. package/test/r175-cli-flags.test.mjs +23 -0
  62. package/test/r175-foreground-boundary.test.mjs +87 -0
  63. package/test/r175-guardian-owner-boundary.test.mjs +56 -0
  64. package/test/r176-pretty-live-log.test.mjs +54 -0
  65. package/test/r177-windows-absolute-live-path.test.mjs +37 -0
  66. package/test/r179-absolute-path-live-log.test.mjs +40 -0
  67. package/test/r179-release-contract.test.mjs +62 -0
  68. package/test/r186-controller-owner-grant.test.mjs +20 -0
  69. package/test/r186-owner-consent-preflight.test.mjs +98 -0
  70. package/test/r186-owner-enrollment.test.mjs +20 -0
  71. package/test/r186-owner-grant-broker.test.mjs +85 -0
  72. package/test/r186-owner-local-signing.test.mjs +30 -0
  73. package/test/r186-owner-request.test.mjs +28 -0
  74. package/test/r186-owner-review-export.test.mjs +52 -0
  75. package/test/r190-foreground-cli.test.mjs +41 -0
  76. package/test/r190-foreground-owner-recovery.test.mjs +61 -0
  77. package/test/r190-release-contract.test.mjs +33 -0
  78. package/test/r48-release-contract.test.mjs +2 -1
  79. package/test/r51-runtime-coherence.test.mjs +4 -2
  80. package/test/r58-cleanup-retention.test.mjs +5 -12
  81. package/test/r58-concurrent-recovery.test.mjs +5 -1
  82. package/test/r58-stale-owner.test.mjs +9 -3
  83. package/test/r59-tool-surface-compat.test.mjs +1 -1
  84. package/test/remote-live-log-r37.test.mjs +2 -2
  85. package/test/remote-live-log-recovery-r36.test.mjs +1 -1
  86. package/test/remote-session-cli.test.mjs +92 -126
  87. package/node_modules/which/CHANGELOG.md +0 -166
@@ -0,0 +1,87 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import {observeOnlyAuthority,assertObserveOnlyMarker,assertMatchingForegroundRelease} from '../src/r175-foreground-boundary.mjs';
5
+
6
+ const planes=['process','coding','autonomous','desktop','host'];
7
+ function observation(states){
8
+ return {state:'ONLINE',authority:Object.fromEntries(planes.map((plane,index)=>
9
+ [plane,{configured:true,armed:Boolean(states[index]),reason:states[index]?'standing-grant':'disarmed',
10
+ epoch_seq:index+1,epoch_sha256:String(index+1).repeat(64)}]))};
11
+ }
12
+ test('R175 bare remote preserves all owner standing grants as observed, never implies revocation',()=>{
13
+ const status=observation([false,true,false,true,true]);
14
+ const scope=observeOnlyAuthority(status);
15
+ assert.deepEqual(scope.expected,{process:false,coding:true,autonomous:false,desktop:true,host:true});
16
+ assert.deepEqual(scope.commands,[]);
17
+ assert.equal(scope.authority_scope,'observe-only');
18
+ });
19
+ test('R175 bare remote leaves all-disarmed state untouched',()=>{
20
+ const scope=observeOnlyAuthority(observation([false,false,false,false,false]));
21
+ assert.deepEqual(scope.commands,[]);
22
+ assert.ok(Object.values(scope.expected).every(value=>value===false));
23
+ });
24
+ test('R175 observation-only marker can be identified and has no owned grants',()=>{
25
+ const marker={schema:'deadbyte.remote-session.v2',owner_pid:1001,session_nonce:'a'.repeat(64),authority_scope:'observe-only'};
26
+ assert.equal(assertObserveOnlyMarker(marker),true);
27
+ assert.equal(assertObserveOnlyMarker({...marker,authority_scope:'legacy'}),false);
28
+ assert.equal(assertObserveOnlyMarker({...marker,session_nonce:'x'}),false);
29
+ });
30
+ test('R175 foreground never issues or revokes grants for observation-only mode',async()=>{
31
+ const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
32
+ assert.match(cli,/observeOnlyAuthority\(authority\)/);
33
+ assert.match(cli,/authority_scope:\s*['"]observe-only['"]/);
34
+ assert.match(cli,/if\s*\(mode!==['"]default['"]\)\s*throw/);
35
+ assert.match(cli,/if\(marker!==null\)/);
36
+ assert.match(cli,/R175_INVALID_OBSERVE_ONLY_MARKER/);
37
+ });
38
+ test('R175 crash guardian must not revoke or park observation-only markers',async()=>{
39
+ const guardian=await readFile(new URL('../src/remote-session-guardian.mjs',import.meta.url),'utf8');
40
+ assert.match(guardian,/if\(observerGuard\)/);
41
+ assert.match(guardian,/if\(!assertObserveOnlyMarker\(marker\)\)/);
42
+ assert.match(guardian,/deletePinnedRemoteMarker/);
43
+ });
44
+
45
+ test('R175 observation-only CLI refuses version drift or stale manifest before authority mutation',()=>{
46
+ const active={version:'0.15.69',manifest_sha256:'a'.repeat(64)};
47
+ assert.equal(assertMatchingForegroundRelease(active,{version:'0.15.69',manifest_sha256:'a'.repeat(64)}),active);
48
+ assert.throws(()=>assertMatchingForegroundRelease(active,{version:'0.15.68',manifest_sha256:'a'.repeat(64)}),/EXACT_ACTIVE_RELEASE/);
49
+ assert.throws(()=>assertMatchingForegroundRelease(active,{version:'0.15.69',manifest_sha256:'b'.repeat(64)}),/EXACT_ACTIVE_RELEASE/);
50
+ assert.throws(()=>assertMatchingForegroundRelease(null,{version:'0.15.69',manifest_sha256:'a'.repeat(64)}),/EXACT_ACTIVE_RELEASE/);
51
+ });
52
+
53
+ test('R175 foreground session has no implicit runtime synchronization, startup or restart',async()=>{
54
+ const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
55
+ const foreground=cli.slice(cli.indexOf('async function runRemoteSession('),cli.indexOf('async function parseRemoteFlags(',cli.indexOf('async function runRemoteSession(')));
56
+ assert.ok(foreground.length>3500,'R175 foreground extraction incomplete');
57
+ assert.doesNotMatch(foreground,/await synchronizeForegroundRuntime\(/);
58
+ assert.doesNotMatch(foreground,/invokeController\(ctx,\s*['"](?:arm|disarm|park|start|restart)/);
59
+ assert.match(foreground,/R175_FOREGROUND_RUNTIME_NOT_READY/);
60
+ assert.match(foreground,/R175_FOREGROUND_ACTIVE_DRIFT/);
61
+ assert.match(foreground,/R175_FOREGROUND_OBSERVE_ONLY/);
62
+ });
63
+ test('R175 standing grants may change during monitoring without implicit revoke',async()=>{
64
+ const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
65
+ const start=cli.indexOf('async function runRemoteSession(');
66
+ const authority=cli.slice(start,cli.indexOf('ready=true;',start));
67
+ assert.doesNotMatch(authority,/assertAuthorityVector\(/);
68
+ assert.match(authority,/observeOnlyAuthority\(authority\)/);
69
+ assert.match(authority,/Observing existing grants without ARM or DISARM/);
70
+ });
71
+
72
+ test('R179 observer never initiates legacy marker reconciliation, epoch mutations or no-marker migration',async()=>{
73
+ const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
74
+ const start=cli.indexOf('async function runRemoteSession(');
75
+ const claim=cli.indexOf('marker=await claimRemoteSession(ctx)',start);
76
+ const section=cli.slice(start,claim);
77
+ assert.match(section,/if\(staleObserver\)/);
78
+ assert.match(section,/if\(!assertObserveOnlyMarker\(staleObserver\)\)/);
79
+ assert.match(section,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/);
80
+ assert.doesNotMatch(section,/\n\s*await recoverRemoteSession\(ctx,\{displayMode\}\);\s*\n\s*startupLock/);
81
+ });
82
+ test('R179 guardian cannot downgrade observation-only marker into legacy cleanup',async()=>{
83
+ const guardian=await readFile(new URL('../src/remote-session-guardian.mjs',import.meta.url),'utf8');
84
+ const fence=guardian.indexOf('R179_OBSERVER_GUARDIAN_SCOPE_DOWNGRADE');
85
+ const revoke=guardian.indexOf('for(const command of CLEANUP)');
86
+ assert.ok(fence>0&&revoke>fence);
87
+ });
@@ -0,0 +1,56 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,writeFile,readFile,rm} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {fileURLToPath} from 'node:url';
8
+
9
+ const winTest=process.platform==='win32'?test:test.skip;
10
+ const guardian=new URL('../src/remote-session-guardian.mjs',import.meta.url);
11
+ const missing=async path=>assert.rejects(()=>readFile(path),{code:'ENOENT'});
12
+ async function fixture(run,{valid=true,observerArg=true}={}){
13
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r175-guardian-'));
14
+ const bin=path.join(dir,'bin'),runtime=path.join(dir,'runtime');
15
+ await mkdir(bin);await mkdir(runtime);
16
+ const trace=path.join(dir,'controller.trace'),grant=path.join(runtime,'host-grant.json');
17
+ const markerPath=path.join(runtime,'remote-session.json');
18
+ const quote=s=>String(s).replaceAll("'","''");
19
+ const launcher=`param([string]$Command)
20
+ [IO.File]::AppendAllText('${quote(trace)}',$Command+[Environment]::NewLine)
21
+ exit 0
22
+ `;
23
+ await writeFile(path.join(bin,'DEADBYTE-MCP.ps1'),launcher);
24
+ await writeFile(grant,'owner-standing-grant\n');
25
+ const dead=spawnSync(process.execPath,['-e','process.exit(0)'],{windowsHide:true});
26
+ assert.equal(dead.status,0);assert.ok(dead.pid>0);
27
+ const marker={schema:'deadbyte.remote-session.v2',owner_pid:dead.pid,guardian_pid:0,
28
+ session_nonce:'a'.repeat(64),...(valid?{authority_scope:'observe-only'}:{})};
29
+ await writeFile(markerPath,JSON.stringify(marker)+'\n');
30
+ try{
31
+ const result=spawnSync(process.execPath,[fileURLToPath(guardian),'--state-root',dir,
32
+ '--owner-pid',String(dead.pid),'--session-nonce',marker.session_nonce,...(observerArg?['--authority-scope','observe-only']:[])],
33
+ {encoding:'utf8',windowsHide:true,timeout:12000,env:{...process.env,DEADBYTE_STATE_ROOT:dir}});
34
+ return await run({dir,trace,grant,markerPath,result,marker});
35
+ }finally{await rm(dir,{recursive:true,force:true});}
36
+ }
37
+ winTest('R175 guardian after owner death removes only observe-only marker and never touches owner grant',()=>fixture(async f=>{
38
+ assert.equal(f.result.error,undefined,String(f.result.error));
39
+ assert.equal(f.result.status,0,f.result.stderr);
40
+ await missing(f.markerPath);
41
+ await missing(f.trace);
42
+ assert.equal(await readFile(f.grant,'utf8'),'owner-standing-grant\n');
43
+ }));
44
+ winTest('R175 guardian rejects tampered observer scope without revoking owner grant or removing evidence',()=>fixture(async f=>{
45
+ assert.notEqual(f.result.status,0,'inherited monitor scope must reject modified marker');
46
+ await missing(f.trace);
47
+ assert.deepEqual(JSON.parse(await readFile(f.markerPath,'utf8')),f.marker);
48
+ assert.equal(await readFile(f.grant,'utf8'),'owner-standing-grant\n');
49
+ },{valid:false}));
50
+
51
+ winTest('R179 guardian refuses missing observe-only scope flag without revoking owner grants',()=>fixture(async f=>{
52
+ assert.notEqual(f.result.status,0,'missing guardian scope must fail closed');
53
+ await missing(f.trace);
54
+ assert.deepEqual(JSON.parse(await readFile(f.markerPath,'utf8')),f.marker);
55
+ assert.equal(await readFile(f.grant,'utf8'),'owner-standing-grant\n');
56
+ },{valid:true,observerArg:false}));
@@ -0,0 +1,54 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createLiveTimelineTracker,defaultForegroundTimelineOptions} from '../src/remote-live-log.mjs';
4
+ const now=()=>Date.parse('2026-10-27T12:25:56.000Z');
5
+ const fixture=()=>createLiveTimelineTracker({clock:now,...defaultForegroundTimelineOptions({verbose:false})});
6
+ test('R176 normal console uses compact aligned emoji rather than legacy CALL/DONE spam',()=>{
7
+ const tracker=fixture();
8
+ const start=tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'r176-abc123xyz',args:{root_id:'projects',path:'src/alpha.mjs',token:'SECRET_DO_NOT_PRINT',content:'SECRET_PAYLOAD'}},{sequence:1});
9
+ assert.match(start,/19:25:56/);assert.match(start,/📝/);assert.match(start,/START/);
10
+ assert.match(start,/coding_file_write/);
11
+ assert.doesNotMatch(start,/SECRET_DO_NOT_PRINT|SECRET_PAYLOAD|Received tool call/);
12
+ const end=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'r176-abc123xyz',outcome:{status:'ok',duration_ms:17,receipt_sha256s:['e'.repeat(64)]}},{sequence:2});
13
+ assert.match(end,/✅/);assert.match(end,/PASS/);assert.match(end,/17ms/);
14
+ assert.match(end,/receipt=e{8}/);assert.doesNotMatch(end,/Tool call .*completed/);
15
+ });
16
+ test('R176 heartbeat chatter is folded into a single compact count',()=>{
17
+ const tracker=fixture();
18
+ for(let i=0;i<5;i++){
19
+ tracker.observe({event_type:'tool.request',tool:'machine_status',request_id:'poll-'+i,args:{}},{sequence:i*2+1});
20
+ tracker.observe({event_type:'tool.result',tool:'machine_status',request_id:'poll-'+i,outcome:{status:'ok',duration_ms:2}},{sequence:i*2+2});
21
+ }
22
+ const tail=tracker.reconcileInterrupted();
23
+ assert.match(tail.join('\n'),/machine_status.*×5/);
24
+ assert.match(tracker.summary(),/5 calls/);
25
+ assert.match(tracker.summary(),/folded 4 heartbeat/);
26
+ });
27
+ test('R176 denied or unknown effects are visually distinct and keep reasons',()=>{
28
+ const tracker=fixture();
29
+ tracker.observe({event_type:'tool.request',tool:'host_file_read',request_id:'deny-one',args:{root_id:'core',path:'a.mjs'}},{sequence:1});
30
+ const denied=tracker.observe({event_type:'tool.result',tool:'host_file_read',request_id:'deny-one',outcome:{status:'denied',is_error:true,reason:'arm file missing',duration_ms:1}},{sequence:2});
31
+ assert.match(denied,/❌|✗/);assert.match(denied,/DENIED|FAIL/);assert.match(denied,/arm file missing/);
32
+ tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'unknown-one',args:{root_id:'core',path:'x.mjs'}},{sequence:3});
33
+ const unknown=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'unknown-one',outcome:{status:'interrupted',side_effect_state:'unknown',reason:'after dispatch'}},{sequence:4});
34
+ assert.match(unknown,/⚠/);assert.match(unknown,/UNKNOWN/);assert.match(unknown,/after dispatch/);
35
+ });
36
+ test('R176 ASCII fallback never emits Unicode graphics in default layout',()=>{
37
+ const tracker=createLiveTimelineTracker({clock:now,displayMode:'ascii',...defaultForegroundTimelineOptions({verbose:false})});
38
+ const start=tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'ascii-one',args:{root_id:'core',path:'x.mjs'}},{sequence:1});
39
+ const done=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'ascii-one',outcome:{status:'ok',duration_ms:6}},{sequence:2});
40
+ assert.doesNotMatch(start+done,/[^\x20-\x7E\n]/);
41
+ assert.match(done,/PASS/);
42
+ });
43
+
44
+ test('R176 session summary is a compact readable multi-line report',()=>{
45
+ const tracker=fixture();
46
+ tracker.observe({event_type:'tool.request',request_id:'one',tool:'coding_file_write',args:{root_id:'core',path:'a.mjs'}},{sequence:1});
47
+ tracker.observe({event_type:'tool.result',request_id:'one',tool:'coding_file_write',outcome:{status:'ok',duration_ms:14}},{sequence:2});
48
+ const report=tracker.summary();
49
+ assert.match(report,/📊.*SESSION/i);
50
+ assert.match(report,/1 calls \| 1 ok/);
51
+ assert.match(report,/\n.*p50/i);
52
+ assert.match(report,/\n.*top:/i);
53
+ assert.doesNotMatch(report,/secret|undefined|NaN/);
54
+ });
@@ -0,0 +1,37 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createLiveTimelineTracker,defaultForegroundTimelineOptions} from '../src/remote-live-log.mjs';
4
+ const fixed=()=>Date.parse('2026-10-27T12:25:56.000Z');
5
+ const root='D:\\Projects';
6
+ const relative='deadbyte-mcp\\development\\deadbyte-mcp-0.15.69-r174-owner-consent\\src\\alpha.mjs';
7
+ const exact=root+'\\'+relative;
8
+ function tracker(){return createLiveTimelineTracker({clock:fixed,rootPaths:{projects:root},...defaultForegroundTimelineOptions({verbose:false})});}
9
+ test('R177 prints exact full Windows path under trusted root on START and PASS',()=>{
10
+ const t=tracker(),id='write-full-path-123';
11
+ const start=t.observe({event_type:'tool.request',request_id:id,tool:'coding_file_write',args:{root_id:'projects',path:relative}},{sequence:1});
12
+ assert.ok(start.includes(exact),start);
13
+ assert.match(start,/\[root=projects\]/);
14
+ const done=t.observe({event_type:'tool.result',request_id:id,tool:'coding_file_write',outcome:{status:'ok',duration_ms:7}},{sequence:2});
15
+ assert.ok(done.includes(exact),done);
16
+ assert.match(done,/PASS/);
17
+ });
18
+ test('R177 no policy mapping means visibly unresolved, never guessed C or D drive',()=>{
19
+ const t=createLiveTimelineTracker({clock:fixed,rootPaths:{},...defaultForegroundTimelineOptions({verbose:false})});
20
+ const line=t.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'unmapped-1',args:{root_id:'projects',path:'src/alpha.mjs'}});
21
+ assert.match(line,/unresolved root/);
22
+ assert.doesNotMatch(line,/[A-Z]:\\/);
23
+ });
24
+ test('R177 full command script path and move source/destination stay explicit',()=>{
25
+ const t=tracker();
26
+ const command=t.observe({event_type:'tool.request',tool:'coding_command_run',request_id:'command-1',args:{root_id:'projects',command_id:'node_script',script_path:relative}});
27
+ assert.ok(command.includes(exact),command);
28
+ const move=t.observe({event_type:'tool.request',tool:'machine_fs',request_id:'move-1',args:{op:'move',root_id:'projects',source_path:'deadbyte-mcp/src/a.js',destination_path:'deadbyte-mcp/src/b.js'}});
29
+ assert.match(move,/FROM.*D:\\Projects\\deadbyte-mcp\\src\\a.js/);
30
+ assert.match(move,/TO.*D:\\Projects\\deadbyte-mcp\\src\\b.js/);
31
+ });
32
+ test('R177 path traversal is flagged rather than displayed as trusted in-root file',()=>{
33
+ const t=tracker();
34
+ const line=t.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'escape-1',args:{root_id:'projects',path:'..\\secrets\\hidden.txt'}});
35
+ assert.match(line,/outside root=projects/);
36
+ assert.doesNotMatch(line,/📁 .*\[root=projects\]/);
37
+ });
@@ -0,0 +1,40 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createLiveTimelineTracker,defaultForegroundTimelineOptions,summarizeLiveToolArgs} from '../src/remote-live-log.mjs';
4
+ const base=String.raw`D:\Projects`;
5
+ const sub=String.raw`deadbyte-mcp\development\deadbyte-mcp-0.15.69-r174-owner-consent\src\alpha.mjs`;
6
+ const full=String.raw`D:\Projects\deadbyte-mcp\development\deadbyte-mcp-0.15.69-r174-owner-consent\src\alpha.mjs`;
7
+ const env={rootPaths:{projects:base,core:String.raw`C:\Users\DEADBYTE\.deadbyte-mcp\releases\v0.15.68-ff29dbb2fbb89862`}};
8
+ const tracker=()=>createLiveTimelineTracker({clock:()=>Date.parse('2026-10-27T12:25:56.000Z'),...defaultForegroundTimelineOptions({verbose:false}),...env});
9
+ test('R179 absolute Windows file path visible, with root identity, on START and PASS',()=>{
10
+ const t=tracker();
11
+ const start=t.observe({event_type:'tool.request',request_id:'win-full-path',tool:'coding_file_write',args:{root_id:'projects',path:sub,content:'safe'}} ,{sequence:1});
12
+ assert.match(start,/📁|📂/);assert.ok(start.includes(full),start);
13
+ assert.match(start,/root=projects/);
14
+ const done=t.observe({event_type:'tool.result',request_id:'win-full-path',tool:'coding_file_write',outcome:{status:'ok',duration_ms:13}},{sequence:2});
15
+ assert.ok(done.includes(full),done);assert.match(done,/root=projects/);
16
+ assert.match(done,/PASS/);
17
+ });
18
+ test('R179 Windows PowerShell command script_path uses canonical full path',()=>{
19
+ const t=tracker();
20
+ const start=t.observe({event_type:'tool.request',request_id:'script-path',tool:'coding_command_run',args:{root_id:'projects',command_id:'powershell_script',script_path:sub}},{sequence:1});
21
+ assert.ok(start.includes(full),start);
22
+ assert.match(start,/powershell_script/);
23
+ });
24
+ test('R179 unknown root fails to invent a drive-letter path',()=>{
25
+ const str=summarizeLiveToolArgs('coding_file_read',{root_id:'mystery',path:'src/alpha.mjs'},{rootPaths:env.rootPaths});
26
+ assert.doesNotMatch(str,/[A-Z]:\\/);
27
+ assert.match(str,/mystery/);
28
+ });
29
+ test('R179 path traversal is not represented as an in-root file',()=>{
30
+ const str=summarizeLiveToolArgs('coding_file_read',{root_id:'projects',path:'..\\secret.txt'},{rootPaths:env.rootPaths});
31
+ assert.doesNotMatch(str,/D:\\secret.txt/);
32
+ assert.match(str,/outside|unresolved|denied/i);
33
+ });
34
+ test('R179 log path presentation keeps tool argument secrets redacted',()=>{
35
+ const t=tracker();
36
+ const start=t.observe({event_type:'tool.request',request_id:'secret-path',tool:'coding_file_write',args:{
37
+ root_id:'projects',path:sub,content:'secret-data',private_key:'DO_NOT_SHOW_KEY'
38
+ }},{sequence:1});
39
+ assert.doesNotMatch(start,/secret-data|DO_NOT_SHOW_KEY/);
40
+ });
@@ -0,0 +1,62 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {fileURLToPath} from 'node:url';
6
+ import {DEADBYTE_VERSION} from '../src/version.mjs';
7
+ import {readReleaseLock} from './helpers/release-lock.mjs';
8
+ const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
9
+ const read=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
10
+ const parent='v0.15.68-ff29dbb2fbb89862';
11
+ const manifest='ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885';
12
+ const generation='2887d4a8d54ea6fc6b28869072f72491c892dfe28178b3537936dce958147bd2';
13
+ const tarball='1a7f1fe05a06178b8d772a140be6e0470790b743cef690c3f8f8165cc61075bf';
14
+ test('R190 version, lock snapshot, bridge, matrix, SBOM and provenance identify exact 0.15.70 successor',async()=>{
15
+ const [pkg,lock,bridge,release,provenance,matrix,sbom]=await Promise.all([
16
+ read('package.json'),readReleaseLock(root),read('bridge/package.json'),read('RELEASE.json'),
17
+ read('PROVENANCE.json'),read('TEST-MATRIX.json'),read('SBOM.cdx.json')]);
18
+ for(const version of [pkg.version,lock.version,lock.packages[''].version,bridge.version,release.version,provenance.version,
19
+ matrix.version,sbom.metadata.component.version,DEADBYTE_VERSION])assert.equal(version,'0.15.70');
20
+ assert.equal(release.train,'R190');assert.equal(matrix.train,'R190');
21
+ assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r190:0.15.70');
22
+ assert.equal(release.parent.release_id,parent);assert.equal(release.parent.manifest_sha256,manifest);
23
+ assert.equal(release.parent.generation_manifest_sha256,generation);
24
+ assert.equal(release.source_predecessor.version,'0.15.69');
25
+ assert.equal(release.source_predecessor.manifest_sha256,'cfede112761da00e73b626cfbce23d17e1d4b6e3a325bb675415196d31142ba0');
26
+ assert.equal(release.source_predecessor.tarball_sha256,'578c1630608f9df816524c78abc9e1a55357fdc3cd25e142122f1a823e1d69e3');
27
+ assert.equal(release.runtime_predecessor.release_id,parent);
28
+ assert.equal(provenance.runtime_predecessor.release_id,parent);
29
+ assert.equal(provenance.parent_tarball_sha256,release.source_predecessor.tarball_sha256);
30
+ assert.equal(provenance.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
31
+ });
32
+ test('R179 does not misrepresent owner Autonomous enrollment, protected regression or publication as finished',async()=>{
33
+ const [release,prov,matrix]=await Promise.all(['RELEASE.json','PROVENANCE.json','TEST-MATRIX.json'].map(read));
34
+ assert.equal(release.r179.owner_approved_autonomous_grant,'NOT_ISSUED');
35
+ assert.equal(release.r179.protected_full_regression,'NOT_VERIFIED');
36
+ assert.equal(release.r179.npm_publish,'NOT_RUN');
37
+ assert.equal(release.r179.production_activation,'NOT_RUN');
38
+ assert.equal(release.r179.stable_final_claim,false);
39
+ assert.equal(prov.r179.independent_owner_enrollment,'NOT_PRESENT');
40
+ assert.equal(prov.r179.protected_full_regression,'NOT_RUN');
41
+ assert.equal(matrix.r179.owner_consent,'NOT_VERIFIED');
42
+ assert.equal(matrix.r179.registry_publish,'NOT_RUN');
43
+ assert.ok(matrix.required.some(r=>r.id==='r179-owner-authorized-autonomous'));
44
+ assert.ok(matrix.required.some(r=>r.id==='r179-protected-full'));
45
+ });
46
+ test('R190 retains historical R179 and R173 source evidence without promoting it to current parent',async()=>{
47
+ const [release,prov]=await Promise.all(['RELEASE.json','PROVENANCE.json'].map(read));
48
+ assert.equal(release.r173.source_predecessor_version,'0.15.67');
49
+ assert.equal(release.r173.source_predecessor_manifest_sha256,'4fd7bfc76b1de6401c142b607cc84e847434b28c888bc99ceb09c9e8e5adc8f8');
50
+ assert.equal(prov.r173.source_predecessor_version,'0.15.67');
51
+ assert.equal(release.source_predecessor.version,'0.15.69');
52
+ assert.notEqual(release.source_predecessor.tarball_sha256,release.r173.source_predecessor_tarball_sha256);
53
+ });
54
+ test('R184 ships a semantic lock snapshot matching installed package identity and dependencies',async()=>{
55
+ const [pkg,lock,snapshot]=await Promise.all([
56
+ read('package.json'),readReleaseLock(root),read('docs/R184-package-lock.json')]);
57
+ assert.deepEqual(snapshot,lock);
58
+ assert.equal(snapshot.name,pkg.name);
59
+ assert.equal(snapshot.version,pkg.version);
60
+ assert.equal(snapshot.packages[''].version,pkg.version);
61
+ assert.deepEqual(snapshot.packages[''].dependencies,pkg.dependencies);
62
+ });
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+
5
+ test('R186 successor Autonomous arm delegates non-legacy issuance to protected owner broker',async()=>{
6
+ const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
7
+ assert.match(source,/function Invoke-R186ProtectedAutonomousGrant/);
8
+ const arm=/function Arm-Autonomous\(\$Cfg\) \{([\s\S]*?)\n\}/.exec(source)?.[1]??'';
9
+ assert.match(arm,/Invoke-R186ProtectedAutonomousGrant/);
10
+ assert.match(arm,/R70_OWNER_ENROLLMENT_REQUIRED/);
11
+ assert.match(arm,/legacyOwnerPolicySha256/,'legacy compatibility path must remain explicit');
12
+ });
13
+
14
+ test('R186 protected broker and consent dependencies are exact-byte pinned by controller',async()=>{
15
+ const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
16
+ for(const file of [
17
+ 'scripts\\r186-owner-grant-cli.mjs','src\\r186-owner-grant-broker.mjs',
18
+ 'src\\r186-owner-consent-preflight.mjs','src\\r70-owner-approval.mjs'
19
+ ])assert.match(source,new RegExp(file.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')));
20
+ });
@@ -0,0 +1,98 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync,sign} from 'node:crypto';
4
+ import {mkdtemp,rm,realpath} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {canonicalJson} from '../src/canonical-json.mjs';
8
+ import {verifyR186OwnerConsent,verifyR186PolicyRootBinding} from '../src/r186-owner-consent-preflight.mjs';
9
+
10
+ const sha=b=>createHash('sha256').update(b).digest('hex');
11
+ const domain=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
12
+ const H=c=>c.repeat(64);
13
+ async function fixture(){
14
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r186-consent-'));
15
+ const real=await realpath(dir);
16
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
17
+ const owner_key_id=sha(publicKey.export({type:'spki',format:'der'}));
18
+ const now=new Date();
19
+ const issued=new Date(now.getTime()-1000).toISOString();
20
+ const expires=new Date(now.getTime()+120000).toISOString();
21
+ const expected={rootId:'core',projectRoot:real,codingPolicySha256:H('a'),
22
+ autonomousPolicySha256:H('b'),reviewedArtifactSha256:H('c')};
23
+ const subject={schema:'deadbyte.project-authority-subject.r70.v1',scope:'coding_autonomous_native_diff',
24
+ root_id:expected.rootId,project_root:real,coding_policy_sha256:expected.codingPolicySha256,
25
+ autonomous_policy_sha256:expected.autonomousPolicySha256,reviewed_artifact_sha256:expected.reviewedArtifactSha256,
26
+ issued_at_utc:issued,expires_at_utc:expires};
27
+ const signed={schema:'deadbyte.project-owner-approval.r70.v1',subject_sha256:sha(Buffer.from(canonicalJson(subject))),owner_key_id};
28
+ const approval={...signed,subject,signature_base64:sign(null,Buffer.concat([domain,Buffer.from(canonicalJson(signed))]),privateKey).toString('base64')};
29
+ const trust={schema:'deadbyte.r186-owner-anchor.v1',owner_key_id};
30
+ return {dir,publicKeyPem:publicKey.export({type:'spki',format:'pem'}),expected,approval,trust,now};
31
+ }
32
+ test('R186 independently pinned Ed25519 owner anchor validates the reviewed artifact and produces no grant',async()=>{
33
+ const f=await fixture();try{
34
+ const d=await verifyR186OwnerConsent({ownerTrust:f.trust,ownerPublicKeyPem:f.publicKeyPem,
35
+ approval:f.approval,expected:f.expected,now:f.now});
36
+ assert.equal(d.status,'verified_readonly');
37
+ assert.equal(d.authority_granted,false);
38
+ assert.equal(d.root_id,'core');
39
+ assert.equal(d.owner_key_id,f.trust.owner_key_id);
40
+ }finally{await rm(f.dir,{recursive:true,force:true});}
41
+ });
42
+ test('R186 cannot self-enroll an alternate key or swap in a signer-provided key anchor',async()=>{
43
+ const f=await fixture();try{
44
+ const rogue=generateKeyPairSync('ed25519');
45
+ await assert.rejects(verifyR186OwnerConsent({ownerTrust:f.trust,
46
+ ownerPublicKeyPem:rogue.publicKey.export({type:'spki',format:'pem'}),
47
+ approval:f.approval,expected:f.expected,now:f.now}),/owner anchor|owner key/i);
48
+ await assert.rejects(verifyR186OwnerConsent({ownerTrust:{...f.trust,override:true},
49
+ ownerPublicKeyPem:f.publicKeyPem,approval:f.approval,expected:f.expected,now:f.now}),/owner anchor/i);
50
+ }finally{await rm(f.dir,{recursive:true,force:true});}
51
+ });
52
+ test('R186 fail closed on artifact, policy, root or expiry drift without granting authority',async()=>{
53
+ const f=await fixture();try{
54
+ for(const changed of [
55
+ {reviewedArtifactSha256:H('d')},{codingPolicySha256:H('e')},
56
+ {autonomousPolicySha256:H('f')},{rootId:'projects'}
57
+ ]){
58
+ await assert.rejects(verifyR186OwnerConsent({ownerTrust:f.trust,
59
+ ownerPublicKeyPem:f.publicKeyPem,approval:f.approval,
60
+ expected:{...f.expected,...changed},now:f.now}),/owner approval/i);
61
+ }
62
+ await assert.rejects(verifyR186OwnerConsent({ownerTrust:f.trust,
63
+ ownerPublicKeyPem:f.publicKeyPem,approval:f.approval,
64
+ expected:f.expected,now:new Date(f.now.getTime()+180000)}),/expired/i);
65
+ }finally{await rm(f.dir,{recursive:true,force:true});}
66
+ });
67
+
68
+ test('R186 CLI has no usable enrollment or grant path from writable source',async()=>{
69
+ const {spawnSync}=await import('node:child_process');
70
+ const {fileURLToPath}=await import('node:url');
71
+ const result=spawnSync(process.execPath,[fileURLToPath(new URL('../scripts/r186-owner-consent-check.mjs',import.meta.url)),
72
+ 'C:\\approval.json','core','D:\\Projects\\deadbyte-mcp','C:\\coding.json','C:\\autonomous.json'],{
73
+ cwd:process.cwd(),encoding:'utf8',windowsHide:true,timeout:18000
74
+ });
75
+ // A read-only preview is not a privileged grant, including on CLI failure.
76
+ assert.equal(result.status,2,'R186 CLI must execute and reject, not fail to resolve its module');
77
+ assert.match(String(result.stderr),/R186_OWNER_PREFLIGHT_BLOCKED/);
78
+ assert.doesNotMatch(String(result.stderr),/ERR_MODULE_NOT_FOUND/);
79
+ assert.doesNotMatch(String(result.stdout),/authority_granted":true/);
80
+ });
81
+
82
+ test('R186 validates actual Coding root map and Autonomous root vector before owner approval',()=>{
83
+ const root='D:\\Projects\\deadbyte-mcp';
84
+ const coding={schema:'deadbyte.coding-policy.v1',enabled:true,
85
+ roots:{core:{path:root,read:true,write:true,exec:true}}};
86
+ const autonomous={schema:'deadbyte.autonomous-policy.v1',enabled:true,
87
+ root_ids:['core'],coding_policy_sha256:H('a')};
88
+ const input={codingPolicy:coding,autonomousPolicy:autonomous,rootId:'core',projectRoot:root,
89
+ codingPolicySha256:H('a')};
90
+ assert.equal(verifyR186PolicyRootBinding(input).root_id,'core');
91
+ assert.throws(()=>verifyR186PolicyRootBinding({...input,projectRoot:'D:\\Other'}),/R186/i);
92
+ assert.throws(()=>verifyR186PolicyRootBinding({...input,
93
+ autonomousPolicy:{...autonomous,root_ids:['core','projects']}}),/R186/i);
94
+ assert.throws(()=>verifyR186PolicyRootBinding({...input,
95
+ autonomousPolicy:{...autonomous,coding_policy_sha256:H('b')}}),/R186/i);
96
+ assert.throws(()=>verifyR186PolicyRootBinding({...input,
97
+ codingPolicy:{...coding,roots:{core:{...coding.roots.core,path:'C:\\'}}}}),/R186/i);
98
+ });
@@ -0,0 +1,20 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync} from 'node:crypto';
4
+ import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
5
+
6
+ test('R186 enrollment material contains only an Ed25519 public identity',()=>{
7
+ const pair=generateKeyPairSync('ed25519');
8
+ const out=createR186OwnerAnchor(pair.publicKey.export({type:'spki',format:'pem'}));
9
+ assert.equal(out.anchor.schema,'deadbyte.r186-owner-anchor.v1');
10
+ assert.match(out.anchor.owner_key_id,/^[0-9a-f]{64}$/);
11
+ assert.match(out.public_key_pem,/BEGIN PUBLIC KEY/);
12
+ assert.doesNotMatch(JSON.stringify(out),/PRIVATE KEY/);
13
+ });
14
+
15
+ test('R186 enrollment rejects non-Ed25519 and private-key input',()=>{
16
+ const rsa=generateKeyPairSync('rsa',{modulusLength:2048});
17
+ assert.throws(()=>createR186OwnerAnchor(rsa.publicKey.export({type:'spki',format:'pem'})),/Ed25519/i);
18
+ const ed=generateKeyPairSync('ed25519');
19
+ assert.throws(()=>createR186OwnerAnchor(ed.privateKey.export({type:'pkcs8',format:'pem'})),/public/i);
20
+ });
@@ -0,0 +1,85 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync,sign} from 'node:crypto';
4
+ import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {canonicalJson} from '../src/canonical-json.mjs';
8
+ import {issueR186AutonomousGrant} from '../src/r186-owner-grant-broker.mjs';
9
+
10
+ const sha=b=>createHash('sha256').update(b).digest('hex');
11
+ const domain=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
12
+ async function fixture(){
13
+ const state=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r186-broker-'));
14
+ const project=path.join(state,'project');await mkdir(project);
15
+ const grantFile=path.join(state,'runtime','autonomous-grant.json');
16
+ const codingPolicy={schema:'deadbyte.coding-policy.v1',enabled:true,
17
+ roots:{core:{path:project,read:true,write:true,exec:true,deny_paths:[]}}};
18
+ const codingBytes=Buffer.from(JSON.stringify(codingPolicy));
19
+ const autonomousPolicy={schema:'deadbyte.autonomous-policy.v1',enabled:true,
20
+ lease_file:grantFile,evidence_dir:path.join(state,'evidence'),coding_policy_sha256:sha(codingBytes),
21
+ root_ids:['core'],planner:{},limits:{},profiles:{}};
22
+ const autonomousBytes=Buffer.from(JSON.stringify(autonomousPolicy));
23
+ const manifestBytes=Buffer.from('manifest fixture');
24
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
25
+ const machine=generateKeyPairSync('ed25519');
26
+ await mkdir(path.join(state,'trust'));
27
+ await writeFile(path.join(state,'trust','ed25519-private.pem'),machine.privateKey.export({type:'pkcs8',format:'pem'}));
28
+ await writeFile(path.join(state,'trust','ed25519-public.pem'),machine.publicKey.export({type:'spki',format:'pem'}));
29
+ const owner_key_id=sha(publicKey.export({type:'spki',format:'der'}));
30
+ const now=new Date('2026-10-28T00:00:00.000Z');
31
+ const subject={schema:'deadbyte.project-authority-subject.r70.v1',scope:'coding_autonomous_native_diff',
32
+ root_id:'core',project_root:project,coding_policy_sha256:sha(codingBytes),
33
+ autonomous_policy_sha256:sha(autonomousBytes),reviewed_artifact_sha256:sha(manifestBytes),
34
+ issued_at_utc:now.toISOString(),expires_at_utc:new Date(now.getTime()+300000).toISOString()};
35
+ const subject_sha256=sha(Buffer.from(canonicalJson(subject)));
36
+ const signed={schema:'deadbyte.project-owner-approval.r70.v1',subject_sha256,owner_key_id};
37
+ const approval={...signed,subject,signature_base64:sign(null,Buffer.concat([domain,Buffer.from(canonicalJson(signed))]),privateKey).toString('base64')};
38
+ const trust={schema:'deadbyte.r186-owner-anchor.v1',owner_key_id};
39
+ const publicPem=publicKey.export({type:'spki',format:'pem'});
40
+ const epoch={head:null,calls:0,async readHead(){return this.head;},async append({grantBytes,grantInstanceNonce}){
41
+ this.calls++;this.head={seq:1,action:'grant',event_sha256:'e'.repeat(64),grant_sha256:sha(grantBytes),grant_instance_nonce:grantInstanceNonce};return this.head;
42
+ },async verifyGrant({grantBytes,grantInstanceNonce}){return {trusted:this.head?.grant_sha256===sha(grantBytes)&&this.head?.grant_instance_nonce===grantInstanceNonce,reason:'epoch-bound-standing-grant',event_sha256:this.head?.event_sha256,seq:1,action:'grant'};}};
43
+ return {state,project,grantFile,codingPolicy,codingBytes,autonomousPolicy,autonomousBytes,manifestBytes,
44
+ trust,publicPem,approval,now,epoch};
45
+ }
46
+ const run=(f,extra={})=>issueR186AutonomousGrant({runtimeDir:path.join(f.state,'runtime'),grantFile:f.grantFile,
47
+ codingPolicy:f.codingPolicy,codingPolicyBytes:f.codingBytes,autonomousPolicy:f.autonomousPolicy,
48
+ autonomousPolicyBytes:f.autonomousBytes,releaseManifestBytes:f.manifestBytes,ownerTrust:f.trust,
49
+ ownerPublicKeyPem:f.publicPem,approval:f.approval,now:f.now,epochStore:f.epoch,...extra});
50
+
51
+ test('R186 broker consumes exact owner approval once and verifies grant plus signed epoch',async()=>{
52
+ const f=await fixture();try{const out=await run(f);
53
+ assert.equal(out.status,'GRANTED');assert.equal(out.authority_granted,true);assert.equal(f.epoch.calls,1);
54
+ const grant=JSON.parse(await readFile(f.grantFile,'utf8'));
55
+ assert.deepEqual(grant.root_ids,['core']);assert.equal(grant.policy_sha256,sha(f.autonomousBytes));
56
+ assert.equal(out.epoch_event_sha256,'e'.repeat(64));
57
+ const terminal=JSON.parse(await readFile(out.terminal_path,'utf8'));
58
+ assert.equal(terminal.attestation.domain,'DEADBYTE-R186-OWNER-GRANT-TERMINAL');
59
+ assert.match(terminal.receipt_sha256,/^[0-9a-f]{64}$/);
60
+ await assert.rejects(run(f),/already consumed|replay/i);assert.equal(f.epoch.calls,1);
61
+ }finally{await rm(f.state,{recursive:true,force:true});}
62
+ });
63
+
64
+ test('R186 broker fails closed before mutation on missing enrollment, drift and expiry',async()=>{
65
+ const f=await fixture();try{
66
+ for(const override of [
67
+ {ownerTrust:null},
68
+ {releaseManifestBytes:Buffer.from('changed')},
69
+ {now:new Date(f.now.getTime()+600000)}
70
+ ]){
71
+ const isolated=await fixture();try{await assert.rejects(run(isolated,override),/R186|owner|artifact|expired|anchor/i);
72
+ await assert.rejects(readFile(isolated.grantFile),{code:'ENOENT'});assert.equal(isolated.epoch.calls,0);
73
+ }finally{await rm(isolated.state,{recursive:true,force:true});}
74
+ }
75
+ }finally{await rm(f.state,{recursive:true,force:true});}
76
+ });
77
+
78
+ test('R186 concurrent broker attempts produce one grant and one epoch mutation',async()=>{
79
+ const f=await fixture();try{
80
+ const results=await Promise.allSettled([run(f),run(f)]);
81
+ assert.equal(results.filter(x=>x.status==='fulfilled').length,1);
82
+ assert.equal(results.filter(x=>x.status==='rejected').length,1);
83
+ assert.equal(f.epoch.calls,1);
84
+ }finally{await rm(f.state,{recursive:true,force:true});}
85
+ });
@@ -0,0 +1,30 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync} from 'node:crypto';
4
+ import {canonicalJson} from '../src/canonical-json.mjs';
5
+ import {createR186OwnerApproval} from '../src/r186-owner-local-signing.mjs';
6
+ import {verifyR186OwnerConsent} from '../src/r186-owner-consent-preflight.mjs';
7
+
8
+ test('R186 local owner signer signs only an exact review request and verifies with public identity',async()=>{
9
+ const pair=generateKeyPairSync('ed25519');
10
+ const now=new Date();
11
+ const subject={schema:'deadbyte.project-authority-subject.r70.v1',scope:'coding_autonomous_native_diff',root_id:'core',
12
+ project_root:process.cwd(),coding_policy_sha256:'a'.repeat(64),autonomous_policy_sha256:'b'.repeat(64),
13
+ reviewed_artifact_sha256:'c'.repeat(64),issued_at_utc:now.toISOString(),expires_at_utc:new Date(now.getTime()+300000).toISOString()};
14
+ const request={schema:'deadbyte.r186-owner-review-request.v1',subject,
15
+ subject_sha256:createHash('sha256').update(Buffer.from(canonicalJson(subject))).digest('hex'),owner_signature_present:false,authority_granted:false};
16
+ const privatePem=pair.privateKey.export({type:'pkcs8',format:'pem'});
17
+ const approval=createR186OwnerApproval({request,privateKeyPem:privatePem});
18
+ const publicPem=pair.publicKey.export({type:'spki',format:'pem'});
19
+ const keyId=approval.owner_key_id;
20
+ const checked=await verifyR186OwnerConsent({ownerTrust:{schema:'deadbyte.r186-owner-anchor.v1',owner_key_id:keyId},
21
+ ownerPublicKeyPem:publicPem,approval,now,expected:{rootId:'core',projectRoot:process.cwd(),
22
+ codingPolicySha256:'a'.repeat(64),autonomousPolicySha256:'b'.repeat(64),reviewedArtifactSha256:'c'.repeat(64)}});
23
+ assert.equal(checked.subject_sha256,approval.subject_sha256);
24
+ });
25
+
26
+ test('R186 local owner signer rejects private-key material embedded in request',()=>{
27
+ const pair=generateKeyPairSync('ed25519');
28
+ assert.throws(()=>createR186OwnerApproval({request:{private_key:'forbidden'},
29
+ privateKeyPem:pair.privateKey.export({type:'pkcs8',format:'pem'})}),/request/i);
30
+ });
@@ -0,0 +1,28 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createR186OwnerReviewRequest} from '../src/r186-owner-request.mjs';
4
+ const H=c=>c.repeat(64);
5
+ const parameters={rootId:'core',projectRoot:'D:\\Projects\\deadbyte-mcp',
6
+ codingPolicySha256:H('a'),autonomousPolicySha256:H('b'),reviewedArtifactSha256:H('c'),
7
+ now:new Date('2026-10-28T01:00:00.000Z'),durationMs:600000};
8
+ test('R186 request previews a bounded exact R70 subject, never an approval or grant',()=>{
9
+ const request=createR186OwnerReviewRequest(parameters);
10
+ assert.equal(request.schema,'deadbyte.r186-owner-review-request.v1');
11
+ assert.equal(request.authority_granted,false);
12
+ assert.equal(request.owner_signature_present,false);
13
+ assert.equal(request.subject.schema,'deadbyte.project-authority-subject.r70.v1');
14
+ assert.equal(request.subject.expires_at_utc,'2026-10-28T01:10:00.000Z');
15
+ assert.equal(request.subject.coding_policy_sha256,H('a'));
16
+ assert.equal(request.subject.autonomous_policy_sha256,H('b'));
17
+ assert.equal(request.subject.reviewed_artifact_sha256,H('c'));
18
+ assert.match(request.subject_sha256,/^[0-9a-f]{64}$/);
19
+ });
20
+ test('R186 request rejects excessive duration, bad roots and fabricated policy digests',()=>{
21
+ for(const changed of [
22
+ {durationMs:0},{durationMs:3600001},{durationMs:-1},
23
+ {rootId:'../core'},{projectRoot:'relative'},
24
+ {codingPolicySha256:H('x')},
25
+ {autonomousPolicySha256:'0'.repeat(63)}]){
26
+ assert.throws(()=>createR186OwnerReviewRequest({...parameters,...changed}),/R186/);
27
+ }
28
+ });