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
@@ -4,28 +4,29 @@ import {readFile} from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import {fileURLToPath} from 'node:url';
6
6
  import {DEADBYTE_VERSION} from '../src/version.mjs';
7
+ import {readReleaseLock} from './helpers/release-lock.mjs';
7
8
 
8
9
  const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
9
10
  const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
10
11
 
11
- test('R173 package identity preserves R132 evidence and published R172 source predecessor',async()=>{
12
+ test('R190 package identity preserves R132 evidence and published R179 source predecessor',async()=>{
12
13
  const [pkg,lock,release,prov,bom,matrix]=await Promise.all([
13
- read('package.json'),read('package-lock.json'),read('RELEASE.json'),
14
+ read('package.json'),readReleaseLock(root),read('RELEASE.json'),
14
15
  read('PROVENANCE.json'),read('SBOM.cdx.json'),read('TEST-MATRIX.json')]);
15
16
  for(const value of [pkg.version,lock.version,lock.packages[''].version,release.version,prov.version,bom.metadata.component.version,matrix.version,DEADBYTE_VERSION]){
16
17
  assert.equal(value,pkg.version);
17
18
  }
18
- assert.equal(release.train,'R173');
19
- assert.equal(matrix.train,'R173');
20
- assert.equal(release.source_predecessor.version,'0.15.67');
21
- assert.equal(release.source_predecessor.train,'R172');
22
- assert.match(release.source_predecessor.source_path,/0\.15\.67-r172-final-remote-recovery/i);
19
+ assert.equal(release.train,'R190');
20
+ assert.equal(matrix.train,'R190');
21
+ assert.equal(release.source_predecessor.version,'0.15.69');
22
+ assert.equal(release.source_predecessor.train,'R179');
23
+ assert.match(release.source_predecessor.source_path,/deadbyte-mcp-0\.15\.69\.tgz/i);
23
24
  assert.equal(prov.r160.source_predecessor_manifest_status,'STALE_REJECTED');
24
25
  assert.equal(release.r165.published_source_manifest_sha256,'4adf7d94682fbb71c4a91eb3147ecc48076bbc5d79cb67fcb875cde6a1bb4805');
25
26
  assert.equal(release.r165.published_source_tarball_sha256,'812e97609b7c51353ed25f2202129737e43bb4fbe0778c3adec43ec065352641');
26
- assert.equal(release.source_predecessor.tarball_sha256,'0fe761f035f38730a4717448da4217f55464c38cd1616126b5850ff44411cd6d');
27
- assert.equal(release.r173.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
28
- assert.equal(release.runtime_predecessor.version,'0.15.61');
27
+ assert.equal(release.source_predecessor.tarball_sha256,'578c1630608f9df816524c78abc9e1a55357fdc3cd25e142122f1a823e1d69e3');
28
+ assert.notEqual(release.r179.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
29
+ assert.equal(release.runtime_predecessor.version,'0.15.68');
29
30
  assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
30
31
  assert.equal(prov.runtime_predecessor.manifest_sha256,release.runtime_predecessor.manifest_sha256);
31
32
  assert.equal(prov.runtime_predecessor.generation_manifest_sha256,release.runtime_predecessor.generation_manifest_sha256);
@@ -36,7 +37,7 @@ test('R173 package identity preserves R132 evidence and published R172 source pr
36
37
  });
37
38
 
38
39
  test('R132 shipped SBOM matches the actual audited bundled MCP SDK',async()=>{
39
- const [pkg,bom,lock]=await Promise.all([read('package.json'),read('SBOM.cdx.json'),read('package-lock.json')]);
40
+ const [pkg,bom,lock]=await Promise.all([read('package.json'),read('SBOM.cdx.json'),readReleaseLock(root)]);
40
41
  for(const name of ['@modelcontextprotocol/client','@modelcontextprotocol/server','@modelcontextprotocol/core']){
41
42
  const entry=bom.components.find(v=>v.name===name);
42
43
  assert.ok(entry,'missing SBOM component: '+name);
@@ -26,7 +26,7 @@ test('R153 historical verifier refuses current successor package identity',async
26
26
  const state={pkg:JSON.parse(await readFile(path.join(root,'package.json'),'utf8')),
27
27
  release:JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8')),
28
28
  bridge:JSON.parse(await readFile(path.join(root,'bridge/package.json'),'utf8'))};
29
- assert.equal(state.pkg.version,'0.15.68');
29
+ assert.equal(state.pkg.version,'0.15.70');
30
30
  assert.throws(()=>verifyR153Lineage(state),/package version/);
31
31
  await assert.rejects(verifyR153IdentityTree(root),/package version/);
32
32
  });
@@ -14,25 +14,25 @@ function defaultTracker(){
14
14
  return live.createLiveTimelineTracker({clock:now,...live.defaultForegroundTimelineOptions({verbose:false})});
15
15
  }
16
16
 
17
- test('R155 ordinary remote has per-call START and DONE without opt-in flag',()=>{
17
+ test('R176 default folds harmless status polling',()=>{
18
18
  const tracker=defaultTracker();
19
19
  const start=tracker.observe({event_type:'tool.request',request_id:'abc012345678',tool:'machine_status',args:{}},{sequence:1});
20
- assert.match(start,/Received tool call/);
21
- assert.match(start,/machine_status/);
20
+ assert.equal(start,null);
22
21
  const done=tracker.observe({event_type:'tool.result',request_id:'abc012345678',tool:'machine_status',outcome:{status:'ok',duration_ms:3}},{sequence:2});
23
- assert.match(done,/completed/);
24
- assert.match(done,/3ms/);
22
+ assert.equal(done,null);
23
+ assert.match(tracker.reconcileInterrupted().join('\n'),/machine_status.*×1/);
25
24
  });
26
25
 
27
- test('R155 ordinary remote does not fold successive status calls into a burst',()=>{
26
+ test('R176 status bursts collapse without losing event totals',()=>{
28
27
  const tracker=defaultTracker();
29
28
  for(let n=0;n<3;n++){
30
29
  const id='repeat-'+n;
31
- assert.match(tracker.observe({event_type:'tool.request',request_id:id,tool:'machine_ping',args:{}},{sequence:n*2+1}),/Received tool call/);
32
- assert.match(tracker.observe({event_type:'tool.result',request_id:id,tool:'machine_ping',outcome:{status:'ok',duration_ms:1}},{sequence:n*2+2}),/completed/);
30
+ assert.equal(tracker.observe({event_type:'tool.request',request_id:id,tool:'machine_ping',args:{}},{sequence:n*2+1}),null);
31
+ assert.equal(tracker.observe({event_type:'tool.result',request_id:id,tool:'machine_ping',outcome:{status:'ok',duration_ms:1}},{sequence:n*2+2}),null);
33
32
  }
33
+ assert.match(tracker.reconcileInterrupted().join('\n'),/machine_ping.*×3/);
34
34
  assert.match(tracker.summary(),/3 calls \| 3 ok/);
35
- assert.doesNotMatch(tracker.summary(),/folded [1-9]/);
35
+ assert.match(tracker.summary(),/folded 2 heartbeat/);
36
36
  });
37
37
 
38
38
  test('R155 ordinary remote keeps credentials and payload opaque by default',()=>{
@@ -42,7 +42,7 @@ test('R155 ordinary remote keeps credentials and payload opaque by default',()=>
42
42
  command_id:'node_script',path:'C:\\Safe\\test.js',
43
43
  metadata:{token:'Bearer should-not-appear'}
44
44
  }},{sequence:1});
45
- assert.match(line,/Received tool call/);
45
+ assert.match(line,/START/);
46
46
  assert.match(line,/node_script/);
47
47
  assert.doesNotMatch(line,/sk-secret-test|PRIVATE SOURCE CONTENT|should-not-appear/);
48
48
  });
@@ -51,10 +51,10 @@ test('R155 ordinary remote prints DENIED and UNKNOWN as failures rather than com
51
51
  const tracker=defaultTracker();
52
52
  tracker.observe({event_type:'tool.request',request_id:'one',tool:'host_file_read',args:{path:'a.txt'}},{sequence:1});
53
53
  const fail=tracker.observe({event_type:'tool.result',request_id:'one',tool:'host_file_read',outcome:{status:'denied',is_error:true,reason:'arm file missing'}},{sequence:2});
54
- assert.match(fail,/failed/);assert.match(fail,/arm file missing/);
54
+ assert.match(fail,/DENIED/);assert.match(fail,/arm file missing/);
55
55
  tracker.observe({event_type:'tool.request',request_id:'two',tool:'coding_file_write',args:{path:'x.txt'}},{sequence:3});
56
56
  const unknown=tracker.observe({event_type:'tool.result',request_id:'two',tool:'coding_file_write',outcome:{status:'interrupted',side_effect_state:'unknown'}},{sequence:4});
57
- assert.match(unknown,/OUTCOME_UNKNOWN/);
57
+ assert.match(unknown,/UNKNOWN/);
58
58
  });
59
59
 
60
60
  test('R155 ordinary remote has no new CLI live-log mode and wired default live behavior',()=>{
@@ -66,16 +66,17 @@ test('R155 ordinary remote has no new CLI live-log mode and wired default live b
66
66
  assert.match(source,/defaultForegroundTimelineOptions\(\{verbose\}\)/);
67
67
  });
68
68
 
69
- test('R155 unchanged capability polling still has a matching visible DONE for every CALL',()=>{
69
+ test('R176 same snapshot suppresses repeated capability messages',()=>{
70
70
  const tracker=defaultTracker();
71
71
  const snapshot='a'.repeat(64);
72
72
  for(let n=0;n<2;n++){
73
73
  const id='cap-'+n;
74
- assert.match(tracker.observe({event_type:'tool.request',request_id:id,tool:'capability_manifest',args:{}},{sequence:n*2+1}),/Received tool call/);
74
+ assert.equal(tracker.observe({event_type:'tool.request',request_id:id,tool:'capability_manifest',args:{}},{sequence:n*2+1}),null);
75
75
  const done=tracker.observe({event_type:'tool.result',request_id:id,tool:'capability_manifest',outcome:{status:'ok',snapshot_id:snapshot,state_epoch:1,duration_ms:2}},{sequence:n*2+2});
76
- assert.match(done,/completed/);
76
+ if(n===0) assert.match(done,/PASS/);
77
+ else assert.equal(done,null);
77
78
  }
78
- assert.match(tracker.summary(),/2 calls \\| 2 ok/);
79
+ assert.match(tracker.summary(),/2 calls \| 2 ok/);
79
80
  });
80
81
 
81
82
  test('R155 runtime-only stream reset does not claim audit corruption, audit reset remains severe',()=>{
@@ -59,7 +59,7 @@ test('R160 startup decision manifest rejects a newly required recovery module om
59
59
  test('R160 remote startup verifies decision bytes before evidence recovery and foreground claim',async()=>{
60
60
  const source=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
61
61
  const verifyAt=source.indexOf('await verifyPackageDecisionManifest({root:CURRENT_PACKAGE_ROOT,expectedVersion:DEADBYTE_VERSION})');
62
- const recoverAt=source.indexOf('const migrationContext=await recoverRemoteSession(ctx,{displayMode})');
62
+ const recoverAt=source.indexOf('await recoverRemoteSession(ctx,{displayMode})');
63
63
  const claimAt=source.indexOf('marker=await claimRemoteSession(ctx)');
64
64
  assert.ok(verifyAt>0&&verifyAt<recoverAt&&recoverAt<claimAt,
65
65
  'package decision manifest must be verified before recovery and claim');
@@ -8,12 +8,11 @@ const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
8
8
 
9
9
  test('R160 historical contract remains distinct from the current package identity',async()=>{
10
10
  const [release,provenance]=await Promise.all([read('RELEASE.json'),read('PROVENANCE.json')]);
11
- assert.equal(release.runtime_predecessor.version,'0.15.61');
12
- assert.equal(release.runtime_predecessor.release_id,'v0.15.61-055d1ff0aa99e60b');
13
- assert.equal(release.runtime_predecessor.manifest_sha256,'055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf');
14
- assert.equal(release.runtime_predecessor.generation_manifest_sha256,'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4');
15
- assert.equal(release.r160.source_predecessor_train,'R159');
11
+ assert.equal(release.runtime_predecessor.version,'0.15.68');
12
+ assert.equal(release.runtime_predecessor.release_id,'v0.15.68-ff29dbb2fbb89862');
13
+ assert.equal(release.runtime_predecessor.manifest_sha256,'ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885');
16
14
  assert.equal(release.r160.runtime_predecessor_version,'0.15.61');
15
+ assert.equal(release.r160.source_predecessor_train,'R159');
17
16
  assert.equal(release.r160.public_tool_delta,0);
18
17
  assert.equal(release.r160.authority_scope_delta,0);
19
18
  assert.equal(release.r160.production_activation,'NOT_RUN');
@@ -8,9 +8,10 @@ import {fileURLToPath} from 'node:url';
8
8
  import {atomicWriteActivePointer,materializeReleaseSlot,parseActivePointer,parseManifest,renderStableLauncherPs1,verifyManifestTreeExact} from '../src/release-slot.mjs';
9
9
 
10
10
  const candidate=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
11
- const version='0.15.68';
12
- const parent=path.join(os.homedir(),'.deadbyte-mcp','releases','v0.15.61-055d1ff0aa99e60b');
13
- const parentDigest='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf';
11
+ const packageIdentity=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
12
+ const version=packageIdentity.version;
13
+ const parent=path.join(os.homedir(),'.deadbyte-mcp','releases','v0.15.68-ff29dbb2fbb89862');
14
+ const parentDigest='ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885';
14
15
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
15
16
  async function generation(state,label){
16
17
  const root=path.join(state,'generations',label);
@@ -21,17 +22,16 @@ async function generation(state,label){
21
22
  await writeFile(path.join(root,'GENERATION.SHA256'),manifest,{flag:'wx'});
22
23
  return {root,manifest_sha256:sha(manifest),config_path:path.join(root,'controller-config.psd1')};
23
24
  }
24
- test('R173 isolated immutable slot activates, rolls back, and restores 0.15.68 without authority or production mutation',async()=>{
25
+ test('R179 isolated immutable slot activates, rolls back, and restores the current successor without authority or production mutation',async()=>{
25
26
  const stateBase=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r163-isolated-'));
26
27
  const state=path.join(stateBase,'state');
27
28
  try{
28
- const pkg=JSON.parse(await readFile(path.join(candidate,'package.json'),'utf8'));
29
- assert.equal(pkg.version,version);
29
+ assert.match(version,/^0\.15\.\d+$/);
30
30
  const parentManifest=await readFile(path.join(parent,'MANIFEST.SHA256'));
31
31
  const nextManifest=await readFile(path.join(candidate,'MANIFEST.SHA256'));
32
32
  assert.equal(sha(parentManifest),parentDigest);
33
33
  const nextDigest=sha(nextManifest);
34
- const parentSlot=await materializeReleaseSlot({source:parent,stateRoot:state,version:'0.15.61',manifestSha256:parentDigest});
34
+ const parentSlot=await materializeReleaseSlot({source:parent,stateRoot:state,version:'0.15.68',manifestSha256:parentDigest});
35
35
  const nextSlot=await materializeReleaseSlot({source:candidate,stateRoot:state,version,manifestSha256:nextDigest});
36
36
  const pGen=await generation(state,'r165-parent');
37
37
  const nGen=await generation(state,'r165-next');
@@ -43,7 +43,7 @@ test('R173 isolated immutable slot activates, rolls back, and restores 0.15.68 w
43
43
  const activated=await atomicWriteActivePointer(pointerPath,ptr(nextSlot,version,nGen));
44
44
  assert.equal(activated.release_id,nextSlot.release_id);
45
45
  await verifyManifestTreeExact(nextSlot.release_root,parseManifest(nextManifest.toString('utf8')));
46
- const rolled=await atomicWriteActivePointer(pointerPath,ptr(parentSlot,'0.15.61',pGen));
46
+ const rolled=await atomicWriteActivePointer(pointerPath,ptr(parentSlot,'0.15.68',pGen));
47
47
  assert.equal(rolled.release_id,parentSlot.release_id);
48
48
  assert.equal(rolled.manifest_sha256,parentDigest);
49
49
  await verifyManifestTreeExact(parentSlot.release_root,parseManifest(parentManifest.toString('utf8')));
@@ -17,7 +17,7 @@ const hashMarker=c=>c.repeat(64);
17
17
  async function fixture(t,kind){
18
18
  assert.equal(typeof envRoot,'string'); assert.ok(path.isAbsolute(envRoot));
19
19
  assert.match(sha(await readFile(path.join(envRoot,'MANIFEST.SHA256'))),/^[0-9a-f]{64}$/);
20
- assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.68');
20
+ assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.70');
21
21
  const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r164-skew-e2e-'));
22
22
  t.after(async()=>{await rm(dir,{recursive:true,force:true,maxRetries:10,retryDelay:150});});
23
23
  const state=path.join(dir,'state'),bin=path.join(state,'bin');
@@ -65,26 +65,26 @@ async function run(fx){
65
65
  assert.doesNotMatch(trace,/\b(?:start|restart|armprocess|armcoding|armautonomous|deploy)\b/i,'unexpected runtime operation');
66
66
  return {output:(result.stderr||'')+'\n'+(result.stdout||''),trace};
67
67
  }
68
- gate('R168 executable CLI blocks npm-cache activation on version skew before touching R159 history',async t=>{
68
+ gate('R175 executable CLI blocks foreground observation on version skew before touching history',async t=>{
69
69
  const fx=await fixture(t,'completed');
70
70
  const r=await run(fx);
71
- assert.match(r.output,/R168 version skew.*0\.15\.61.*0\.15\.68/i,r.output);
71
+ assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
72
72
  assert.doesNotMatch(r.output,/R159 ambiguous multiple completed/i);
73
73
  assert.equal(r.trace,'');
74
74
  assert.doesNotMatch(r.trace,/disarmdesktop/);
75
75
  });
76
76
  gate('R165 executable CLI rejects pending current reconciliation before upgrade',async t=>{
77
77
  const fx=await fixture(t,'pending'),r=await run(fx);
78
- assert.match(r.output,/R168 version skew/i,r.output);
78
+ assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
79
79
  assert.doesNotMatch(r.trace,/disarmhost/);
80
80
  });
81
81
  gate('R165 executable CLI rejects tampered terminal before upgrade',async t=>{
82
82
  const fx=await fixture(t,'tampered'),r=await run(fx);
83
- assert.match(r.output,/R168 version skew/i,r.output);
83
+ assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
84
84
  assert.doesNotMatch(r.trace,/disarmhost/);
85
85
  });
86
86
  gate('R165 executable CLI keeps ambiguous history fail-closed when predecessor is not ONLINE',async t=>{
87
87
  const fx=await fixture(t,'offline'),r=await run(fx);
88
- assert.match(r.output,/R168 version skew/i,r.output);
88
+ assert.match(r.output,/R175_EXACT_ACTIVE_RELEASE_REQUIRED/i,r.output);
89
89
  assert.doesNotMatch(r.trace,/disarmhost/);
90
90
  });
@@ -8,11 +8,11 @@ import {DEADBYTE_VERSION} from '../src/version.mjs';
8
8
  const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
9
9
  const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
10
10
  const readOptional=async rel=>{try{return await read(rel);}catch(error){if(error?.code==='ENOENT')return null;throw error;}};
11
- const VERSION='0.15.68';
11
+ const VERSION='0.15.70';
12
12
  const R166_MANIFEST='ad3c091fbbe2bac6d234548724008b67305fd483ad6542f92d5aeb1aa84f5849';
13
13
  const R166_TARBALL='3d22412c3cebabd66bfa92ca4dbaaf3ef49acba7f92436a16485d7e4cb18c653';
14
14
 
15
- test('R173 package identity binds published R172 source separately from active R153 runtime',async()=>{
15
+ test('current package identity retains the immutable R167/R173 activation lineage',async()=>{
16
16
  const [pkg,lock,bridge,release,provenance,matrix,bom]=await Promise.all([
17
17
  read('package.json'),readOptional('package-lock.json'),read('bridge/package.json'),read('RELEASE.json'),
18
18
  read('PROVENANCE.json'),read('TEST-MATRIX.json'),read('SBOM.cdx.json')]);
@@ -20,19 +20,21 @@ test('R173 package identity binds published R172 source separately from active R
20
20
  provenance.version,matrix.version,bom.metadata.component.version,DEADBYTE_VERSION];
21
21
  if(lock)versions.push(lock.version,lock.packages[''].version);
22
22
  for(const version of versions)assert.equal(version,VERSION);
23
- assert.equal(release.train,'R173');
24
- assert.equal(matrix.train,'R173');
25
- assert.equal(bom.serialNumber,'urn:cdx:deadbyte-mcp:r173:0.15.68');
23
+ assert.equal(release.train,'R190');
24
+ assert.equal(matrix.train,'R190');
25
+ assert.equal(bom.serialNumber,'urn:cdx:deadbyte-mcp:r190:0.15.70');
26
26
  assert.deepEqual(release.runtime_predecessor,{
27
- version:'0.15.61',release_id:'v0.15.61-055d1ff0aa99e60b',
28
- manifest_sha256:'055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf',
29
- generation_manifest_sha256:'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4'
27
+ version:'0.15.68',release_id:'v0.15.68-ff29dbb2fbb89862',
28
+ manifest_sha256:'ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885',
29
+ generation_manifest_sha256:'2887d4a8d54ea6fc6b28869072f72491c892dfe28178b3537936dce958147bd2'
30
30
  });
31
- assert.equal(release.source_predecessor.version,'0.15.67');
32
- assert.equal(release.source_predecessor.train,'R172');
33
- assert.equal(release.source_predecessor.tarball_sha256,'0fe761f035f38730a4717448da4217f55464c38cd1616126b5850ff44411cd6d');
31
+ assert.equal(release.source_predecessor.version,'0.15.69');
32
+ assert.equal(release.source_predecessor.train,'R179');
33
+ assert.equal(release.source_predecessor.tarball_sha256,'578c1630608f9df816524c78abc9e1a55357fdc3cd25e142122f1a823e1d69e3');
34
+ assert.equal(release.r173.source_predecessor_version,'0.15.67');
34
35
  assert.equal(release.r167.source_predecessor_manifest_sha256,R166_MANIFEST);
35
- assert.equal(provenance.source_predecessor_manifest_file_sha256,'4fd7bfc76b1de6401c142b607cc84e847434b28c888bc99ceb09c9e8e5adc8f8');
36
+ assert.equal(provenance.source_predecessor_manifest_file_sha256,'cfede112761da00e73b626cfbce23d17e1d4b6e3a325bb675415196d31142ba0');
37
+ assert.equal(provenance.r173.source_predecessor_manifest_sha256,'4fd7bfc76b1de6401c142b607cc84e847434b28c888bc99ceb09c9e8e5adc8f8');
36
38
  assert.equal(release.r172.source_predecessor_manifest_sha256,'962e227dace247d59aa860211388fbde461c5f9c8a7e8a878e34a14a27d355f8');
37
39
  assert.equal(release.r172.source_predecessor_tarball_sha256,'31b194a4fbff7dbdb9f4f275b4ea49c5115f7b0bb2241c0c72d72ef690311a5d');
38
40
  assert.equal(release.r167.public_tool_delta,0);
@@ -41,7 +43,7 @@ test('R173 package identity binds published R172 source separately from active R
41
43
  assert.equal(release.r167.npm_publish,'NOT_RUN');
42
44
  });
43
45
 
44
- test('R167 protected continuity remains pending until exact Windows fixture evidence exists',async()=>{
46
+ test('R167 historical protected continuity remains recorded without overriding current release truth',async()=>{
45
47
  const [release,provenance,matrix]=await Promise.all([
46
48
  read('RELEASE.json'),read('PROVENANCE.json'),read('TEST-MATRIX.json')]);
47
49
  assert.equal(release.r167.protected_entrypoint_continuity,'PENDING_PROTECTED_WINDOWS_FIXTURE');
@@ -10,13 +10,16 @@ const script=path.join(root,'scripts','r173-protected-preflight.mjs');
10
10
  const protectedRoot='C:\\Program Files';
11
11
  const relative=path.win32.relative(protectedRoot,root);
12
12
  const isProtected=process.platform==='win32'&&relative!==''&&relative!=='..'&&!relative.startsWith('..'+path.win32.sep)&&!path.win32.isAbsolute(relative);
13
+ const packageIdentity=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
14
+ const releaseIdentity=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
15
+ const isR173Identity=packageIdentity.version==='0.15.68'&&releaseIdentity.version==='0.15.68'&&releaseIdentity.train==='R173';
13
16
  function launch(file,...args){return spawnSync(process.execPath,[file,...args],{
14
17
  encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:65536
15
18
  });}
16
19
 
17
20
  test('R173 installed preflight respects R89 protected-vs-writable execution context',()=>{
18
21
  const result=launch(script);
19
- if(isProtected){
22
+ if(isProtected&&isR173Identity){
20
23
  assert.equal(result.status,0,result.stderr);
21
24
  assert.match(result.stdout,/R173_PROTECTED_PREFLIGHT_READONLY_PASS/);
22
25
  assert.match(result.stdout,/"terminalization_authorized":false/);
@@ -24,7 +27,7 @@ test('R173 installed preflight respects R89 protected-vs-writable execution cont
24
27
  }else{
25
28
  assert.notEqual(result.status,0);
26
29
  assert.match(result.stderr,/R173_PROTECTED_PREFLIGHT=BLOCKED/);
27
- assert.match(result.stderr,/R173_.*_(UNPROTECTED|VERSION_DRIFT)/);
30
+ assert.match(result.stderr,isProtected?/R173_VERSION_DRIFT/:/R173_.*_UNPROTECTED/);
28
31
  }
29
32
  });
30
33
 
@@ -13,9 +13,10 @@ const predecessorTarball='fc3ae5b1505e316bc9dfedf8d063cb00641754c8f5f1306a31f8cc
13
13
 
14
14
  test('R172 preserves the immutable R168 publication lineage separately from current identity',async()=>{
15
15
  const release=await read('RELEASE.json');
16
- assert.equal(release.runtime_predecessor.version,'0.15.61');
17
- assert.equal(release.runtime_predecessor.release_id,parent);
18
- assert.equal(release.parent.release_id,parent);
16
+ assert.equal(release.runtime_predecessor.version,'0.15.68');
17
+ assert.equal(release.parent.version,'0.15.68');
18
+ assert.notEqual(release.runtime_predecessor.release_id,parent);
19
+ assert.notEqual(release.parent.release_id,parent);
19
20
  assert.equal(release.r168.scope,'REMOTE_VERSION_SKEW_FAIL_CLOSED_AND_READONLY_RECONCILIATION');
20
21
  assert.equal(release.r168.source_predecessor_version,'0.15.65');
21
22
  assert.equal(release.r168.source_predecessor_manifest_sha256,predecessorManifest);
@@ -60,7 +60,8 @@ test('R168 foreground remote refuses npm-cache version skew before session marke
60
60
  const source=cli.slice(start,end);
61
61
  assert.match(source,/selectR168OneShotRemotePath/);
62
62
  const safety=source.indexOf('selectR168OneShotRemotePath');
63
- for(const operation of ['recoverRemoteSession(ctx','acquireForegroundSessionLock({root','synchronizeForegroundRuntime(ctx']){
63
+ for(const operation of ['recoverRemoteSession(ctx','acquireForegroundSessionLock({root','readRemoteSessionMarker(ctx.root)','claimRemoteSession(ctx)']){
64
64
  assert.ok(safety<source.indexOf(operation),'version guard must precede '+operation);
65
65
  }
66
+ assert.doesNotMatch(source,/synchronizeForegroundRuntime\(/,'R175 foreground must remain observation-only');
66
67
  });
@@ -9,19 +9,19 @@ const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
9
9
  const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
10
10
  const readOptional=async rel=>{try{return await read(rel);}catch(error){if(error?.code==='ENOENT')return null;throw error;}};
11
11
 
12
- test('R173 binds the published source predecessor separately from the active runtime predecessor',async()=>{
12
+ test('R190 binds the published source predecessor separately from the active runtime predecessor',async()=>{
13
13
  const [pkg,lock,bridge,release,provenance,matrix,bom]=await Promise.all([
14
14
  read('package.json'),readOptional('package-lock.json'),read('bridge/package.json'),read('RELEASE.json'),
15
15
  read('PROVENANCE.json'),read('TEST-MATRIX.json'),read('SBOM.cdx.json')]);
16
16
  const versions=[pkg.version,bridge.version,release.version,provenance.version,matrix.version,
17
17
  bom.metadata.component.version,DEADBYTE_VERSION];
18
18
  if(lock)versions.push(lock.version,lock.packages[''].version);
19
- for(const value of versions)assert.equal(value,'0.15.68');
20
- assert.equal(release.train,'R173'); assert.equal(matrix.train,'R173');
21
- assert.equal(release.source_predecessor.version,'0.15.67');
22
- assert.equal(release.source_predecessor.manifest_sha256,'4fd7bfc76b1de6401c142b607cc84e847434b28c888bc99ceb09c9e8e5adc8f8');
23
- assert.equal(release.source_predecessor.tarball_sha256,'0fe761f035f38730a4717448da4217f55464c38cd1616126b5850ff44411cd6d');
24
- assert.equal(release.runtime_predecessor.release_id,'v0.15.61-055d1ff0aa99e60b');
19
+ for(const value of versions)assert.equal(value,'0.15.70');
20
+ assert.equal(release.train,'R190'); assert.equal(matrix.train,'R190');
21
+ assert.equal(release.source_predecessor.version,'0.15.69');
22
+ assert.equal(release.source_predecessor.manifest_sha256,'cfede112761da00e73b626cfbce23d17e1d4b6e3a325bb675415196d31142ba0');
23
+ assert.equal(release.source_predecessor.tarball_sha256,'578c1630608f9df816524c78abc9e1a55357fdc3cd25e142122f1a823e1d69e3');
24
+ assert.equal(release.runtime_predecessor.release_id,'v0.15.68-ff29dbb2fbb89862');
25
25
  assert.equal(release.r173.public_tool_delta,0); assert.equal(release.r173.authority_scope_delta,0);
26
26
  });
27
27
 
@@ -3,30 +3,31 @@ import assert from 'node:assert/strict';
3
3
  import {readFile} from 'node:fs/promises';
4
4
  import path from 'node:path';
5
5
  import {fileURLToPath} from 'node:url';
6
+ import {readReleaseLock} from './helpers/release-lock.mjs';
6
7
 
7
8
  const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
8
9
  const json=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
9
10
 
10
- test('R173 package identity binds the exact published R172 source and live runtime predecessor',async()=>{
11
+ test('R190 package identity binds the exact published R179 source and live runtime predecessor',async()=>{
11
12
  const [pkg,lock,bridge,release,provenance,matrix,sbom]=await Promise.all([
12
- json('package.json'),json('package-lock.json'),json('bridge/package.json'),json('RELEASE.json'),
13
+ json('package.json'),readReleaseLock(root),json('bridge/package.json'),json('RELEASE.json'),
13
14
  json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json')
14
15
  ]);
15
- assert.equal(pkg.version,'0.15.68');
16
- assert.equal(lock.version,'0.15.68');
17
- assert.equal(lock.packages[''].version,'0.15.68');
18
- assert.equal(bridge.version,'0.15.68');
19
- assert.equal(release.version,'0.15.68');
20
- assert.equal(release.train,'R173');
21
- assert.equal(matrix.version,'0.15.68');
22
- assert.equal(matrix.train,'R173');
23
- assert.equal(sbom.metadata.component.version,'0.15.68');
24
- assert.equal(release.source_predecessor.version,'0.15.67');
25
- assert.equal(release.source_predecessor.train,'R172');
26
- assert.equal(release.source_predecessor.manifest_sha256,'4fd7bfc76b1de6401c142b607cc84e847434b28c888bc99ceb09c9e8e5adc8f8');
27
- assert.equal(release.source_predecessor.tarball_sha256,'0fe761f035f38730a4717448da4217f55464c38cd1616126b5850ff44411cd6d');
28
- assert.equal(release.runtime_predecessor.version,'0.15.61');
29
- assert.equal(provenance.version,'0.15.68');
16
+ assert.equal(pkg.version,'0.15.70');
17
+ assert.equal(lock.version,'0.15.70');
18
+ assert.equal(lock.packages[''].version,'0.15.70');
19
+ assert.equal(bridge.version,'0.15.70');
20
+ assert.equal(release.version,'0.15.70');
21
+ assert.equal(release.train,'R190');
22
+ assert.equal(matrix.version,'0.15.70');
23
+ assert.equal(matrix.train,'R190');
24
+ assert.equal(sbom.metadata.component.version,'0.15.70');
25
+ assert.equal(release.source_predecessor.version,'0.15.69');
26
+ assert.equal(release.source_predecessor.train,'R179');
27
+ assert.equal(release.source_predecessor.manifest_sha256,'cfede112761da00e73b626cfbce23d17e1d4b6e3a325bb675415196d31142ba0');
28
+ assert.equal(release.source_predecessor.tarball_sha256,'578c1630608f9df816524c78abc9e1a55357fdc3cd25e142122f1a823e1d69e3');
29
+ assert.equal(release.runtime_predecessor.version,'0.15.68');
30
+ assert.equal(provenance.version,'0.15.70');
30
31
  assert.equal(provenance.parent_tarball_sha256,release.source_predecessor.tarball_sha256);
31
32
  assert.equal(provenance.source_predecessor_manifest_file_sha256,release.source_predecessor.manifest_sha256);
32
33
  });
@@ -0,0 +1,144 @@
1
+ import test from 'node:test';
2
+ import {mkdtemp,readdir,readFile,rm,symlink} from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import assert from 'node:assert/strict';
6
+ import {createHash,generateKeyPairSync,randomBytes,sign} from 'node:crypto';
7
+ import {canonicalJson} from '../src/canonical-json.mjs';
8
+ import {verifyR174OwnerAssertion,acceptR174OwnerProof} from '../src/r174-owner-webauthn.mjs';
9
+ const sha=b=>createHash('sha256').update(b).digest();
10
+ const hex=b=>sha(b).toString('hex');
11
+ const b64=b=>Buffer.from(b).toString('base64url');
12
+ function fixture(){
13
+ const pair=generateKeyPairSync('ec',{namedCurve:'prime256v1'});
14
+ const credential={schema:'deadbyte.r174-owner-credential.v1',
15
+ credential_id_b64url:b64(randomBytes(32)),rp_id:'owner.deadbyte.local',
16
+ origin:'https://owner.deadbyte.local',
17
+ public_key_spki_pem:pair.publicKey.export({type:'spki',format:'pem'}),
18
+ algorithm:'ES256',sign_count:4};
19
+ const subject={schema:'deadbyte.r174-autonomous-owner-subject.v1',
20
+ release_manifest_sha256:'a'.repeat(64),coding_policy_sha256:'b'.repeat(64),
21
+ autonomous_policy_sha256:'c'.repeat(64),root_id:'core',
22
+ project_root:'D:\\Projects\\deadbyte-mcp',effect:'autonomous_goal_run',max_seconds:3600};
23
+ const transaction={schema:'deadbyte.r174-owner-challenge.v1',
24
+ challenge_b64url:b64(randomBytes(32)),subject_sha256:hex(Buffer.from(canonicalJson(subject))),
25
+ issued_at_utc:'2026-10-27T06:59:00.000Z',expires_at_utc:'2026-10-27T07:04:00.000Z'};
26
+ const clientData=Buffer.from(JSON.stringify({type:'webauthn.get',challenge:transaction.challenge_b64url,origin:credential.origin,crossOrigin:false}));
27
+ const authData=Buffer.alloc(37);
28
+ sha(Buffer.from(credential.rp_id)).copy(authData);
29
+ authData[32]=5;authData.writeUInt32BE(5,33);
30
+ const assertion={credential_id_b64url:credential.credential_id_b64url,
31
+ client_data_json_b64url:b64(clientData),authenticator_data_b64url:b64(authData),
32
+ signature_b64url:b64(sign('sha256',Buffer.concat([authData,sha(clientData)]),pair.privateKey))};
33
+ const ownerTrust={schema:'deadbyte.r174-owner-trust-binding.v1',
34
+ credential_id_b64url:credential.credential_id_b64url,
35
+ public_key_spki_sha256:hex(pair.publicKey.export({type:'spki',format:'der'})),
36
+ rp_id:credential.rp_id,origin:credential.origin,
37
+ subject_sha256:transaction.subject_sha256};
38
+ return {credential,subject,transaction,assertion,ownerTrust,now:new Date('2026-10-27T07:00:00.000Z'),privateKey:pair.privateKey,clientData,authData};
39
+ }
40
+ function reSign(f){
41
+ f.assertion.client_data_json_b64url=b64(f.clientData);
42
+ f.assertion.authenticator_data_b64url=b64(f.authData);
43
+ f.assertion.signature_b64url=b64(sign('sha256',Buffer.concat([f.authData,sha(f.clientData)]),f.privateKey));
44
+ }
45
+ test('R174 verifies ES256 authenticator signature and owner UV with bound challenge',async()=>{
46
+ const f=fixture();const r=await verifyR174OwnerAssertion(f);
47
+ assert.equal(r.status,'verified');assert.equal(r.sign_count,5);
48
+ assert.equal(r.subject_sha256,f.transaction.subject_sha256);assert.equal(r.user_verified,true);
49
+ });
50
+ test('R174 refuses invalid signature and UV=false even when resigned',async()=>{
51
+ const f=fixture();f.assertion.signature_b64url=b64(randomBytes(70));
52
+ await assert.rejects(verifyR174OwnerAssertion(f),/signature/i);
53
+ const g=fixture();g.authData[32]=1;reSign(g);
54
+ await assert.rejects(verifyR174OwnerAssertion(g),/user verification/i);
55
+ });
56
+ test('R174 refuses RP ID, origin, credential ID, and signature counter drift',async()=>{
57
+ const f=fixture();f.credential.rp_id='evil.example';
58
+ await assert.rejects(verifyR174OwnerAssertion(f),/rp id/i);
59
+ const g=fixture();g.credential.origin='https://evil.example';
60
+ await assert.rejects(verifyR174OwnerAssertion(g),/origin/i);
61
+ const h=fixture();h.credential.credential_id_b64url=b64(randomBytes(32));
62
+ await assert.rejects(verifyR174OwnerAssertion(h),/credential id/i);
63
+ const j=fixture();j.credential.sign_count=5;
64
+ await assert.rejects(verifyR174OwnerAssertion(j),/sign counter/i);
65
+ });
66
+ test('R174 refuses signed wrong challenge, cross origin, expired transaction',async()=>{
67
+ const f=fixture();f.clientData=Buffer.from(JSON.stringify({type:'webauthn.get',challenge:b64(randomBytes(32)),origin:f.credential.origin}));reSign(f);
68
+ await assert.rejects(verifyR174OwnerAssertion(f),/challenge/i);
69
+ const g=fixture();g.clientData=Buffer.from(JSON.stringify({type:'webauthn.get',challenge:g.transaction.challenge_b64url,origin:g.credential.origin,crossOrigin:true}));reSign(g);
70
+ await assert.rejects(verifyR174OwnerAssertion(g),/cross.origin/i);
71
+ const h=fixture();h.now=new Date('2026-10-27T07:05:00.000Z');
72
+ await assert.rejects(verifyR174OwnerAssertion(h),/expired/i);
73
+ });
74
+ test('R174 refuses policy-bound subject drift and overlong approval',async()=>{
75
+ const f=fixture();f.subject.root_id='projects';
76
+ await assert.rejects(verifyR174OwnerAssertion(f),/subject/i);
77
+ const g=fixture();g.transaction.expires_at_utc='2026-10-27T09:04:00.000Z';
78
+ await assert.rejects(verifyR174OwnerAssertion(g),/lifetime/i);
79
+ });
80
+ async function withLedger(work){
81
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r174-owner-ledger-'));
82
+ try{return await work(dir);}finally{await rm(dir,{recursive:true,force:true});}
83
+ }
84
+ test('R174 consumes verified assertion once with append-only JSON receipt and no authority grant',()=>withLedger(async ledgerRoot=>{
85
+ const result=await acceptR174OwnerProof({...fixture(),ledgerRoot});
86
+ assert.equal(result.status,'consumed');
87
+ assert.equal(result.authority_granted,false);
88
+ const files=await readdir(ledgerRoot);
89
+ assert.equal(files.length,1);
90
+ const receipt=JSON.parse(await readFile(path.join(ledgerRoot,files[0]),'utf8'));
91
+ assert.equal(receipt.schema,'deadbyte.r174-consumed-owner-challenge.v1');
92
+ assert.equal(receipt.subject_sha256,result.subject_sha256);
93
+ assert.equal(receipt.authority_granted,false);
94
+ }));
95
+ test('R174 rejects repeated or concurrent presentation of the same signed challenge',()=>withLedger(async ledgerRoot=>{
96
+ const f=fixture();const once=await acceptR174OwnerProof({...f,ledgerRoot});
97
+ assert.equal(once.status,'consumed');
98
+ await assert.rejects(acceptR174OwnerProof({...f,ledgerRoot}),/replay/i);
99
+ const another=fixture();
100
+ const results=await Promise.allSettled([
101
+ acceptR174OwnerProof({...another,ledgerRoot}),
102
+ acceptR174OwnerProof({...another,ledgerRoot})
103
+ ]);
104
+ assert.deepEqual(results.map(x=>x.status).sort(),['fulfilled','rejected']);
105
+ assert.equal((await readdir(ledgerRoot)).length,2);
106
+ }));
107
+ test('R174 invalid assertion never consumes challenge',()=>withLedger(async ledgerRoot=>{
108
+ const f=fixture();f.assertion.signature_b64url=b64(randomBytes(70));
109
+ await assert.rejects(acceptR174OwnerProof({...f,ledgerRoot}),/signature/i);
110
+ assert.equal((await readdir(ledgerRoot)).length,0);
111
+ }));
112
+ test('R174 same challenge cannot be reused with a different credential',()=>withLedger(async ledgerRoot=>{
113
+ const f=fixture();
114
+ const g=fixture();
115
+ g.transaction.challenge_b64url=f.transaction.challenge_b64url;
116
+ g.clientData=Buffer.from(JSON.stringify({type:'webauthn.get',challenge:g.transaction.challenge_b64url,origin:g.credential.origin,crossOrigin:false}));
117
+ reSign(g);
118
+ assert.equal((await acceptR174OwnerProof({...f,ledgerRoot})).status,'consumed');
119
+ await assert.rejects(acceptR174OwnerProof({...g,ledgerRoot}),/replay/i);
120
+ }));
121
+ test('R174 rejects counter downgrade from nonzero previous to zero',async()=>{
122
+ const f=fixture();f.authData.writeUInt32BE(0,33);reSign(f);
123
+ await assert.rejects(verifyR174OwnerAssertion(f),/sign counter/i);
124
+ });
125
+ test('R174 refuses consumption when independently pinned owner trust is missing',()=>withLedger(async ledgerRoot=>{
126
+ const f=fixture();delete f.ownerTrust;
127
+ await assert.rejects(acceptR174OwnerProof({...f,ledgerRoot}),/owner trust/i);
128
+ assert.equal((await readdir(ledgerRoot)).length,0);
129
+ }));
130
+ test('R174 refuses self-signed substitute owner credential against pinned trust',()=>withLedger(async ledgerRoot=>{
131
+ const known=fixture(),impostor=fixture();
132
+ impostor.ownerTrust=known.ownerTrust;
133
+ await assert.rejects(acceptR174OwnerProof({...impostor,ledgerRoot}),/owner trust|credential|public key/i);
134
+ assert.equal((await readdir(ledgerRoot)).length,0);
135
+ }));
136
+ test('R174 refuses legitimate key if expected reviewed subject changed',()=>withLedger(async ledgerRoot=>{
137
+ const f=fixture();
138
+ f.subject.max_seconds=100;
139
+ f.transaction.subject_sha256=hex(Buffer.from(canonicalJson(f.subject)));
140
+ await assert.rejects(acceptR174OwnerProof({...f,ledgerRoot}),/owner trust|subject/i);
141
+ assert.equal((await readdir(ledgerRoot)).length,0);
142
+ }));
143
+
144
+
@@ -0,0 +1,23 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,readdir,rm} from 'node:fs/promises';
4
+ import {spawnSync} from 'node:child_process';
5
+ import {fileURLToPath} from 'node:url';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+
9
+ const cli=fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url));
10
+ for(const mode of ['--operator','--desktop','--host','--full']){
11
+ test('R175 '+mode+' refuses implicit session-owned grant escalation before runtime state access',async()=>{
12
+ const state=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r175-flags-'));
13
+ try{
14
+ const child=spawnSync(process.execPath,[cli,'remote',mode],{
15
+ encoding:'utf8',windowsHide:true,timeout:15000,env:{...process.env,DEADBYTE_STATE_ROOT:state}
16
+ });
17
+ assert.equal(child.error,undefined,String(child.error));
18
+ assert.notEqual(child.status,0);
19
+ assert.match(child.stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
20
+ assert.deepEqual(await readdir(state),[],'failed flag must not create an authority marker or any state');
21
+ }finally{await rm(state,{recursive:true,force:true});}
22
+ });
23
+ }