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,136 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import {EventEmitter} from 'node:events';
6
+ import {mkdtemp, mkdir, readFile, writeFile, rm} from 'node:fs/promises';
7
+ import {syncBuiltinESMExports} from 'node:module';
8
+ import {tmpdir} from 'node:os';
9
+ import path from 'node:path';
10
+ import {loadCodingPolicy} from '../src/coding-plane.mjs';
11
+ import {createMachineFsRuntime} from '../src/machine-fs.mjs';
12
+ import {generateTrustKeypair} from '../src/trust.mjs';
13
+ import {machineFsGatewayInputSchema} from '../src/compact-gateway-mcp-tools.mjs';
14
+ import {registerMachineFsTools} from '../src/machine-fs-mcp-tools.mjs';
15
+
16
+ const PAGE=4*1024*1024;
17
+
18
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
19
+
20
+ async function fixture(t,{read=true}={}){
21
+ const root=await mkdtemp(path.join(tmpdir(),'deadbyte-r64-read-'));
22
+ t.after(()=>rm(root,{recursive:true,force:true}));
23
+ const project=path.join(root,'project');
24
+ await mkdir(project);
25
+ const policyPath=path.join(root,'policy.json');
26
+ await writeFile(policyPath,JSON.stringify({
27
+ schema:'deadbyte.coding-policy.v1',enabled:true,
28
+ lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'evidence'),
29
+ roots:{project:{path:project,read,write:false,exec:false,deny_paths:[]}},
30
+ commands:{},max_read_bytes:PAGE,max_write_bytes:65536
31
+ }));
32
+ const policy=await loadCodingPolicy(policyPath);
33
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
34
+ return {root,project,policy,keys,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
35
+ }
36
+
37
+ test('R64 full and compact read schemas accept next_offset beyond one-page bound',()=>{
38
+ const server={registerTool(name,definition){if(name==='machine_fs_read')this.read=definition;}};
39
+ registerMachineFsTools(server,{runtime:{}});
40
+ const args={root_id:'project',path:'large.bin',offset:PAGE+2,max_bytes:64,hash_file:false,timeout_ms:30000};
41
+ assert.equal(server.read.inputSchema.safeParse(args).success,true,'full tool rejects a valid next_offset');
42
+ assert.equal(machineFsGatewayInputSchema.safeParse({op:'read',...args}).success,true,'compact tool rejects a valid next_offset');
43
+ assert.equal(server.read.inputSchema.safeParse({...args,max_bytes:PAGE+1}).success,false,'per-call byte cap must remain');
44
+ assert.equal(machineFsGatewayInputSchema.safeParse({op:'read',...args,max_bytes:PAGE+1}).success,false);
45
+ assert.equal(server.read.inputSchema.safeParse({...args,offset:Number.MAX_SAFE_INTEGER+1}).success,false);
46
+ assert.equal(machineFsGatewayInputSchema.safeParse({op:'read',...args,offset:Number.MAX_SAFE_INTEGER+1}).success,false);
47
+ });
48
+
49
+ test('R64 reads page two using next_offset beyond 4 MiB',async t=>{
50
+ const f=await fixture(t);
51
+ const bytes=Buffer.alloc(PAGE+32,0x61);
52
+ bytes[0]=0x5a;
53
+ bytes[1]=0x5b;
54
+ bytes[PAGE+2]=0x7f;
55
+ await writeFile(path.join(f.project,'large.bin'),bytes);
56
+ const first=await f.runtime.read({rootId:'project',relativePath:'large.bin',offset:2,maxBytes:PAGE});
57
+ assert.equal(first.next_offset,PAGE+2);
58
+ assert.equal(first.eof,false);
59
+ const second=await f.runtime.read({rootId:'project',relativePath:'large.bin',offset:first.next_offset,maxBytes:64});
60
+ assert.equal(second.offset,PAGE+2);
61
+ assert.equal(Buffer.from(second.data_base64,'base64')[0],0x7f);
62
+ assert.equal(second.eof,true);
63
+ assert.equal(second.next_offset,bytes.length);
64
+ const atEof=await f.runtime.read({rootId:'project',relativePath:'large.bin',offset:bytes.length,maxBytes:1});
65
+ assert.equal(atEof.eof,true);
66
+ assert.equal(Buffer.from(atEof.data_base64,'base64').length,0);
67
+ assert.equal(atEof.next_offset,bytes.length);
68
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'large.bin',offset:bytes.length+1,maxBytes:1}),/exceeds file size/);
69
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'large.bin',offset:Number.MAX_SAFE_INTEGER+1,maxBytes:1}),/safe integer/);
70
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'large.bin',offset:0,maxBytes:PAGE+1}),/max_bytes/);
71
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'large.bin',offset:-PAGE-1,maxBytes:1}),/offset/);
72
+ });
73
+
74
+ test('R64 pages are independent observations and hash_file reveals replacement between calls',async t=>{
75
+ const f=await fixture(t);
76
+ const file=path.join(f.project,'large.bin');
77
+ const before=Buffer.alloc(PAGE+32,0x61);
78
+ const after=Buffer.alloc(PAGE+32,0x62);
79
+ await writeFile(file,before);
80
+ const first=await f.runtime.read({rootId:'project',relativePath:'large.bin',offset:2,maxBytes:PAGE,hashFile:true});
81
+ assert.equal(first.file_sha256,sha(before));
82
+ await writeFile(file,after);
83
+ const second=await f.runtime.read({rootId:'project',relativePath:'large.bin',offset:first.next_offset,maxBytes:64,hashFile:true});
84
+ assert.equal(second.file_sha256,sha(after));
85
+ assert.notEqual(second.file_sha256,first.file_sha256,'the caller must not treat these pages as one snapshot');
86
+ });
87
+
88
+ test('R64 a denied read root remains denied at a large offset',async t=>{
89
+ const f=await fixture(t,{read:false});
90
+ await writeFile(path.join(f.project,'large.bin'),Buffer.alloc(PAGE+16));
91
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'large.bin',offset:PAGE+1,maxBytes:1}),/does not allow read/);
92
+ });
93
+
94
+ test('R64 large offset does not bypass root containment',async t=>{
95
+ const f=await fixture(t);
96
+ await writeFile(path.join(f.root,'outside.bin'),Buffer.alloc(PAGE+16));
97
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'../outside.bin',offset:PAGE+1,maxBytes:1}),/relative|traversal|path|outside/i);
98
+ });
99
+
100
+ test('R64 large-offset read retains bounded worker cancellation',async t=>{
101
+ const f=await fixture(t);
102
+ await writeFile(path.join(f.project,'large.bin'),Buffer.alloc(PAGE+16));
103
+ const worker=path.join(f.root,'slow-read-worker.mjs');
104
+ await writeFile(worker,'setTimeout(() => process.stdout.write("{}"), 30000);\n');
105
+ const runtime=createMachineFsRuntime({policy:f.policy,signingKeyPath:f.keys.privateKeyPath,verifyKeyPath:f.keys.publicKeyPath,readWorkerPath:worker});
106
+ await assert.rejects(()=>runtime.read({rootId:'project',relativePath:'large.bin',offset:PAGE+1,maxBytes:1,timeoutMs:100}),/timed out after 100ms/);
107
+ });
108
+
109
+ test('R64 default hash stream is abort-bound to read timeout',async()=>{
110
+ const source=await readFile(new URL('../src/machine-fs.mjs',import.meta.url),'utf8');
111
+ assert.ok(/createReadStream\(file,\s*\{\s*signal\s*\}\)/.test(source),'hash stream must receive the read abort signal');
112
+ assert.ok(/abortController\.abort\(\)/.test(source),'read timeout must abort its hash stream');
113
+ });
114
+
115
+ test('R64 timed-out default hash stream receives abort and stops',async t=>{
116
+ const f=await fixture(t);
117
+ await writeFile(path.join(f.project,'large.bin'),Buffer.alloc(PAGE+16));
118
+ const original=fs.createReadStream;
119
+ let observedAbort=false;
120
+ fs.createReadStream=(_file,options={})=>{
121
+ const stream=new EventEmitter();
122
+ options.signal?.addEventListener('abort',()=>{
123
+ observedAbort=true;
124
+ queueMicrotask(()=>stream.emit('error',new Error('aborted')));
125
+ },{once:true});
126
+ return stream;
127
+ };
128
+ syncBuiltinESMExports();
129
+ try{
130
+ await assert.rejects(()=>f.runtime.read({rootId:'project',relativePath:'large.bin',offset:PAGE+1,maxBytes:1,hashFile:true,timeoutMs:100}),/timed out after 100ms/);
131
+ assert.equal(observedAbort,true,'default hash stream continued after timeout');
132
+ }finally{
133
+ fs.createReadStream=original;
134
+ syncBuiltinESMExports();
135
+ }
136
+ });
@@ -0,0 +1,52 @@
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 currentPackage=await json('package.json');
8
+ const sourceSha='d6f75feada9ad0ae2c248b3a833ac9cf0e04150d2430ddaa70b3dfb64a0b0a86';
9
+ const runtime={
10
+ release_id:'v0.15.39-3cda3b1c65b7b7c5',
11
+ manifest_sha256:'3cda3b1c65b7b7c561c5a8617156ab87d69cf162f447f3e99a4cb4a76471b3de',
12
+ generation_manifest_sha256:'f47cb96083679e764725df1bde6eda53169a39f55772c173ab22f89a1dfaa7ab'
13
+ };
14
+
15
+ test('R64 release identity distinguishes unsealed source predecessor from active runtime predecessor',{skip:currentPackage.version!=='0.15.41'},async()=>{
16
+ const [pkg,lock,bridge,release,provenance,matrix,sbom,scope,gaps,version,gate]=await Promise.all([
17
+ json('package.json'),json('package-lock.json'),json('bridge/package.json'),json('RELEASE.json'),
18
+ json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),json('docs/R64-SCOPE.json'),
19
+ json('docs/R64-REFERENCE-GAP-MATRIX.json'),read('src/version.mjs'),read('scripts/gate-windows-r64-prepublish.ps1')
20
+ ]);
21
+ assert.equal(pkg.version,'0.15.41');
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\.41'/);
26
+ assert.equal(release.version,pkg.version);
27
+ assert.equal(release.train,'R64');
28
+ assert.equal(provenance.version,pkg.version);
29
+ assert.equal(matrix.version,pkg.version);
30
+ assert.equal(matrix.train,'R64');
31
+ assert.equal(sbom.metadata.component.version,pkg.version);
32
+ assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r64:0.15.41');
33
+ assert.deepEqual(release.runtime_predecessor,runtime);
34
+ assert.deepEqual(provenance.runtime_predecessor,runtime);
35
+ assert.equal(release.source_predecessor.version,'0.15.40');
36
+ assert.equal(release.source_predecessor.manifest_file_sha256,sourceSha);
37
+ assert.equal(release.source_predecessor.release_status,'NOT_PROVEN_RELEASED');
38
+ assert.equal(provenance.source_predecessor_manifest_file_sha256,sourceSha);
39
+ assert.equal(scope.source_predecessor.manifest_sha256,sourceSha);
40
+ assert.deepEqual(scope.runtime_predecessor,runtime);
41
+ assert.equal(release.r64_bounded_large_file_read.new_public_tools,0);
42
+ assert.equal(release.r64_bounded_large_file_read.new_authority_scopes,0);
43
+ assert.equal(release.r64_bounded_large_file_read.cross_page_atomic_snapshot_claim,false);
44
+ assert.equal(matrix.r64.prepublish_only,true);
45
+ assert.equal(gaps.references.length,7);
46
+ assert.equal(gaps.references.filter(row=>row.proposed_action.startsWith('ADAPT')).length,1);
47
+ assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r64-prepublish.ps1');
48
+ assert.match(pkg.scripts['r64:focused:test'],/r64-machine-fs-large-read\.test\.mjs/);
49
+ assert.match(gate,/d6f75feada9ad0ae2c248b3a833ac9cf0e04150d2430ddaa70b3dfb64a0b0a86/);
50
+ assert.match(gate,/v0\.15\.39-3cda3b1c65b7b7c5/);
51
+ assert.doesNotMatch(gate,/npm\.cmd publish|--publish-only/);
52
+ });
@@ -0,0 +1,72 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash,generateKeyPairSync,sign as cryptoSign} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {canonicalJson} from '../src/canonical-json.mjs';
8
+ import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
9
+
10
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ const DOMAIN=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
12
+
13
+ function signReceipt(body,privateKey,publicKey){
14
+ const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
15
+ const signature=cryptoSign(null,Buffer.concat([DOMAIN,bodyBytes]),privateKey);
16
+ const keyId=sha(Buffer.from(publicKey.export({type:'spki',format:'der'})));
17
+ const envelope={body,body_sha256:sha(bodyBytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',key_id:keyId,signature_base64:signature.toString('base64')}};
18
+ envelope.receipt_sha256=sha(Buffer.from(canonicalJson(envelope),'utf8'));
19
+ return envelope;
20
+ }
21
+
22
+ test('stale activation lock is reclaimed only from exact terminal signed activation evidence',async()=>{
23
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-activation-lock-'));
24
+ try{
25
+ const updates=path.join(root,'evidence','updates'),trust=path.join(root,'trust');
26
+ await mkdir(updates,{recursive:true});await mkdir(trust,{recursive:true});
27
+ const {privateKey,publicKey}=generateKeyPairSync('ed25519');
28
+ await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}));
29
+ const subject='a'.repeat(64),manager=createActivationClaimManager({stateRoot:root});
30
+ const first=await manager.claim(subject);
31
+ const updateId='r27-test-terminal';
32
+ const dir=path.join(updates,updateId);await mkdir(dir,{recursive:true});
33
+ const activePointer={schema:'deadbyte.active-release.v1',release_id:'v0.15.42-test',version:'0.15.42',
34
+ manifest_sha256:'b'.repeat(64),release_root:path.join(root,'releases','v0.15.42-test'),
35
+ generation_root:path.join(root,'generations','v0.15.42-test-g1'),generation_manifest_sha256:'c'.repeat(64),
36
+ launcher_sha256:'d'.repeat(64),config_path:path.join(root,'generations','v0.15.42-test-g1','controller-config.psd1')};
37
+ const receipt=signReceipt({schema:'deadbyte.r27-slot-update-receipt.v1',update_id:updateId,release_version:'0.15.42',
38
+ candidate_manifest_sha256:'b'.repeat(64),rollback_performed:false,prior_pointer:{release_id:'v0.15.41-parent'},
39
+ active_pointer:activePointer},privateKey,publicKey);
40
+ const receiptPath=path.join(dir,'receipt.json');await writeFile(receiptPath,JSON.stringify(receipt));
41
+ await writeFile(path.join(root,'active-release.json'),JSON.stringify(activePointer));
42
+ const outcome={schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
43
+ preflight_subject_sha256:subject,candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
44
+ predecessor_release_id:'v0.15.41-parent',outcome:{state:'ACTIVATED'},update_id:updateId,
45
+ state_path:path.join(dir,'state.json'),receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256};
46
+ await writeFile(path.join(updates,'r65-activation-outcome-terminal.json'),JSON.stringify(outcome));
47
+ const second=await manager.claim('e'.repeat(64));
48
+ assert.notEqual(second.operation_id,first.operation_id);
49
+ await second.release();
50
+ }finally{await rm(root,{recursive:true,force:true});}
51
+ });
52
+
53
+ test('terminal-looking outcome with invalid receipt cannot reclaim activation lock',async()=>{
54
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-activation-lock-forged-'));
55
+ try{
56
+ const updates=path.join(root,'evidence','updates'),trust=path.join(root,'trust');
57
+ await mkdir(updates,{recursive:true});await mkdir(trust,{recursive:true});
58
+ const {publicKey}=generateKeyPairSync('ed25519');
59
+ await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}));
60
+ const subject='1'.repeat(64),manager=createActivationClaimManager({stateRoot:root});
61
+ const first=await manager.claim(subject);
62
+ const receiptPath=path.join(updates,'forged-receipt.json');
63
+ await writeFile(receiptPath,JSON.stringify({body:{schema:'deadbyte.r27-slot-update-receipt.v1'},receipt_sha256:'2'.repeat(64)}));
64
+ await writeFile(path.join(updates,'r65-activation-outcome-forged.json'),JSON.stringify({
65
+ schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,preflight_subject_sha256:subject,
66
+ candidate_version:'0.15.42',candidate_manifest_sha256:'3'.repeat(64),predecessor_release_id:'v0.15.41-parent',
67
+ outcome:{state:'ACTIVATED'},update_id:'r27-forged',state_path:path.join(updates,'x','state.json'),
68
+ receipt_path:receiptPath,receipt_sha256:'2'.repeat(64)
69
+ }));
70
+ await assert.rejects(()=>manager.claim('4'.repeat(64)),/unresolved|in progress|receipt|terminal/i);
71
+ }finally{await rm(root,{recursive:true,force:true});}
72
+ });
@@ -0,0 +1,59 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,rm,writeFile} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
7
+
8
+ const subject='a'.repeat(64);
9
+
10
+ test('terminal pre-mutation rejected claim can be reclaimed by exact operation evidence',async()=>{
11
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-lock-terminal-'));
12
+ try{
13
+ const state=path.join(root,'state'),manager=createActivationClaimManager({stateRoot:state});
14
+ const first=await manager.claim(subject);
15
+ const updates=path.join(state,'evidence','updates');await mkdir(updates,{recursive:true});
16
+ await writeFile(path.join(updates,'r65-activation-outcome-terminal.json'),JSON.stringify({
17
+ schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
18
+ preflight_subject_sha256:subject,candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
19
+ outcome:{state:'ACTIVATION_REJECTED'},update_id:null,state_path:null
20
+ }));
21
+ const second=await manager.claim(subject);
22
+ assert.notEqual(second.operation_id,first.operation_id);
23
+ await second.release();
24
+ await assert.rejects(first.release(),/changed|missing|ownership/i);
25
+ }finally{await rm(root,{recursive:true,force:true});}
26
+ });
27
+
28
+ test('unknown activation outcome is never reclaimed automatically',async()=>{
29
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-lock-unknown-'));
30
+ try{
31
+ const state=path.join(root,'state'),manager=createActivationClaimManager({stateRoot:state});
32
+ const first=await manager.claim(subject);
33
+ const updates=path.join(state,'evidence','updates');
34
+ await writeFile(path.join(updates,'r65-activation-outcome-unknown.json'),JSON.stringify({
35
+ schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
36
+ preflight_subject_sha256:subject,candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
37
+ outcome:{state:'ACTIVATION_OUTCOME_UNKNOWN'},update_id:'r27-unknown',state_path:path.join(updates,'unknown','state.json')
38
+ }));
39
+ await assert.rejects(()=>manager.claim(subject),/unresolved|unknown|in progress/i);
40
+ }finally{await rm(root,{recursive:true,force:true});}
41
+ });
42
+
43
+ test('corrupt or foreign-subject lock evidence fails closed',async()=>{
44
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-lock-corrupt-'));
45
+ try{
46
+ const state=path.join(root,'state'),updates=path.join(state,'evidence','updates');await mkdir(updates,{recursive:true});
47
+ await writeFile(path.join(updates,'r65-activation.lock'),'{bad');
48
+ const manager=createActivationClaimManager({stateRoot:state});
49
+ await assert.rejects(()=>manager.claim(subject),/corrupt|invalid/i);
50
+ await rm(path.join(updates,'r65-activation.lock'));
51
+ const first=await manager.claim(subject);
52
+ await writeFile(path.join(updates,'r65-activation-outcome-foreign.json'),JSON.stringify({
53
+ schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
54
+ preflight_subject_sha256:'c'.repeat(64),candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
55
+ outcome:{state:'ACTIVATION_REJECTED'},update_id:null,state_path:null
56
+ }));
57
+ await assert.rejects(()=>manager.claim(subject),/unresolved|in progress|subject/i);
58
+ }finally{await rm(root,{recursive:true,force:true});}
59
+ });
@@ -0,0 +1,179 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {orchestrateActivation} from '../src/activation-preflight-r65.mjs';
4
+
5
+ const parent={release_id:'v0.15.41-63a78ad159ad3003',version:'0.15.41',manifest_sha256:'63a78ad159ad3003db3fefafcd468bef22599c829a867f4f10a46c7a20748b3e',generation_manifest_sha256:'19ae2efcdcde9f80c474f0e55851d4ada51c22813d3e05af03b11acb28c3fca5'};
6
+ const candidate={version:'0.15.42',manifest_sha256:'a'.repeat(64)};
7
+ const ids={bridge:{alive:true,reason:'verified',pid:10},core:{alive:true,reason:'verified',pid:11},cloud:{alive:true,reason:'verified',pid:12}};
8
+ const policies={process:'a'.repeat(64),coding:'b'.repeat(64),autonomous:'c'.repeat(64),desktop:'d'.repeat(64),host:'e'.repeat(64)};
9
+
10
+ function legacyEntry(armed){return {configured:true,armed,reason:armed?'standing-grant':'disarmed',epoch_seq:null,epoch_sha256:null};}
11
+ function epochEntry(armed,seq=7,sha='f'.repeat(64)){return {configured:true,armed,reason:armed?'standing-grant':'disarmed',epoch_seq:armed?seq:null,epoch_sha256:armed?sha:null};}
12
+ function authorityEntries(vector,epochNative=false){
13
+ return Object.fromEntries(Object.entries(vector).map(([plane,armed])=>[plane,epochNative?epochEntry(armed):legacyEntry(armed)]));
14
+ }
15
+
16
+ function fixture(initial={}){
17
+ const calls=[];
18
+ const initialVector=initial.authority??{process:true,coding:true,autonomous:true,desktop:false,host:true};
19
+ let current={state:'ONLINE',local_ready:true,...ids,supervisor:{running:true},authority:authorityEntries(initialVector,initial.epochNative===true),policy_sha256:policies,
20
+ active_release_id:parent.release_id,active_manifest_sha256:parent.manifest_sha256,active_generation_manifest_sha256:parent.generation_manifest_sha256,...initial};
21
+ if(initial.authority)current={...current,authority:authorityEntries(initial.authority,initial.epochNative===true)};
22
+ return {calls,observe:async()=>current,command:async name=>{
23
+ calls.push(name);
24
+ if(name.startsWith('disarm')){
25
+ const plane=name.slice(6);
26
+ current={...current,authority:{...current.authority,[plane]:legacyEntry(false)}};
27
+ if(plane==='coding')current={...current,authority:{...current.authority,autonomous:legacyEntry(false)}};
28
+ }else if(name==='supervisorstop')current={...current,supervisor:{running:false}};
29
+ else if(name==='stop')current={...current,state:'OFFLINE',local_ready:false,bridge:{alive:false},core:{alive:false},cloud:{alive:false}};
30
+ else throw new Error('unexpected authority widening command '+name);
31
+ },activate:async()=>{calls.push('activate');current={...current,state:'ONLINE',local_ready:true,...ids,supervisor:{running:true}};return {release_id:'v0.15.42-candidate',verified:true};},
32
+ setCurrent:value=>{current=value;},getCurrent:()=>current};
33
+ }
34
+
35
+ test('legacy standing authority is revoked before activation and never auto-restored',async()=>{
36
+ const f=fixture();
37
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
38
+ assert.equal(result.state,'ACTIVATED');
39
+ assert.equal(result.runtime_activation,'ACTIVATED');
40
+ assert.equal(result.authority_continuity,'REAUTH_REQUIRED');
41
+ assert.deepEqual(result.reauth_planes,['process','coding','autonomous','host']);
42
+ assert.deepEqual(f.calls,['disarmhost','disarmautonomous','disarmcoding','disarmprocess','activate']);
43
+ assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
44
+ assert.deepEqual(result.final_authority,{process:false,coding:false,autonomous:false,desktop:false,host:false});
45
+ });
46
+
47
+ test('parked dirty predecessor reaches clean offline before activation',async()=>{
48
+ const f=fixture({state:'PARKED',authority:{process:false,coding:false,autonomous:false,desktop:false,host:false}});
49
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
50
+ assert.equal(result.state,'ACTIVATED');
51
+ assert.equal(result.authority_continuity,'NOT_APPLICABLE');
52
+ assert.deepEqual(f.calls,['supervisorstop','stop','activate']);
53
+ });
54
+
55
+ test('legacy revoke failure is terminal for the original action and never dispatches activation',async()=>{
56
+ const f=fixture();
57
+ const command=f.command;
58
+ f.command=async name=>{if(name==='disarmcoding'){f.calls.push(name);throw new Error('forced revoke failure');}await command(name);};
59
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
60
+ assert.equal(result.state,'ACTIVATION_OUTCOME_UNKNOWN');
61
+ assert.equal(f.calls.includes('activate'),false);
62
+ assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
63
+ });
64
+
65
+ test('unexpected successor authority after activation is not reported as safe activation',async()=>{
66
+ const f=fixture();
67
+ const activate=f.activate;
68
+ f.activate=async()=>{
69
+ const result=await activate();
70
+ const current=f.getCurrent();
71
+ f.setCurrent({...current,authority:{...current.authority,process:legacyEntry(true)}});
72
+ return result;
73
+ };
74
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
75
+ assert.equal(result.state,'ACTIVATION_OUTCOME_UNKNOWN');
76
+ assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
77
+ });
78
+
79
+ test('concurrent activation attempts admit only one transition',async()=>{
80
+ const f=fixture({authority:{process:false,coding:false,autonomous:false,desktop:false,host:false}});
81
+ let held=false,releaseFirst;
82
+ const claim=async()=>{if(held)throw new Error('activation already in progress');held=true;return async()=>{held=false;};};
83
+ const activate=f.activate;
84
+ f.activate=async()=>{await new Promise(resolve=>{releaseFirst=resolve;});return activate();};
85
+ const options={pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f,claim};
86
+ const first=orchestrateActivation(options);
87
+ while(!releaseFirst)await new Promise(resolve=>setTimeout(resolve,1));
88
+ let second;
89
+ try{
90
+ second=await Promise.race([orchestrateActivation(options),new Promise(resolve=>setTimeout(()=>resolve({state:'TIMED_OUT'}),50))]);
91
+ assert.equal(second.state,'ACTIVATION_REJECTED');
92
+ }finally{releaseFirst();}
93
+ assert.equal((await first).state,'ACTIVATED');
94
+ assert.deepEqual(f.calls,['activate']);
95
+ });
96
+
97
+ test('successor policy drift cannot become an exact legacy authority restoration',async()=>{
98
+ for(const plane of ['host','process']){
99
+ const vector={process:false,coding:false,autonomous:false,desktop:false,host:false};
100
+ vector[plane]=true;
101
+ const f=fixture({authority:vector});
102
+ const activate=f.activate;
103
+ let drift=false;
104
+ f.activate=async()=>{const result=await activate();drift=true;return result;};
105
+ const observe=f.observe;
106
+ f.observe=async()=>{
107
+ const value=await observe();
108
+ return drift?{...value,policy_sha256:{...value.policy_sha256,[plane]:'9'.repeat(64)}}:value;
109
+ };
110
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
111
+ assert.equal(result.state,'ACTIVATED');
112
+ assert.equal(result.authority_continuity,'REAUTH_REQUIRED');
113
+ assert.deepEqual(result.reauth_planes,[plane]);
114
+ assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
115
+ }
116
+ });
117
+
118
+ test('epoch-native standing authority remains fail-closed until transaction continuity engine is wired',async()=>{
119
+ const f=fixture({epochNative:true,authority:{process:true,coding:true,autonomous:true,desktop:false,host:false}});
120
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
121
+ assert.equal(result.state,'ACTIVATION_REJECTED');
122
+ assert.equal(result.preflight,'AUTHORITY_CONTINUITY_ENGINE_REQUIRED');
123
+ assert.equal(result.authority_continuity,'UNVERIFIED');
124
+ assert.deepEqual(f.calls,[]);
125
+ });
126
+
127
+ test('epoch-native continuity engine revokes before switch and restores exact bounded authority afterward',async()=>{
128
+ const f=fixture({epochNative:true,authority:{process:true,coding:true,autonomous:true,desktop:false,host:false}});
129
+ const capturedNonces={process:'1'.repeat(64),coding:'2'.repeat(64),autonomous:'3'.repeat(64)};
130
+ const restoredNonces={process:'4'.repeat(64),coding:'5'.repeat(64),autonomous:'6'.repeat(64)};
131
+ const continuityEngine={
132
+ capture:async({observation,subject_sha256})=>{
133
+ f.calls.push('continuity:capture');
134
+ assert.equal(observation.authority.process.armed,true);
135
+ return {update_id:'r65-epoch-native-test',subject_sha256,planes:['process','coding','autonomous'],captured_nonces:capturedNonces};
136
+ },
137
+ revoke:async capture=>{
138
+ f.calls.push('continuity:revoke');
139
+ assert.equal(capture.update_id,'r65-epoch-native-test');
140
+ const current=f.getCurrent();
141
+ f.setCurrent({...current,authority:authorityEntries({process:false,coding:false,autonomous:false,desktop:false,host:false},true)});
142
+ return {process:'TRANSACTION_REVOKED',coding:'TRANSACTION_REVOKED',autonomous:'TRANSACTION_REVOKED'};
143
+ },
144
+ restore:async ({capture,successor_observation})=>{
145
+ f.calls.push('continuity:restore');
146
+ assert.equal(successor_observation.authority.process.armed,false);
147
+ const current=f.getCurrent();
148
+ f.setCurrent({...current,authority:{
149
+ process:epochEntry(true,9,'7'.repeat(64)),coding:epochEntry(true,9,'8'.repeat(64)),autonomous:epochEntry(true,9,'9'.repeat(64)),
150
+ desktop:epochEntry(false),host:epochEntry(false)
151
+ }});
152
+ return {authority_outcomes:{process:'RESTORED_EXACT',coding:'RESTORED_EXACT',autonomous:'RESTORED_EXACT',desktop:'NOT_APPLICABLE',host:'NOT_APPLICABLE'},
153
+ restored_nonces:restoredNonces,reauth_planes:[]};
154
+ }
155
+ };
156
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f,continuityEngine});
157
+ assert.equal(result.state,'ACTIVATED');
158
+ assert.equal(result.runtime_activation,'ACTIVATED');
159
+ assert.equal(result.authority_continuity,'RESTORED_EXACT');
160
+ assert.deepEqual(result.authority_outcomes,{process:'RESTORED_EXACT',coding:'RESTORED_EXACT',autonomous:'RESTORED_EXACT',desktop:'NOT_APPLICABLE',host:'NOT_APPLICABLE'});
161
+ assert.deepEqual(f.calls,['continuity:capture','continuity:revoke','activate','continuity:restore']);
162
+ assert.notEqual(restoredNonces.process,capturedNonces.process);
163
+ assert.notEqual(restoredNonces.coding,capturedNonces.coding);
164
+ assert.notEqual(restoredNonces.autonomous,capturedNonces.autonomous);
165
+ assert.deepEqual(result.final_authority,{process:true,coding:true,autonomous:true,desktop:false,host:false});
166
+ });
167
+
168
+
169
+ test('terminal activation outcome is persisted before activation claim release',async()=>{
170
+ const f=fixture({authority:{process:false,coding:false,autonomous:false,desktop:false,host:false}});
171
+ const order=[];
172
+ const claim=async()=>{order.push('claim');return async()=>{order.push('release');};};
173
+ const activate=f.activate;
174
+ f.activate=async()=>{order.push('activate-start');const value=await activate();order.push('activate-end');return value;};
175
+ const recordOutcome=async outcome=>{assert.equal(outcome.state,'ACTIVATED');order.push('record');};
176
+ const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f,claim,recordOutcome});
177
+ assert.equal(result.state,'ACTIVATED');
178
+ assert.deepEqual(order,['claim','activate-start','activate-end','record','release']);
179
+ });
@@ -0,0 +1,35 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {classifyActivationPreflight,authorityTransitionPlan} from '../src/activation-preflight-r65.mjs';
4
+
5
+ const parent={release_id:'v0.15.41-63a78ad159ad3003',version:'0.15.41',manifest_sha256:'63a78ad159ad3003db3fefafcd468bef22599c829a867f4f10a46c7a20748b3e',generation_manifest_sha256:'19ae2efcdcde9f80c474f0e55851d4ada51c22813d3e05af03b11acb28c3fca5'};
6
+ const candidate={version:'0.15.42',manifest_sha256:'a'.repeat(64)};
7
+ const authority={process:true,coding:true,autonomous:true,desktop:false,host:false};
8
+ const policies={process:'1'.repeat(64),coding:'2'.repeat(64),autonomous:'3'.repeat(64),desktop:'4'.repeat(64),host:'5'.repeat(64)};
9
+ const online={state:'ONLINE',local_ready:true,bridge:{alive:true,reason:'verified',pid:10},core:{alive:true,reason:'verified',pid:11},cloud:{alive:true,reason:'verified',pid:12},supervisor:{running:true},authority,policy_sha256:policies,
10
+ active_release_id:parent.release_id,active_manifest_sha256:parent.manifest_sha256,active_generation_manifest_sha256:parent.generation_manifest_sha256};
11
+ const input={pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,observation:online};
12
+
13
+ test('preflight accepts exact online predecessor with full observed identity and authority vector',()=>{
14
+ const result=classifyActivationPreflight(input);
15
+ assert.equal(result.state,'ONLINE_READY');assert.equal(result.safe_to_activate,true);
16
+ assert.deepEqual(result.authority,authority);
17
+ assert.match(result.subject_sha256,/^[0-9a-f]{64}$/);
18
+ });
19
+
20
+ test('host armed is not safe; plan only disarms Host and restores exact captured vector',()=>{
21
+ const armed={...authority,host:true};
22
+ const result=classifyActivationPreflight({...input,observation:{...online,authority:armed}});
23
+ assert.equal(result.state,'ONLINE_HOST_ARMED');assert.equal(result.safe_to_activate,false);
24
+ const plan=authorityTransitionPlan(result.authority);
25
+ assert.deepEqual(plan.before,['disarmhost']);
26
+ assert.deepEqual(plan.after,['armhost']);
27
+ });
28
+
29
+ test('parked dirty, generation drift and identity loss fail closed without an activation command',()=>{
30
+ assert.equal(classifyActivationPreflight({...input,observation:{...online,state:'PARKED'}}).state,'PARKED_DIRTY');
31
+ assert.equal(classifyActivationPreflight({...input,pointer:{...parent,generation_manifest_sha256:'b'.repeat(64)}}).state,'POINTER_DRIFT');
32
+ assert.equal(classifyActivationPreflight({...input,observation:{...online,active_release_id:'wrong'}}).state,'POINTER_DRIFT');
33
+ assert.equal(classifyActivationPreflight({...input,observation:{...online,policy_sha256:{...policies,host:null}}}).state,'UNKNOWN');
34
+ assert.equal(classifyActivationPreflight({...input,observation:{...online,cloud:{alive:false,reason:'missing'}}}).state,'RUNTIME_DRIFT');
35
+ });
@@ -0,0 +1,58 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {captureStandingGrant,deriveSecurityEnvelope,compareSecurityEnvelopes} from '../src/authority-continuity-r65.mjs';
5
+
6
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
7
+ const beforePolicy={schema:'deadbyte.process-policy.v1',enabled:true,grant_file:'C:\\state\\runtime\\process-grant.json',
8
+ state_dir:'C:\\state\\evidence\\process',native_host:{path:'C:\\slot\\host.exe',sha256:'a'.repeat(64)},
9
+ roots:{core:{path:'D:\\Projects',deny_paths:['secrets']}},profiles:{ping:{executable:'C:\\Windows\\cmd.exe',
10
+ executable_sha256:'b'.repeat(64),root_ids:['core'],prefix_args:['/d'],max_input_bytes:4096}}};
11
+ const policyBytes=Buffer.from(JSON.stringify(beforePolicy));
12
+ const grant={schema:'deadbyte.process-grant.v1',policy_sha256:sha(policyBytes),instance_nonce:'c'.repeat(64),
13
+ profile_ids:['ping'],root_ids:['core'],granted_at_utc:'2026-10-07T00:00:00Z'};
14
+ const grantBytes=Buffer.from(JSON.stringify(grant));
15
+ const predecessor={release_id:'v0.15.41-parent',generation_manifest_sha256:'d'.repeat(64)};
16
+ const candidate={version:'0.15.42',manifest_sha256:'e'.repeat(64)};
17
+ const updateId='r65-activation-test';
18
+
19
+ test('continuity record binds exact policy bytes, grant bytes, scope and release lineage',()=>{
20
+ const record=captureStandingGrant({plane:'process',policyBytes,grantBytes,predecessor,candidate,updateId});
21
+ assert.equal(record.prior_policy_sha256,sha(policyBytes));
22
+ assert.equal(record.grant_sha256,sha(grantBytes));
23
+ assert.equal(record.grant_instance_nonce,grant.instance_nonce);
24
+ assert.deepEqual(record.granted_scope,{profile_ids:['ping'],root_ids:['core']});
25
+ assert.equal(record.predecessor_release,predecessor.release_id);
26
+ assert.equal(record.predecessor_generation_manifest_sha256,predecessor.generation_manifest_sha256);
27
+ assert.equal(record.candidate_version,candidate.version);
28
+ assert.equal(record.candidate_manifest_sha256,candidate.manifest_sha256);
29
+ assert.match(record.subject_sha256,/^[0-9a-f]{64}$/);
30
+ });
31
+
32
+ test('removing unused authority is narrower while adding a root is wider',()=>{
33
+ const prior={...beforePolicy,roots:{...beforePolicy.roots,unused:{path:'D:\\Unused',deny_paths:[]}}};
34
+ const priorEnvelope=deriveSecurityEnvelope('process',prior);
35
+ const currentEnvelope=deriveSecurityEnvelope('process',beforePolicy);
36
+ const added={...beforePolicy,roots:{...beforePolicy.roots,extra:{path:'D:\\Extra',deny_paths:[]}}};
37
+ assert.equal(compareSecurityEnvelopes('process',priorEnvelope,currentEnvelope).relation,'NARROWER');
38
+ assert.equal(compareSecurityEnvelopes('process',currentEnvelope,deriveSecurityEnvelope('process',added)).relation,'WIDER');
39
+ });
40
+
41
+ test('changed executable pin or disjoint root remains unverified instead of guessed safe',()=>{
42
+ const base=deriveSecurityEnvelope('process',beforePolicy);
43
+ const changedPin={...beforePolicy,native_host:{...beforePolicy.native_host,sha256:'f'.repeat(64)}};
44
+ const moved={...beforePolicy,roots:{core:{path:'E:\\Other',deny_paths:['secrets']}}};
45
+ assert.equal(compareSecurityEnvelopes('process',base,deriveSecurityEnvelope('process',changedPin)).relation,'UNVERIFIED');
46
+ assert.equal(compareSecurityEnvelopes('process',base,deriveSecurityEnvelope('process',moved)).relation,'UNVERIFIED');
47
+ });
48
+
49
+ test('grant outside predecessor scope is rejected before a continuity record exists',()=>{
50
+ const badGrant={...grant,root_ids:['missing'],policy_sha256:sha(policyBytes)};
51
+ const badBytes=Buffer.from(JSON.stringify(badGrant));
52
+ assert.throws(()=>captureStandingGrant({plane:'process',policyBytes,grantBytes:badBytes,predecessor,candidate,updateId}),/scope invalid/i);
53
+ });
54
+
55
+ test('candidate or predecessor identity must be complete and hash-bound',()=>{
56
+ assert.throws(()=>captureStandingGrant({plane:'process',policyBytes,grantBytes,predecessor:{release_id:'x',generation_manifest_sha256:'bad'},candidate,updateId}),/identity invalid/i);
57
+ assert.throws(()=>captureStandingGrant({plane:'process',policyBytes,grantBytes,predecessor,candidate:{version:'0.15.42',manifest_sha256:'bad'},updateId}),/identity invalid/i);
58
+ });
@@ -0,0 +1,34 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
4
+ import {createHash} from 'node:crypto';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createAuthorityEpochStore,deriveSecurityEnvelope} from '../src/authority-continuity-r65.mjs';
8
+ import {prepareAuthorityTestTrust} from './r65-authority-test-trust.mjs';
9
+
10
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+
12
+ test('durable standing epoch binds exact policy bytes and canonical security ceiling',async()=>{
13
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-durable-policy-'));
14
+ try{
15
+ const runtime=await prepareAuthorityTestTrust(root);
16
+ const grantFile=path.join(runtime,'coding-grant.json');
17
+ const policy={schema:'deadbyte.coding-policy.v1',enabled:true,lease_file:grantFile,
18
+ evidence_dir:path.join(root,'evidence'),max_read_bytes:4096,max_write_bytes:4096,
19
+ roots:{core:{path:path.join(root,'project'),read:true,write:true,exec:false,deny_paths:[]}},commands:{}};
20
+ const policyBytes=Buffer.from(JSON.stringify(policy));
21
+ const grantBytes=Buffer.from(JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:sha(policyBytes),
22
+ instance_nonce:'a'.repeat(64),root_ids:['core'],granted_at_utc:'2026-10-06T00:00:00Z'}));
23
+ await writeFile(grantFile,grantBytes);
24
+ const store=createAuthorityEpochStore({runtimeDir:runtime,policyBytes});
25
+ const head=await store.append({plane:'coding',action:'grant',grantBytes,grantInstanceNonce:'a'.repeat(64),updateId:null});
26
+ const event=JSON.parse(await readFile(path.join(runtime,'authority-epochs','coding','events',head.event_file),'utf8'));
27
+ assert.equal(event.policy_sha256,sha(policyBytes));
28
+ assert.equal(event.policy_envelope_sha256,deriveSecurityEnvelope('coding',policy).sha256);
29
+ assert.equal((await store.verifyGrant({plane:'coding',grantBytes,grantInstanceNonce:'a'.repeat(64),policyBytes})).trusted,true);
30
+ const changed=Buffer.from(JSON.stringify({...policy,max_read_bytes:8192}));
31
+ assert.equal((await store.verifyGrant({plane:'coding',grantBytes,grantInstanceNonce:'a'.repeat(64),policyBytes:changed})).trusted,false,
32
+ 'a changed policy must not inherit a prior epoch even with the same grant bytes');
33
+ }finally{await rm(root,{recursive:true,force:true});}
34
+ });