deadbyte-mcp 0.15.37 → 0.15.39
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.
- package/MANIFEST.SHA256 +41 -22
- package/PROVENANCE.json +55 -20
- package/README.txt +19 -19
- package/RELEASE.json +50 -21
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +67 -15
- package/bridge/package.json +1 -1
- package/bridge/src/bridge.mjs +4 -4
- package/bridge/src/r40-governor.mjs +109 -19
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R61-TASK-CONTRACT.md +11 -0
- package/docs/r61-reference-evidence.md +18 -0
- package/package.json +5 -3
- package/scripts/gate-windows-r61-prepublish.ps1 +45 -0
- package/scripts/gate-windows-r62-prepublish.ps1 +45 -0
- package/scripts/npm-artifact-identity-r61.mjs +22 -0
- package/scripts/npm-release-r48.mjs +21 -5
- package/scripts/r61-mixed-lane-benchmark.mjs +98 -0
- package/scripts/r62-focused.ps1 +9 -0
- package/scripts/r62-full.ps1 +9 -0
- package/scripts/r62-live-transaction-audit.mjs +17 -0
- package/scripts/r62-unresolved-transaction-inspect.mjs +54 -0
- package/src/deadbyte-cli.mjs +9 -6
- package/src/doctor-transactions-r58.mjs +79 -8
- package/src/remote-live-log.mjs +28 -5
- package/src/runtime-update.mjs +28 -4
- package/src/version.mjs +1 -1
- package/test/bridge-runtime-r59.test.mjs +5 -5
- package/test/commander-schema-differential-r48.test.mjs +1 -1
- package/test/r58-doctor-transactions.test.mjs +2 -2
- package/test/r60-release-contract.test.mjs +4 -3
- package/test/r61-control-lane.test.mjs +154 -0
- package/test/r61-live-timeline-bounds.test.mjs +31 -0
- package/test/r61-npm-artifact-identity.test.mjs +17 -0
- package/test/r61-release-bridge-binding.test.mjs +32 -0
- package/test/r61-release-contract.test.mjs +45 -0
- package/test/r62-doctor-receipt-semantics.test.mjs +50 -0
- package/test/r62-doctor-transactions-reconcile.test.mjs +208 -0
- package/test/r62-release-contract.test.mjs +59 -0
- package/test/r62-rollback-failure-receipt.test.mjs +19 -0
- package/test/remote-live-log-r37.test.mjs +23 -6
- package/test/remote-session-cli.test.mjs +1 -1
|
@@ -12,21 +12,21 @@ const limits={
|
|
|
12
12
|
|
|
13
13
|
test('R59 bridge governor removes cancelled queued work and drains without replay',async()=>{
|
|
14
14
|
const governor=createBridgeGovernor({limits});
|
|
15
|
-
const first=await governor.acquire('
|
|
15
|
+
const first=await governor.acquire('coding_file_read');
|
|
16
16
|
const abort=new AbortController();
|
|
17
|
-
const queued=governor.acquire('
|
|
17
|
+
const queued=governor.acquire('coding_file_read',{signal:abort.signal});
|
|
18
18
|
abort.abort(new Error('caller disconnected'));
|
|
19
19
|
await assert.rejects(queued,/caller disconnected|aborted/);
|
|
20
20
|
assert.equal(governor.snapshot().normal.queued,0);
|
|
21
21
|
assert.equal(governor.snapshot().normal.cancelled,1);
|
|
22
|
-
const queuedDuringRecovery=governor.acquire('
|
|
22
|
+
const queuedDuringRecovery=governor.acquire('coding_file_read');
|
|
23
23
|
assert.equal(governor.reconcile('core generation changed'),1);
|
|
24
24
|
await assert.rejects(queuedDuringRecovery,/core generation changed/);
|
|
25
25
|
first.release();
|
|
26
|
-
const after=await governor.acquire('
|
|
26
|
+
const after=await governor.acquire('coding_file_read');
|
|
27
27
|
after.release();
|
|
28
28
|
const drain=governor.close('bridge reconciling');
|
|
29
|
-
await assert.rejects(governor.acquire('
|
|
29
|
+
await assert.rejects(governor.acquire('coding_file_read'),/closed/);
|
|
30
30
|
await drain;
|
|
31
31
|
assert.equal(governor.snapshot().normal.active,0);
|
|
32
32
|
});
|
|
@@ -5,7 +5,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
5
5
|
import {createCommanderWireAdapter} from '../src/commander-wire-adapter-r47.mjs';
|
|
6
6
|
import {registerCommanderExtendedBridge} from '../src/commander-extended-bridge-r47.mjs';
|
|
7
7
|
|
|
8
|
-
const referencePath='D:\\Projects\\
|
|
8
|
+
const referencePath='D:\\Projects\\deadbyte-mcp\\references\\desktop-commander\\dist\\tools\\schemas.js';
|
|
9
9
|
const expectedHash='f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140';
|
|
10
10
|
async function referenceSchemas(){
|
|
11
11
|
let source=await readFile(referencePath,'utf8');
|
|
@@ -10,12 +10,12 @@ test('R58 bounded transaction inventory flags residue without recovering or over
|
|
|
10
10
|
assert.equal((await inspectDoctorTransactions(root)).status,'PASS');assert.deepEqual(await readdir(root),[]);
|
|
11
11
|
const transactions=path.join(root,'evidence','updates');await mkdir(transactions,{recursive:true});
|
|
12
12
|
const write=async(name,body)=>{const dir=path.join(transactions,name);await mkdir(dir);const file=path.join(dir,'state.json'),bytes=JSON.stringify(body);await writeFile(file,bytes);return {file,bytes};};
|
|
13
|
-
const done=await write('done',{schema:'deadbyte.r27-slot-update-state.v1',phase:'
|
|
13
|
+
const done=await write('done',{schema:'deadbyte.r27-slot-update-state.v1',phase:'superseded'});
|
|
14
14
|
assert.equal((await inspectDoctorTransactions(root)).status,'PASS');
|
|
15
15
|
const pending=await write('pending',{schema:'deadbyte.r31-slot-rollback-state.v1',phase:'prior_pointer_restored',supervisor_pid:process.pid});
|
|
16
16
|
const result=await inspectDoctorTransactions(root);assert.equal(result.status,'WARN');assert.ok(result.findings.some(f=>f.evidence.recorded_phase==='prior_pointer_restored'));
|
|
17
17
|
for(const item of [done,pending])assert.equal(await readFile(item.file,'utf8'),item.bytes);
|
|
18
18
|
const bounded=await inspectDoctorTransactions(root,{maxEntries:1});assert.equal(bounded.status,'WARN');assert.ok(bounded.findings.some(f=>f.evidence.inventory_complete===false));
|
|
19
19
|
const corrupt=await write('corrupt',{schema:'INVALID',phase:'completed'});assert.equal((await inspectDoctorTransactions(root)).status,'FAIL');assert.equal(await readFile(corrupt.file,'utf8'),corrupt.bytes);
|
|
20
|
-
await assert.rejects(inspectDoctorTransactions(root,{maxEntries:
|
|
20
|
+
await assert.rejects(inspectDoctorTransactions(root,{maxEntries:1025}),/budget invalid/);
|
|
21
21
|
});
|
|
@@ -4,6 +4,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
4
4
|
|
|
5
5
|
const read=rel=>readFile(new URL('../'+rel,import.meta.url),'utf8');
|
|
6
6
|
const json=rel=>read(rel).then(raw=>JSON.parse(raw.replace(/^\uFEFF/,'')));
|
|
7
|
+
const currentPackage=await json('package.json');
|
|
7
8
|
|
|
8
9
|
const PARENT=Object.freeze({
|
|
9
10
|
version:'0.15.36',
|
|
@@ -12,7 +13,7 @@ const PARENT=Object.freeze({
|
|
|
12
13
|
generation_manifest_sha256:'6134f9f26b680e13ed545da4ab5f3020f411ea2569062fb9c154c8a7e036c9dc'
|
|
13
14
|
});
|
|
14
15
|
|
|
15
|
-
test('R60 identity is coherent and binds the exact production-accepted 0.15.36 predecessor',async()=>{
|
|
16
|
+
test('R60 identity is coherent and binds the exact production-accepted 0.15.36 predecessor',{skip:currentPackage.version!=='0.15.37'},async()=>{
|
|
16
17
|
const [pkg,lock,release,prov,matrix,sbom,version,bridgePkg,contract]=await Promise.all([
|
|
17
18
|
json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),
|
|
18
19
|
json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),json('bridge/package.json'),
|
|
@@ -46,7 +47,7 @@ test('R60 identity is coherent and binds the exact production-accepted 0.15.36 p
|
|
|
46
47
|
assert.match(contract,/0\.15\.37/);
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
test('R60 release contract exposes the proven remote truth semantics without widening dependencies',async()=>{
|
|
50
|
+
test('R60 release contract exposes the proven remote truth semantics without widening dependencies',{skip:currentPackage.version!=='0.15.37'},async()=>{
|
|
50
51
|
const [pkg,release,matrix]=await Promise.all([json('package.json'),json('RELEASE.json'),json('TEST-MATRIX.json')]);
|
|
51
52
|
assert.equal(Object.keys(pkg.dependencies??{}).length,4);
|
|
52
53
|
assert.equal(release.r60_remote_hardening.authority_truth_coherence,true);
|
|
@@ -69,7 +70,7 @@ test('R60 release contract exposes the proven remote truth semantics without wid
|
|
|
69
70
|
assert.equal(matrix.observed_pre_release_evidence.r60_package.status,'PASS_VERIFY_ONLY_PRE_FINAL_SEAL');
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
test('R60 package scripts bind the new focused, full-regression and Windows-gate entrypoints',async()=>{
|
|
73
|
+
test('R60 package scripts bind the new focused, full-regression and Windows-gate entrypoints',{skip:currentPackage.version!=='0.15.37'},async()=>{
|
|
73
74
|
const [pkg,gate]=await Promise.all([json('package.json'),read('scripts/gate-windows-r60-prepublish.ps1')]);
|
|
74
75
|
assert.match(pkg.scripts['r60:focused:test'],/r60-authority-coherence\.test\.mjs/);
|
|
75
76
|
assert.match(pkg.scripts['r60:focused:test'],/r60-remote-call-recovery\.test\.mjs/);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createBridgeGovernor,bridgeToolClass} from '../bridge/src/r40-governor.mjs';
|
|
4
|
+
|
|
5
|
+
test('control MCP execution is admitted while unrelated bulk work saturates',async()=>{
|
|
6
|
+
const governor=createBridgeGovernor();
|
|
7
|
+
const bulk=[];
|
|
8
|
+
let control=null;
|
|
9
|
+
try{
|
|
10
|
+
for(let i=0;i<8;i++)bulk.push(await governor.acquire('coding_search'));
|
|
11
|
+
const abort=new AbortController();
|
|
12
|
+
const pending=governor.acquire('machine_ping',{signal:abort.signal});
|
|
13
|
+
const observed=await Promise.race([
|
|
14
|
+
pending.then(ticket=>({kind:'admitted',ticket})),
|
|
15
|
+
new Promise(resolve=>setTimeout(()=>resolve({kind:'blocked'}),25))
|
|
16
|
+
]);
|
|
17
|
+
if(observed.kind==='admitted')control=observed.ticket;
|
|
18
|
+
else abort.abort(new Error('control blocked behind bulk'));
|
|
19
|
+
assert.equal(observed.kind,'admitted','control plane must not queue behind bulk');
|
|
20
|
+
assert.equal(governor.snapshot().control.active,1);
|
|
21
|
+
}finally{
|
|
22
|
+
control?.release();
|
|
23
|
+
for(const ticket of bulk)ticket.release();
|
|
24
|
+
await governor.close();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('compact routes use argument operation and never promote bulk to control',()=>{
|
|
29
|
+
assert.equal(bridgeToolClass('machine_observe',{op:'machine_status'}),'control');
|
|
30
|
+
assert.equal(bridgeToolClass('machine_observe',{op:'search_page'}),'bulk');
|
|
31
|
+
assert.equal(bridgeToolClass('process_session',{op:'read'}),'bulk');
|
|
32
|
+
assert.equal(bridgeToolClass('desktop_control',{op:'action'}),'desktop_action');
|
|
33
|
+
assert.equal(bridgeToolClass('coding_file_write',{root_id:'scratch'}),'normal');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('control queue saturates, cancels and drains without admitting cancelled work',async()=>{
|
|
37
|
+
const governor=createBridgeGovernor({limits:{control:{max_active:1,max_queue:1,queue_timeout_ms:100}}});
|
|
38
|
+
const first=await governor.acquire('machine_ping');
|
|
39
|
+
const abort=new AbortController();
|
|
40
|
+
const queued=governor.acquire('machine_status',{signal:abort.signal});
|
|
41
|
+
await assert.rejects(governor.acquire('machine_ping'),/queue full/);
|
|
42
|
+
assert.equal(governor.snapshot().control.queued,1);
|
|
43
|
+
abort.abort(new Error('client cancelled'));
|
|
44
|
+
await assert.rejects(queued,/client cancelled/);
|
|
45
|
+
assert.equal(governor.snapshot().control.queued,0);
|
|
46
|
+
const timed=governor.acquire('machine_status');
|
|
47
|
+
await assert.rejects(timed,/queue timeout/);
|
|
48
|
+
assert.equal(governor.snapshot().control.timed_out,1);
|
|
49
|
+
first.release();
|
|
50
|
+
const after=await governor.acquire('machine_status');
|
|
51
|
+
after.release();
|
|
52
|
+
await governor.close();
|
|
53
|
+
assert.equal(governor.snapshot().control.active,0);
|
|
54
|
+
});
|
|
55
|
+
test('mixed normal and bulk lanes share one bounded downstream budget while control retains a reserved slot',async()=>{
|
|
56
|
+
const governor=createBridgeGovernor();
|
|
57
|
+
const work=[];
|
|
58
|
+
let control=null;
|
|
59
|
+
let queuedTicket=null;
|
|
60
|
+
try{
|
|
61
|
+
for(let i=0;i<4;i++)work.push(await governor.acquire('coding_search'));
|
|
62
|
+
for(let i=0;i<4;i++)work.push(await governor.acquire('coding_file_read'));
|
|
63
|
+
|
|
64
|
+
const saturated=governor.snapshot();
|
|
65
|
+
assert.equal(saturated.shared.active,8);
|
|
66
|
+
assert.equal(saturated.shared.non_control_active,8);
|
|
67
|
+
assert.equal(saturated.shared.max_active,9);
|
|
68
|
+
assert.equal(saturated.shared.max_non_control_active,8);
|
|
69
|
+
|
|
70
|
+
const pendingWork=governor.acquire('coding_file_read');
|
|
71
|
+
const observedWork=await Promise.race([
|
|
72
|
+
pendingWork.then(ticket=>({kind:'admitted',ticket})),
|
|
73
|
+
new Promise(resolve=>setTimeout(()=>resolve({kind:'blocked'}),25))
|
|
74
|
+
]);
|
|
75
|
+
assert.equal(observedWork.kind,'blocked','ninth non-control call must respect shared budget');
|
|
76
|
+
|
|
77
|
+
control=await governor.acquire('machine_ping');
|
|
78
|
+
assert.equal(governor.snapshot().shared.active,9,'control uses reserved downstream slot');
|
|
79
|
+
assert.equal(governor.snapshot().control.active,1);
|
|
80
|
+
control.release();
|
|
81
|
+
control=null;
|
|
82
|
+
|
|
83
|
+
const released=work.shift();
|
|
84
|
+
released.release();
|
|
85
|
+
|
|
86
|
+
queuedTicket=await pendingWork;
|
|
87
|
+
assert.equal(governor.snapshot().shared.non_control_active,8);
|
|
88
|
+
}finally{
|
|
89
|
+
control?.release();
|
|
90
|
+
queuedTicket?.release();
|
|
91
|
+
for(const ticket of work)ticket.release();
|
|
92
|
+
await governor.close();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const final=governor.snapshot();
|
|
96
|
+
assert.equal(final.shared.active,0);
|
|
97
|
+
assert.equal(final.shared.non_control_active,0);
|
|
98
|
+
});
|
|
99
|
+
test('oldest admissible waiter is not starved by control backlog',async()=>{
|
|
100
|
+
const governor=createBridgeGovernor({
|
|
101
|
+
shared:{max_active:3,max_non_control_active:2}
|
|
102
|
+
});
|
|
103
|
+
const held=[];
|
|
104
|
+
const resolved=[];
|
|
105
|
+
const normalAbort=new AbortController();
|
|
106
|
+
const controlAbort=new AbortController();
|
|
107
|
+
let normalPending=null;
|
|
108
|
+
let controlPending=null;
|
|
109
|
+
|
|
110
|
+
try{
|
|
111
|
+
held.push(await governor.acquire('coding_search'));
|
|
112
|
+
held.push(await governor.acquire('coding_search'));
|
|
113
|
+
held.push(await governor.acquire('machine_ping'));
|
|
114
|
+
|
|
115
|
+
normalPending=governor.acquire(
|
|
116
|
+
'coding_file_read',
|
|
117
|
+
{signal:normalAbort.signal}
|
|
118
|
+
).then(ticket=>{
|
|
119
|
+
resolved.push(ticket);
|
|
120
|
+
return ticket;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
controlPending=governor.acquire(
|
|
124
|
+
'machine_status',
|
|
125
|
+
{signal:controlAbort.signal}
|
|
126
|
+
).then(ticket=>{
|
|
127
|
+
resolved.push(ticket);
|
|
128
|
+
return ticket;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
const releasedBulk=held.shift();
|
|
132
|
+
releasedBulk.release();
|
|
133
|
+
|
|
134
|
+
await new Promise(resolve=>setImmediate(resolve));
|
|
135
|
+
|
|
136
|
+
assert.equal(
|
|
137
|
+
governor.snapshot().normal.active,
|
|
138
|
+
1,
|
|
139
|
+
'older non-control waiter must win the newly available admissible slot'
|
|
140
|
+
);
|
|
141
|
+
}finally{
|
|
142
|
+
normalAbort.abort(new Error('test cleanup'));
|
|
143
|
+
controlAbort.abort(new Error('test cleanup'));
|
|
144
|
+
|
|
145
|
+
await Promise.allSettled(
|
|
146
|
+
[normalPending,controlPending].filter(Boolean)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
for(const ticket of resolved)ticket.release();
|
|
150
|
+
for(const ticket of held)ticket.release();
|
|
151
|
+
|
|
152
|
+
await governor.close();
|
|
153
|
+
}
|
|
154
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createLiveTimelineTracker,createLiveTimelineFilter} from '../src/remote-live-log.mjs';
|
|
4
|
+
|
|
5
|
+
test('long LIVELOG session retains bounded presentation identities and suppresses old audit replay',()=>{
|
|
6
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000});
|
|
7
|
+
let sourceSequence=0;
|
|
8
|
+
for(let i=0;i<12000;i++){
|
|
9
|
+
const id=`bounded-${i}`;
|
|
10
|
+
tracker.observe({event_type:'tool.request',tool:'machine_status',request_id:id,args:{}},
|
|
11
|
+
{sourceSequence:++sourceSequence});
|
|
12
|
+
tracker.observe({event_type:'tool.result',tool:'machine_status',request_id:id,
|
|
13
|
+
outcome:{is_error:false,duration_ms:1}},{sourceSequence:++sourceSequence});
|
|
14
|
+
}
|
|
15
|
+
const stats=tracker.retention();
|
|
16
|
+
assert.ok(stats.completed<=4096,JSON.stringify(stats));
|
|
17
|
+
assert.equal(stats.pending,0);
|
|
18
|
+
const replay=tracker.observe({event_type:'tool.result',tool:'machine_status',request_id:'bounded-0',
|
|
19
|
+
outcome:{is_error:false,duration_ms:1}},{sourceSequence:2});
|
|
20
|
+
assert.equal(replay,null,'old source sequence cannot reappear as a duplicate terminal line');
|
|
21
|
+
assert.match(tracker.summary(),/12000 calls \| 12000 ok/);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('filtered long session retains bounded request presentation state',()=>{
|
|
25
|
+
const filter=createLiveTimelineFilter('status=error,trace=goal-r61');
|
|
26
|
+
for(let i=0;i<12000;i++)filter.accept({event_type:'tool.request',tool:'coding_file_write',request_id:`pending-${i}`,
|
|
27
|
+
args:{goal_id:'goal-r61',root_id:'scratch',path:'x.txt'}});
|
|
28
|
+
assert.ok(filter.retention().pending<=4096);
|
|
29
|
+
const latest=filter.accept({event_type:'tool.result',tool:'coding_file_write',request_id:'pending-11999',outcome:{is_error:true}});
|
|
30
|
+
assert.equal(latest,true);
|
|
31
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {verifyPublishedTarballIdentity} from '../scripts/npm-artifact-identity-r61.mjs';
|
|
5
|
+
|
|
6
|
+
const bytes=Buffer.from('deadbyte-r61-exact-tarball-fixture');
|
|
7
|
+
const sha=(name,value)=>createHash(name).update(value).digest(name==='sha512'?'base64':'hex');
|
|
8
|
+
const published={version:'0.15.38',dist:{integrity:'sha512-'+sha('sha512',bytes),shasum:sha('sha1',bytes),tarball:'https://registry.npmjs.org/deadbyte-mcp/-/deadbyte-mcp-0.15.38.tgz'}};
|
|
9
|
+
|
|
10
|
+
test('R61 npm round trip requires exact registry bytes, metadata, version and latest tag',()=>{
|
|
11
|
+
const accepted=verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:Buffer.from(bytes),published,tags:{latest:'0.15.38'}});
|
|
12
|
+
assert.equal(accepted.tarball_sha256,sha('sha256',bytes));
|
|
13
|
+
assert.equal(accepted.npm_integrity,published.dist.integrity);
|
|
14
|
+
assert.throws(()=>verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:Buffer.from('different'),published,tags:{latest:'0.15.38'}}),/registry tarball bytes differ/);
|
|
15
|
+
assert.throws(()=>verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:bytes,published:{...published,dist:{...published.dist,integrity:'sha512-false'}},tags:{latest:'0.15.38'}}),/registry metadata/);
|
|
16
|
+
assert.throws(()=>verifyPublishedTarballIdentity({version:'0.15.38',localBytes:bytes,registryBytes:bytes,published,tags:{latest:'0.15.37'}}),/latest tag/);
|
|
17
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {prepareReleaseBridgeSpec} from '../src/runtime-update.mjs';
|
|
8
|
+
|
|
9
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
|
|
11
|
+
test('R61 update binds Bridge script and local dependencies to immutable release slot',async()=>{
|
|
12
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'r61-bridge-slot-'));
|
|
13
|
+
const source=path.join(root,'source','bridge'),slot=path.join(root,'releases','v0.15.38-test'),releaseBridge=path.join(slot,'bridge');
|
|
14
|
+
const script='import "./dep.mjs";\n';
|
|
15
|
+
try{
|
|
16
|
+
for(const bridge of [source,releaseBridge]){
|
|
17
|
+
await mkdir(path.join(bridge,'src'),{recursive:true});
|
|
18
|
+
await writeFile(path.join(bridge,'package.json'),'{"name":"deadbyte-chatgpt-bridge","version":"0.15.38"}\n');
|
|
19
|
+
await writeFile(path.join(bridge,'src','bridge.mjs'),script);
|
|
20
|
+
await writeFile(path.join(bridge,'src','dep.mjs'),'export const bound=true;\n');
|
|
21
|
+
}
|
|
22
|
+
const config={Bridge:{FilePath:process.execPath,FileSha256:sha(await readFile(process.execPath)),
|
|
23
|
+
Arguments:'src\\bridge.mjs',WorkingDirectory:source},Cloud:{}};
|
|
24
|
+
const result=await prepareReleaseBridgeSpec(config,{root:source,scriptSha256:sha(script)},slot);
|
|
25
|
+
assert.equal(result.Bridge.WorkingDirectory,releaseBridge);
|
|
26
|
+
assert.equal(result.Bridge.ScriptPath,path.join(releaseBridge,'src','bridge.mjs'));
|
|
27
|
+
assert.ok(result.Bridge.Dependencies.length>=1);
|
|
28
|
+
for(const dependency of result.Bridge.Dependencies)assert.ok(dependency.Path.startsWith(path.join(releaseBridge,'src')+path.sep));
|
|
29
|
+
await writeFile(path.join(releaseBridge,'src','bridge.mjs'),'changed\n');
|
|
30
|
+
await assert.rejects(prepareReleaseBridgeSpec(config,{root:source,scriptSha256:sha(script)},slot),/bridge script SHA-256 mismatch/);
|
|
31
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
32
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
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=rel=>read(rel).then(raw=>JSON.parse(raw.replace(/^\uFEFF/,'')));
|
|
7
|
+
const currentPackage=await json('package.json');
|
|
8
|
+
const PARENT={
|
|
9
|
+
release_id:'v0.15.37-1cfecd7889419da7',
|
|
10
|
+
manifest_sha256:'1cfecd7889419da7800814e3a2a4ffa93d48dfdb0c344a5b7b91fe8d093230aa',
|
|
11
|
+
generation_manifest_sha256:'54842021c75f01bf7ecbd5b10ab8a482ecf107765b45c46f9d39b12bab65eac4'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
test('R61 identity binds exact sealed 0.15.37 predecessor without a public surface expansion',{skip:currentPackage.version!=='0.15.38'},async()=>{
|
|
15
|
+
const [pkg,lock,bridge,release,provenance,matrix,sbom,version,gate]=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-r61-prepublish.ps1')
|
|
19
|
+
]);
|
|
20
|
+
assert.equal(pkg.version,'0.15.38');
|
|
21
|
+
assert.equal(lock.version,pkg.version);
|
|
22
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
23
|
+
assert.equal(bridge.version,pkg.version);
|
|
24
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.15\.38'/);
|
|
25
|
+
assert.equal(release.version,pkg.version);
|
|
26
|
+
assert.equal(release.train,'R61');
|
|
27
|
+
assert.equal(release.parent.version,'0.15.37');
|
|
28
|
+
for(const [key,value] of Object.entries(PARENT)){
|
|
29
|
+
assert.equal(release.parent[key],value);
|
|
30
|
+
assert.equal(release.runtime_predecessor[key],value);
|
|
31
|
+
assert.equal(provenance.runtime_predecessor[key],value);
|
|
32
|
+
}
|
|
33
|
+
assert.equal(provenance.version,pkg.version);
|
|
34
|
+
assert.equal(matrix.version,pkg.version);
|
|
35
|
+
assert.equal(matrix.train,'R61');
|
|
36
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
37
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r61:0.15.38');
|
|
38
|
+
assert.equal(Object.keys(pkg.dependencies??{}).length,4);
|
|
39
|
+
assert.equal(release.r61_multi_workload.public_surface_tool_count,43);
|
|
40
|
+
assert.equal(release.r61_multi_workload.new_authority_scopes,0);
|
|
41
|
+
assert.equal(release.r61_multi_workload.bridge_bound_to_immutable_slot,true);
|
|
42
|
+
assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r61-prepublish.ps1');
|
|
43
|
+
assert.match(gate,/v0\.15\.37-1cfecd7889419da7/);
|
|
44
|
+
assert.doesNotMatch(gate,/v0\.15\.36-090d9db3e0679c0d/);
|
|
45
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdir,mkdtemp,writeFile} from 'node:fs/promises';
|
|
4
|
+
import {generateKeyPairSync,createHash,sign} from 'node:crypto';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
|
|
8
|
+
|
|
9
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
10
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
11
|
+
?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
|
|
12
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
13
|
+
const domain=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0');
|
|
14
|
+
function envelope(body,keys){
|
|
15
|
+
const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
|
|
16
|
+
const unsigned={body,body_sha256:sha(bodyBytes),attestation:{
|
|
17
|
+
algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',
|
|
18
|
+
key_id:sha(keys.publicKey.export({type:'spki',format:'der'})),
|
|
19
|
+
signature_base64:sign(null,Buffer.concat([domain,bodyBytes]),keys.privateKey).toString('base64')
|
|
20
|
+
}};
|
|
21
|
+
return {...unsigned,receipt_sha256:sha(Buffer.from(canonicalJson(unsigned),'utf8'))};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test('R62 doctor rejects signed success receipts whose rollback semantics contradict their schema',async()=>{
|
|
25
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-receipt-semantics-'));
|
|
26
|
+
const trust=path.join(root,'trust'),updates=path.join(root,'evidence','updates');
|
|
27
|
+
await mkdir(trust,{recursive:true});await mkdir(updates,{recursive:true});
|
|
28
|
+
const keys=generateKeyPairSync('ed25519');
|
|
29
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
|
|
30
|
+
|
|
31
|
+
const updateDir=path.join(updates,'bad-update-success');await mkdir(updateDir);
|
|
32
|
+
await writeFile(path.join(updateDir,'state.json'),JSON.stringify({
|
|
33
|
+
schema:'deadbyte.r27-slot-update-state.v1',update_id:'bad-update-success',phase:'completed'
|
|
34
|
+
}));
|
|
35
|
+
await writeFile(path.join(updateDir,'receipt.json'),JSON.stringify(envelope({
|
|
36
|
+
schema:'deadbyte.r27-slot-update-receipt.v1',update_id:'bad-update-success',rollback_performed:true
|
|
37
|
+
},keys)));
|
|
38
|
+
|
|
39
|
+
const rollbackDir=path.join(updates,'bad-rollback-success');await mkdir(rollbackDir);
|
|
40
|
+
await writeFile(path.join(rollbackDir,'state.json'),JSON.stringify({
|
|
41
|
+
schema:'deadbyte.r31-slot-rollback-state.v1',rollback_id:'bad-rollback-success',phase:'completed'
|
|
42
|
+
}));
|
|
43
|
+
await writeFile(path.join(rollbackDir,'receipt.json'),JSON.stringify(envelope({
|
|
44
|
+
schema:'deadbyte.r31-slot-rollback-receipt.v1',rollback_id:'bad-rollback-success',rollback_performed:false
|
|
45
|
+
},keys)));
|
|
46
|
+
|
|
47
|
+
const result=await inspectDoctorTransactions(root);
|
|
48
|
+
assert.equal(result.status,'FAIL');
|
|
49
|
+
assert.equal(result.findings.filter(f=>f.evidence?.check==='terminal_receipt_verification').length,2);
|
|
50
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdir,mkdtemp,writeFile,readFile} from 'node:fs/promises';
|
|
4
|
+
import {generateKeyPairSync,createHash,sign} from 'node:crypto';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {inspectDoctorTransactions} from '../src/doctor-transactions-r58.mjs';
|
|
8
|
+
|
|
9
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
10
|
+
const canonical=value=>Array.isArray(value)?value.map(canonical):value&&typeof value==='object'
|
|
11
|
+
?Object.fromEntries(Object.keys(value).sort().map(key=>[key,canonical(value[key])])):value;
|
|
12
|
+
const canonicalJson=value=>JSON.stringify(canonical(value));
|
|
13
|
+
const domain=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0');
|
|
14
|
+
|
|
15
|
+
function envelope(body,keys){
|
|
16
|
+
const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
|
|
17
|
+
const unsigned={
|
|
18
|
+
body,
|
|
19
|
+
body_sha256:sha(bodyBytes),
|
|
20
|
+
attestation:{
|
|
21
|
+
algorithm:'ed25519',
|
|
22
|
+
domain:'DEADBYTE-R27-SLOT-UPDATE-V1',
|
|
23
|
+
key_id:sha(keys.publicKey.export({type:'spki',format:'der'})),
|
|
24
|
+
signature_base64:sign(null,Buffer.concat([domain,bodyBytes]),keys.privateKey).toString('base64')
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return {...unsigned,receipt_sha256:sha(Buffer.from(canonicalJson(unsigned),'utf8'))};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('R62 doctor reconciles signed terminal receipts instead of warning on stale nonterminal journal phases',async()=>{
|
|
31
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-reconcile-'));
|
|
32
|
+
const trust=path.join(root,'trust');
|
|
33
|
+
const updates=path.join(root,'evidence','updates');
|
|
34
|
+
await mkdir(trust,{recursive:true});
|
|
35
|
+
await mkdir(updates,{recursive:true});
|
|
36
|
+
const keys=generateKeyPairSync('ed25519');
|
|
37
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
|
|
38
|
+
|
|
39
|
+
const writeTransaction=async(name,state,receiptName,receiptBody)=>{
|
|
40
|
+
const dir=path.join(updates,name);
|
|
41
|
+
await mkdir(dir);
|
|
42
|
+
const statePath=path.join(dir,'state.json');
|
|
43
|
+
const stateBytes=JSON.stringify(state);
|
|
44
|
+
await writeFile(statePath,stateBytes);
|
|
45
|
+
await writeFile(path.join(dir,receiptName),JSON.stringify(envelope(receiptBody,keys)));
|
|
46
|
+
return {statePath,stateBytes};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const success=await writeTransaction(
|
|
50
|
+
'success',
|
|
51
|
+
{schema:'deadbyte.r27-slot-update-state.v1',update_id:'success',phase:'pointer_committed'},
|
|
52
|
+
'receipt.json',
|
|
53
|
+
{schema:'deadbyte.r27-slot-update-receipt.v1',update_id:'success',rollback_performed:false}
|
|
54
|
+
);
|
|
55
|
+
const failure=await writeTransaction(
|
|
56
|
+
'failure',
|
|
57
|
+
{schema:'deadbyte.r27-slot-update-state.v1',update_id:'failure',phase:'candidate_prepared'},
|
|
58
|
+
'failure-receipt.json',
|
|
59
|
+
{schema:'deadbyte.r27-slot-update-failure.v1',update_id:'failure',failed_at_utc:'2026-10-04T00:00:00.000Z'}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const result=await inspectDoctorTransactions(root);
|
|
63
|
+
assert.equal(result.status,'PASS');
|
|
64
|
+
assert.equal(result.findings.some(f=>f.level==='WARN'),false);
|
|
65
|
+
const summary=result.findings.find(f=>f.evidence?.receipt_verification_performed===true);
|
|
66
|
+
assert.ok(summary,'doctor must report signed receipt verification');
|
|
67
|
+
assert.equal(summary.evidence.verified_terminal_receipts,2);
|
|
68
|
+
assert.equal(summary.evidence.unresolved_transactions,0);
|
|
69
|
+
assert.equal(await readFile(success.statePath,'utf8'),success.stateBytes);
|
|
70
|
+
assert.equal(await readFile(failure.statePath,'utf8'),failure.stateBytes);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
test('R62 doctor default transaction budget covers more than the legacy 256-entry ceiling while remaining bounded',async()=>{
|
|
75
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-budget-'));
|
|
76
|
+
const updates=path.join(root,'evidence','updates');
|
|
77
|
+
await mkdir(updates,{recursive:true});
|
|
78
|
+
for(let index=0;index<300;index++){
|
|
79
|
+
const dir=path.join(updates,`tx-${String(index).padStart(3,'0')}`);
|
|
80
|
+
await mkdir(dir);
|
|
81
|
+
await writeFile(path.join(dir,'state.json'),JSON.stringify({
|
|
82
|
+
schema:'deadbyte.r27-slot-update-state.v1',
|
|
83
|
+
update_id:`tx-${index}`,
|
|
84
|
+
phase:'superseded'
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
const result=await inspectDoctorTransactions(root);
|
|
88
|
+
assert.equal(result.status,'PASS');
|
|
89
|
+
assert.equal(result.findings.some(f=>f.evidence?.inventory_complete===false),false);
|
|
90
|
+
const summary=result.findings.find(f=>f.evidence?.receipt_verification_performed===true);
|
|
91
|
+
assert.ok(summary);
|
|
92
|
+
assert.equal(summary.evidence.states_checked,300);
|
|
93
|
+
await assert.rejects(inspectDoctorTransactions(root,{maxEntries:1025}),/budget invalid/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
test('R62 doctor verifies signed receipts even when the journal already claims a terminal phase',async()=>{
|
|
98
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-terminal-proof-'));
|
|
99
|
+
const trust=path.join(root,'trust');
|
|
100
|
+
const updates=path.join(root,'evidence','updates');
|
|
101
|
+
await mkdir(trust,{recursive:true});
|
|
102
|
+
await mkdir(updates,{recursive:true});
|
|
103
|
+
const keys=generateKeyPairSync('ed25519');
|
|
104
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
|
|
105
|
+
|
|
106
|
+
const provenDir=path.join(updates,'proven');
|
|
107
|
+
await mkdir(provenDir);
|
|
108
|
+
await writeFile(path.join(provenDir,'state.json'),JSON.stringify({
|
|
109
|
+
schema:'deadbyte.r27-slot-update-state.v1',
|
|
110
|
+
update_id:'proven',
|
|
111
|
+
phase:'completed'
|
|
112
|
+
}));
|
|
113
|
+
await writeFile(path.join(provenDir,'receipt.json'),JSON.stringify(envelope({
|
|
114
|
+
schema:'deadbyte.r27-slot-update-receipt.v1',
|
|
115
|
+
update_id:'proven',
|
|
116
|
+
rollback_performed:false
|
|
117
|
+
},keys)));
|
|
118
|
+
|
|
119
|
+
const proven=await inspectDoctorTransactions(root);
|
|
120
|
+
assert.equal(proven.status,'PASS');
|
|
121
|
+
const provenSummary=proven.findings.find(f=>f.evidence?.receipt_verification_performed===true);
|
|
122
|
+
assert.equal(provenSummary.evidence.verified_terminal_receipts,1);
|
|
123
|
+
|
|
124
|
+
const unprovenDir=path.join(updates,'unproven');
|
|
125
|
+
await mkdir(unprovenDir);
|
|
126
|
+
await writeFile(path.join(unprovenDir,'state.json'),JSON.stringify({
|
|
127
|
+
schema:'deadbyte.r27-slot-update-state.v1',
|
|
128
|
+
update_id:'unproven',
|
|
129
|
+
phase:'completed'
|
|
130
|
+
}));
|
|
131
|
+
const unproven=await inspectDoctorTransactions(root);
|
|
132
|
+
assert.equal(unproven.status,'WARN');
|
|
133
|
+
assert.ok(unproven.findings.some(f=>f.evidence?.recorded_phase==='completed'));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
test('R62 doctor fails closed on tampered or missing claimed terminal receipts without mutating journals',async()=>{
|
|
138
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-doctor-adversarial-'));
|
|
139
|
+
const trust=path.join(root,'trust');
|
|
140
|
+
const updates=path.join(root,'evidence','updates');
|
|
141
|
+
await mkdir(trust,{recursive:true});
|
|
142
|
+
await mkdir(updates,{recursive:true});
|
|
143
|
+
const keys=generateKeyPairSync('ed25519');
|
|
144
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
|
|
145
|
+
|
|
146
|
+
const tamperedDir=path.join(updates,'tampered');
|
|
147
|
+
await mkdir(tamperedDir);
|
|
148
|
+
const tamperedStatePath=path.join(tamperedDir,'state.json');
|
|
149
|
+
const tamperedState=JSON.stringify({
|
|
150
|
+
schema:'deadbyte.r27-slot-update-state.v1',
|
|
151
|
+
update_id:'tampered',
|
|
152
|
+
phase:'completed'
|
|
153
|
+
});
|
|
154
|
+
await writeFile(tamperedStatePath,tamperedState);
|
|
155
|
+
const good=envelope({
|
|
156
|
+
schema:'deadbyte.r27-slot-update-receipt.v1',
|
|
157
|
+
update_id:'tampered',
|
|
158
|
+
rollback_performed:false
|
|
159
|
+
},keys);
|
|
160
|
+
const bad={...good,attestation:{...good.attestation,signature_base64:Buffer.alloc(64).toString('base64')}};
|
|
161
|
+
await writeFile(path.join(tamperedDir,'receipt.json'),JSON.stringify(bad));
|
|
162
|
+
|
|
163
|
+
const missingDir=path.join(updates,'missing');
|
|
164
|
+
await mkdir(missingDir);
|
|
165
|
+
const missingStatePath=path.join(missingDir,'state.json');
|
|
166
|
+
const missingState=JSON.stringify({
|
|
167
|
+
schema:'deadbyte.r27-slot-update-state.v1',
|
|
168
|
+
update_id:'missing',
|
|
169
|
+
phase:'failed_receipted'
|
|
170
|
+
});
|
|
171
|
+
await writeFile(missingStatePath,missingState);
|
|
172
|
+
|
|
173
|
+
const result=await inspectDoctorTransactions(root);
|
|
174
|
+
assert.equal(result.status,'FAIL');
|
|
175
|
+
assert.ok(result.findings.some(f=>f.evidence?.check==='terminal_receipt_verification'));
|
|
176
|
+
assert.ok(result.findings.some(f=>f.evidence?.check==='missing_failure_receipt'));
|
|
177
|
+
assert.equal(await readFile(tamperedStatePath,'utf8'),tamperedState);
|
|
178
|
+
assert.equal(await readFile(missingStatePath,'utf8'),missingState);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
test('R62 doctor reconciles a signed R31 rollback failure receipt by rollback_id',async()=>{
|
|
183
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r62-r31-failure-proof-'));
|
|
184
|
+
const trust=path.join(root,'trust');
|
|
185
|
+
const dir=path.join(root,'evidence','updates','rollback-failure');
|
|
186
|
+
await mkdir(trust,{recursive:true});
|
|
187
|
+
await mkdir(dir,{recursive:true});
|
|
188
|
+
const keys=generateKeyPairSync('ed25519');
|
|
189
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}));
|
|
190
|
+
const rollbackId='r31-rollback-r62-fixture';
|
|
191
|
+
await writeFile(path.join(dir,'state.json'),JSON.stringify({
|
|
192
|
+
schema:'deadbyte.r31-slot-rollback-state.v1',
|
|
193
|
+
rollback_id:rollbackId,
|
|
194
|
+
phase:'failed'
|
|
195
|
+
}));
|
|
196
|
+
await writeFile(path.join(dir,'failure-receipt.json'),JSON.stringify(envelope({
|
|
197
|
+
schema:'deadbyte.r31-slot-rollback-failure.v1',
|
|
198
|
+
rollback_id:rollbackId,
|
|
199
|
+
failed_at_utc:'2026-10-04T00:00:00.000Z',
|
|
200
|
+
error:{name:'Error',message:'fixture'}
|
|
201
|
+
},keys)));
|
|
202
|
+
const result=await inspectDoctorTransactions(root);
|
|
203
|
+
assert.equal(result.status,'PASS');
|
|
204
|
+
const summary=result.findings.find(f=>f.evidence?.receipt_verification_performed===true);
|
|
205
|
+
assert.equal(summary.evidence.verified_terminal_receipts,1);
|
|
206
|
+
assert.equal(summary.evidence.verified_failure_receipts,1);
|
|
207
|
+
assert.equal(summary.evidence.unresolved_transactions,0);
|
|
208
|
+
});
|