deadbyte-mcp 0.15.39 → 0.15.42

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 (100) hide show
  1. package/DEADBYTE-MCP.cmd +8 -4
  2. package/MANIFEST.SHA256 +99 -45
  3. package/PROVENANCE.json +90 -20
  4. package/README.txt +39 -18
  5. package/RELEASE.json +85 -20
  6. package/SBOM.cdx.json +2 -2
  7. package/TEST-MATRIX.json +134 -34
  8. package/bridge/package.json +1 -1
  9. package/controller/deadbyte-controller.ps1 +132 -12
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/R64-EVIDENCE.json +95 -0
  12. package/docs/R64-REFERENCE-GAP-MATRIX.json +98 -0
  13. package/docs/R64-SCOPE.json +41 -0
  14. package/docs/R65-AUTHORITY-CONTINUITY-DESIGN.json +27 -0
  15. package/docs/R65-REFERENCE-GAP-MATRIX.json +22 -0
  16. package/package.json +7 -4
  17. package/scripts/deploy-r48.mjs +123 -7
  18. package/scripts/gate-windows-r63-prepublish.ps1 +45 -0
  19. package/scripts/gate-windows-r64-prepublish.ps1 +68 -0
  20. package/scripts/gate-windows-r65-prepublish.ps1 +65 -0
  21. package/scripts/npm-publication-r65.mjs +139 -0
  22. package/scripts/npm-release-r48.mjs +62 -37
  23. package/scripts/r64-predecessor-reproducer.mjs +32 -0
  24. package/scripts/r64-read-benchmark.mjs +84 -0
  25. package/scripts/r65-canonical-full.mjs +45 -0
  26. package/scripts/r65-focused.mjs +37 -0
  27. package/scripts/verify-r65-parent-source.mjs +75 -0
  28. package/src/activation-continuity-engine-r65.mjs +225 -0
  29. package/src/activation-lock-r65.mjs +155 -0
  30. package/src/activation-preflight-r65.mjs +197 -0
  31. package/src/authority-continuity-r65.mjs +541 -0
  32. package/src/authority-epoch-cli-r65.mjs +10 -0
  33. package/src/autonomous-policy.mjs +7 -3
  34. package/src/coding-plane.mjs +12 -3
  35. package/src/compact-gateway-mcp-tools.mjs +1 -1
  36. package/src/deadbyte-cli.mjs +16 -2
  37. package/src/desktop-policy-r40.mjs +6 -3
  38. package/src/doctor-transactions-r58.mjs +1 -1
  39. package/src/host-plane.mjs +9 -3
  40. package/src/machine-fs-mcp-tools.mjs +1 -1
  41. package/src/machine-fs.mjs +7 -4
  42. package/src/process-policy.mjs +7 -4
  43. package/src/remote-authority.mjs +20 -1
  44. package/src/runtime-update.mjs +15 -5
  45. package/src/version.mjs +1 -1
  46. package/test/audit-context.test.mjs +15 -4
  47. package/test/autonomous-local-supervisor.test.mjs +7 -6
  48. package/test/autonomous-supervisor-integration.test.mjs +8 -7
  49. package/test/capability-resolver.test.mjs +11 -5
  50. package/test/codex-autonomous-r44.test.mjs +7 -6
  51. package/test/coding-observation.test.mjs +4 -3
  52. package/test/commander-real-parity-r47.test.mjs +5 -4
  53. package/test/commander-torture-r47.test.mjs +5 -4
  54. package/test/desktop-policy-r40.test.mjs +6 -4
  55. package/test/desktop-runtime-r40.test.mjs +8 -3
  56. package/test/host-inline-r45.test.mjs +4 -3
  57. package/test/machine-fs-runtime.test.mjs +4 -3
  58. package/test/process-longrun-r36.test.mjs +3 -2
  59. package/test/process-policy.test.mjs +10 -6
  60. package/test/process-runtime.test.mjs +9 -6
  61. package/test/r52-host-revocation.test.mjs +1 -0
  62. package/test/r59-tool-surface-compat.test.mjs +14 -4
  63. package/test/r62-release-contract.test.mjs +2 -1
  64. package/test/r63-one-shot-coherence.test.mjs +38 -0
  65. package/test/r63-release-contract.test.mjs +59 -0
  66. package/test/r64-machine-fs-large-read.test.mjs +136 -0
  67. package/test/r64-release-contract.test.mjs +52 -0
  68. package/test/r65-activation-lock-recovery.test.mjs +72 -0
  69. package/test/r65-activation-lock.test.mjs +59 -0
  70. package/test/r65-activation-orchestration.test.mjs +179 -0
  71. package/test/r65-activation-preflight.test.mjs +35 -0
  72. package/test/r65-authority-continuity-decision.test.mjs +58 -0
  73. package/test/r65-authority-durable-policy-binding.test.mjs +34 -0
  74. package/test/r65-authority-epoch-cli.test.mjs +125 -0
  75. package/test/r65-authority-epoch-races.test.mjs +87 -0
  76. package/test/r65-authority-epoch-recovery.test.mjs +82 -0
  77. package/test/r65-authority-epoch-store.test.mjs +94 -0
  78. package/test/r65-authority-fixture.mjs +41 -0
  79. package/test/r65-authority-grant-capture.test.mjs +37 -0
  80. package/test/r65-authority-observation-epoch.test.mjs +25 -0
  81. package/test/r65-authority-policy-envelope.test.mjs +46 -0
  82. package/test/r65-authority-restored-safe.test.mjs +75 -0
  83. package/test/r65-authority-runtime-epoch.test.mjs +115 -0
  84. package/test/r65-authority-runtime-policy-binding.test.mjs +29 -0
  85. package/test/r65-authority-test-trust.mjs +20 -0
  86. package/test/r65-cli-invocation.test.mjs +38 -0
  87. package/test/r65-controller-authority-epoch-live.test.mjs +100 -0
  88. package/test/r65-controller-authority-epoch.test.mjs +37 -0
  89. package/test/r65-deploy-preflight.test.mjs +55 -0
  90. package/test/r65-epoch-native-activation-integration.test.mjs +141 -0
  91. package/test/r65-epoch-native-continuity-engine.test.mjs +112 -0
  92. package/test/r65-epoch-native-cross-plane.test.mjs +95 -0
  93. package/test/r65-legacy-authority-migration.test.mjs +63 -0
  94. package/test/r65-legacy-auto-restore-rejection.test.mjs +17 -0
  95. package/test/r65-npm-publication.test.mjs +114 -0
  96. package/test/r65-parent-source-verifier.test.mjs +63 -0
  97. package/test/r65-release-contract.test.mjs +74 -0
  98. package/test/r65-transaction-precondition.test.mjs +36 -0
  99. package/test/remote-session-cli.test.mjs +13 -11
  100. package/test/standing-grants.test.mjs +20 -4
