deadbyte-mcp 0.15.48 → 0.15.49

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 (50) hide show
  1. package/MANIFEST.SHA256 +48 -22
  2. package/PROVENANCE.json +11 -22
  3. package/README.txt +18 -18
  4. package/RELEASE.json +368 -378
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bench/r121-epoch-readhead.mjs +54 -0
  8. package/bench/r124-epoch-predecessor.mjs +53 -0
  9. package/controller/deadbyte-process-policy.json +4 -4
  10. package/docs/HARNESS2-ADAPTATION-20261020.md +24 -0
  11. package/docs/R128-RELEASE-CANDIDATE.md +26 -0
  12. package/package.json +1 -1
  13. package/scripts/npm-release-r48.mjs +0 -4
  14. package/scripts/verify-authority-epoch-history.mjs +38 -0
  15. package/src/activation-continuity-engine-r65.mjs +9 -15
  16. package/src/agent-eval-r42.mjs +30 -0
  17. package/src/authority-continuity-r65.mjs +182 -13
  18. package/src/autonomous-local-supervisor.mjs +77 -47
  19. package/src/deadbyte-cli.mjs +18 -28
  20. package/src/remote-live-log.mjs +15 -8
  21. package/src/remote-log-decode-r60.mjs +65 -0
  22. package/src/remote-session-guardian.mjs +4 -5
  23. package/src/version.mjs +1 -1
  24. package/test/autonomous-local-supervisor.test.mjs +1 -1
  25. package/test/harness2-eval-context-isolation.test.mjs +90 -0
  26. package/test/r108-remote-powershell-path.test.mjs +59 -0
  27. package/test/r110-supervisor-powershell-path.test.mjs +28 -0
  28. package/test/r111-supervisor-marker-cas.test.mjs +116 -0
  29. package/test/r112-supervisor-claim-cas.test.mjs +63 -0
  30. package/test/r113-supervisor-cleanup-terminality.test.mjs +100 -0
  31. package/test/r114-supervisor-absence-proof.test.mjs +45 -0
  32. package/test/r115-posix-continuity-entrypoint.test.mjs +17 -0
  33. package/test/r116-authority-crash-matrix.test.mjs +83 -0
  34. package/test/r117-concurrent-orphan-recovery.test.mjs +54 -0
  35. package/test/r118-cross-process-orphan-recovery.test.mjs +99 -0
  36. package/test/r119-recovery-crash-after-commit.test.mjs +91 -0
  37. package/test/r120-authority-history-audit.test.mjs +103 -0
  38. package/test/r121-signed-transition-invariant.test.mjs +132 -0
  39. package/test/r122-epoch-bounded-read.test.mjs +58 -0
  40. package/test/r123-recovery-bounded-read.test.mjs +64 -0
  41. package/test/r124-live-log-integrity.test.mjs +66 -0
  42. package/test/r124-live-log-tail-boundaries.test.mjs +57 -0
  43. package/test/r124-signed-predecessor-shape.test.mjs +66 -0
  44. package/test/r125-restore-binding-types.test.mjs +63 -0
  45. package/test/r126-restore-policy-envelope.test.mjs +89 -0
  46. package/test/r127-restore-append-policy-boundary.test.mjs +25 -0
  47. package/test/r59-tool-surface-compat.test.mjs +1 -1
  48. package/test/r65-authority-epoch-store.test.mjs +9 -6
  49. package/test/r74-prepublish-contract.test.mjs +3 -1
  50. package/scripts/npm-release-readiness-r108.mjs +0 -134
