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,12 @@
1
+ import {createHash,createPublicKey} from 'node:crypto';
2
+
3
+ export function createR186OwnerAnchor(publicKeyPem){
4
+ if(typeof publicKeyPem!=='string'||publicKeyPem.length<32||publicKeyPem.length>8192||/PRIVATE KEY/.test(publicKeyPem))
5
+ throw new Error('R186 owner enrollment requires a public PEM only');
6
+ let key;try{key=createPublicKey(publicKeyPem);}catch{throw new Error('R186 owner public key invalid');}
7
+ if(key.asymmetricKeyType!=='ed25519')throw new Error('R186 owner public key must be Ed25519');
8
+ const canonicalPem=key.export({type:'spki',format:'pem'}).toString();
9
+ const owner_key_id=createHash('sha256').update(key.export({type:'spki',format:'der'})).digest('hex');
10
+ return Object.freeze({anchor:Object.freeze({schema:'deadbyte.r186-owner-anchor.v1',owner_key_id}),
11
+ public_key_pem:canonicalPem});
12
+ }
@@ -0,0 +1,63 @@
1
+ // R186: only a read-only verifier for independently enrolled owner consent.
2
+ // A successful result MUST NOT be treated as an Autonomous grant. The caller
3
+ // must separately prove protected enrollment, single-use consumption and epoch.
4
+ import {createHash,createPublicKey} from 'node:crypto';
5
+ import path from 'node:path';
6
+ import {verifyProjectOwnerApproval} from './r70-owner-approval.mjs';
7
+
8
+ const SHA=/^[0-9a-f]{64}$/;
9
+ function exact(value,fields,label){
10
+ if(!value||typeof value!=='object'||Array.isArray(value)||
11
+ Object.keys(value).length!==fields.length||
12
+ fields.some(field=>!Object.hasOwn(value,field)))
13
+ throw new Error('R186 '+label+' invalid');
14
+ }
15
+ export async function verifyR186OwnerConsent({ownerTrust,ownerPublicKeyPem,approval,expected,now=new Date()}={}){
16
+ exact(ownerTrust,['schema','owner_key_id'],'owner anchor');
17
+ if(ownerTrust.schema!=='deadbyte.r186-owner-anchor.v1'||
18
+ typeof ownerTrust.owner_key_id!=='string'||!SHA.test(ownerTrust.owner_key_id))
19
+ throw new Error('R186 owner anchor schema or fingerprint invalid');
20
+ if(typeof ownerPublicKeyPem!=='string'||ownerPublicKeyPem.length>8192)
21
+ throw new Error('R186 owner anchor public key invalid');
22
+ let pub;
23
+ try{pub=createPublicKey(ownerPublicKeyPem);}catch{throw new Error('R186 owner anchor public key invalid');}
24
+ if(pub.asymmetricKeyType!=='ed25519')throw new Error('R186 owner anchor must be Ed25519');
25
+ const keySha=createHash('sha256').update(pub.export({type:'spki',format:'der'})).digest('hex');
26
+ if(ownerTrust.owner_key_id!==keySha)
27
+ throw new Error('R186 owner anchor public key mismatch');
28
+ const approved=await verifyProjectOwnerApproval({approval,ownerPublicKey:pub,expected,now});
29
+ return Object.freeze({status:'verified_readonly',authority_granted:false,
30
+ owner_key_id:keySha,subject_sha256:approved.subject_sha256,
31
+ root_id:approved.root_id,project_root:approved.project_root,
32
+ expires_at_utc:approved.expires_at_utc,
33
+ next_gate:'PROTECTED_OWNER_ENROLLMENT_AND_SINGLE_USE_EPOCH_ISSUANCE_REQUIRED'});
34
+ }
35
+
36
+ export function verifyR186PolicyRootBinding({
37
+ codingPolicy,autonomousPolicy,rootId,projectRoot,codingPolicySha256
38
+ }={}){
39
+ if(!codingPolicy||typeof codingPolicy!=='object'||
40
+ codingPolicy.schema!=='deadbyte.coding-policy.v1'||codingPolicy.enabled!==true)
41
+ throw new Error('R186 coding policy schema invalid');
42
+ if(!autonomousPolicy||typeof autonomousPolicy!=='object'||
43
+ autonomousPolicy.schema!=='deadbyte.autonomous-policy.v1'||autonomousPolicy.enabled!==true)
44
+ throw new Error('R186 autonomous policy schema invalid');
45
+ if(typeof rootId!=='string'||!/^[A-Za-z0-9._-]{1,64}$/.test(rootId)||
46
+ !Array.isArray(autonomousPolicy.root_ids)||
47
+ autonomousPolicy.root_ids.length!==1||autonomousPolicy.root_ids[0]!==rootId)
48
+ throw new Error('R186 Autonomous root vector exceeds owner-reviewed scope');
49
+ if(typeof codingPolicySha256!=='string'||!SHA.test(codingPolicySha256)||
50
+ autonomousPolicy.coding_policy_sha256!==codingPolicySha256)
51
+ throw new Error('R186 coding-policy digest binding mismatch');
52
+ const bound=codingPolicy.roots?.[rootId]?.path;
53
+ if(typeof bound!=='string'||typeof projectRoot!=='string'||
54
+ !path.win32.isAbsolute(bound)||!path.win32.isAbsolute(projectRoot)||
55
+ bound.startsWith('\\\\')||projectRoot.startsWith('\\\\'))
56
+ throw new Error('R186 coding project root is not a real absolute drive path');
57
+ const actual=path.win32.normalize(bound).toLowerCase();
58
+ const requested=path.win32.normalize(projectRoot).toLowerCase();
59
+ if(actual!==requested||
60
+ actual===path.win32.parse(actual).root.toLowerCase())
61
+ throw new Error('R186 reviewed root must match selected Coding project root');
62
+ return Object.freeze({root_id:rootId,project_root:bound});
63
+ }
@@ -0,0 +1,96 @@
1
+ // Single-use R186 Autonomous grant issuance. This module does not enroll owner
2
+ // keys and accepts no caller-selected authority scope: the policy, reviewed
3
+ // manifest and independently protected owner anchor are the complete subject.
4
+ import {createHash,createPrivateKey,createPublicKey,randomBytes,sign,verify} from 'node:crypto';
5
+ import {mkdir,open,readFile,writeFile} from 'node:fs/promises';
6
+ import path from 'node:path';
7
+ import {canonicalJson} from './canonical-json.mjs';
8
+ import {createAuthorityEpochStore} from './authority-continuity-r65.mjs';
9
+ import {verifyR186OwnerConsent,verifyR186PolicyRootBinding} from './r186-owner-consent-preflight.mjs';
10
+
11
+ const SHA=/^[0-9a-f]{64}$/;
12
+ const TERMINAL_DOMAIN=Buffer.from('DEADBYTE-R186-OWNER-GRANT-TERMINAL\0','utf8');
13
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
14
+ const bytes=value=>Buffer.from(canonicalJson(value),'utf8');
15
+ async function writeExclusive(file,data){
16
+ await mkdir(path.dirname(file),{recursive:true});
17
+ const handle=await open(file,'wx',0o600);
18
+ try{await handle.writeFile(data);await handle.sync();}
19
+ finally{await handle.close();}
20
+ }
21
+ function insist(condition,message){if(!condition)throw new Error(`R186 owner grant blocked: ${message}`);}
22
+ async function signTerminal(runtimeDir,body){
23
+ const trustDir=path.join(path.dirname(path.resolve(runtimeDir)),'trust');
24
+ const [privatePem,publicPem]=await Promise.all([
25
+ readFile(path.join(trustDir,'ed25519-private.pem')),readFile(path.join(trustDir,'ed25519-public.pem'))]);
26
+ const privateKey=createPrivateKey(privatePem),publicKey=createPublicKey(publicPem);
27
+ insist(privateKey.asymmetricKeyType==='ed25519'&&publicKey.asymmetricKeyType==='ed25519','machine receipt key type invalid');
28
+ const privateKeyId=sha(createPublicKey(privateKey).export({type:'spki',format:'der'}));
29
+ const publicKeyId=sha(publicKey.export({type:'spki',format:'der'}));
30
+ insist(privateKeyId===publicKeyId,'machine receipt key pair mismatch');
31
+ const signature=sign(null,Buffer.concat([TERMINAL_DOMAIN,bytes(body)]),privateKey);
32
+ insist(verify(null,Buffer.concat([TERMINAL_DOMAIN,bytes(body)]),publicKey,signature),'machine terminal signature self-check failed');
33
+ return Object.freeze({algorithm:'ed25519',domain:'DEADBYTE-R186-OWNER-GRANT-TERMINAL',key_id:publicKeyId,signature_base64:signature.toString('base64')});
34
+ }
35
+
36
+ export async function issueR186AutonomousGrant({
37
+ runtimeDir,grantFile,codingPolicy,codingPolicyBytes,autonomousPolicy,autonomousPolicyBytes,
38
+ releaseManifestBytes,ownerTrust,ownerPublicKeyPem,approval,now=new Date(),epochStore=null
39
+ }={}){
40
+ insist(typeof runtimeDir==='string'&&path.isAbsolute(runtimeDir),'runtime directory invalid');
41
+ insist(typeof grantFile==='string'&&path.isAbsolute(grantFile),'grant path invalid');
42
+ for(const [name,value] of [['Coding policy',codingPolicyBytes],['Autonomous policy',autonomousPolicyBytes],['release manifest',releaseManifestBytes]])
43
+ insist(Buffer.isBuffer(value)&&value.length>0,`${name} bytes invalid`);
44
+ insist(autonomousPolicy?.schema==='deadbyte.autonomous-policy.v1'&&autonomousPolicy.enabled===true,'Autonomous policy invalid');
45
+ insist(path.resolve(autonomousPolicy.lease_file)===path.resolve(grantFile),'policy grant path mismatch');
46
+ const runtimePrefix=path.resolve(runtimeDir)+path.sep;
47
+ insist(path.resolve(grantFile).startsWith(runtimePrefix),'grant path outside runtime');
48
+ const codingSha=sha(codingPolicyBytes),autonomousSha=sha(autonomousPolicyBytes),manifestSha=sha(releaseManifestBytes);
49
+ const rootId=Array.isArray(autonomousPolicy.root_ids)&&autonomousPolicy.root_ids.length===1?autonomousPolicy.root_ids[0]:null;
50
+ const projectRoot=rootId?codingPolicy?.roots?.[rootId]?.path:null;
51
+ const binding=verifyR186PolicyRootBinding({codingPolicy,autonomousPolicy,rootId,projectRoot,codingPolicySha256:codingSha});
52
+ const consent=await verifyR186OwnerConsent({ownerTrust,ownerPublicKeyPem,approval,now,expected:{
53
+ rootId:binding.root_id,projectRoot:binding.project_root,codingPolicySha256:codingSha,
54
+ autonomousPolicySha256:autonomousSha,reviewedArtifactSha256:manifestSha
55
+ }});
56
+ const evidenceDir=path.join(runtimeDir,'owner-consent','r186');
57
+ const claimPath=path.join(evidenceDir,`${consent.subject_sha256}.claim.json`);
58
+ const terminalPath=path.join(evidenceDir,`${consent.subject_sha256}.terminal.json`);
59
+ try{await readFile(terminalPath);throw new Error('R186 owner approval already consumed; replay denied');}
60
+ catch(error){if(error?.code!=='ENOENT')throw error;}
61
+ const nonce=randomBytes(32).toString('hex');
62
+ const claim={schema:'deadbyte.r186-owner-grant-claim.v1',subject_sha256:consent.subject_sha256,
63
+ owner_key_id:consent.owner_key_id,policy_sha256:autonomousSha,coding_policy_sha256:codingSha,
64
+ release_manifest_sha256:manifestSha,root_id:binding.root_id,project_root:binding.project_root,
65
+ grant_instance_nonce:nonce,claimed_at_utc:now.toISOString()};
66
+ try{await writeExclusive(claimPath,bytes(claim));}
67
+ catch(error){if(error?.code==='EEXIST')throw new Error('R186 owner approval already consumed or in progress; replay denied');throw error;}
68
+ const grant={schema:'deadbyte.autonomous-grant.v2',policy_sha256:autonomousSha,
69
+ coding_policy_sha256:codingSha,instance_nonce:nonce,root_ids:[binding.root_id],
70
+ granted_at_utc:now.toISOString(),expires_at_utc:consent.expires_at_utc};
71
+ const grantBytes=bytes(grant);
72
+ try{await writeExclusive(grantFile,grantBytes);}
73
+ catch(error){throw Object.assign(new Error(`R186 grant write failed after durable claim: ${error?.message??error}`),{outcome:'OUTCOME_UNKNOWN'});}
74
+ const store=epochStore??createAuthorityEpochStore({runtimeDir,policyBytes:autonomousPolicyBytes});
75
+ let head;
76
+ try{head=await store.append({plane:'autonomous',action:'grant',grantBytes,grantInstanceNonce:nonce,updateId:null});}
77
+ catch(error){throw Object.assign(new Error(`R186 epoch issuance failed after grant write: ${error?.message??error}`),{outcome:'OUTCOME_UNKNOWN'});}
78
+ insist(SHA.test(String(head?.event_sha256)),'epoch identity invalid after issuance');
79
+ const verified=await store.verifyGrant({plane:'autonomous',grantBytes,grantInstanceNonce:nonce,policyBytes:autonomousPolicyBytes});
80
+ insist(verified?.trusted===true&&verified.event_sha256===head.event_sha256,'grant epoch read-back failed');
81
+ insist((await readFile(grantFile)).equals(grantBytes),'grant changed after epoch issuance');
82
+ const terminalBody={schema:'deadbyte.r186-owner-grant-terminal.v1',status:'GRANTED',
83
+ subject_sha256:consent.subject_sha256,owner_key_id:consent.owner_key_id,
84
+ claim_sha256:sha(bytes(claim)),grant_sha256:sha(grantBytes),grant_instance_nonce:nonce,
85
+ policy_sha256:autonomousSha,coding_policy_sha256:codingSha,release_manifest_sha256:manifestSha,
86
+ root_id:binding.root_id,project_root:binding.project_root,epoch_event_sha256:head.event_sha256,
87
+ epoch_seq:head.seq,expires_at_utc:consent.expires_at_utc,completed_at_utc:new Date().toISOString()};
88
+ const attestation=await signTerminal(runtimeDir,terminalBody);
89
+ const unsigned={body:terminalBody,attestation};
90
+ const terminal={...unsigned,receipt_sha256:sha(bytes(unsigned))};
91
+ try{await writeExclusive(terminalPath,bytes(terminal));}
92
+ catch(error){throw Object.assign(new Error(`R186 terminal receipt write failed: ${error?.message??error}`),{outcome:'OUTCOME_UNKNOWN'});}
93
+ return Object.freeze({status:'GRANTED',authority_granted:true,subject_sha256:consent.subject_sha256,
94
+ grant_sha256:terminalBody.grant_sha256,epoch_event_sha256:head.event_sha256,epoch_seq:head.seq,
95
+ terminal_path:terminalPath,receipt_sha256:terminal.receipt_sha256});
96
+ }
@@ -0,0 +1,25 @@
1
+ // Human-invoked local signer. Callers receive approval JSON only; the private
2
+ // key is never returned or embedded in a request/receipt.
3
+ import {createHash,createPrivateKey,createPublicKey,sign} from 'node:crypto';
4
+ import {canonicalJson} from './canonical-json.mjs';
5
+
6
+ const DOMAIN=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
7
+ const SHA=/^[0-9a-f]{64}$/;
8
+ const fields=['schema','subject','subject_sha256','owner_signature_present','authority_granted'];
9
+ const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ export function createR186OwnerApproval({request,privateKeyPem}={}){
11
+ if(!request||typeof request!=='object'||Array.isArray(request)||Object.keys(request).length!==fields.length||fields.some(k=>!Object.hasOwn(request,k))||
12
+ request.schema!=='deadbyte.r186-owner-review-request.v1'||request.owner_signature_present!==false||request.authority_granted!==false)
13
+ throw new Error('R186 owner review request invalid');
14
+ const subjectSha=hash(Buffer.from(canonicalJson(request.subject),'utf8'));
15
+ if(!SHA.test(String(request.subject_sha256))||request.subject_sha256!==subjectSha)
16
+ throw new Error('R186 owner review request subject mismatch');
17
+ if(typeof privateKeyPem!=='string'||!/PRIVATE KEY/.test(privateKeyPem))throw new Error('R186 owner private key invalid');
18
+ let privateKey;try{privateKey=createPrivateKey(privateKeyPem);}catch{throw new Error('R186 owner private key invalid');}
19
+ if(privateKey.asymmetricKeyType!=='ed25519')throw new Error('R186 owner private key must be Ed25519');
20
+ const publicKey=createPublicKey(privateKey);
21
+ const owner_key_id=hash(publicKey.export({type:'spki',format:'der'}));
22
+ const signed={schema:'deadbyte.project-owner-approval.r70.v1',subject_sha256:subjectSha,owner_key_id};
23
+ const signature_base64=sign(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),privateKey).toString('base64');
24
+ return Object.freeze({...signed,subject:request.subject,signature_base64});
25
+ }
@@ -0,0 +1,40 @@
1
+ // Owner-review subject construction only. No keys, signatures or grants.
2
+ import {createHash} from 'node:crypto';
3
+ import path from 'node:path';
4
+ import {canonicalJson} from './canonical-json.mjs';
5
+
6
+ const SHA=/^[0-9a-f]{64}$/;
7
+ const ROOT=/^[A-Za-z0-9._-]{1,64}$/;
8
+ export function createR186OwnerReviewRequest({
9
+ rootId,projectRoot,codingPolicySha256,autonomousPolicySha256,
10
+ reviewedArtifactSha256,now=new Date(),durationMs=600000
11
+ }={}){
12
+ if(typeof rootId!=='string'||!ROOT.test(rootId))
13
+ throw new Error('R186 invalid root ID');
14
+ if(typeof projectRoot!=='string'||!path.win32.isAbsolute(projectRoot)||
15
+ projectRoot.startsWith('\\\\'))
16
+ throw new Error('R186 invalid project root');
17
+ for(const [name,value] of [
18
+ ['codingPolicySha256',codingPolicySha256],
19
+ ['autonomousPolicySha256',autonomousPolicySha256],
20
+ ['reviewedArtifactSha256',reviewedArtifactSha256]
21
+ ])if(typeof value!=='string'||!SHA.test(value))
22
+ throw new Error('R186 invalid '+name);
23
+ if(!(now instanceof Date)||!Number.isFinite(now.getTime())||
24
+ !Number.isSafeInteger(durationMs)||durationMs<1000||durationMs>3600000)
25
+ throw new Error('R186 invalid bounded consent duration');
26
+ const issued_at_utc=now.toISOString();
27
+ const expires_at_utc=new Date(now.getTime()+durationMs).toISOString();
28
+ const subject=Object.freeze({
29
+ schema:'deadbyte.project-authority-subject.r70.v1',
30
+ scope:'coding_autonomous_native_diff',
31
+ root_id:rootId,project_root:path.win32.normalize(projectRoot),
32
+ coding_policy_sha256:codingPolicySha256,
33
+ autonomous_policy_sha256:autonomousPolicySha256,
34
+ reviewed_artifact_sha256:reviewedArtifactSha256,
35
+ issued_at_utc,expires_at_utc
36
+ });
37
+ const subject_sha256=createHash('sha256').update(Buffer.from(canonicalJson(subject))).digest('hex');
38
+ return Object.freeze({schema:'deadbyte.r186-owner-review-request.v1',
39
+ subject,subject_sha256,owner_signature_present:false,authority_granted:false});
40
+ }
@@ -0,0 +1,49 @@
1
+ // Read-only export of the exact subject a physical owner may review and sign.
2
+ // This boundary neither reads a private key nor creates/changes authority.
3
+ import {createHash} from 'node:crypto';
4
+ import {lstat,open,readFile} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import {canonicalJson} from './canonical-json.mjs';
7
+ import {verifyR186PolicyRootBinding} from './r186-owner-consent-preflight.mjs';
8
+ import {createR186OwnerReviewRequest} from './r186-owner-request.mjs';
9
+
10
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ async function boundedRegular(file,limit,label){
12
+ const info=await lstat(file);
13
+ if(!info.isFile()||info.isSymbolicLink()||info.size<1||info.size>limit)
14
+ throw new Error(`R186 ${label} is a reparse, symlink or invalid file`);
15
+ return readFile(file);
16
+ }
17
+ async function writeExclusive(file,bytes){
18
+ const parent=await lstat(path.dirname(file));
19
+ if(!parent.isDirectory()||parent.isSymbolicLink())
20
+ throw new Error('R186 review output parent is a reparse or symlink');
21
+ const handle=await open(file,'wx',0o600);
22
+ try{await handle.writeFile(bytes);await handle.sync();}
23
+ finally{await handle.close();}
24
+ }
25
+
26
+ export async function writeR186OwnerReviewRequest({
27
+ rootId,projectRoot,codingPolicyPath,autonomousPolicyPath,manifestPath,outputPath,
28
+ now=new Date(),durationMs=600000
29
+ }={}){
30
+ for(const [name,value] of [['project root',projectRoot],['Coding policy',codingPolicyPath],
31
+ ['Autonomous policy',autonomousPolicyPath],['manifest',manifestPath],['output',outputPath]]){
32
+ if(typeof value!=='string'||!path.isAbsolute(value))throw new Error(`R186 ${name} path must be absolute`);
33
+ }
34
+ const [codingBytes,autonomousBytes,manifestBytes]=await Promise.all([
35
+ boundedRegular(codingPolicyPath,1024*1024,'Coding policy'),
36
+ boundedRegular(autonomousPolicyPath,1024*1024,'Autonomous policy'),
37
+ boundedRegular(manifestPath,512*1024,'release manifest')]);
38
+ const codingPolicy=JSON.parse(codingBytes.toString('utf8'));
39
+ const autonomousPolicy=JSON.parse(autonomousBytes.toString('utf8'));
40
+ const codingPolicySha256=sha(codingBytes);
41
+ const binding=verifyR186PolicyRootBinding({codingPolicy,autonomousPolicy,rootId,
42
+ projectRoot,codingPolicySha256});
43
+ const request=createR186OwnerReviewRequest({rootId:binding.root_id,
44
+ projectRoot:binding.project_root,codingPolicySha256,
45
+ autonomousPolicySha256:sha(autonomousBytes),reviewedArtifactSha256:sha(manifestBytes),
46
+ now,durationMs});
47
+ await writeExclusive(outputPath,Buffer.from(`${canonicalJson(request)}\n`,'utf8'));
48
+ return request;
49
+ }
@@ -11,7 +11,7 @@ const LIFECYCLE=Object.freeze({
11
11
  session_new:'[SESSION] New foreground session',
12
12
  session_restored:'♻️ [SESSION] Previous result stream replayed',
13
13
  ready:'✅ [READY] DEADBYTE MCP online',
14
- cleanup:'🔐 [AUTH] Revoking session authority...'
14
+ cleanup:'[SESSION] Closing observation session...'
15
15
  });