@@ -0,0 +1,112 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync} from 'node:crypto';
4
+ import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
8
+ import {createEpochNativeContinuityEngine} from '../src/activation-continuity-engine-r65.mjs';
9
+
10
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const candidate={version:'0.15.42',manifest_sha256:'a'.repeat(64)};
12
+ const predecessor={release_id:'v0.15.41-parent',generation_manifest_sha256:'b'.repeat(64)};
13
+
14
+ async function fixture(){
15
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-continuity-engine-'));
16
+ const state=path.join(root,'state'),runtime=path.join(state,'runtime'),trust=path.join(state,'trust'),policies=path.join(root,'policies');
17
+ await mkdir(runtime,{recursive:true});await mkdir(trust,{recursive:true});await mkdir(policies,{recursive:true});
18
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
19
+ await writeFile(path.join(trust,'ed25519-private.pem'),privateKey.export({format:'pem',type:'pkcs8'}));
20
+ await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({format:'pem',type:'spki'}));
21
+ const grantFile=path.join(runtime,'process-grant.json');
22
+ const policy={
23
+ schema:'deadbyte.process-policy.v1',enabled:true,grant_file:grantFile,state_dir:path.join(state,'evidence','process'),
24
+ native_host:{path:'C:\\Windows\\System32\\cmd.exe',sha256:'c'.repeat(64)},
25
+ roots:{core:{path:'D:\\Projects',deny_paths:[]}},
26
+ profiles:{safe:{executable:'C:\\Windows\\System32\\cmd.exe',executable_sha256:'d'.repeat(64),root_ids:['core'],prefix_args:['/d'],max_input_bytes:4096}}
27
+ };
28
+ const predecessorPolicyPath=path.join(policies,'process-before.json'),successorPolicyPath=path.join(policies,'process-after.json');
29
+ const policyBytes=Buffer.from(JSON.stringify(policy));
30
+ await writeFile(predecessorPolicyPath,policyBytes);await writeFile(successorPolicyPath,policyBytes);
31
+ const grant={schema:'deadbyte.process-grant.v1',policy_sha256:sha(policyBytes),instance_nonce:'e'.repeat(64),
32
+ profile_ids:['safe'],root_ids:['core'],granted_at_utc:'2026-10-07T00:00:00Z'};
33
+ const grantBytes=Buffer.from(JSON.stringify(grant));
34
+ await writeFile(grantFile,grantBytes);
35
+ const store=createAuthorityEpochStore({runtimeDir:runtime,policyBytes});
36
+ const head=await store.append({plane:'process',action:'grant',grantBytes,grantInstanceNonce:grant.instance_nonce});
37
+ const policyPaths={process:predecessorPolicyPath,coding:null,autonomous:null,desktop:null,host:null};
38
+ const observation={authority:{
39
+ process:{configured:true,armed:true,reason:'standing-grant',epoch_seq:head.seq,epoch_sha256:head.event_sha256},
40
+ coding:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null},
41
+ autonomous:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null},
42
+ desktop:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null},
43
+ host:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null}
44
+ }};
45
+ const engine=createEpochNativeContinuityEngine({stateRoot:state,predecessorPolicyPaths:policyPaths,
46
+ successorPolicyPaths:async()=>({...policyPaths,process:successorPolicyPath}),updateId:'r65-engine-e2e'});
47
+ return {root,state,runtime,grantFile,policyBytes,grantBytes,grant,head,observation,engine,store};
48
+ }
49
+
50
+ test('epoch-native engine revokes then restores exact Process scope with a fresh nonce',async()=>{
51
+ const f=await fixture();
52
+ try{
53
+ const capture=await f.engine.capture({observation:f.observation,subject_sha256:'f'.repeat(64),pointer:predecessor,
54
+ expectedParent:predecessor,candidate,expectedCandidate:candidate});
55
+ assert.deepEqual(capture.planes,['process']);
56
+ await f.engine.revoke(capture);
57
+ const staleAfterRevoke=await readFile(f.grantFile);
58
+ assert.equal(sha(staleAfterRevoke),sha(f.grantBytes),'transaction revoke preserves the old bytes as a no-clobber sentinel');
59
+ const revoked=await f.store.readHead({plane:'process'});
60
+ assert.equal(revoked.action,'transaction_revoke');
61
+ assert.equal((await f.store.verifyGrant({plane:'process',grantBytes:staleAfterRevoke,grantInstanceNonce:f.grant.instance_nonce,policyBytes:f.policyBytes})).trusted,false);
62
+ const restored=await f.engine.restore({capture,successor_observation:{},activation:{verified:true,release_id:'v0.15.42-candidate'},
63
+ pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate,subject_sha256:'f'.repeat(64)});
64
+ assert.equal(restored.authority_outcomes.process,'RESTORED_EXACT');
65
+ assert.deepEqual(restored.reauth_planes,[]);
66
+ const nextBytes=await readFile(f.grantFile),next=JSON.parse(nextBytes);
67
+ assert.notEqual(next.instance_nonce,f.grant.instance_nonce);
68
+ assert.deepEqual(next.profile_ids,f.grant.profile_ids);
69
+ assert.deepEqual(next.root_ids,f.grant.root_ids);
70
+ assert.equal(next.policy_sha256,sha(f.policyBytes));
71
+ const finalHead=await f.store.readHead({plane:'process'});
72
+ assert.equal(finalHead.action,'transaction_restore');
73
+ const verified=await f.store.verifyGrant({plane:'process',grantBytes:nextBytes,grantInstanceNonce:next.instance_nonce,policyBytes:f.policyBytes});
74
+ assert.equal(verified.trusted,true);
75
+ }finally{await rm(f.root,{recursive:true,force:true});}
76
+ });
77
+
78
+ test('user revoke committed after transaction revoke prevents restoration',async()=>{
79
+ const f=await fixture();
80
+ try{
81
+ const capture=await f.engine.capture({observation:f.observation,subject_sha256:'f'.repeat(64),pointer:predecessor,
82
+ expectedParent:predecessor,candidate,expectedCandidate:candidate});
83
+ await f.engine.revoke(capture);
84
+ const revoked=await f.store.readHead({plane:'process'});
85
+ await f.store.append({plane:'process',action:'user_revoke',grantBytes:f.grantBytes,grantInstanceNonce:f.grant.instance_nonce});
86
+ const restored=await f.engine.restore({capture,successor_observation:{},activation:{verified:true,release_id:'v0.15.42-candidate'},
87
+ pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate,subject_sha256:'f'.repeat(64)});
88
+ assert.equal(restored.authority_outcomes.process,'REVOKED_DURING_UPDATE');
89
+ assert.deepEqual(restored.reauth_planes,['process']);
90
+ const current=await f.store.readHead({plane:'process'});
91
+ assert.equal(current.action,'user_revoke');
92
+ assert.notEqual(current.event_sha256,revoked.event_sha256);
93
+ await assert.rejects(readFile(f.grantFile),error=>error?.code==='ENOENT');
94
+ }finally{await rm(f.root,{recursive:true,force:true});}
95
+ });
96
+
97
+
98
+ test('grant disappearance after transaction revoke is treated as external/user revocation and is never auto-restored',async()=>{
99
+ const f=await fixture();
100
+ try{
101
+ const capture=await f.engine.capture({observation:f.observation,subject_sha256:'f'.repeat(64),pointer:predecessor,
102
+ expectedParent:predecessor,candidate,expectedCandidate:candidate});
103
+ await f.engine.revoke(capture);
104
+ await rm(f.grantFile,{force:true});
105
+ const restored=await f.engine.restore({capture,successor_observation:{},activation:{verified:true,release_id:'v0.15.42-candidate'},
106
+ pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate,subject_sha256:'f'.repeat(64)});
107
+ assert.equal(restored.authority_outcomes.process,'REVOKED_DURING_UPDATE');
108
+ assert.deepEqual(restored.reauth_planes,['process']);
109
+ await assert.rejects(readFile(f.grantFile),error=>error?.code==='ENOENT');
110
+ assert.equal((await f.store.readHead({plane:'process'})).action,'transaction_revoke');
111
+ }finally{await rm(f.root,{recursive:true,force:true});}
112
+ });
@@ -0,0 +1,95 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync} from 'node:crypto';
4
+ import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
8
+ import {createEpochNativeContinuityEngine} from '../src/activation-continuity-engine-r65.mjs';
9
+
10
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const predecessor={release_id:'v0.15.41-parent',generation_manifest_sha256:'a'.repeat(64)};
12
+ const candidate={version:'0.15.42',manifest_sha256:'b'.repeat(64)};
13
+ const subject='c'.repeat(64);
14
+
15
+ async function setup({widenCoding=false}={}){
16
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-cross-plane-'));
17
+ const state=path.join(root,'state'),runtime=path.join(state,'runtime'),trust=path.join(state,'trust'),policies=path.join(root,'policies');
18
+ await mkdir(runtime,{recursive:true});await mkdir(trust,{recursive:true});await mkdir(policies,{recursive:true});
19
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
20
+ await writeFile(path.join(trust,'ed25519-private.pem'),privateKey.export({format:'pem',type:'pkcs8'}));
21
+ await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({format:'pem',type:'spki'}));
22
+
23
+ const codingGrantFile=path.join(runtime,'coding-grant.json'),autonomousGrantFile=path.join(runtime,'autonomous-grant.json');
24
+ const codingBefore={schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:codingGrantFile,evidence_dir:path.join(state,'evidence','coding'),
25
+ max_read_bytes:4096,max_write_bytes:4096,roots:{core:{path:'D:\\Projects',read:true,write:true,exec:false,deny_paths:[]}},commands:{}};
26
+ const codingAfter=widenCoding?{...codingBefore,roots:{...codingBefore.roots,extra:{path:'D:\\Extra',read:true,write:true,exec:false,deny_paths:[]}}}:codingBefore;
27
+ const codingBeforeBytes=Buffer.from(JSON.stringify(codingBefore)),codingAfterBytes=Buffer.from(JSON.stringify(codingAfter));
28
+ const autonomousBefore={schema:'deadbyte.autonomous-policy.v1',enabled:true,lease_file:autonomousGrantFile,evidence_dir:path.join(state,'evidence','autonomous'),
29
+ coding_policy_sha256:sha(codingBeforeBytes),root_ids:['core'],planner:{backend:'client_sampling',max_tokens:1000},
30
+ limits:{max_iterations:4,max_mutations:4,max_wall_ms:10000,max_context_bytes:10000,max_diff_bytes:10000,max_files_per_diff:10},profiles:{}};
31
+ const autonomousAfter={...autonomousBefore,coding_policy_sha256:sha(codingAfterBytes)};
32
+ const autonomousBeforeBytes=Buffer.from(JSON.stringify(autonomousBefore)),autonomousAfterBytes=Buffer.from(JSON.stringify(autonomousAfter));
33
+
34
+ const cb=path.join(policies,'coding-before.json'),ca=path.join(policies,'coding-after.json'),ab=path.join(policies,'autonomous-before.json'),aa=path.join(policies,'autonomous-after.json');
35
+ await writeFile(cb,codingBeforeBytes);await writeFile(ca,codingAfterBytes);await writeFile(ab,autonomousBeforeBytes);await writeFile(aa,autonomousAfterBytes);
36
+
37
+ const codingGrant={schema:'deadbyte.coding-grant.v2',policy_sha256:sha(codingBeforeBytes),instance_nonce:'d'.repeat(64),root_ids:['core'],granted_at_utc:'2026-10-07T00:00:00Z'};
38
+ const autonomousGrant={schema:'deadbyte.autonomous-grant.v2',policy_sha256:sha(autonomousBeforeBytes),coding_policy_sha256:sha(codingBeforeBytes),
39
+ instance_nonce:'e'.repeat(64),root_ids:['core'],granted_at_utc:'2026-10-07T00:00:00Z'};
40
+ const codingGrantBytes=Buffer.from(JSON.stringify(codingGrant)),autonomousGrantBytes=Buffer.from(JSON.stringify(autonomousGrant));
41
+ await writeFile(codingGrantFile,codingGrantBytes);await writeFile(autonomousGrantFile,autonomousGrantBytes);
42
+ const codingStore=createAuthorityEpochStore({runtimeDir:runtime,policyBytes:codingBeforeBytes});
43
+ const autonomousStore=createAuthorityEpochStore({runtimeDir:runtime,policyBytes:autonomousBeforeBytes});
44
+ const codingHead=await codingStore.append({plane:'coding',action:'grant',grantBytes:codingGrantBytes,grantInstanceNonce:codingGrant.instance_nonce});
45
+ const autonomousHead=await autonomousStore.append({plane:'autonomous',action:'grant',grantBytes:autonomousGrantBytes,grantInstanceNonce:autonomousGrant.instance_nonce});
46
+
47
+ const predecessorPolicyPaths={process:null,coding:cb,autonomous:ab,desktop:null,host:null};
48
+ const successorPolicyPaths={process:null,coding:ca,autonomous:aa,desktop:null,host:null};
49
+ const observation={authority:{
50
+ process:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null},
51
+ coding:{configured:true,armed:true,reason:'standing-grant',epoch_seq:codingHead.seq,epoch_sha256:codingHead.event_sha256},
52
+ autonomous:{configured:true,armed:true,reason:'standing-grant',epoch_seq:autonomousHead.seq,epoch_sha256:autonomousHead.event_sha256},
53
+ desktop:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null},
54
+ host:{configured:false,armed:false,reason:'no-policy',epoch_seq:null,epoch_sha256:null}
55
+ }};
56
+ const engine=createEpochNativeContinuityEngine({stateRoot:state,predecessorPolicyPaths,successorPolicyPaths:async()=>successorPolicyPaths,updateId:'r65-cross-plane'});
57
+ return {root,state,runtime,codingGrantFile,autonomousGrantFile,codingStore,autonomousStore,codingGrant,autonomousGrant,codingBeforeBytes,autonomousBeforeBytes,observation,engine};
58
+ }
59
+
60
+ test('Coding restores before Autonomous and both receive fresh nonces under equal authority ceilings',async()=>{
61
+ const f=await setup();
62
+ try{
63
+ const capture=await f.engine.capture({observation:f.observation,subject_sha256:subject,pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate});
64
+ await f.engine.revoke(capture);
65
+ const restored=await f.engine.restore({capture,successor_observation:{},activation:{verified:true,release_id:'v0.15.42-candidate'},
66
+ pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate,subject_sha256:subject});
67
+ assert.equal(restored.authority_outcomes.coding,'RESTORED_EXACT');
68
+ assert.equal(restored.authority_outcomes.autonomous,'RESTORED_EXACT');
69
+ const coding=JSON.parse(await readFile(f.codingGrantFile)),autonomous=JSON.parse(await readFile(f.autonomousGrantFile));
70
+ assert.notEqual(coding.instance_nonce,f.codingGrant.instance_nonce);
71
+ assert.notEqual(autonomous.instance_nonce,f.autonomousGrant.instance_nonce);
72
+ assert.equal((await f.codingStore.readHead({plane:'coding'})).action,'transaction_restore');
73
+ assert.equal((await f.autonomousStore.readHead({plane:'autonomous'})).action,'transaction_restore');
74
+ }finally{await rm(f.root,{recursive:true,force:true});}
75
+ });
76
+
77
+ test('Coding policy widening blocks Coding and therefore Autonomous remains DISARMED/REAUTH_REQUIRED',async()=>{
78
+ const f=await setup({widenCoding:true});
79
+ try{
80
+ const capture=await f.engine.capture({observation:f.observation,subject_sha256:subject,pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate});
81
+ await f.engine.revoke(capture);
82
+ const restored=await f.engine.restore({capture,successor_observation:{},activation:{verified:true,release_id:'v0.15.42-candidate'},
83
+ pointer:predecessor,expectedParent:predecessor,candidate,expectedCandidate:candidate,subject_sha256:subject});
84
+ assert.equal(restored.authority_outcomes.coding,'POLICY_WIDENED');
85
+ assert.equal(restored.authority_outcomes.autonomous,'REAUTH_REQUIRED');
86
+ assert.ok(restored.reauth_planes.includes('coding'));
87
+ assert.ok(restored.reauth_planes.includes('autonomous'));
88
+ const codingBytes=await readFile(f.codingGrantFile),coding=JSON.parse(codingBytes);
89
+ const autonomousBytes=await readFile(f.autonomousGrantFile),autonomous=JSON.parse(autonomousBytes);
90
+ assert.equal((await f.codingStore.readHead({plane:'coding'})).action,'transaction_revoke');
91
+ assert.equal((await f.autonomousStore.readHead({plane:'autonomous'})).action,'transaction_revoke');
92
+ assert.equal((await f.codingStore.verifyGrant({plane:'coding',grantBytes:codingBytes,grantInstanceNonce:coding.instance_nonce,policyBytes:f.codingBeforeBytes})).trusted,false);
93
+ assert.equal((await f.autonomousStore.verifyGrant({plane:'autonomous',grantBytes:autonomousBytes,grantInstanceNonce:autonomous.instance_nonce,policyBytes:f.autonomousBeforeBytes})).trusted,false);
94
+ }finally{await rm(f.root,{recursive:true,force:true});}
95
+ });
@@ -0,0 +1,63 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {classifyAuthorityContinuity,orchestrateActivation} from '../src/activation-preflight-r65.mjs';
4
+
5
+ const parent={release_id:'v0.15.41-parent',version:'0.15.41',manifest_sha256:'1'.repeat(64),generation_manifest_sha256:'2'.repeat(64)};
6
+ const candidate={version:'0.15.42',manifest_sha256:'3'.repeat(64)};
7
+ const policy_sha256={process:'4'.repeat(64),coding:'5'.repeat(64),autonomous:'6'.repeat(64),desktop:'7'.repeat(64),host:'8'.repeat(64)};
8
+ const liveIds={bridge:{alive:true,reason:'verified',pid:10},core:{alive:true,reason:'verified',pid:11},cloud:{alive:true,reason:'verified',pid:12}};
9
+
10
+ function legacyEntry(armed){return {configured:true,armed,reason:armed?'standing-grant':'disarmed',epoch_seq:null,epoch_sha256:null};}
11
+ function fixture(vector){
12
+ const calls=[];
13
+ let observation={state:'ONLINE',local_ready:true,...liveIds,supervisor:{running:true},policy_sha256,
14
+ active_release_id:parent.release_id,active_manifest_sha256:parent.manifest_sha256,active_generation_manifest_sha256:parent.generation_manifest_sha256,
15
+ authority:Object.fromEntries(Object.entries(vector).map(([plane,armed])=>[plane,legacyEntry(armed)]))};
16
+ return {
17
+ calls,
18
+ observe:async()=>observation,
19
+ command:async command=>{
20
+ calls.push(command);
21
+ if(command.startsWith('disarm')){
22
+ const plane=command.slice(6);
23
+ observation={...observation,authority:{...observation.authority,[plane]:legacyEntry(false)}};
24
+ if(plane==='coding')observation={...observation,authority:{...observation.authority,autonomous:legacyEntry(false)}};
25
+ }else throw new Error('unexpected command '+command);
26
+ },
27
+ activate:async()=>{
28
+ calls.push('activate');
29
+ observation={...observation,state:'ONLINE',local_ready:true,...liveIds,supervisor:{running:true}};
30
+ return {verified:true,release_id:'v0.15.42-new'};
31
+ }
32
+ };
33
+ }
34
+
35
+ test('legacy R64 standing grants are classified REAUTH_REQUIRED, never continuity-trusted',()=>{
36
+ const observation=fixture({process:true,coding:true,autonomous:true,desktop:true,host:true}).observe();
37
+ return observation.then(value=>{
38
+ const result=classifyAuthorityContinuity(value);
39
+ assert.equal(result.state,'LEGACY_REAUTH_REQUIRED');
40
+ assert.deepEqual(result.reauth_planes,['process','coding','autonomous','desktop','host']);
41
+ assert.deepEqual(result.epoch_planes,[]);
42
+ });
43
+ });
44
+
45
+ test('R64 to R65 activation revokes legacy authority before mutation and leaves successor disarmed',async()=>{
46
+ const f=fixture({process:true,coding:true,autonomous:true,desktop:true,host:true});
47
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
48
+ assert.equal(result.state,'ACTIVATED');
49
+ assert.equal(result.runtime_activation,'ACTIVATED');
50
+ assert.equal(result.authority_continuity,'REAUTH_REQUIRED');
51
+ assert.deepEqual(result.reauth_planes,['process','coding','autonomous','desktop','host']);
52
+ assert.deepEqual(f.calls,['disarmhost','disarmdesktop','disarmautonomous','disarmcoding','disarmprocess','activate']);
53
+ assert.deepEqual(result.final_authority,{process:false,coding:false,autonomous:false,desktop:false,host:false});
54
+ });
55
+
56
+ test('activation with no standing authority has no reauth requirement',async()=>{
57
+ const f=fixture({process:false,coding:false,autonomous:false,desktop:false,host:false});
58
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
59
+ assert.equal(result.state,'ACTIVATED');
60
+ assert.equal(result.authority_continuity,'NOT_APPLICABLE');
61
+ assert.deepEqual(result.reauth_planes,[]);
62
+ assert.deepEqual(f.calls,['activate']);
63
+ });
@@ -0,0 +1,17 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,readdir,rm} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {performSlotUpdate} from '../src/runtime-update.mjs';
7
+
8
+ test('R65 updater rejects legacy standing-grant auto-restore before transaction creation',async()=>{
9
+ const stateRoot=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-no-auto-restore-'));
10
+ try{
11
+ await assert.rejects(()=>performSlotUpdate({stage:path.join(stateRoot,'stage'),
12
+ developmentRoot:path.join(stateRoot,'development'),expectedVersion:'0.15.42',
13
+ expectedManifest:'a'.repeat(64),stateRoot,restoreStandingGrants:true}),
14
+ /standing.*restore.*(disabled|unsupported|forbidden)/i);
15
+ assert.deepEqual(await readdir(stateRoot),[],'rejected request must not create update residue');
16
+ }finally{await rm(stateRoot,{recursive:true,force:true});}
17
+ });
@@ -0,0 +1,114 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp, readFile, rm, mkdir, writeFile} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {createHash} from 'node:crypto';
7
+ import {reconcileNpmPublication} from '../scripts/npm-publication-r65.mjs';
8
+
9
+ const bytes=Buffer.from('r65 immutable npm artifact');
10
+ const hash=(name)=>createHash(name).update(bytes).digest(name==='sha512'?'base64':'hex');
11
+ const version='0.15.42';
12
+ const manifestSha256='b'.repeat(64);
13
+ const published={version,dist:{integrity:`sha512-${hash('sha512')}`,shasum:hash('sha1'),tarball:`https://registry.npmjs.org/deadbyte-mcp/-/deadbyte-mcp-${version}.tgz`}};
14
+
15
+ async function fixture(run){
16
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-npm-'));
17
+ try{return await run(path.join(root,'publication'));}finally{await rm(root,{recursive:true,force:true});}
18
+ }
19
+
20
+ test('accepted publication survives two transient 404s and verifies exact registry bytes',()=>fixture(async recordPath=>{
21
+ let publishes=0,views=0;
22
+ const result=await reconcileNpmPublication({recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:1000,pollMs:1,
23
+ publish:async()=>{publishes++;return {accepted:true};},
24
+ lookup:async()=>{views++;return views<4?null:published;},
25
+ tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}});
26
+ assert.equal(result.state,'REGISTRY_IDENTITY_VERIFIED');
27
+ assert.equal(publishes,1);assert.equal(views,4);
28
+ assert.match(await readFile(path.join(recordPath,'claim.json'),'utf8'),/0\.15\.42/);
29
+ }));
30
+
31
+ test('accepted publication timing out remains processing and is never resubmitted',()=>fixture(async recordPath=>{
32
+ let publishes=0;
33
+ const options={recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
34
+ publish:async()=>{publishes++;return {accepted:true};},lookup:async()=>null,
35
+ tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}};
36
+ const first=await reconcileNpmPublication(options);
37
+ const second=await reconcileNpmPublication(options);
38
+ assert.equal(first.state,'REGISTRY_PROCESSING');assert.equal(second.state,'REGISTRY_PROCESSING');assert.equal(publishes,1);
39
+ }));
40
+
41
+ test('transport loss after dispatch remains unknown; later exact registry bytes reconcile without republish',()=>fixture(async recordPath=>{
42
+ let publishes=0,visible=false;
43
+ const options={recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
44
+ publish:async()=>{publishes++;throw Object.assign(new Error('connection lost'),{code:'ECONNRESET'});},
45
+ lookup:async()=>visible?published:null,tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}};
46
+ assert.equal((await reconcileNpmPublication(options)).state,'OUTCOME_UNKNOWN');
47
+ visible=true;
48
+ assert.equal((await reconcileNpmPublication(options)).state,'REGISTRY_IDENTITY_VERIFIED');
49
+ assert.equal(publishes,1);
50
+ }));
51
+
52
+ test('same immutable version with different bytes hard fails, not republished',()=>fixture(async recordPath=>{
53
+ let publishes=0;
54
+ const result=await reconcileNpmPublication({recordPath,version,manifestSha256,localBytes:Buffer.from('different'),timeoutMs:0,pollMs:1,
55
+ publish:async()=>{publishes++;return {accepted:true};},lookup:async()=>published,
56
+ tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}});
57
+ assert.equal(result.state,'REGISTRY_REJECTED');assert.equal(publishes,0);
58
+ }));
59
+
60
+ test('pre-submission authentication rejection is terminal and does not retry on resume',()=>fixture(async recordPath=>{
61
+ let publishes=0;
62
+ const options={recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
63
+ publish:async()=>{publishes++;throw Object.assign(new Error('authentication required'),{code:'E401'});},
64
+ lookup:async()=>null,tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}};
65
+ assert.equal((await reconcileNpmPublication(options)).state,'AUTH_REQUIRED');
66
+ assert.equal((await reconcileNpmPublication(options)).state,'AUTH_REQUIRED');
67
+ assert.equal(publishes,1);
68
+ }));
69
+
70
+ test('registry tag mismatch rejects exact bytes without another publish',()=>fixture(async recordPath=>{
71
+ let publishes=0;
72
+ const result=await reconcileNpmPublication({recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
73
+ publish:async()=>{publishes++;return {accepted:true};},lookup:async()=>published,
74
+ tags:async()=>({latest:'0.15.41'}),fetchTarball:async()=>bytes,sleep:async()=>{}});
75
+ assert.equal(result.state,'REGISTRY_REJECTED');assert.equal(publishes,0);
76
+ }));
77
+
78
+ test('concurrent same-version attempts have one publication dispatch',()=>fixture(async recordPath=>{
79
+ let publishes=0;
80
+ const options={recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
81
+ publish:async()=>{publishes++;await new Promise(resolve=>setTimeout(resolve,5));return {accepted:true};},
82
+ lookup:async()=>null,tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}};
83
+ const outcomes=await Promise.all([reconcileNpmPublication(options),reconcileNpmPublication(options)]);
84
+ assert.equal(publishes,1);
85
+ assert.ok(outcomes.every(item=>['REGISTRY_PROCESSING','OUTCOME_UNKNOWN'].includes(item.state)));
86
+ }));
87
+
88
+ test('published metadata with temporarily unavailable tarball is processing, never a reason to submit again',()=>fixture(async recordPath=>{
89
+ let publishes=0;
90
+ const result=await reconcileNpmPublication({recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
91
+ publish:async()=>{publishes++;return {accepted:true};},lookup:async()=>published,
92
+ tags:async()=>({latest:version}),fetchTarball:async()=>{throw Object.assign(new Error('not found'),{status:404});},sleep:async()=>{}});
93
+ assert.equal(result.state,'REGISTRY_PROCESSING');assert.equal(publishes,0);
94
+ }));
95
+
96
+ test('candidate manifest drift after a submission claim cannot reuse the published tarball',()=>fixture(async recordPath=>{
97
+ let publishes=0;
98
+ const options={recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
99
+ publish:async()=>{publishes++;return {accepted:true};},lookup:async()=>null,
100
+ tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}};
101
+ assert.equal((await reconcileNpmPublication(options)).state,'REGISTRY_PROCESSING');
102
+ assert.equal((await reconcileNpmPublication({...options,manifestSha256:'c'.repeat(64),lookup:async()=>published})).state,'REGISTRY_REJECTED');
103
+ assert.equal(publishes,1);
104
+ }));
105
+
106
+ test('incomplete concurrent claim fails closed without dispatching publication',()=>fixture(async recordPath=>{
107
+ await mkdir(recordPath,{recursive:true});
108
+ await writeFile(path.join(recordPath,'claim.json'),'{"schema":');
109
+ let publishes=0;
110
+ const result=await reconcileNpmPublication({recordPath,version,manifestSha256,localBytes:bytes,timeoutMs:0,pollMs:1,
111
+ publish:async()=>{publishes++;},lookup:async()=>null,tags:async()=>({latest:version}),fetchTarball:async()=>bytes,sleep:async()=>{}});
112
+ assert.equal(result.state,'OUTCOME_UNKNOWN');
113
+ assert.equal(publishes,0);
114
+ }));
@@ -0,0 +1,63 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,rm,readFile} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {spawnSync} from 'node:child_process';
8
+ import {fileURLToPath} from 'node:url';
9
+
10
+ const here=path.dirname(fileURLToPath(import.meta.url));
11
+ const repo=path.resolve(here,'..');
12
+ const verifier=path.join(repo,'scripts','verify-r65-parent-source.mjs');
13
+ const sha=b=>createHash('sha256').update(b).digest('hex');
14
+
15
+ async function fixture(){
16
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-parent-'));
17
+ await mkdir(path.join(root,'sub'),{recursive:true});
18
+ await writeFile(path.join(root,'a.txt'),'alpha\n');
19
+ await writeFile(path.join(root,'sub','b.txt'),'beta\n');
20
+ const manifest=[
21
+ `${sha(Buffer.from('alpha\n'))} a.txt`,
22
+ `${sha(Buffer.from('beta\n'))} sub/b.txt`,
23
+ ''
24
+ ].join('\n');
25
+ await writeFile(path.join(root,'MANIFEST.SHA256'),manifest);
26
+ await writeFile(path.join(root,'deadbyte-mcp-0.15.41.tgz'),'unlisted package artifact');
27
+ return {root,manifestSha:sha(Buffer.from(manifest))};
28
+ }
29
+ function run(root,manifestSha){
30
+ return spawnSync(process.execPath,[verifier,'--root',root,'--expected-manifest-sha256',manifestSha],{
31
+ cwd:repo,encoding:'utf8',windowsHide:true
32
+ });
33
+ }
34
+
35
+ test('R65 parent verifier accepts exact declared manifest entries while ignoring unlisted package artifacts',async()=>{
36
+ const f=await fixture();
37
+ try{
38
+ const result=run(f.root,f.manifestSha);
39
+ assert.equal(result.status,0,result.stderr||result.stdout);
40
+ assert.match(result.stdout,/R65_PARENT_SOURCE_VERIFY_OK files=2/);
41
+ }finally{await rm(f.root,{recursive:true,force:true});}
42
+ });
43
+
44
+ test('R65 parent verifier fails on missing or changed declared bytes and manifest identity drift',async()=>{
45
+ const f=await fixture();
46
+ try{
47
+ await rm(path.join(f.root,'sub','b.txt'));
48
+ let result=run(f.root,f.manifestSha);
49
+ assert.notEqual(result.status,0);
50
+ assert.match(result.stderr,/listed file missing|ENOENT/i);
51
+
52
+ await mkdir(path.join(f.root,'sub'),{recursive:true});
53
+ await writeFile(path.join(f.root,'sub','b.txt'),'changed\n');
54
+ result=run(f.root,f.manifestSha);
55
+ assert.notEqual(result.status,0);
56
+ assert.match(result.stderr,/hash mismatch/i);
57
+
58
+ await writeFile(path.join(f.root,'sub','b.txt'),'beta\n');
59
+ result=run(f.root,'0'.repeat(64));
60
+ assert.notEqual(result.status,0);
61
+ assert.match(result.stderr,/manifest SHA-256 mismatch/i);
62
+ }finally{await rm(f.root,{recursive:true,force:true});}
63
+ });
@@ -0,0 +1,74 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {readFile} from 'node:fs/promises';
4
+
5
+ const read=rel=>readFile(new URL('../'+rel,import.meta.url),'utf8');
6
+ const json=async rel=>JSON.parse((await read(rel)).replace(/^\uFEFF/,''));
7
+ const PARENT={
8
+ version:'0.15.41',
9
+ release_id:'v0.15.41-63a78ad159ad3003',
10
+ manifest_sha256:'63a78ad159ad3003db3fefafcd468bef22599c829a867f4f10a46c7a20748b3e',
11
+ generation_manifest_sha256:'19ae2efcdcde9f80c474f0e55851d4ada51c22813d3e05af03b11acb28c3fca5'
12
+ };
13
+
14
+ test('R65 release identity binds exact production R64 parent and durable authority-continuity train',async()=>{
15
+ const [pkg,lock,bridge,release,provenance,matrix,sbom,version,gate,gaps,design]=await Promise.all([
16
+ json('package.json'),json('package-lock.json'),json('bridge/package.json'),json('RELEASE.json'),
17
+ json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),
18
+ read('scripts/gate-windows-r65-prepublish.ps1'),json('docs/R65-REFERENCE-GAP-MATRIX.json'),
19
+ json('docs/R65-AUTHORITY-CONTINUITY-DESIGN.json')
20
+ ]);
21
+ assert.equal(pkg.version,'0.15.42');
22
+ assert.equal(lock.version,pkg.version);
23
+ assert.equal(lock.packages[''].version,pkg.version);
24
+ assert.equal(bridge.version,pkg.version);
25
+ assert.match(version,/DEADBYTE_VERSION = '0\.15\.42'/);
26
+ assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r65-prepublish.ps1');
27
+ assert.equal(pkg.scripts['r65:focused:test'],'node scripts/r65-focused.mjs');
28
+
29
+ assert.equal(release.version,pkg.version);
30
+ assert.equal(release.train,'R65');
31
+ assert.equal(release.parent.version,PARENT.version);
32
+ assert.equal(release.parent.release_id,PARENT.release_id);
33
+ assert.equal(release.parent.manifest_sha256,PARENT.manifest_sha256);
34
+ assert.equal(release.parent.generation_manifest_sha256,PARENT.generation_manifest_sha256);
35
+ assert.deepEqual(release.runtime_predecessor,{
36
+ release_id:PARENT.release_id,
37
+ manifest_sha256:PARENT.manifest_sha256,
38
+ generation_manifest_sha256:PARENT.generation_manifest_sha256
39
+ });
40
+ assert.equal(release.source_predecessor.version,PARENT.version);
41
+ assert.equal(release.source_predecessor.manifest_file_sha256,PARENT.manifest_sha256);
42
+ assert.equal(release.source_predecessor.release_status,'PRODUCTION_RELEASED');
43
+ assert.equal(release.r65_release_control_hardening.new_public_tools,0);
44
+ assert.equal(release.r65_release_control_hardening.new_authority_scopes,0);
45
+ assert.equal(release.r65_release_control_hardening.legacy_r64_auto_restore,false);
46
+ assert.equal(release.r65_release_control_hardening.epoch_native_restore_requires_continuity_proof,true);
47
+ assert.equal(release.r65_release_control_hardening.explicit_revoke_wins,true);
48
+ assert.equal(release.r65_release_control_hardening.revoked_nonce_resurrection,false);
49
+ assert.equal(release.r65_release_control_hardening.stable_final_claim,false);
50
+
51
+ assert.equal(provenance.version,pkg.version);
52
+ assert.equal(provenance.parent_release_id,PARENT.release_id);
53
+ assert.equal(provenance.parent_manifest_sha256,PARENT.manifest_sha256);
54
+ assert.equal(provenance.parent_generation_manifest_sha256,PARENT.generation_manifest_sha256);
55
+ assert.equal(provenance.source_predecessor_version,PARENT.version);
56
+ assert.equal(provenance.source_predecessor_manifest_file_sha256,PARENT.manifest_sha256);
57
+ assert.equal(provenance.r65.production_mutations,0);
58
+ assert.equal(provenance.r65.npm_publications,0);
59
+
60
+ assert.equal(matrix.version,pkg.version);
61
+ assert.equal(matrix.train,'R65');
62
+ assert.ok(matrix.required.some(row=>row.id==='r65-focused'));
63
+ assert.ok(matrix.required.some(row=>row.id==='r65-full-regression'));
64
+ assert.equal(sbom.metadata.component.version,pkg.version);
65
+ assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r65:0.15.42');
66
+ assert.equal(gaps.decisions.length,7);
67
+ assert.equal(design.selected_method.r64_predecessor_continuity.includes('REAUTH_REQUIRED'),true);
68
+
69
+ assert.match(gate,/v0\.15\.41-63a78ad159ad3003/);
70
+ assert.match(gate,/63a78ad159ad3003db3fefafcd468bef22599c829a867f4f10a46c7a20748b3e/);
71
+ assert.match(gate,/19ae2efcdcde9f80c474f0e55851d4ada51c22813d3e05af03b11acb28c3fca5/);
72
+ assert.match(gate,/r65:focused:test/);
73
+ assert.doesNotMatch(gate,/npm\.cmd publish|--publish-only/);
74
+ });
@@ -0,0 +1,36 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync} from 'node:crypto';
4
+ import {mkdtemp,mkdir,readFile,rm,writeFile,unlink} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {performSlotUpdate,verifySlotUpdateEnvelope} from '../src/runtime-update.mjs';
8
+ import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
9
+
10
+ test('deterministic candidate precondition rejection is a signed terminal phase, not unresolved residue',async()=>{
11
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-precondition-'));
12
+ try{
13
+ const state=path.join(root,'state'),trust=path.join(state,'trust');
14
+ await mkdir(trust,{recursive:true});
15
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
16
+ await writeFile(path.join(trust,'ed25519-private.pem'),privateKey.export({type:'pkcs8',format:'pem'}));
17
+ await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}));
18
+ const stage=path.join(root,'missing-candidate');
19
+ let failure;
20
+ try{await performSlotUpdate({stage,developmentRoot:path.join(root,'dev'),expectedVersion:'0.15.42',expectedManifest:'a'.repeat(64),stateRoot:state});}
21
+ catch(error){failure=error;}
22
+ assert.ok(failure?.statePath,'expected isolated precondition failure evidence');
23
+ const journal=JSON.parse(await readFile(failure.statePath,'utf8'));
24
+ assert.equal(journal.phase,'precondition_rejected');
25
+ const receipt=JSON.parse(await readFile(failure.failureReceiptPath,'utf8'));
26
+ assert.equal(receipt.body.result,'PRECONDITION_REJECTED');
27
+ assert.equal(verifySlotUpdateEnvelope(receipt,publicKey),true);
28
+ const doctor=await inspectDoctorTransactions(state);
29
+ assert.equal(doctor.status,'PASS');
30
+ assert.equal(doctor.findings.at(-1).evidence.unresolved_transactions,0);
31
+ await unlink(failure.failureReceiptPath);
32
+ const missing=await inspectDoctorTransactions(state);
33
+ assert.equal(missing.status,'FAIL');
34
+ assert.ok(missing.findings.some(item=>item.evidence?.check==='missing_failure_receipt'));
35
+ }finally{await rm(root,{recursive:true,force:true});}
36
+ });