@@ -0,0 +1,58 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync, randomBytes} from 'node:crypto';
4
+ import {mkdtemp, mkdir, readFile, writeFile, rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {pathToFileURL} from 'node:url';
8
+ import {createAuthorityEpochStore as createCandidateStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const protectedModule = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE;
11
+ const createWriterStore = protectedModule
12
+ ? (await import(pathToFileURL(protectedModule).href)).createAuthorityEpochStore
13
+ : createCandidateStore;
14
+ const maxRecordBytes = 1048576;
15
+
16
+ async function withSignedHead(run) {
17
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r122-bounded-'));
18
+ try {
19
+ const runtimeDir = path.join(root, 'runtime');
20
+ const trust = path.join(root, 'trust');
21
+ await mkdir(runtimeDir); await mkdir(trust);
22
+ const keys = generateKeyPairSync('ed25519');
23
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
24
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
25
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
26
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
27
+ const nonce = randomBytes(32).toString('hex');
28
+ const grantBytes = Buffer.from(JSON.stringify({instance_nonce:nonce}));
29
+ const writer = createWriterStore({runtimeDir});
30
+ const head = await writer.append({plane:'process', action:'grant', grantBytes,
31
+ grantInstanceNonce:nonce});
32
+ const candidate = createCandidateStore({runtimeDir});
33
+ await run({runtimeDir, candidate, head});
34
+ } finally {
35
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
36
+ }
37
+ }
38
+
39
+ test('R122 oversized but parseable head is rejected by hot read and history audit',
40
+ () => withSignedHead(async ({runtimeDir, candidate}) => {
41
+ const headPath = path.join(runtimeDir, 'authority-epochs', 'process', 'head.json');
42
+ const normal = await readFile(headPath);
43
+ await writeFile(headPath, Buffer.concat([normal,
44
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
45
+ await assert.rejects(candidate.readHead({plane:'process'}), /authority epoch head oversized/);
46
+ await assert.rejects(candidate.auditHistory({plane:'process'}), /authority epoch head oversized/);
47
+ }));
48
+
49
+ test('R122 oversized event is rejected before signature or hash work',
50
+ () => withSignedHead(async ({runtimeDir, candidate, head}) => {
51
+ const eventPath = path.join(runtimeDir, 'authority-epochs', 'process',
52
+ 'events', head.event_file);
53
+ const normal = await readFile(eventPath);
54
+ await writeFile(eventPath, Buffer.concat([normal,
55
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
56
+ await assert.rejects(candidate.readHead({plane:'process'}), /authority epoch event oversized/);
57
+ await assert.rejects(candidate.auditHistory({plane:'process'}), /authority epoch event oversized/);
58
+ }));
@@ -0,0 +1,64 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync, randomBytes} from 'node:crypto';
4
+ import {mkdtemp, mkdir, readFile, writeFile, rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {pathToFileURL} from 'node:url';
8
+ import {createAuthorityEpochStore as createCandidateStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const protectedModule = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE;
11
+ const createWriterStore = protectedModule
12
+ ? (await import(pathToFileURL(protectedModule).href)).createAuthorityEpochStore
13
+ : createCandidateStore;
14
+ const maxRecordBytes = 1048576;
15
+
16
+ async function withOrphan(run) {
17
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r123-recovery-'));
18
+ try {
19
+ const runtimeDir = path.join(root, 'runtime');
20
+ const trust = path.join(root, 'trust');
21
+ await mkdir(runtimeDir); await mkdir(trust);
22
+ const keys = generateKeyPairSync('ed25519');
23
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
24
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
25
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
26
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
27
+ const nonce = randomBytes(32).toString('hex');
28
+ const grantBytes = Buffer.from(JSON.stringify({instance_nonce:nonce}));
29
+ const writer = createWriterStore({runtimeDir});
30
+ const prior = await writer.append({plane:'process', action:'grant', grantBytes,
31
+ grantInstanceNonce:nonce});
32
+ const dir = path.join(runtimeDir, 'authority-epochs', 'process');
33
+ const headPath = path.join(dir, 'head.json');
34
+ const priorHeadBytes = await readFile(headPath);
35
+ const orphan = await writer.append({plane:'process', action:'user_revoke', grantBytes,
36
+ grantInstanceNonce:nonce});
37
+ await writeFile(headPath, priorHeadBytes);
38
+ await run({runtimeDir, dir, headPath, prior, orphan,
39
+ candidate:createCandidateStore({runtimeDir})});
40
+ } finally {
41
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
42
+ }
43
+ }
44
+
45
+ test('R123 recovery rejects oversized parseable predecessor head',
46
+ () => withOrphan(async ({headPath, prior, orphan, candidate}) => {
47
+ const normal = await readFile(headPath);
48
+ await writeFile(headPath, Buffer.concat([normal,
49
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
50
+ await assert.rejects(candidate.reconcileOrphanedRevocation({plane:'process',
51
+ expectedHeadSha256:prior.event_sha256, expectedOrphanSha256:orphan.event_sha256,
52
+ operatorConfirmed:true}), /authority epoch head oversized/);
53
+ }));
54
+
55
+ test('R123 recovery rejects oversized orphan event before hash or lock acquisition',
56
+ () => withOrphan(async ({dir, prior, orphan, candidate}) => {
57
+ const eventPath = path.join(dir, 'events', orphan.event_file);
58
+ const normal = await readFile(eventPath);
59
+ await writeFile(eventPath, Buffer.concat([normal,
60
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
61
+ await assert.rejects(candidate.reconcileOrphanedRevocation({plane:'process',
62
+ expectedHeadSha256:prior.event_sha256, expectedOrphanSha256:orphan.event_sha256,
63
+ operatorConfirmed:true}), /authority epoch event oversized/);
64
+ }));
@@ -0,0 +1,66 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,writeFile,appendFile,rename,rm} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import * as live from '../src/remote-live-log.mjs';
7
+ import * as log from '../src/remote-log-decode-r60.mjs';
8
+
9
+ test('R124 missing terminal outcome cannot show success',()=>{
10
+ const tracker=live.createLiveTimelineTracker({clock:()=>1000,displayMode:'ascii'});
11
+ tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'r124-missing',args:{root_id:'core',path:'a.txt'}});
12
+ const line=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'r124-missing'});
13
+ assert.match(line,/OUTCOME_UNKNOWN|FAILED_AFTER_UNKNOWN_EFFECT/);
14
+ assert.doesNotMatch(line,/SUCCEEDED|\bOK\b/);
15
+ assert.equal(live.classifyRemoteAuditOutcome({event_type:'tool.result'}),'unknown');
16
+ });
17
+ test('R124 unexpected terminal status cannot imply success',()=>{
18
+ assert.equal(live.classifyRemoteAuditOutcome({outcome:{status:'threw',is_error:true}}),'error');
19
+ assert.equal(live.classifyRemoteAuditOutcome({outcome:{status:'alien',is_error:false}}),'unknown');
20
+ assert.equal(live.classifyRemoteAuditOutcome({outcome:{status:'ok',is_error:false}}),'ok');
21
+ });
22
+ test('R124 audit errors must not hide under missing outcomes in filters',()=>{
23
+ const filter=live.createLiveTimelineFilter('status=unknown');
24
+ filter.accept({event_type:'tool.request',tool:'coding_file_write',request_id:'r124-filter',args:{}});
25
+ assert.equal(filter.accept({event_type:'tool.result',tool:'coding_file_write',request_id:'r124-filter'}),true);
26
+ });
27
+ test('R124 tail reader keeps line bytes and detects rewrite and rotation on the same-size file',async()=>{
28
+ assert.equal(typeof log.readRemoteLogAppend,'function','export the bounded handle-pinned log reader');
29
+ const dir=await mkdtemp(path.join(os.tmpdir(),'r124-tail-'));
30
+ const file=path.join(dir,'segment-00000001.jsonl');
31
+ try{
32
+ await writeFile(file,'abc');
33
+ let {cursor}=await log.readRemoteLogAppend(file,null,{startAtEnd:true});
34
+ assert.equal(cursor.offset,3);
35
+ await appendFile(file,'def\n');
36
+ let chunk=await log.readRemoteLogAppend(file,cursor);
37
+ assert.equal(chunk.text,'def\n');
38
+ assert.equal(chunk.reset,false);
39
+ cursor=chunk.cursor;
40
+ await writeFile(file,'UVWXYZ\n');
41
+ chunk=await log.readRemoteLogAppend(file,cursor);
42
+ assert.equal(chunk.reset,true,'overwrite must be observed, not skipped');
43
+ assert.equal(chunk.text,'UVWXYZ\n');
44
+ cursor=chunk.cursor;
45
+ await rename(file,file+'.old');
46
+ await writeFile(file,'second\n');
47
+ chunk=await log.readRemoteLogAppend(file,cursor);
48
+ assert.equal(chunk.reset,true,'rotation must reset cursor');
49
+ assert.equal(chunk.text,'second\n');
50
+ }finally{await rm(dir,{recursive:true,force:true});}
51
+ });
52
+ test('R124 UTF8 partial codepoint stays unread until complete; no duplicate bytes',async()=>{
53
+ assert.equal(typeof log.readRemoteLogAppend,'function');
54
+ const dir=await mkdtemp(path.join(os.tmpdir(),'r124-utf8-'));
55
+ const file=path.join(dir,'bridge.stdout.log');
56
+ try{
57
+ await writeFile(file,Buffer.from([0xe0,0xb8]));
58
+ let chunk=await log.readRemoteLogAppend(file,{offset:0});
59
+ assert.equal(chunk.text,'');
60
+ assert.equal(chunk.cursor.offset,0);
61
+ await appendFile(file,Buffer.from([0x81,10]));
62
+ chunk=await log.readRemoteLogAppend(file,chunk.cursor);
63
+ assert.equal(chunk.text,'ก\n');
64
+ assert.equal(chunk.cursor.offset,4);
65
+ }finally{await rm(dir,{recursive:true,force:true});}
66
+ });
@@ -0,0 +1,57 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,writeFile,appendFile,rm} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import {readRemoteLogAppend} from '../src/remote-log-decode-r60.mjs';
7
+
8
+ async function fixture(fn){
9
+ const dir=await mkdtemp(path.join(os.tmpdir(),'r124-log-edge-'));
10
+ try{await fn(dir);}finally{await rm(dir,{recursive:true,force:true});}
11
+ }
12
+
13
+ test('R124 log catch-up is bounded and byte-exact across multiple chunks',()=>fixture(async dir=>{
14
+ const file=path.join(dir,'segment-00000001.jsonl');
15
+ const payload='0123456789'.repeat(47)+'\n';
16
+ await writeFile(file,payload);
17
+ let cursor=null,seen='';
18
+ for(let i=0;i<100&&seen.length<payload.length;i++){
19
+ const page=await readRemoteLogAppend(file,cursor,{maxBytes:11});
20
+ assert.ok(page.text.length<=11);
21
+ assert.equal(page.reset,false);
22
+ cursor=page.cursor;seen+=page.text;
23
+ }
24
+ assert.equal(seen,payload);
25
+ assert.equal(cursor.offset,Buffer.byteLength(payload));
26
+ }));
27
+
28
+ test('R124 UTF16LE tail makes forward progress on complete code units',()=>fixture(async dir=>{
29
+ const file=path.join(dir,'controller.stdout.log');
30
+ const data=Buffer.concat([Buffer.from([0xff,0xfe]),Buffer.from('กข\n','utf16le')]);
31
+ await writeFile(file,data);
32
+ let cursor=null,text='';
33
+ for(let i=0;i<10&&cursor?.offset!==data.length;i++){
34
+ const page=await readRemoteLogAppend(file,cursor,{maxBytes:4});
35
+ cursor=page.cursor;text+=page.text;
36
+ }
37
+ assert.equal(text,'กข\n');
38
+ assert.equal(cursor.offset,data.length);
39
+ }));
40
+
41
+ test('R124 disappearance surfaces explicit reset and does not silently preserve stale cursor',()=>fixture(async dir=>{
42
+ const file=path.join(dir,'bridge.stdout.log');
43
+ await writeFile(file,'abc\n');
44
+ const old=(await readRemoteLogAppend(file,null,{startAtEnd:true})).cursor;
45
+ await rm(file);
46
+ const disappeared=await readRemoteLogAppend(file,old);
47
+ assert.equal(disappeared.reset,true);
48
+ assert.equal(disappeared.resetReason,'file disappeared');
49
+ assert.equal(disappeared.cursor,null);
50
+ await writeFile(file,'new\n');
51
+ const newlyCreated=await readRemoteLogAppend(file,disappeared.cursor);
52
+ assert.equal(newlyCreated.text,'new\n');
53
+ }));
54
+
55
+ test('R124 invalid too-small chunk is rejected rather than livelocking on UTF8',async()=>{
56
+ await assert.rejects(readRemoteLogAppend('ignore.log',null,{maxBytes:1}),/arguments invalid/);
57
+ });
@@ -0,0 +1,66 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash, generateKeyPairSync, randomBytes, sign} from 'node:crypto';
4
+ import {mkdtemp, mkdir, rm, writeFile} 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 {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const domain = Buffer.from('DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1\0', 'utf8');
11
+ const sha = bytes => createHash('sha256').update(bytes).digest('hex');
12
+
13
+ for (const [caseName, priorIdentity] of [
14
+ ['unknown action', {action:'made_up', update_id:null, restore_binding:null}],
15
+ ['stray update identity', {action:'grant', update_id:'unexpected', restore_binding:null}],
16
+ ['unexpected restore binding', {action:'grant', update_id:null, restore_binding:{unapproved:true}}],
17
+ ['wrong event schema', {action:'grant', update_id:null, restore_binding:null, schema:'not-an-epoch-event'}],
18
+ ['invalid grant nonce', {action:'grant', update_id:null, restore_binding:null, grant_instance_nonce:'bad'}],
19
+ ['non-genesis predecessor link', {action:'grant', update_id:null, restore_binding:null, previous_event_sha256:'0'.repeat(64)}],
20
+ ['incomplete policy binding', {action:'grant', update_id:null, restore_binding:null, policy_sha256:'0'.repeat(64)}],
21
+ ['array grant nonce', {action:'grant', update_id:null, restore_binding:null, grant_instance_nonce:['1'.repeat(64)]}],
22
+ ]) test(`R124 hot authority read rejects signed predecessor with ${caseName}`, async () => {
23
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r124-predecessor-'));
24
+ try {
25
+ const runtimeDir = path.join(root, 'runtime');
26
+ const trust = path.join(root, 'trust');
27
+ const dir = path.join(runtimeDir, 'authority-epochs', 'process');
28
+ await mkdir(path.join(dir, 'events'), {recursive:true});
29
+ await mkdir(trust);
30
+ const keys = generateKeyPairSync('ed25519');
31
+ const publicBytes = keys.publicKey.export({type:'spki', format:'pem'});
32
+ await writeFile(path.join(trust, 'ed25519-public.pem'), publicBytes);
33
+ const keyId = sha(keys.publicKey.export({type:'spki', format:'der'}));
34
+ const signed = body => {
35
+ const signature = sign(null, Buffer.concat([domain, Buffer.from(canonicalJson(body))]), keys.privateKey);
36
+ const event = {...body, attestation:{algorithm:'ed25519', domain:'DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1', key_id:keyId, signature_base64:signature.toString('base64')}};
37
+ const bytes = Buffer.from(canonicalJson(event));
38
+ return {bytes, hash:sha(bytes), event};
39
+ };
40
+ const grantBytes = Buffer.from('isolated grant');
41
+ const oldNonce = randomBytes(32).toString('hex');
42
+ const nonce = randomBytes(32).toString('hex');
43
+ const old = signed({schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', seq:1,
44
+ action:'grant', update_id:null, grant_sha256:sha(Buffer.from('old grant')),
45
+ grant_instance_nonce:oldNonce, restore_binding:null, previous_event_sha256:null,
46
+ recorded_at_utc:new Date().toISOString(), ...priorIdentity});
47
+ const currentBody = {schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', seq:2,
48
+ action:'grant', update_id:null, grant_sha256:sha(grantBytes), grant_instance_nonce:nonce,
49
+ restore_binding:null, previous_event_sha256:old.hash, recorded_at_utc:new Date().toISOString()};
50
+ const current = signed(currentBody);
51
+ const eventName = (seq, hash) => `${String(seq).padStart(16, '0')}-${hash}.json`;
52
+ await writeFile(path.join(dir, 'events', eventName(1, old.hash)), old.bytes);
53
+ await writeFile(path.join(dir, 'events', eventName(2, current.hash)), current.bytes);
54
+ await writeFile(path.join(dir, 'head.json'), Buffer.from(canonicalJson({
55
+ schema:'deadbyte.authority-epoch-head.r65.v1', plane:'process', seq:2,
56
+ action:'grant', update_id:null, grant_sha256:currentBody.grant_sha256,
57
+ grant_instance_nonce:nonce, restore_binding:null, previous_event_sha256:old.hash,
58
+ event_sha256:current.hash, event_file:eventName(2, current.hash)})));
59
+ const store = createAuthorityEpochStore({runtimeDir});
60
+ await assert.rejects(store.readHead({plane:'process'}), /authority epoch transition invalid/);
61
+ const decision = await store.verifyGrant({plane:'process', grantBytes, grantInstanceNonce:nonce});
62
+ assert.equal(decision.trusted, false);
63
+ } finally {
64
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
65
+ }
66
+ });
@@ -0,0 +1,63 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash, generateKeyPairSync, randomBytes, sign} from 'node:crypto';
4
+ import {mkdtemp, mkdir, rm, writeFile} 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 {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const sha = bytes => createHash('sha256').update(bytes).digest('hex');
11
+ const domain = Buffer.from('DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1\0');
12
+ const eventName = (seq, hash) => `${String(seq).padStart(16, '0')}-${hash}.json`;
13
+
14
+ test('R125 hot read rejects signed restore predecessor with array-valued SHA binding', async () => {
15
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r125-binding-'));
16
+ try {
17
+ const runtimeDir = path.join(root, 'runtime');
18
+ const trust = path.join(root, 'trust');
19
+ const dir = path.join(runtimeDir, 'authority-epochs', 'process');
20
+ await mkdir(path.join(dir, 'events'), {recursive:true});
21
+ await mkdir(trust);
22
+ const keys = generateKeyPairSync('ed25519');
23
+ await writeFile(path.join(trust, 'ed25519-public.pem'), keys.publicKey.export({type:'spki', format:'pem'}));
24
+ const keyId = sha(keys.publicKey.export({type:'spki', format:'der'}));
25
+ const signEvent = body => {
26
+ const signature = sign(null, Buffer.concat([domain, Buffer.from(canonicalJson(body))]), keys.privateKey);
27
+ const event = {...body, attestation:{algorithm:'ed25519', domain:'DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1', key_id:keyId, signature_base64:signature.toString('base64')}};
28
+ const bytes = Buffer.from(canonicalJson(event));
29
+ return {bytes, hash:sha(bytes), event};
30
+ };
31
+ const nonce1 = randomBytes(32).toString('hex');
32
+ const nonce2 = randomBytes(32).toString('hex');
33
+ const nonce3 = randomBytes(32).toString('hex');
34
+ const oldGrant = sha(Buffer.from('old'));
35
+ const restoredGrant = sha(Buffer.from('restored'));
36
+ const currentGrantBytes = Buffer.from('current');
37
+ const common = {schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', recorded_at_utc:new Date().toISOString()};
38
+ const one = signEvent({...common, seq:1, action:'grant', update_id:null,
39
+ grant_sha256:oldGrant, grant_instance_nonce:nonce1, restore_binding:null, previous_event_sha256:null});
40
+ const two = signEvent({...common, seq:2, action:'transaction_revoke', update_id:'r125-update',
41
+ grant_sha256:oldGrant, grant_instance_nonce:nonce1, restore_binding:null, previous_event_sha256:one.hash});
42
+ const binding = {prior_grant_sha256:[oldGrant], prior_grant_instance_nonce:nonce1,
43
+ predecessor_policy_envelope_sha256:'1'.repeat(64), successor_policy_envelope_sha256:'2'.repeat(64),
44
+ candidate_manifest_sha256:'3'.repeat(64), continuity_subject_sha256:'4'.repeat(64)};
45
+ const three = signEvent({...common, seq:3, action:'transaction_restore', update_id:'r125-update',
46
+ grant_sha256:restoredGrant, grant_instance_nonce:nonce2, restore_binding:binding, previous_event_sha256:two.hash});
47
+ const fourBody = {...common, seq:4, action:'grant', update_id:null,
48
+ grant_sha256:sha(currentGrantBytes), grant_instance_nonce:nonce3, restore_binding:null,
49
+ previous_event_sha256:three.hash};
50
+ const four = signEvent(fourBody);
51
+ for (const [seq, signed] of [[1,one],[2,two],[3,three],[4,four]])
52
+ await writeFile(path.join(dir, 'events', eventName(seq, signed.hash)), signed.bytes);
53
+ await writeFile(path.join(dir, 'head.json'), Buffer.from(canonicalJson({
54
+ schema:'deadbyte.authority-epoch-head.r65.v1', plane:'process', seq:4, action:'grant', update_id:null,
55
+ grant_sha256:fourBody.grant_sha256, grant_instance_nonce:nonce3, restore_binding:null,
56
+ previous_event_sha256:three.hash, event_sha256:four.hash, event_file:eventName(4,four.hash)})));
57
+ const store = createAuthorityEpochStore({runtimeDir});
58
+ await assert.rejects(store.readHead({plane:'process'}), /authority epoch transaction restore binding invalid/);
59
+ assert.equal((await store.verifyGrant({plane:'process', grantBytes:currentGrantBytes, grantInstanceNonce:nonce3})).trusted, false);
60
+ } finally {
61
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
62
+ }
63
+ });
@@ -0,0 +1,89 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash, generateKeyPairSync, randomBytes, sign} from 'node:crypto';
4
+ import {mkdtemp, mkdir, rm, writeFile} 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 {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const sha = bytes => createHash('sha256').update(bytes).digest('hex');
11
+ const domain = Buffer.from('DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1\0');
12
+ const eventName = (seq, hash) => `${String(seq).padStart(16, '0')}-${hash}.json`;
13
+
14
+ async function signedRestoreFixture(bindingOverride) {
15
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r126-envelope-'));
16
+ const runtimeDir = path.join(root, 'runtime');
17
+ const trustDir = path.join(root, 'trust');
18
+ const eventDir = path.join(runtimeDir, 'authority-epochs', 'process', 'events');
19
+ await mkdir(eventDir, {recursive:true});
20
+ await mkdir(trustDir);
21
+ const keys = generateKeyPairSync('ed25519');
22
+ await writeFile(path.join(trustDir, 'ed25519-public.pem'), keys.publicKey.export({type:'spki', format:'pem'}));
23
+ const keyId = sha(keys.publicKey.export({type:'spki', format:'der'}));
24
+ const signEvent = body => {
25
+ const signature = sign(null, Buffer.concat([domain, Buffer.from(canonicalJson(body))]), keys.privateKey);
26
+ const event = {...body, attestation:{algorithm:'ed25519', domain:'DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1', key_id:keyId, signature_base64:signature.toString('base64')}};
27
+ const bytes = Buffer.from(canonicalJson(event));
28
+ return {bytes, hash:sha(bytes)};
29
+ };
30
+ const oldNonce = randomBytes(32).toString('hex');
31
+ const newNonce = randomBytes(32).toString('hex');
32
+ const oldGrantSha = sha(Buffer.from('old-grant'));
33
+ const newGrantBytes = Buffer.from('new-grant');
34
+ const priorEnvelope = '1'.repeat(64);
35
+ const nextEnvelope = '2'.repeat(64);
36
+ const common = {schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', recorded_at_utc:new Date().toISOString()};
37
+ const one = signEvent({...common, seq:1, action:'grant', update_id:null, grant_sha256:oldGrantSha,
38
+ grant_instance_nonce:oldNonce, restore_binding:null, previous_event_sha256:null,
39
+ policy_sha256:'a'.repeat(64), policy_envelope_sha256:priorEnvelope});
40
+ const two = signEvent({...common, seq:2, action:'transaction_revoke', update_id:'r126-update',
41
+ grant_sha256:oldGrantSha, grant_instance_nonce:oldNonce, restore_binding:null,
42
+ previous_event_sha256:one.hash, policy_sha256:'a'.repeat(64), policy_envelope_sha256:priorEnvelope});
43
+ const restoreBinding = {prior_grant_sha256:oldGrantSha, prior_grant_instance_nonce:oldNonce,
44
+ predecessor_policy_envelope_sha256:priorEnvelope, successor_policy_envelope_sha256:nextEnvelope,
45
+ candidate_manifest_sha256:'3'.repeat(64), continuity_subject_sha256:'4'.repeat(64),
46
+ ...bindingOverride};
47
+ const threeBody = {...common, seq:3, action:'transaction_restore', update_id:'r126-update',
48
+ grant_sha256:sha(newGrantBytes), grant_instance_nonce:newNonce, restore_binding:restoreBinding,
49
+ previous_event_sha256:two.hash, policy_sha256:'b'.repeat(64), policy_envelope_sha256:nextEnvelope};
50
+ const three = signEvent(threeBody);
51
+ for (const [seq, signed] of [[1,one],[2,two],[3,three]])
52
+ await writeFile(path.join(eventDir, eventName(seq, signed.hash)), signed.bytes);
53
+ await writeFile(path.join(runtimeDir, 'authority-epochs', 'process', 'head.json'), Buffer.from(canonicalJson({
54
+ schema:'deadbyte.authority-epoch-head.r65.v1', plane:'process', seq:3,
55
+ action:threeBody.action, update_id:threeBody.update_id,
56
+ grant_sha256:threeBody.grant_sha256, grant_instance_nonce:newNonce,
57
+ restore_binding:restoreBinding, previous_event_sha256:two.hash,
58
+ policy_sha256:threeBody.policy_sha256, policy_envelope_sha256:nextEnvelope,
59
+ event_sha256:three.hash, event_file:eventName(3,three.hash)
60
+ })));
61
+ return {root, runtimeDir, newGrantBytes, newNonce};
62
+ }
63
+
64
+ for (const [name, override] of [
65
+ ['predecessor policy envelope', {predecessor_policy_envelope_sha256:'f'.repeat(64)}],
66
+ ['successor policy envelope', {successor_policy_envelope_sha256:'e'.repeat(64)}]
67
+ ]) test(`R126 signed restore rejects wrong ${name} despite valid signature`, async () => {
68
+ const fixture = await signedRestoreFixture(override);
69
+ try {
70
+ const store = createAuthorityEpochStore({runtimeDir:fixture.runtimeDir});
71
+ await assert.rejects(store.readHead({plane:'process'}), /authority epoch transition invalid/);
72
+ assert.equal((await store.verifyGrant({plane:'process', grantBytes:fixture.newGrantBytes,
73
+ grantInstanceNonce:fixture.newNonce})).trusted, false);
74
+ } finally {
75
+ await rm(fixture.root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
76
+ }
77
+ });
78
+
79
+ test('R126 correctly bound signed restore stays readable', async () => {
80
+ const fixture = await signedRestoreFixture({});
81
+ try {
82
+ const store = createAuthorityEpochStore({runtimeDir:fixture.runtimeDir});
83
+ assert.equal((await store.readHead({plane:'process'})).action, 'transaction_restore');
84
+ assert.equal((await store.verifyGrant({plane:'process', grantBytes:fixture.newGrantBytes,
85
+ grantInstanceNonce:fixture.newNonce})).trusted, true);
86
+ } finally {
87
+ await rm(fixture.root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
88
+ }
89
+ });
@@ -0,0 +1,25 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {mkdtemp,rm} 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
+
9
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
10
+ test('R127 wrong signed successor policy envelope fails before native lock or event write',async()=>{
11
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r127-binding-'));
12
+ try{
13
+ const policy=Buffer.from(JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:false}));
14
+ const nonce='a'.repeat(64);
15
+ const grant=Buffer.from(JSON.stringify({instance_nonce:nonce,policy_sha256:sha(policy)}));
16
+ const binding={prior_grant_sha256:sha(grant),prior_grant_instance_nonce:'b'.repeat(64),
17
+ predecessor_policy_envelope_sha256:'c'.repeat(64),
18
+ successor_policy_envelope_sha256:'d'.repeat(64),
19
+ candidate_manifest_sha256:'e'.repeat(64),continuity_subject_sha256:'f'.repeat(64)};
20
+ const store=createAuthorityEpochStore({runtimeDir:path.join(root,'runtime'),policyBytes:policy});
21
+ await assert.rejects(store.append({plane:'coding',action:'transaction_restore',grantBytes:grant,
22
+ grantInstanceNonce:nonce,updateId:'r127',expectedHeadSha256:'1'.repeat(64),restoreBinding:binding}),
23
+ /successor policy envelope mismatch/);
24
+ }finally{await rm(root,{recursive:true,force:true});}
25
+ });
@@ -83,7 +83,7 @@ test('R72 compact baseline permits only R64 read offset and authorized coding-on
83
83
  assert.deepEqual(diff.added,[]);
84
84
  assert.deepEqual(diff.removed,[]);
85
85
  const packageVersion=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8')).version;
86
- if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48'].includes(packageVersion)){
86
+ if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49'].includes(packageVersion)){
87
87
  assert.deepEqual([...diff.schema_changed].sort(),['machine_fs','native_diff_preview']);
88
88
  assert.equal(diff.unchanged.length,41);
89
89
  const prior=baseline.tools.find(tool=>tool.name==='machine_fs');
@@ -4,7 +4,7 @@ import {createHash} from 'node:crypto';
4
4
  import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
- import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
7
+ import {createAuthorityEpochStore,deriveSecurityEnvelope} from '../src/authority-continuity-r65.mjs';
8
8
  import {prepareAuthorityTestTrust} from './r65-authority-test-trust.mjs';
9
9
 
10
10
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
@@ -32,9 +32,12 @@ async function fixture(run){
32
32
  }finally{await rm(root,{recursive:true,force:true});}
33
33
  }
34
34
 
35
- function restoreBinding(oldGrant,oldNonce){
35
+ function restoreBinding(oldGrant,oldNonce,policyBytes){
36
+ // A successful restore must describe the real signed policy envelope.
37
+ // This fixture keeps the same policy on both sides of the transaction.
38
+ const envelope=deriveSecurityEnvelope('process',JSON.parse(policyBytes)).sha256;
36
39
  return {prior_grant_sha256:sha(oldGrant),prior_grant_instance_nonce:oldNonce,
37
- predecessor_policy_envelope_sha256:'1'.repeat(64),successor_policy_envelope_sha256:'2'.repeat(64),
40
+ predecessor_policy_envelope_sha256:envelope,successor_policy_envelope_sha256:envelope,
38
41
  candidate_manifest_sha256:'3'.repeat(64),continuity_subject_sha256:'4'.repeat(64)};
39
42
  }
40
43
 
@@ -63,7 +66,7 @@ test('stale transaction CAS cannot overwrite a later user revoke',()=>fixture(as
63
66
  const nextNonce='e'.repeat(64),nextGrant=Buffer.from(JSON.stringify({...f.grant,instance_nonce:nextNonce}));
64
67
  await assert.rejects(()=>f.store.append({plane:'process',action:'transaction_restore',grantBytes:nextGrant,
65
68
  grantInstanceNonce:nextNonce,updateId:'r65-store-test',expectedHeadSha256:tx.event_sha256,
66
- restoreBinding:restoreBinding(f.grantBytes,f.grant.instance_nonce)}),/compare-and-swap conflict/);
69
+ restoreBinding:restoreBinding(f.grantBytes,f.grant.instance_nonce,f.policyBytes)}),/compare-and-swap conflict/);
67
70
  }));
68
71
 
69
72
  test('replaying an older signed head fails closed when a later event remains',()=>fixture(async f=>{
@@ -83,11 +86,11 @@ test('transaction restore requires a fresh nonce and becomes the trusted head',(
83
86
  grantInstanceNonce:f.grant.instance_nonce,updateId,expectedHeadSha256:granted.event_sha256});
84
87
  await assert.rejects(()=>f.store.append({plane:'process',action:'transaction_restore',grantBytes:f.grantBytes,
85
88
  grantInstanceNonce:f.grant.instance_nonce,updateId,expectedHeadSha256:revoked.event_sha256,
86
- restoreBinding:restoreBinding(f.grantBytes,f.grant.instance_nonce)}),/fresh nonce|resurrect/i);
89
+ restoreBinding:restoreBinding(f.grantBytes,f.grant.instance_nonce,f.policyBytes)}),/fresh nonce|resurrect/i);
87
90
  const newNonce='f'.repeat(64),newGrant=Buffer.from(JSON.stringify({...f.grant,instance_nonce:newNonce}));
88
91
  const restored=await f.store.append({plane:'process',action:'transaction_restore',grantBytes:newGrant,
89
92
  grantInstanceNonce:newNonce,updateId,expectedHeadSha256:revoked.event_sha256,
90
- restoreBinding:restoreBinding(f.grantBytes,f.grant.instance_nonce)});
93
+ restoreBinding:restoreBinding(f.grantBytes,f.grant.instance_nonce,f.policyBytes)});
91
94
  const verified=await f.store.verifyGrant({plane:'process',grantBytes:newGrant,grantInstanceNonce:newNonce,policyBytes:f.policyBytes});
92
95
  assert.equal(verified.trusted,true);
93
96
  assert.equal(verified.event_sha256,restored.event_sha256);
@@ -18,7 +18,9 @@ test('R74 prepublish contract: positive path succeeds and verifies identities',
18
18
  assert.equal(result.checks.package_leak_check.ok, true);
19
19
  assert.equal(result.checks.no_self_referential_tarball_hash, true);
20
20
  assert.equal(result.identities.package_name, 'deadbyte-mcp');
21
- assert.equal(result.identities.package_version, '0.15.48');
21
+ // This R74 component contract remains scoped to train R74, even when
22
+ // the containing npm package is the R128 0.15.49 release candidate.
23
+ assert.equal(result.identities.package_version, '0.15.49');
22
24
  assert.equal(result.identities.target_train, 'R74');
23
25
  assert.equal(result.identities.published_source_predecessor, '0.15.46');
24
26
  assert.notEqual(result.identities.published_source_predecessor, result.identities.runtime_predecessor);