16
16
  const LIFECYCLE_ASCII=Object.freeze({
17
17
  device_start:'[DEVICE] Starting DEADBYTE MCP...',
@@ -20,7 +20,7 @@ const LIFECYCLE_ASCII=Object.freeze({
20
20
  session_new:'[SESSION] New foreground session',
21
21
  session_restored:'[SESSION] Previous result stream replayed',
22
22
  ready:'[READY] DEADBYTE MCP online',
23
- cleanup:'[AUTH] Revoking session authority...'
23
+ cleanup:'[SESSION] Closing observation session...'
24
24
  });
25
25
 
26
26
  function safeAtom(value){
@@ -104,13 +104,33 @@ function rootedPath(args,key='path',rootPaths={}){
104
104
  if(base){
105
105
  const flavor=/^(?:[A-Za-z]:[\\/]|\\\\)/.test(base)?path.win32:path.posix;
106
106
  const absolute=p==='.'?flavor.resolve(base):flavor.resolve(base,p);
107
+ const relative=flavor.relative(flavor.resolve(base),absolute);
108
+ // A display-only resolver must not misleadingly mark traversal as in-root.
109
+ if(relative==='..'||relative.startsWith('..'+flavor.sep)||flavor.isAbsolute(relative))
110
+ return '[outside root='+root+'] '+p;
107
111
  return absolute+' [root='+root+']';
108
112
  }
113
+ // We do not invent drive letters when the trusted runtime policy has
114
+ // not supplied the actual root mapping.
109
115
  return root+':'+p;
110
116
  }
111
117
  if(root) return 'root='+root;
112
118
  return p?'path='+p:'';
113
119
  }
120
+ function detailedDisplayPaths(args,rootPaths){
121
+ const safe=sanitizeAuditArgs(args??{});
122
+ const paths=[];
123
+ const entry=(key,label)=>{
124
+ if(!safePathAtom(safe?.[key]))return;
125
+ const value=rootedPath(safe,key,rootPaths)+(safe.root_id&&!safePathAtom(rootPaths?.[safe.root_id])?' [unresolved root]':'');
126
+ if(value)paths.push((label?label+': ':'')+value);
127
+ };
128
+ if(safe?.source_path&&safe?.destination_path){
129
+ entry('source_path','FROM');entry('destination_path','TO');
130
+ }else if(safe?.path)entry('path','');
131
+ else if(safe?.script_path)entry('script_path','SCRIPT');
132
+ return paths;
133
+ }
114
134
  function displayToolName(tool,args){
115
135
  const op=safeAtom(args?.op);
116
136
  if(op&&['machine_fs','coding_inspect','process_session','desktop_control','machine_observe'].includes(tool)) return tool+'.'+op;
@@ -399,10 +419,12 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
399
419
 
400
420
  export function defaultForegroundTimelineOptions({verbose=false}={}){
401
421
  if(typeof verbose!=='boolean')throw new Error('default live timeline verbosity invalid');
402
- return Object.freeze({commanderStyle:!verbose,foldHeartbeats:false});
422
+ // R176 default: readable emoji timeline with compact heartbeat groups.
423
+ // Commander/verbose presentation remains explicitly selectable by callers.
424
+ return Object.freeze({commanderStyle:false,foldHeartbeats:!verbose,prettyStyle:!verbose});
403
425
  }
404
426
 
405
- export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,foldHeartbeats=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000,commanderStyle=false}={}){
427
+ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,foldHeartbeats=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000,commanderStyle=false,prettyStyle=false}={}){
406
428
  if(typeof clock!=='function') throw new Error('live timeline clock must be a function');
407
429
  if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('remote display mode invalid');
408
430
  if(!Number.isInteger(heartbeatBurstLimit)||heartbeatBurstLimit<2||heartbeatBurstLimit>100) throw new Error('heartbeatBurstLimit invalid');
@@ -410,6 +432,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
410
432
  if(!rootPaths||typeof rootPaths!=='object'||Array.isArray(rootPaths)) throw new Error('live timeline rootPaths invalid');
411
433
  if(typeof trace!=='boolean') throw new Error('live timeline trace invalid');
412
434
  if(typeof commanderStyle!=='boolean') throw new Error('live timeline commanderStyle invalid');
435
+ if(typeof prettyStyle!=='boolean') throw new Error('live timeline prettyStyle invalid');
413
436
  if(typeof foldHeartbeats!=='boolean') throw new Error('live timeline foldHeartbeats invalid');
414
437
  if(!Number.isInteger(slowCallMs)||slowCallMs<100||slowCallMs>600000) throw new Error('live timeline slowCallMs invalid');
415
438
  const pending=new Map();
@@ -428,6 +451,23 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
428
451
  return visual+' '+displayClock(eventMs(event,clock))+' '+seq+'['+shortSpanId(event.request_id)+'] '+tool;
429
452
  }
