deadbyte-mcp 0.15.68 → 0.15.69

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 (83) hide show
  1. package/MANIFEST.SHA256 +81 -38
  2. package/PROVENANCE.json +35 -23
  3. package/README.txt +44 -31
  4. package/RELEASE.json +30 -17
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +25 -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 +69 -56
  31. package/src/r174-owner-webauthn.mjs +176 -0
  32. package/src/r175-foreground-boundary.mjs +28 -0
  33. package/src/r186-owner-anchor.mjs +12 -0
  34. package/src/r186-owner-consent-preflight.mjs +63 -0
  35. package/src/r186-owner-grant-broker.mjs +96 -0
  36. package/src/r186-owner-local-signing.mjs +25 -0
  37. package/src/r186-owner-request.mjs +40 -0
  38. package/src/r186-owner-review-export.mjs +49 -0
  39. package/src/remote-live-log.mjs +81 -5
  40. package/src/remote-session-guardian.mjs +23 -0
  41. package/src/version.mjs +1 -1
  42. package/test/helpers/r175-active-pointer.mjs +28 -0
  43. package/test/helpers/release-lock.mjs +12 -0
  44. package/test/r129-sdk-security-baseline.test.mjs +2 -1
  45. package/test/r130-release-security-contract.test.mjs +11 -10
  46. package/test/r132-release-identity.test.mjs +12 -11
  47. package/test/r153-prepublish-identity.test.mjs +1 -1
  48. package/test/r155-default-live-log.test.mjs +17 -16
  49. package/test/r160-package-decision-manifest.test.mjs +1 -1
  50. package/test/r160-release-contract.test.mjs +4 -5
  51. package/test/r163-isolated-slot-rollback-restore.test.mjs +6 -6
  52. package/test/r165-live-cli-skew.test.mjs +6 -6
  53. package/test/r167-release-contract.test.mjs +15 -13
  54. package/test/r168-protected-entrypoint.test.mjs +5 -2
  55. package/test/r168-release-contract.test.mjs +4 -3
  56. package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
  57. package/test/r172-release-contract.test.mjs +7 -7
  58. package/test/r173-release-contract.test.mjs +18 -17
  59. package/test/r174-owner-webauthn.test.mjs +144 -0
  60. package/test/r175-cli-flags.test.mjs +23 -0
  61. package/test/r175-foreground-boundary.test.mjs +87 -0
  62. package/test/r175-guardian-owner-boundary.test.mjs +56 -0
  63. package/test/r176-pretty-live-log.test.mjs +54 -0
  64. package/test/r177-windows-absolute-live-path.test.mjs +37 -0
  65. package/test/r179-absolute-path-live-log.test.mjs +40 -0
  66. package/test/r179-release-contract.test.mjs +62 -0
  67. package/test/r186-controller-owner-grant.test.mjs +20 -0
  68. package/test/r186-owner-consent-preflight.test.mjs +98 -0
  69. package/test/r186-owner-enrollment.test.mjs +20 -0
  70. package/test/r186-owner-grant-broker.test.mjs +85 -0
  71. package/test/r186-owner-local-signing.test.mjs +30 -0
  72. package/test/r186-owner-request.test.mjs +28 -0
  73. package/test/r186-owner-review-export.test.mjs +52 -0
  74. package/test/r48-release-contract.test.mjs +2 -1
  75. package/test/r51-runtime-coherence.test.mjs +4 -2
  76. package/test/r58-cleanup-retention.test.mjs +5 -12
  77. package/test/r58-concurrent-recovery.test.mjs +5 -1
  78. package/test/r58-stale-owner.test.mjs +9 -3
  79. package/test/r59-tool-surface-compat.test.mjs +1 -1
  80. package/test/remote-live-log-r37.test.mjs +2 -2
  81. package/test/remote-live-log-recovery-r36.test.mjs +1 -1
  82. package/test/remote-session-cli.test.mjs +92 -126
  83. package/node_modules/which/CHANGELOG.md +0 -166
@@ -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.69';
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,'R179');
24
+ assert.equal(matrix.train,'R179');
25
+ assert.equal(bom.serialNumber,'urn:cdx:deadbyte-mcp:r179:0.15.69');
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.68');
32
+ assert.equal(release.source_predecessor.train,'R173');
33
+ assert.equal(release.source_predecessor.tarball_sha256,'1a7f1fe05a06178b8d772a140be6e0470790b743cef690c3f8f8165cc61075bf');
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,'ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885');
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('R179 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.69');
20
+ assert.equal(release.train,'R179'); assert.equal(matrix.train,'R179');
21
+ assert.equal(release.source_predecessor.version,'0.15.68');
22
+ assert.equal(release.source_predecessor.manifest_sha256,'ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885');
23
+ assert.equal(release.source_predecessor.tarball_sha256,'1a7f1fe05a06178b8d772a140be6e0470790b743cef690c3f8f8165cc61075bf');
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('R179 package identity binds the exact published R173 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.69');
17
+ assert.equal(lock.version,'0.15.69');
18
+ assert.equal(lock.packages[''].version,'0.15.69');
19
+ assert.equal(bridge.version,'0.15.69');
20
+ assert.equal(release.version,'0.15.69');
21
+ assert.equal(release.train,'R179');
22
+ assert.equal(matrix.version,'0.15.69');
23
+ assert.equal(matrix.train,'R179');
24
+ assert.equal(sbom.metadata.component.version,'0.15.69');
25
+ assert.equal(release.source_predecessor.version,'0.15.68');
26
+ assert.equal(release.source_predecessor.train,'R173');
27
+ assert.equal(release.source_predecessor.manifest_sha256,'ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885');
28
+ assert.equal(release.source_predecessor.tarball_sha256,'1a7f1fe05a06178b8d772a140be6e0470790b743cef690c3f8f8165cc61075bf');
29
+ assert.equal(release.runtime_predecessor.version,'0.15.68');
30
+ assert.equal(provenance.version,'0.15.69');
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
+ }
@@ -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
+ });