430
453
  function traceSuffix(meta){return trace&&meta?' '+traceText(meta,displayMode):'';}
454
+ function prettyLine(event,tool,tag,detail='',glyph=null,displayPaths=[]){
455
+ const time=displayClock(eventMs(event,clock)).slice(0,8);
456
+ const title=tool.length>30?tool.slice(0,29)+'…':tool;
457
+ // Spaces avoid incorrect console-cell widths for double-width emoji.
458
+ const icon=glyph??iconForTool(tool,displayMode);
459
+ const leader=displayMode==='ascii'?' | ':' │ ';
460
+ const shortId=shortSpanId(event.request_id);
461
+ const label=title.padEnd(31,' ');
462
+ const headline=time+' '+icon+' '+tag.padEnd(8,' ')+' '+label+
463
+ leader+'['+shortId+']'+(detail?' '+clipped(detail,220):'');
464
+ if(!displayPaths.length)return remoteDisplayText(headline,displayMode);
465
+ const pathIcon=displayMode==='ascii'?'PATH: ':'📁 ';
466
+ // Paths are not shortened to an ambiguous basename: retain the Windows
467
+ // drive, directory tree and policy root. Respect the bounded input limit.
468
+ const descriptions=displayPaths.map(p=>' '+pathIcon+p);
469
+ return remoteDisplayText([headline,...descriptions].join('\n'),displayMode);
470
+ }
431
471
  function resultLine(event,span,sequence){
432
472
  const outcome=outcomeClass(event);
433
473
  const error=outcome==='error';
@@ -458,6 +498,16 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
458
498
  const prefixIcon=unknown?(displayMode==='ascii'?'WARN':'⚠️'):null;
459
499
  const evidence=receipt?' '+(displayMode==='ascii'?'RECEIPT':'🧾 receipt')+(displayMode==='ascii'?'=':'=')+receipt:'';
460
500
  const reasonSep=displayMode==='ascii'?' - ':' — ';
501
+ if(prettyStyle){
502
+ const tag=unknown?'UNKNOWN':pendingResult?'PENDING':error?(rawStatus==='denied'?'DENIED':'FAIL'):'PASS';
503
+ const glyph=displayMode==='ascii'?(unknown?'!':error?'X':pendingResult?'~':'+'):
504
+ unknown?'⚠️':error?'❌':pendingResult?'⏳':'✅';
505
+ const pathList=span?.display_paths??[];
506
+ const nonPathSummary=pathList.length?summary.replace(pathList[0],'').trim():summary;
507
+ const detail=[duration,trace&&span?.trace_meta?traceText(span.trace_meta,displayMode):'',nonPathSummary,statusDetail.trim(),receipt?'receipt='+receipt:'',
508
+ reason?reason:''].filter(Boolean).join(' | ');
509
+ return prettyLine(event,displayTool,tag,detail,glyph,pathList);
510
+ }
461
511
  if(commanderStyle){
462
512
  const tag=unknown?'OUTCOME_UNKNOWN':pendingResult?'pending':error?'failed':'completed';
463
513
  const state=rawStatus?' status='+rawStatus:'';
@@ -475,6 +525,11 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
475
525
  const endClock=displayClock(value.last_ms);
476
526
  const range=value.first_ms===value.last_ms?endClock:startClock.slice(0,8)+'–'+endClock.slice(0,8);
477
527
  if(value.count>1) foldedHeartbeatLines+=value.count-1;
528
+ if(prettyStyle){
529
+ return prettyLine({request_id:'polling',created_at_utc:new Date(value.last_ms).toISOString()},
530
+ value.tool,'FOLDED','×'+value.count+' avg '+formatDuration(avg),
531
+ displayMode==='ascii'?'H':'📡');
532
+ }
478
533
  const marker=displayMode==='ascii'?'OK':'✓';
479
534
  return remoteDisplayText(iconForTool(value.tool,displayMode)+' '+range+' '+seq+value.tool+' ×'+value.count+' avg '+formatDuration(avg)+' '+marker,displayMode);
480
535
  }
@@ -512,12 +567,13 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
512
567
  const args=event.args??{};
513
568
  const displayTool=displayToolName(tool,args);
514
569
  const summary=summarizeLiveToolArgs(displayTool,args,{rootPaths});
570
+ const displayPaths=prettyStyle?detailedDisplayPaths(args,rootPaths):[];
515
571
  const traceMeta=traceIdentity(event,args);
516
572
  if(pending.size>=MAX_PRESENTATION_IDENTITIES){
517
573
  pendingOverflow++;
518
574
  return pendingOverflow===1?remoteDisplayText('LIVELOG pending span cap reached; full audit remains on disk',displayMode):null;
519
575
  }
520
- pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,trace_meta:traceMeta,started_at_ms:clock()});
576
+ pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,display_paths:displayPaths,trace_meta:traceMeta,started_at_ms:clock()});
521
577
  peakInFlight=Math.max(peakInFlight,pending.size);
522
578
  if(commanderStyle){
523
579
  const safeArgs=sanitizeAuditArgs(args);
@@ -527,6 +583,9 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
527
583
  }
528
584
  if(verbose) return formatRemoteToolCall({...event,tool:displayTool},{clock,displayMode})+traceSuffix(traceMeta);
529
585
  if(visibleStart(displayTool)){
586
+ if(prettyStyle) return prettyLine(event,displayTool,'START',
587
+ [trace?traceText(traceMeta,displayMode):'',displayPaths.length?summary.replace(displayPaths[0],'').trim():summary].filter(Boolean).join(' | '),
588
+ null,displayPaths);
530
589
  const startMarker=displayMode==='ascii'?'START':'▶ START';
531
590
  return remoteDisplayText(eventPrefix(event,sequence,{displayTool})+traceSuffix(traceMeta)+' '+startMarker+(summary?' '+summary:''),displayMode);
532
591
  }
@@ -606,6 +665,23 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
606
665
  const p50=sorted.length?(sorted.length%2?sorted[(sorted.length-1)/2]:(sorted[sorted.length/2-1]+sorted[sorted.length/2])/2):null;
607
666
  const p95=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.95)-1)]:null;
608
667
  const p99=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.99)-1)]:null;
668
+ if(prettyStyle){
669
+ const ascii=displayMode==='ascii';
670
+ const lines=[
671
+ (ascii?'[SUMMARY]':'📊 SESSION')+' '+resultCount+' calls | '+okCount+' ok | '+
672
+ errorCount+(errorCount===1?' error | ':' errors | ')+unknownCount+' unknown'+(pendingCount?' | '+pendingCount+' pending':''),
673
+ (ascii?' work:':' 🛠 work:')+' '+mutationCount+' mutations | peak '+peakInFlight+' in-flight'+
674
+ (foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeats':''),
675
+ (ascii?' timing:':' ⏱ timing:')+' p50 '+(p50===null?'n/a':formatDuration(p50))+
676
+ ' | p95 '+(p95===null?'n/a':formatDuration(p95))+
677
+ ' | p99 '+(p99===null?'n/a':formatDuration(p99))+
678
+ (slowest?' | slowest '+slowest.tool+' '+formatDuration(slowest.duration_ms):''),
679
+ (ascii?' top:':' 🔝 top:')+(top?' '+top:' none')
680
+ ];
681
+ if(pendingOverflow)lines.push((ascii?' WARNING:':' ⚠️ WARNING:')+
682
+ ' '+pendingOverflow+' untracked presentation spans; inspect signed audit');
683
+ return remoteDisplayText(lines.join('\n'),displayMode);
684
+ }
609
685
  return remoteDisplayText((displayMode==='ascii'?'[SUMMARY]':'📊 [SUMMARY]')+' '+resultCount+' calls | '+okCount+' ok | '+
610
686
  errorCount+' error'+(errorCount===1?'':'s')+' | '+unknownCount+' unknown'+(pendingCount?' | '+pendingCount+' pending':'')+' | '+mutationCount+' mutation'+(mutationCount===1?'':'s')+
611
687
  ' | tool time '+(formatDuration(totalDurationMs)||'0ms')+
@@ -8,6 +8,7 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
8
  import { resolveStateRoot } from './state-root.mjs';
9
9
  import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
10
10
  import { readRemoteMarkerSnapshot, deletePinnedRemoteMarker } from './foreground-marker-cas-win32.mjs';
11
+ import {assertObserveOnlyMarker} from './r175-foreground-boundary.mjs';
11
12
 
12
13
  const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
13
14
  const TRUSTED_WINDOWS_ROOT='C:\\Windows';
@@ -68,6 +69,9 @@ async function main(){
68
69
  const ownerPid=Number(args['owner-pid']);
69
70
  const nonce=String(args['session-nonce']||'');
70
71
  const ipcOwner=args['owner-channel']==='ipc';
72
+ const observerGuard=args['authority-scope']==='observe-only';
73
+ if(args['authority-scope']!==undefined&&!observerGuard)
74
+ throw new Error('R175 guardian authority scope invalid');
71
75
  if(ipcOwner&&typeof process.send!=='function')throw new Error('guardian owner IPC channel missing');
72
76
  if(!Number.isInteger(ownerPid)||ownerPid<1) throw new Error('guardian owner pid invalid');
73
77
  if(ipcOwner&&process.ppid!==ownerPid)throw new Error('guardian IPC parent does not match declared owner');
@@ -85,6 +89,25 @@ async function main(){
85
89
  continue;
86
90
  }
87
91
 
92
+ // R179: a missing command-line flag is never permission to downgrade an
93
+ // observation-only marker to legacy broad authority revocation.
94
+ if(assertObserveOnlyMarker(marker)&&!observerGuard)
95
+ throw new Error('R179_OBSERVER_GUARDIAN_SCOPE_DOWNGRADE: owner grants retained');
96
+ if(observerGuard){
97
+ // Guardian scope is inherited from the parent process command line.
98
+ // Marker mismatch can never degrade into legacy unconditional revoke.
99
+ if(!assertObserveOnlyMarker(marker))
100
+ throw new Error('R175 observer guardian marker invalid; no grants touched');
101
+ // Observation-only sessions own the foreground marker, never the
102
+ // process/coding/host/desktop/autonomous grants or runtime.
103
+ const current=await readRemoteMarkerSnapshot(markerPath);
104
+ if(!current)return;
105
+ if(!markerMatches(current.marker,{ownerPid,nonce})||!assertObserveOnlyMarker(current.marker))return;
106
+ if(deletePinnedRemoteMarker(markerPath,current.sha256)!=='REMOVED')
107
+ throw new Error('R175 guardian monitor marker CAS failed; marker retained');
108
+ return;
109
+ }
110
+
88
111
  const failed=[];
89
112
  for(const command of CLEANUP){
90
113
  const latest=await readMarker(markerPath);
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.68';
1
+ export const DEADBYTE_VERSION = '0.15.70';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -0,0 +1,28 @@
1
+ import {createHash} from 'node:crypto';
2
+ import {mkdir,readFile,writeFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const sha256=value=>createHash('sha256').update(value).digest('hex');
6
+
7
+ export async function installExactActivePointer({stateRoot,packageRoot,launcherPath}){
8
+ const manifestBytes=await readFile(path.join(packageRoot,'MANIFEST.SHA256'));
9
+ const manifestSha256=sha256(manifestBytes);
10
+ const pkg=JSON.parse(await readFile(path.join(packageRoot,'package.json'),'utf8'));
11
+ const generationRoot=path.join(stateRoot,'generations','test-current');
12
+ const configPath=path.join(generationRoot,'controller-config.psd1');
13
+ await mkdir(generationRoot,{recursive:true});
14
+ await writeFile(configPath,"@{ Label = 'test-current' }\r\n",'utf8');
15
+ const pointer={
16
+ schema:'deadbyte.active-release.v1',
17
+ release_id:`v${pkg.version}-${manifestSha256.slice(0,16)}`,
18
+ version:pkg.version,
19
+ manifest_sha256:manifestSha256,
20
+ release_root:path.resolve(packageRoot),
21
+ generation_root:generationRoot,
22
+ generation_manifest_sha256:sha256(await readFile(configPath)),
23
+ launcher_sha256:sha256(await readFile(launcherPath)),
24
+ config_path:configPath
25
+ };
26
+ await writeFile(path.join(stateRoot,'active-release.json'),`${JSON.stringify(pointer)}\n`,'utf8');
27
+ return pointer;
28
+ }
@@ -0,0 +1,12 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
4
+
5
+ export async function readReleaseLock(root){
6
+ const rootPath=root instanceof URL?fileURLToPath(root):root;
7
+ for(const relative of ['package-lock.json','docs/R184-package-lock.json']){
8
+ try{return JSON.parse(await readFile(path.join(rootPath,relative),'utf8'));}
9
+ catch(error){if(error?.code!=='ENOENT')throw error;}
10
+ }
11
+ throw new Error('R184_RELEASE_LOCK_EVIDENCE_MISSING');
12
+ }
@@ -3,6 +3,7 @@ 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(fileURLToPath(new URL('..',import.meta.url)));
8
9
  async function json(file){return JSON.parse(await readFile(path.join(root,file),'utf8'));}
@@ -29,7 +30,7 @@ test('R129 shipped client and server SDK exclude all recorded 2.1.0 security adv
29
30
  });
30
31
 
31
32
  test('R129 lockfile resolves security-fixed SDK packages without prerelease versions',async()=>{
32
- const lock=await json('package-lock.json');
33
+ const lock=await readReleaseLock(root);
33
34
  const pins=['@modelcontextprotocol/client','@modelcontextprotocol/server'];
34
35
  for(const dep of pins){
35
36
  const version=lock.packages?.['node_modules/'+dep]?.version;
@@ -4,23 +4,24 @@ import {readFile} from 'node:fs/promises';
4
4
  import {fileURLToPath} from 'node:url';
5
5
  import path from 'node:path';
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 file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
10
- test('current successor retains the R130 security baseline with truthful R173 identity',async()=>{
11
+ test('current successor retains the R130 security baseline with truthful R190 identity',async()=>{
11
12
  const [pkg,release,provenance,bom,lock,matrix]=await Promise.all([
12
13
  read('package.json'),read('RELEASE.json'),read('PROVENANCE.json'),
13
- read('SBOM.cdx.json'),read('package-lock.json'),read('TEST-MATRIX.json')]);
14
+ read('SBOM.cdx.json'),readReleaseLock(root),read('TEST-MATRIX.json')]);
14
15
  assert.equal(pkg.name,'deadbyte-mcp');
15
16
  for(const actual of [pkg.version,release.version,provenance.version,matrix.version,bom.metadata.component.version,lock.version,lock.packages[''].version,DEADBYTE_VERSION])
16
- assert.equal(actual,'0.15.68');
17
- assert.equal(release.train,'R173');
18
- assert.equal(matrix.train,'R173');
19
- assert.equal(release.source_predecessor.version,'0.15.67');
20
- assert.equal(release.source_predecessor.train,'R172');
21
- assert.match(release.source_predecessor.source_path,/0\.15\.67-r172-final-remote-recovery/i);
22
- assert.equal(release.source_predecessor.manifest_status,'VERIFIED_FINAL_BYTES');
23
- assert.equal(release.runtime_predecessor.version,'0.15.61');
17
+ assert.equal(actual,'0.15.70');
18
+ assert.equal(release.train,'R190');
19
+ assert.equal(matrix.train,'R190');
20
+ assert.equal(release.source_predecessor.version,'0.15.69');
21
+ assert.equal(release.source_predecessor.train,'R179');
22
+ assert.match(release.source_predecessor.source_path,/deadbyte-mcp-0\.15\.69\.tgz/i);
23
+ assert.equal(release.source_predecessor.manifest_status,'PUBLISHED_PREVIEW_MANIFEST_VERIFIED_NOT_ACTIVE');
24
+ assert.equal(release.runtime_predecessor.version,'0.15.68');
24
25
  assert.equal(provenance.r160.source_predecessor_manifest_status,'STALE_REJECTED');
25
26
  assert.equal(pkg.dependencies['@modelcontextprotocol/client'],'2.3.1');
26
27
  assert.equal(pkg.dependencies['@modelcontextprotocol/server'],'2.3.1');