deadbyte-mcp 0.15.43 → 0.15.44
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 +128 -67
- package/PROVENANCE.json +23 -12
- package/README.txt +18 -18
- package/RELEASE.json +28 -14
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +41 -10
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-native-inspect-r67.exe +0 -0
- package/bin/deadbyte-native-inspect-r67.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-desktop-policy.json +5 -5
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R67-ALPHA-REFERENCE-DIFFERENTIAL.md +42 -0
- package/docs/R67-ALPHA-STATIC-COMPARISON.json +205 -0
- package/docs/R67-CANDIDATE-PARENT.json +10 -0
- package/docs/R67-EVIDENCE-RECONCILE-CLOSEOUT-20261011.md +64 -0
- package/docs/R67-FULL-REGRESSION-UNKNOWN.json +27 -0
- package/docs/R67-NATIVE-INSPECT-BINARY.json +11 -0
- package/docs/R67-NPM-EXDEV-PRESERVATION-20261011.md +39 -0
- package/docs/R67-OBSERVATION-TRUTH.md +82 -0
- package/docs/R67-REFERENCE-GAP-MATRIX.json +57 -0
- package/docs/R67-REGRESSION-CLOSURE-20261011.md +24 -0
- package/native/deadbyte-native-inspect-r67.cpp +252 -0
- package/package.json +6 -3
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-native-inspector-r67.ps1 +28 -0
- package/scripts/gate-windows-r67.ps1 +56 -0
- package/scripts/npm-preserve-artifact-r67.mjs +99 -0
- package/scripts/npm-release-r48.mjs +10 -2
- package/scripts/npm-release-readiness-r67.mjs +26 -0
- package/scripts/r67-acquire-alpha-reference.mjs +95 -0
- package/scripts/r67-audit-nonpass-source.mjs +39 -0
- package/scripts/r67-compare-desktop-alpha.mjs +48 -0
- package/scripts/r67-diagnose-failed-shard.mjs +23 -0
- package/scripts/r67-durable-batch.mjs +42 -0
- package/scripts/r67-focused-gate.mjs +58 -0
- package/scripts/r67-full-regression.mjs +28 -0
- package/scripts/r67-independent-observation-verify.mjs +36 -0
- package/scripts/r67-independent-runtime-observation-verify.mjs +63 -0
- package/scripts/r67-nonpass-adjudicate.mjs +20 -0
- package/scripts/r67-publish-boundary-audit.mjs +32 -0
- package/scripts/r67-regression-reconcile.mjs +10 -0
- package/scripts/r67-regression-shard-run.mjs +70 -0
- package/scripts/r67-run-shard-batch.mjs +18 -0
- package/scripts/r67-stage-candidate.mjs +30 -0
- package/scripts/r67-verify-parent-ping.mjs +22 -0
- package/scripts/release-manifest.mjs +1 -0
- package/src/activation-continuity-engine-r65.mjs +2 -1
- package/src/autonomous-runtime.mjs +1 -1
- package/src/deadbyte-cli.mjs +33 -1
- package/src/machine-fs-smoke-client.mjs +13 -5
- package/src/machine-identity.mjs +59 -2
- package/src/machine-mcp-tools.mjs +8 -0
- package/src/mcp-server.mjs +11 -2
- package/src/native-inspector-r67.mjs +66 -0
- package/src/r67-diagnostic-export.mjs +118 -0
- package/src/r67-diagnostics-verify.mjs +82 -0
- package/src/r67-evidence-reconcile.mjs +128 -0
- package/src/r67-native-observation-authority.mjs +45 -0
- package/src/r67-observation-receipt.mjs +43 -0
- package/src/r67-regression-shards.mjs +176 -0
- package/src/remote-live-log.mjs +14 -10
- package/src/tool-audit.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-loop.test.mjs +11 -6
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/output-schema.test.mjs +1 -1
- package/test/r51-live-log-polling.test.mjs +12 -0
- package/test/r53-release-contract.test.mjs +1 -1
- package/test/r54-release-contract.test.mjs +1 -1
- package/test/r55-release-contract.test.mjs +1 -1
- package/test/r56-release-contract.test.mjs +1 -1
- package/test/r57-release-contract.test.mjs +1 -1
- package/test/r58-bundled-bridge.test.mjs +1 -1
- package/test/r58-release-contract.test.mjs +2 -2
- package/test/r58-windows-gate.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r60-release-contract.test.mjs +3 -3
- package/test/r61-release-contract.test.mjs +1 -1
- package/test/r62-release-contract.test.mjs +1 -1
- package/test/r63-release-contract.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +1 -1
- package/test/r65-epoch-native-activation-integration.test.mjs +4 -4
- package/test/r65-release-contract.test.mjs +1 -1
- package/test/r66-release-contract.test.mjs +2 -1
- package/test/r67-cli-integration.test.mjs +41 -0
- package/test/r67-diagnostics-cli.test.mjs +60 -0
- package/test/r67-diagnostics-verify.test.mjs +49 -0
- package/test/r67-diagnostics-zip-oracle.test.mjs +38 -0
- package/test/r67-diagnostics.test.mjs +60 -0
- package/test/r67-evidence-reconcile.test.mjs +186 -0
- package/test/r67-isolated-slot-rollback-restore.test.mjs +62 -0
- package/test/r67-live-log-secret-canary.test.mjs +44 -0
- package/test/r67-machine-fs-smoke-epoch.test.mjs +22 -0
- package/test/r67-native-inspector.test.mjs +131 -0
- package/test/r67-native-observation-authority.test.mjs +49 -0
- package/test/r67-npm-cross-volume.test.mjs +74 -0
- package/test/r67-npm-publish-gate.test.mjs +38 -0
- package/test/r67-regression-shards.test.mjs +184 -0
- package/test/r67-release-contract.test.mjs +44 -0
- package/test/r67-runtime-observation-binding.test.mjs +86 -0
- package/test/r67-server-observation-wiring.test.mjs +12 -0
- package/test/r67-signed-receipt.test.mjs +66 -0
- package/test/r67-skip-rationale.test.mjs +31 -0
- package/test/r67-tool-annotations.test.mjs +21 -0
- package/test/r67-windows-gate-argv.test.mjs +13 -0
- package/test/release-version.test.mjs +1 -1
- package/test/remote-live-log-r36.test.mjs +10 -0
- package/test/remote-live-log-r37.test.mjs +29 -0
- package/test/semantic-multilang.test.mjs +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {readFile,writeFile,mkdtemp,rm} from 'node:fs/promises';
|
|
5
|
+
import {tmpdir} from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
import {spawnSync} from 'node:child_process';
|
|
9
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
const exe=path.join(root,'bin','deadbyte-native-inspect-r67.exe');
|
|
11
|
+
const hash=b=>createHash('sha256').update(b).digest('hex');
|
|
12
|
+
function run(...args){
|
|
13
|
+
const r=spawnSync(exe,args,{windowsHide:true,shell:false,encoding:'utf8',timeout:30000,maxBuffer:4*1024*1024});
|
|
14
|
+
assert.equal(r.error,undefined,r.error?.message);
|
|
15
|
+
const out=JSON.parse((r.stdout??'').trim());
|
|
16
|
+
assert.equal(out.schema,'deadbyte.native-inspect.r67.v1');
|
|
17
|
+
if(out.status==='ok')assert.equal(out.data_sha256,hash(Buffer.from(JSON.stringify(out.data),'utf8')),'independent serialized data digest');
|
|
18
|
+
return {...out,exit_code:r.status};
|
|
19
|
+
}
|
|
20
|
+
test('R67 native process snapshot is bounded, identity-bearing, and honestly partial',()=>{
|
|
21
|
+
const r=run('process','48');
|
|
22
|
+
assert.equal(r.status,'ok');
|
|
23
|
+
assert.equal(r.exit_code,0);
|
|
24
|
+
assert.equal(r.operation,'process');
|
|
25
|
+
assert.equal(r.data.api,'CreateToolhelp32Snapshot+OpenProcess+GetProcessTimes+QueryFullProcessImageNameW');
|
|
26
|
+
assert.ok(r.data.processes.length>=1&&r.data.processes.length<=48);
|
|
27
|
+
for(const p of r.data.processes){
|
|
28
|
+
assert.ok(Number.isSafeInteger(p.pid)&&p.pid>=0);
|
|
29
|
+
assert.ok(Number.isSafeInteger(p.parent_pid)&&p.parent_pid>=0);
|
|
30
|
+
assert.ok(['observed','unavailable','stale_or_reused'].includes(p.state));
|
|
31
|
+
if(p.state==='unavailable')assert.ok(Number.isInteger(p.win32_error)&&p.win32_error>0);
|
|
32
|
+
else if(p.state==='stale_or_reused'){assert.equal(p.snapshot_binding,'NAME_MISMATCH');assert.equal(p.win32_error,0);}
|
|
33
|
+
else {assert.equal(p.snapshot_binding,'UNVERIFIED');assert.match(p.creation_time_utc,/^\d{4}-\d\d-\d\dT/);assert.match(p.creation_filetime,/^\d+$/);assert.equal(p.win32_error,0);}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
test('R67 VirtualQueryEx memory map records process identity, monotone bounded regions, and zero private memory data',()=>{
|
|
37
|
+
const r=run('map','self','64');
|
|
38
|
+
assert.equal(r.status,'ok');
|
|
39
|
+
assert.equal(r.exit_code,0);
|
|
40
|
+
assert.equal(r.operation,'map');
|
|
41
|
+
assert.equal(r.data.api,'VirtualQueryEx');
|
|
42
|
+
assert.match(r.data.creation_filetime,/^\d+$/);
|
|
43
|
+
assert.ok(r.data.regions.length>=1&&r.data.regions.length<=64);
|
|
44
|
+
let prev=-1n;
|
|
45
|
+
for(const row of r.data.regions){
|
|
46
|
+
const base=BigInt(row.base),size=BigInt(row.size);
|
|
47
|
+
assert.ok(base>prev&&size>0n);
|
|
48
|
+
prev=base;
|
|
49
|
+
assert.ok(Number.isInteger(row.state)&&Number.isInteger(row.protect)&&Number.isInteger(row.type));
|
|
50
|
+
assert.deepEqual(Object.keys(row),['base','allocation_base','size','state','protect','type']);
|
|
51
|
+
}
|
|
52
|
+
assert.equal(r.data.process_exited,false);
|
|
53
|
+
assert.equal(r.data.outcome,'HANDLE_IDENTITY_CONFIRMED');
|
|
54
|
+
});
|
|
55
|
+
test('R67 PE bytes and section table agree with an independent raw-file oracle',async()=>{
|
|
56
|
+
const b=await readFile(exe);
|
|
57
|
+
assert.equal(b.readUInt16LE(0),0x5a4d);
|
|
58
|
+
const p=b.readInt32LE(0x3c);
|
|
59
|
+
assert.ok(p>=64&&p+24<b.length);
|
|
60
|
+
assert.equal(b.readUInt32LE(p),0x4550);
|
|
61
|
+
const machine=b.readUInt16LE(p+4),sectionCount=b.readUInt16LE(p+6);
|
|
62
|
+
const optLength=b.readUInt16LE(p+20),magic=b.readUInt16LE(p+24);
|
|
63
|
+
const sectionOff=p+24+optLength;
|
|
64
|
+
const r=run('pe',exe);
|
|
65
|
+
assert.equal(r.status,'ok');assert.equal(r.exit_code,0);
|
|
66
|
+
assert.equal(r.data.file_sha256,hash(b));
|
|
67
|
+
assert.equal(r.data.file_size,b.length);
|
|
68
|
+
assert.equal(r.data.machine,machine);
|
|
69
|
+
assert.equal(r.data.number_of_sections,sectionCount);
|
|
70
|
+
assert.equal(r.data.format,magic===0x20b?'PE32+':'PE32');
|
|
71
|
+
assert.equal(r.data.sections.length,sectionCount);
|
|
72
|
+
for(let i=0;i<sectionCount;i++){
|
|
73
|
+
const off=sectionOff+i*40,row=r.data.sections[i];
|
|
74
|
+
assert.equal(row.rva,'0x'+b.readUInt32LE(off+12).toString(16));
|
|
75
|
+
assert.equal(row.raw_size,b.readUInt32LE(off+16));
|
|
76
|
+
assert.equal(row.raw_offset,b.readUInt32LE(off+20));
|
|
77
|
+
assert.equal(row.characteristics,b.readUInt32LE(off+36));
|
|
78
|
+
assert.ok(!row.raw_size||row.raw_offset+row.raw_size<=b.length);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
test('R67 returns failed/error for inaccessible PID and malformed PE, never fabricated empty observations',async()=>{
|
|
82
|
+
const bad=run('map','4294967295','1','4');
|
|
83
|
+
assert.equal(bad.status,'failed');assert.equal(bad.exit_code,2);
|
|
84
|
+
assert.match(bad.error,/OpenProcess.*win32=|invalid PID/);
|
|
85
|
+
assert.ok(['ACCESS_DENIED','INVALID_PARAMETER','WIN32_FAILURE'].includes(bad.error_code));
|
|
86
|
+
assert.ok(Number.isInteger(bad.win32_error)&&bad.win32_error>0);
|
|
87
|
+
const folder=await mkdtemp(path.join(tmpdir(),'deadbyte-r67-pe-'));
|
|
88
|
+
try{
|
|
89
|
+
const file=path.join(folder,'invalid.exe');await writeFile(file,Buffer.alloc(128,0));
|
|
90
|
+
const invalid=run('pe',file);
|
|
91
|
+
assert.equal(invalid.status,'failed');assert.equal(invalid.exit_code,2);assert.match(invalid.error,/MZ|PE/);
|
|
92
|
+
assert.equal(invalid.error_code,'INVALID_INPUT');assert.equal(invalid.win32_error,0);
|
|
93
|
+
}finally{await rm(folder,{recursive:true,force:true});}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('R67 external PID memory map fails closed on missing or mismatched creation FILETIME',()=>{
|
|
97
|
+
const missing=run('map',String(process.pid));
|
|
98
|
+
assert.equal(missing.status,'failed');assert.equal(missing.exit_code,2);
|
|
99
|
+
assert.match(missing.error,/creation_filetime/);
|
|
100
|
+
const swapped=run('map',String(process.pid),'1','4');
|
|
101
|
+
assert.equal(swapped.status,'failed');assert.equal(swapped.exit_code,2);
|
|
102
|
+
assert.equal(swapped.error_code,'PROCESS_IDENTITY_MISMATCH');
|
|
103
|
+
assert.equal(swapped.win32_error,0);
|
|
104
|
+
const observed=run('identity',String(process.pid));
|
|
105
|
+
assert.equal(observed.status,'ok',observed.error);
|
|
106
|
+
assert.equal(observed.data.pid,process.pid);
|
|
107
|
+
const valid=run('map',String(process.pid),observed.data.creation_filetime,'4');
|
|
108
|
+
assert.equal(valid.status,'ok',valid.error);
|
|
109
|
+
assert.equal(valid.data.creation_filetime,observed.data.creation_filetime);
|
|
110
|
+
assert.equal(valid.data.pid,process.pid);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('R67 exited process cannot silently turn into a successful PID-reused memory map',async()=>{
|
|
114
|
+
const {spawn}=await import('node:child_process');
|
|
115
|
+
const {once}=await import('node:events');
|
|
116
|
+
const child=spawn(process.execPath,['-e','setInterval(()=>{},500)'],{shell:false,windowsHide:true,stdio:'ignore'});
|
|
117
|
+
try{
|
|
118
|
+
await once(child,'spawn');
|
|
119
|
+
const identity=run('identity',String(child.pid));
|
|
120
|
+
assert.equal(identity.status,'ok',identity.error);
|
|
121
|
+
assert.equal(identity.data.pid,child.pid);
|
|
122
|
+
const stopped=once(child,'exit');
|
|
123
|
+
assert.equal(child.kill(),true);
|
|
124
|
+
await stopped;
|
|
125
|
+
const after=run('map',String(child.pid),identity.data.creation_filetime,'4');
|
|
126
|
+
assert.equal(after.status,'failed');
|
|
127
|
+
assert.equal(after.exit_code,2);
|
|
128
|
+
assert.equal(Object.hasOwn(after,'data'),false);
|
|
129
|
+
assert.ok(['WIN32_FAILURE','INVALID_PARAMETER','PROCESS_EXITED','PROCESS_IDENTITY_MISMATCH'].includes(after.error_code),after.error_code);
|
|
130
|
+
}finally{if(child.exitCode===null)child.kill();}
|
|
131
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createR67NativeObservationAuthority} from '../src/r67-native-observation-authority.mjs';
|
|
4
|
+
|
|
5
|
+
const hex=c=>c.repeat(64);
|
|
6
|
+
const policy={policy_sha256:hex('a'),roots:{projects:{read:true}}};
|
|
7
|
+
let alive=true,generation=17;
|
|
8
|
+
const getSession=async()=>alive?{
|
|
9
|
+
armed:true,standing:true,reason:'standing-grant',instance_nonce:hex('b'),root_ids:['projects'],
|
|
10
|
+
authority_epoch:{trusted:true,seq:generation,event_sha256:hex('c')}
|
|
11
|
+
}:{armed:false,reason:'disarmed'};
|
|
12
|
+
|
|
13
|
+
test('R67 observer fails closed without explicit owner opt-in, and denies other PID reads',async()=>{
|
|
14
|
+
const disabled=createR67NativeObservationAuthority({getCodingPolicy:()=>policy,sessionStatus:getSession});
|
|
15
|
+
await assert.rejects(()=>disabled({operation:'process',args:['process','4']}),/DISABLED/);
|
|
16
|
+
const enabled=createR67NativeObservationAuthority({getCodingPolicy:()=>policy,sessionStatus:getSession,enabled:true});
|
|
17
|
+
await assert.rejects(()=>enabled({operation:'map',args:['map','4148','3']}),/OTHER_PID/);
|
|
18
|
+
await assert.rejects(()=>enabled({operation:'identity',args:['identity','4148']}),/OTHER_PID/);
|
|
19
|
+
await assert.rejects(()=>enabled({operation:'exec',args:['exec','whoami']}),/SCOPE_DENIED/);
|
|
20
|
+
});
|
|
21
|
+
test('R67 observer binds machine-supplied authority to real standing epoch, not client grant',async()=>{
|
|
22
|
+
alive=true;
|
|
23
|
+
const enabled=createR67NativeObservationAuthority({getCodingPolicy:()=>policy,sessionStatus:getSession,enabled:true});
|
|
24
|
+
const first=await enabled({operation:'map',args:['map','self','4']});
|
|
25
|
+
assert.equal(first.scope,'native-observe-readonly');
|
|
26
|
+
assert.equal(first.policy_sha256,policy.policy_sha256);
|
|
27
|
+
assert.equal(first.authority_generation,generation);
|
|
28
|
+
assert.match(first.grant_id,/^coding\.r67\.[0-9a-f]{32}$/);
|
|
29
|
+
const next=await enabled({operation:'map',args:['map','self','4']});
|
|
30
|
+
assert.deepEqual(first,next);
|
|
31
|
+
generation+=1;
|
|
32
|
+
const rotated=await enabled({operation:'map',args:['map','self','4']});
|
|
33
|
+
assert.notEqual(first.authority_generation,rotated.authority_generation);
|
|
34
|
+
alive=false;
|
|
35
|
+
await assert.rejects(()=>enabled({operation:'process',args:['process','4']}),/AUTHORITY_REVOKED/);
|
|
36
|
+
});
|
|
37
|
+
test('R67 observer rejects unverified lease epoch or changed policy',async()=>{
|
|
38
|
+
const disabled=createR67NativeObservationAuthority({
|
|
39
|
+
getCodingPolicy:()=>policy,enabled:true,sessionStatus:async()=>({
|
|
40
|
+
armed:true,standing:true,reason:'standing-grant',instance_nonce:hex('b'),root_ids:['projects'],
|
|
41
|
+
authority_epoch:{trusted:false,seq:100,event_sha256:hex('c')}
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
await assert.rejects(()=>disabled({operation:'process',args:['process','4']}),/AUTHORITY_REVOKED/);
|
|
45
|
+
const malformed=createR67NativeObservationAuthority({
|
|
46
|
+
getCodingPolicy:()=>({policy_sha256:hex('a'),roots:{}}),enabled:true,sessionStatus:getSession
|
|
47
|
+
});
|
|
48
|
+
await assert.rejects(()=>malformed({operation:'process',args:['process','4']}),/POLICY_MISSING/);
|
|
49
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,readFile,writeFile,rm,lstat,stat,readdir,symlink} from 'node:fs/promises';
|
|
5
|
+
import {tmpdir} from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
import {preserveNpmTarball,assertVacantPackDestination} from '../scripts/npm-preserve-artifact-r67.mjs';
|
|
9
|
+
|
|
10
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
const digest=(bytes,algo='sha256')=>createHash(algo).update(bytes).digest(algo==='sha512'?'base64':'hex');
|
|
12
|
+
const artifact=Buffer.concat([Buffer.from('R67 immutable npm tarball fixture\\n'),Buffer.alloc(4194305,0x5a)]);
|
|
13
|
+
const integrity='sha512-'+digest(artifact,'sha512');
|
|
14
|
+
|
|
15
|
+
async function fixture(fn){
|
|
16
|
+
await mkdir(path.join(root,'tmp'),{recursive:true});
|
|
17
|
+
const srcDir=await mkdtemp(path.join(root,'tmp','r67-xdev-src-'));
|
|
18
|
+
const dstDir=await mkdtemp(path.join(tmpdir(),'r67-xdev-dst-'));
|
|
19
|
+
const source=path.join(srcDir,'deadbyte-mcp-0.15.44.tgz');
|
|
20
|
+
const destination=path.join(dstDir,'deadbyte-mcp-0.15.44.tgz');
|
|
21
|
+
try{return await fn({source,destination,srcDir,dstDir});}
|
|
22
|
+
finally{await rm(srcDir,{recursive:true,force:true});await rm(dstDir,{recursive:true,force:true});}
|
|
23
|
+
}
|
|
24
|
+
test('R67 preserves immutable package across Windows drives without EXDEV, hash-verifies, and removes source only after successful commit',()=>fixture(async ({source,destination})=>{
|
|
25
|
+
await writeFile(source,artifact);
|
|
26
|
+
const srcDrive=(await stat(path.dirname(source))).dev,dstDrive=(await stat(path.dirname(destination))).dev;
|
|
27
|
+
if(process.platform==='win32')assert.notEqual(srcDrive,dstDrive,'Windows test must cover different volumes D and C');
|
|
28
|
+
const result=await preserveNpmTarball({source,destination,expectedIntegrity:integrity});
|
|
29
|
+
assert.equal(result.status,'VERIFIED_PRESERVED');
|
|
30
|
+
assert.equal(result.sha256,digest(artifact));
|
|
31
|
+
assert.equal(result.bytes,artifact.length);
|
|
32
|
+
assert.deepEqual(await readFile(destination),artifact);
|
|
33
|
+
await assert.rejects(()=>lstat(source),{code:'ENOENT'});
|
|
34
|
+
assert.deepEqual((await readdir(path.dirname(destination))).sort(),[path.basename(destination)]);
|
|
35
|
+
}));
|
|
36
|
+
test('R67 rejects incorrect npm-integrity before writing any destination and retains source',()=>fixture(async ({source,destination})=>{
|
|
37
|
+
await writeFile(source,artifact);
|
|
38
|
+
await assert.rejects(()=>preserveNpmTarball({source,destination,expectedIntegrity:'sha512-'+digest(Buffer.from('wrong'),'sha512')}),/integrity/i);
|
|
39
|
+
assert.deepEqual(await readFile(source),artifact);
|
|
40
|
+
await assert.rejects(()=>lstat(destination),{code:'ENOENT'});
|
|
41
|
+
}));
|
|
42
|
+
test('R67 never clobbers pre-existing different destination bytes',()=>fixture(async ({source,destination})=>{
|
|
43
|
+
await writeFile(source,artifact);await writeFile(destination,Buffer.from('other release artifact'));
|
|
44
|
+
await assert.rejects(()=>preserveNpmTarball({source,destination,expectedIntegrity:integrity}),/exists|conflict|different/i);
|
|
45
|
+
assert.deepEqual(await readFile(source),artifact);
|
|
46
|
+
assert.equal((await readFile(destination)).toString(),'other release artifact');
|
|
47
|
+
}));
|
|
48
|
+
test('R67 repairs post-commit/pre-cleanup crash without repeating publication and validates immutable destination',()=>fixture(async ({source,destination})=>{
|
|
49
|
+
await writeFile(source,artifact);await writeFile(destination,artifact);
|
|
50
|
+
const result=await preserveNpmTarball({source,destination,expectedIntegrity:integrity});
|
|
51
|
+
assert.equal(result.status,'VERIFIED_ALREADY_PRESERVED');
|
|
52
|
+
assert.deepEqual(await readFile(destination),artifact);
|
|
53
|
+
await assert.rejects(()=>lstat(source),{code:'ENOENT'});
|
|
54
|
+
}));
|
|
55
|
+
test('R67 rejects a symlink/reparse source and leaves destination absent',()=>fixture(async ({source,destination})=>{
|
|
56
|
+
await writeFile(source+'.real',artifact);
|
|
57
|
+
try{await symlink(source+'.real',source);}
|
|
58
|
+
catch(error){
|
|
59
|
+
if(process.platform!=='win32'||error?.code!=='EPERM')throw error;
|
|
60
|
+
const junctionTarget=source+'.junction-target';
|
|
61
|
+
await mkdir(junctionTarget);
|
|
62
|
+
await symlink(junctionTarget,source,'junction');
|
|
63
|
+
}
|
|
64
|
+
await assert.rejects(()=>preserveNpmTarball({source,destination,expectedIntegrity:integrity}),/symlink|regular/i);
|
|
65
|
+
await assert.rejects(()=>lstat(destination),{code:'ENOENT'});
|
|
66
|
+
}));
|
|
67
|
+
|
|
68
|
+
test('R67 npm pack never overwrites pre-existing unclaimed artifact after EXDEV',()=>fixture(async ({source})=>{
|
|
69
|
+
await writeFile(source,artifact);
|
|
70
|
+
await assert.rejects(()=>assertVacantPackDestination({root:path.dirname(source),filename:path.basename(source)}),/PACK_BLOCKED|preserv|exists/i);
|
|
71
|
+
assert.deepEqual(await readFile(source),artifact);
|
|
72
|
+
const fresh=await assertVacantPackDestination({root:path.dirname(source),filename:'new-artifact.tgz'});
|
|
73
|
+
assert.equal(fresh,path.join(path.dirname(source),'new-artifact.tgz'));
|
|
74
|
+
}));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {assertR67NpmPublishReady} from '../scripts/npm-release-readiness-r67.mjs';
|
|
7
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
test('R67 current sealed prepublish candidate satisfies the exact publish-readiness metadata gate',async()=>{
|
|
9
|
+
const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
|
|
10
|
+
assert.equal(release.r67_native_observation.status,'CANDIDATE_SEALED');
|
|
11
|
+
assert.doesNotThrow(()=>assertR67NpmPublishReady(release));
|
|
12
|
+
});
|
|
13
|
+
test('R67 exact mandatory gate flags must all pass before candidate npm publication',()=>{
|
|
14
|
+
const seed={version:'0.15.44',train:'R67',r67_native_observation:{
|
|
15
|
+
status:'CANDIDATE_SEALED',full_release_gate:'PASS',current_source_full_shards:'PASS',
|
|
16
|
+
native_fault_injection:'PASS',protocol_parity:'PASS',isolated_activation_rollback_restore:'PASS',
|
|
17
|
+
independent_machine_observation:'PASS'}};
|
|
18
|
+
assert.doesNotThrow(()=>assertR67NpmPublishReady(seed));
|
|
19
|
+
for(const k of Object.keys(seed.r67_native_observation)){
|
|
20
|
+
const bad=structuredClone(seed);bad.r67_native_observation[k]='PENDING';
|
|
21
|
+
assert.throws(()=>assertR67NpmPublishReady(bad),/PUBLISH_BLOCKED/);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
test('R67 real npm publisher must enforce publish readiness before npm pack / dispatch',async()=>{
|
|
25
|
+
const src=await readFile(path.join(root,'scripts','npm-release-r48.mjs'),'utf8');
|
|
26
|
+
const guard=src.indexOf('assertR67NpmPublishReady(release)');
|
|
27
|
+
const pack=src.indexOf("'pack', '--json'");
|
|
28
|
+
const submit=src.indexOf("'publish',publishTarball");
|
|
29
|
+
assert.ok(guard>0&&guard<pack&&guard<submit,'trusted metadata gate precedes network/npm pack and publication');
|
|
30
|
+
assert.ok(src.includes('preserveNpmTarball({source:tarball,destination:preservedTarball'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('R67 publisher checks for an existing tarball before npm pack can overwrite historical evidence',async()=>{
|
|
34
|
+
const src=await readFile(path.join(root,'scripts','npm-release-r48.mjs'),'utf8');
|
|
35
|
+
const guard=src.indexOf('await assertVacantPackDestination(');
|
|
36
|
+
const pack=src.indexOf("'pack', '--json'");
|
|
37
|
+
assert.ok(guard>0&&guard<pack,'pre-existing tarball guard must run before npm pack');
|
|
38
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createR67ShardPlan,reconcileR67Shards,r67ShardTimeoutMs,r67RegressionTreeHash,parseR67TapSummary} from '../src/r67-regression-shards.mjs';
|
|
4
|
+
import {mkdtemp,mkdir,writeFile,rm,symlink} from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
const names=Array.from({length:326},(_,i)=>'test/f-'+String(i).padStart(3,'0')+'.test.mjs');
|
|
8
|
+
test('R67 deterministic shards cover 326 files exactly once and remain stable under input reordering',()=>{
|
|
9
|
+
const plan=createR67ShardPlan(names,32);
|
|
10
|
+
const other=createR67ShardPlan([...names].reverse(),32);
|
|
11
|
+
assert.deepEqual(other,plan);
|
|
12
|
+
assert.equal(plan.shards.length,32);
|
|
13
|
+
assert.deepEqual(plan.shards.flatMap(x=>x.files).sort(),names);
|
|
14
|
+
assert.equal(new Set(plan.shards.flatMap(x=>x.files)).size,326);
|
|
15
|
+
assert.match(plan.plan_sha256,/^[a-f0-9]{64}$/);
|
|
16
|
+
for(const shard of plan.shards){
|
|
17
|
+
assert.ok(shard.files.length>=10&&shard.files.length<=11);
|
|
18
|
+
assert.match(shard.files_sha256,/^[a-f0-9]{64}$/);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
test('R67 verifier must BLOCK incomplete coverage, reject duplicates and never forge full PASS',()=>{
|
|
22
|
+
const plan=createR67ShardPlan(names,32);
|
|
23
|
+
const make=shard=>({shard_index:shard.index,plan_sha256:plan.plan_sha256,
|
|
24
|
+
files_sha256:shard.files_sha256,status:'PASS',exit_code:0,test_count:shard.files.length,passed:shard.files.length,failed:0});
|
|
25
|
+
const receipts=plan.shards.slice(0,31).map(make);
|
|
26
|
+
const incomplete=reconcileR67Shards(plan,receipts);
|
|
27
|
+
assert.equal(incomplete.status,'BLOCKED');
|
|
28
|
+
assert.deepEqual(incomplete.missing,[31]);
|
|
29
|
+
assert.throws(()=>reconcileR67Shards(plan,[...receipts,receipts[0]]),/duplicate/i);
|
|
30
|
+
const complete=reconcileR67Shards(plan,plan.shards.map(make));
|
|
31
|
+
assert.equal(complete.status,'PASS');
|
|
32
|
+
assert.equal(complete.file_count,326);
|
|
33
|
+
assert.equal(complete.failed,0);
|
|
34
|
+
});
|
|
35
|
+
test('R67 verifier fails closed for tampered test partition, nonzero exit and UNKNOWN outcomes',()=>{
|
|
36
|
+
const plan=createR67ShardPlan(names,32);
|
|
37
|
+
const good=plan.shards.map(shard=>({shard_index:shard.index,plan_sha256:plan.plan_sha256,
|
|
38
|
+
files_sha256:shard.files_sha256,status:'PASS',exit_code:0,test_count:shard.files.length,passed:shard.files.length,failed:0}));
|
|
39
|
+
const altered=structuredClone(good);altered[5].files_sha256='0'.repeat(64);
|
|
40
|
+
assert.equal(reconcileR67Shards(plan,altered).status,'FAILED');
|
|
41
|
+
const failed=structuredClone(good);failed[3].exit_code=1;failed[3].status='FAILED';failed[3].failed=1;
|
|
42
|
+
assert.equal(reconcileR67Shards(plan,failed).status,'FAILED');
|
|
43
|
+
const unknown=structuredClone(good);unknown[2].status='OUTCOME_UNKNOWN';unknown[2].exit_code=null;
|
|
44
|
+
assert.equal(reconcileR67Shards(plan,unknown).status,'OUTCOME_UNKNOWN');
|
|
45
|
+
assert.throws(()=>createR67ShardPlan([...names,names[0]],32),/duplicate/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('R67 real timed-out shard with null TAP counts remains OUTCOME_UNKNOWN, not forged FAILED',()=>{
|
|
49
|
+
const plan=createR67ShardPlan(names,32);
|
|
50
|
+
const receipts=plan.shards.map(shard=>({shard_index:shard.index,plan_sha256:plan.plan_sha256,
|
|
51
|
+
files_sha256:shard.files_sha256,status:'PASS',exit_code:0,test_count:shard.files.length,passed:shard.files.length,failed:0}));
|
|
52
|
+
receipts[5]={...receipts[5],status:'OUTCOME_UNKNOWN',exit_code:null,test_count:null,passed:null,failed:null};
|
|
53
|
+
const result=reconcileR67Shards(plan,receipts);
|
|
54
|
+
assert.equal(result.status,'OUTCOME_UNKNOWN');
|
|
55
|
+
assert.equal(result.integrity_failed,false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('R67 measured 110.4s remote and 53.2s controller integration suites get isolated bounded deadlines',()=>{
|
|
59
|
+
const remote='test/remote-session-cli.test.mjs';
|
|
60
|
+
const controller='test/r58-controller-live-local.test.mjs';
|
|
61
|
+
const input=[...names,remote,controller];
|
|
62
|
+
const first=createR67ShardPlan(input,32);
|
|
63
|
+
const repeat=createR67ShardPlan([...input].reverse(),32);
|
|
64
|
+
assert.deepEqual(repeat,first);
|
|
65
|
+
const remoteShard=first.shards.find(s=>s.files.includes(remote));
|
|
66
|
+
const controllerShard=first.shards.find(s=>s.files.includes(controller));
|
|
67
|
+
assert.deepEqual(remoteShard.files,[remote]);
|
|
68
|
+
assert.deepEqual(controllerShard.files,[controller]);
|
|
69
|
+
assert.notEqual(remoteShard.index,controllerShard.index);
|
|
70
|
+
assert.equal(r67ShardTimeoutMs(remoteShard),165000);
|
|
71
|
+
assert.equal(r67ShardTimeoutMs(controllerShard),120000);
|
|
72
|
+
assert.deepEqual(first.shards.flatMap(s=>s.files).sort(),[...input].sort());
|
|
73
|
+
for(const shard of first.shards.filter(s=>s.index!==remoteShard.index&&s.index!==controllerShard.index)){
|
|
74
|
+
assert.ok(shard.files.length>=10&&shard.files.length<=11);
|
|
75
|
+
assert.equal(r67ShardTimeoutMs(shard),58000);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('R67 regression fingerprint detects implementation-only source drift even if tests are untouched',async()=>{
|
|
80
|
+
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r67-fingerprint-'));
|
|
81
|
+
try{
|
|
82
|
+
await mkdir(path.join(dir,'src'));await mkdir(path.join(dir,'test'));
|
|
83
|
+
await writeFile(path.join(dir,'package.json'),'{}');
|
|
84
|
+
await writeFile(path.join(dir,'src','core.mjs'),'export const number = 1;');
|
|
85
|
+
await writeFile(path.join(dir,'test','core.test.mjs'),'test does not change');
|
|
86
|
+
const opts={roots:['src','test'],topLevel:['package.json'],extra:[]};
|
|
87
|
+
const baseline=await r67RegressionTreeHash(dir,opts);
|
|
88
|
+
assert.match(baseline.sha256,/^[a-f0-9]{64}$/);
|
|
89
|
+
assert.equal(baseline.file_count,3);
|
|
90
|
+
const same=await r67RegressionTreeHash(dir,opts);
|
|
91
|
+
assert.deepEqual(same,baseline);
|
|
92
|
+
await writeFile(path.join(dir,'src','core.mjs'),'export const number = 2;');
|
|
93
|
+
const drift=await r67RegressionTreeHash(dir,opts);
|
|
94
|
+
assert.notEqual(drift.sha256,baseline.sha256,'changing only source must invalidate receipts');
|
|
95
|
+
try{await symlink(path.join(dir,'package.json'),path.join(dir,'src','backdoor.mjs'));}
|
|
96
|
+
catch(error){
|
|
97
|
+
if(process.platform!=='win32'||error?.code!=='EPERM')throw error;
|
|
98
|
+
const junctionTarget=path.join(dir,'junction-target');
|
|
99
|
+
await mkdir(junctionTarget);
|
|
100
|
+
await symlink(junctionTarget,path.join(dir,'src','backdoor'),'junction');
|
|
101
|
+
}
|
|
102
|
+
await assert.rejects(()=>r67RegressionTreeHash(dir,opts),/symlink/i);
|
|
103
|
+
}finally{await rm(dir,{recursive:true,force:true});}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('R67 terminal TAP summary enforces complete counts and rejects forged/truncated PASS',()=>{
|
|
107
|
+
const ok=['✔ fixture succeeded (1ms)','ℹ tests 5','ℹ suites 0','ℹ pass 4','ℹ fail 0','ℹ cancelled 0','ℹ skipped 1','ℹ todo 0'].join('\n');
|
|
108
|
+
const parsed=parseR67TapSummary(ok);
|
|
109
|
+
assert.deepEqual(parsed,{tests:5,pass:4,fail:0,cancelled:0,skipped:1,todo:0});
|
|
110
|
+
assert.equal(parseR67TapSummary('test does not carry terminal TAP'),null);
|
|
111
|
+
assert.equal(parseR67TapSummary(ok.replace('ℹ tests 5','ℹ tests 6')),null);
|
|
112
|
+
assert.equal(parseR67TapSummary(ok.replace('ℹ cancelled 0','ℹ cancelled 1')),null);
|
|
113
|
+
assert.equal(parseR67TapSummary(ok.replace('ℹ todo 0','ℹ todo 1')),null);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('R67 v2 shard receipt rejects inconsistent skip totals and cancelled outcomes',()=>{
|
|
117
|
+
const plan=createR67ShardPlan(names,32);
|
|
118
|
+
const good=plan.shards.map(shard=>({schema:'deadbyte.r67-shard-result.v2',shard_index:shard.index,
|
|
119
|
+
plan_sha256:plan.plan_sha256,files_sha256:shard.files_sha256,status:'PASS',exit_code:0,
|
|
120
|
+
test_count:shard.files.length,passed:shard.files.length,failed:0,skipped:0,cancelled:0,todo:0}));
|
|
121
|
+
assert.equal(reconcileR67Shards(plan,good).status,'PASS');
|
|
122
|
+
const altered=structuredClone(good);altered[1].skipped=1;
|
|
123
|
+
assert.equal(reconcileR67Shards(plan,altered).status,'FAILED');
|
|
124
|
+
const cancelled=structuredClone(good);cancelled[1].passed-=1;cancelled[1].cancelled=1;
|
|
125
|
+
assert.equal(reconcileR67Shards(plan,cancelled).status,'FAILED');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('R67 CLI reconciliation delegates to same independently tested v2 evaluator',async()=>{
|
|
129
|
+
const {readFile}=await import('node:fs/promises');
|
|
130
|
+
const {fileURLToPath}=await import('node:url');
|
|
131
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
132
|
+
const runner=await readFile(path.join(here,'..','scripts','r67-regression-shard-run.mjs'),'utf8');
|
|
133
|
+
const cli=await readFile(path.join(here,'..','scripts','r67-regression-reconcile.mjs'),'utf8');
|
|
134
|
+
const core=await readFile(path.join(here,'..','src','r67-evidence-reconcile.mjs'),'utf8');
|
|
135
|
+
assert.ok(runner.includes("schema:'deadbyte.r67-shard-result.v2'"));
|
|
136
|
+
assert.ok(cli.includes("import {reconcileR67Evidence} from"));
|
|
137
|
+
assert.ok(core.includes("result.schema!=='deadbyte.r67-shard-result.v2'"));
|
|
138
|
+
assert.ok(cli.includes("if(result.status!=='PASS')process.exitCode=2;"));
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('R67 evidence reconciler fails closed on absent operation identity',async()=>{
|
|
142
|
+
const {reconcileR67Evidence}=await import('../src/r67-evidence-reconcile.mjs');
|
|
143
|
+
await assert.rejects(()=>reconcileR67Evidence({root:'X',evidenceRoot:'Y',operationId:'../escape'}),/bounded root|operation identity/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('R67 TAP nonpass ledger preserves test name and explicit SKIP reason, rejects unnamed skips',async()=>{
|
|
147
|
+
const {parseR67NonpassCases}=await import('../src/r67-regression-shards.mjs');
|
|
148
|
+
const tap=[
|
|
149
|
+
'TAP version 13',
|
|
150
|
+
'ok 1 - works',
|
|
151
|
+
'ok 2 - Windows-only hook # SKIP unavailable on platform',
|
|
152
|
+
' ok 1 - case without reason # SKIP',
|
|
153
|
+
'ok 3 - not yet implemented # TODO waiting on API',
|
|
154
|
+
'# tests 4','# pass 1','# fail 0','# cancelled 0','# skipped 2','# todo 1'
|
|
155
|
+
].join('\n');
|
|
156
|
+
const v=parseR67NonpassCases(tap);
|
|
157
|
+
assert.equal(v.cases.length,3);
|
|
158
|
+
assert.equal(v.cases[0].name,'Windows-only hook');
|
|
159
|
+
assert.equal(v.cases[0].reason,'unavailable on platform');
|
|
160
|
+
assert.equal(v.cases[1].reason,null);
|
|
161
|
+
assert.equal(v.cases[2].kind,'TODO');
|
|
162
|
+
assert.equal(v.fully_classified,false);
|
|
163
|
+
assert.equal(v.unclassified_count,1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test('R67 preserves bounded case identity for a TAP FAILED leaf instead of empty nonpass evidence',async()=>{
|
|
167
|
+
const {parseR67FailedCases}=await import('../src/r67-regression-shards.mjs');
|
|
168
|
+
const tap=['TAP version 13','# Subtest: remote test','not ok 4 - remote owner cleanup is durable',
|
|
169
|
+
' ---',' error: \'sensitive runtime stack omitted\'',' ...','# tests 26','# pass 25','# fail 1','# cancelled 0','# skipped 0','# todo 0'].join('\n');
|
|
170
|
+
const parsed=parseR67FailedCases(tap);
|
|
171
|
+
assert.equal(parsed.count,1);
|
|
172
|
+
assert.equal(parsed.cases[0].name,'remote owner cleanup is durable');
|
|
173
|
+
assert.match(parsed.cases[0].name_sha256,/^[a-f0-9]{64}$/);
|
|
174
|
+
assert.ok(!JSON.stringify(parsed).includes('sensitive runtime stack'));
|
|
175
|
+
});
|
|
176
|
+
test('R67 failed-case parser remains fail closed on missing TAP and secret-shaped titles',async()=>{
|
|
177
|
+
const {parseR67FailedCases}=await import('../src/r67-regression-shards.mjs');
|
|
178
|
+
const missing=parseR67FailedCases('TAP version 13\n# tests 2\n# pass 1\n# fail 1\n');
|
|
179
|
+
assert.equal(missing.complete,false);
|
|
180
|
+
const title='remote failed sk-proj-secret-token-value';
|
|
181
|
+
const guarded=parseR67FailedCases('not ok 1 - '+title+'\n# tests 1\n# pass 0\n# fail 1\n# cancelled 0\n# skipped 0\n# todo 0');
|
|
182
|
+
assert.equal(guarded.cases[0].name,'[redacted]');
|
|
183
|
+
assert.equal(guarded.count,1);
|
|
184
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
const get=async rel=>JSON.parse(await readFile(new URL('../'+rel,import.meta.url),'utf8'));
|
|
5
|
+
const parent={
|
|
6
|
+
release_id:'v0.15.43-4c4a64a4c8e9f5a6',
|
|
7
|
+
manifest_sha256:'4c4a64a4c8e9f5a6e9eecce85fd0007ebcac99121b29c07f1a1fc81d1643cd33',
|
|
8
|
+
generation_manifest_sha256:'ef3b041e2a87c2e143d4733a98a52d9db80cd16ad7007e112fcb5202e70c77c3'
|
|
9
|
+
};
|
|
10
|
+
test('R67 release version, predecessor evidence, and observed prepublish seal are exact',async()=>{
|
|
11
|
+
const [pkg,lock,bridge,release,prov,matrix,sbom,version]=await Promise.all([
|
|
12
|
+
get('package.json'),get('package-lock.json'),get('bridge/package.json'),
|
|
13
|
+
get('RELEASE.json'),get('PROVENANCE.json'),get('TEST-MATRIX.json'),get('SBOM.cdx.json'),
|
|
14
|
+
readFile(new URL('../src/version.mjs',import.meta.url),'utf8')
|
|
15
|
+
]);
|
|
16
|
+
assert.equal(pkg.version,'0.15.44');
|
|
17
|
+
assert.equal(lock.version,pkg.version);
|
|
18
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
19
|
+
assert.equal(bridge.version,pkg.version);
|
|
20
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.15\.44'/);
|
|
21
|
+
assert.equal(release.version,pkg.version);
|
|
22
|
+
assert.equal(release.train,'R67');
|
|
23
|
+
assert.equal(release.parent.version,'0.15.43');
|
|
24
|
+
assert.equal(release.parent.release_id,parent.release_id);
|
|
25
|
+
assert.equal(release.parent.manifest_sha256,parent.manifest_sha256);
|
|
26
|
+
assert.equal(release.parent.generation_manifest_sha256,parent.generation_manifest_sha256);
|
|
27
|
+
assert.deepEqual(release.runtime_predecessor,parent);
|
|
28
|
+
assert.equal(prov.version,pkg.version);
|
|
29
|
+
assert.equal(prov.parent_release_id,parent.release_id);
|
|
30
|
+
assert.equal(prov.parent_manifest_sha256,parent.manifest_sha256);
|
|
31
|
+
assert.equal(prov.parent_generation_manifest_sha256,parent.generation_manifest_sha256);
|
|
32
|
+
assert.equal(matrix.version,pkg.version);
|
|
33
|
+
assert.equal(matrix.train,'R67');
|
|
34
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
35
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r67:0.15.44');
|
|
36
|
+
assert.equal(release.r67_native_observation.status,'CANDIDATE_SEALED');
|
|
37
|
+
assert.equal(release.r67_native_observation.new_public_tools,0);
|
|
38
|
+
assert.equal(release.r67_native_observation.full_release_gate,'PASS');
|
|
39
|
+
assert.equal(release.r67_native_observation.current_source_full_shards,'PASS');
|
|
40
|
+
assert.equal(release.r67_native_observation.isolated_activation_rollback_restore,'PASS');
|
|
41
|
+
assert.ok(matrix.required.some(x=>x.id==='r67-sharded-full'));
|
|
42
|
+
assert.match(pkg.scripts['gate:windows'],/gate-windows-r67\.ps1/);
|
|
43
|
+
assert.match(pkg.scripts['r67:focused:test'],/r67-focused-gate\.mjs/);
|
|
44
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {createMachineRuntime} from '../src/machine-identity.mjs';
|
|
8
|
+
import {independentlyVerifyR67RuntimeObservation} from '../scripts/r67-independent-runtime-observation-verify.mjs';
|
|
9
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
10
|
+
const POLICY='a'.repeat(64),GRANT='r67-fixture-grant-1';
|
|
11
|
+
async function fixture(callback){
|
|
12
|
+
const stateRoot=await mkdtemp(path.join(os.tmpdir(),'r67-owned-observation-'));
|
|
13
|
+
const release=path.join(stateRoot,'releases','r67'),generation=path.join(stateRoot,'generations','r67');
|
|
14
|
+
await mkdir(release,{recursive:true});await mkdir(generation,{recursive:true});
|
|
15
|
+
const releaseBytes=Buffer.from('candidate-r67-release\n'),generationBytes=Buffer.from('candidate-r67-generation\n');
|
|
16
|
+
await writeFile(path.join(release,'MANIFEST.SHA256'),releaseBytes);
|
|
17
|
+
await writeFile(path.join(generation,'GENERATION.SHA256'),generationBytes);
|
|
18
|
+
const releaseHash=sha(releaseBytes),generationHash=sha(generationBytes);
|
|
19
|
+
const pointer={
|
|
20
|
+
schema:'deadbyte.active-release.v1',release_id:'v0.15.43-'+releaseHash.slice(0,16),version:'0.15.43',
|
|
21
|
+
manifest_sha256:releaseHash,release_root:release,generation_root:generation,
|
|
22
|
+
generation_manifest_sha256:generationHash,launcher_sha256:'1'.repeat(64),
|
|
23
|
+
config_path:path.join(generation,'controller-config.psd1')
|
|
24
|
+
};
|
|
25
|
+
const activePointerPath=path.join(stateRoot,'active-release.json');
|
|
26
|
+
await writeFile(activePointerPath,JSON.stringify(pointer)+'\n');
|
|
27
|
+
const runtime=await createMachineRuntime({stateRoot,activePointerPath,assertObservationAuthority:callback});
|
|
28
|
+
return {runtime,stateRoot,cleanup:()=>rm(stateRoot,{recursive:true,force:true})};
|
|
29
|
+
}
|
|
30
|
+
const grant=()=>({grant_id:GRANT,policy_sha256:POLICY,scope:'native-observe-readonly',authority_generation:4});
|
|
31
|
+
test('R67 isolated machine-owned observation signs actual Windows native process data with generation and scoped standing-grant',async()=>{
|
|
32
|
+
const phases=[];
|
|
33
|
+
const x=await fixture(async({phase,operation})=>{phases.push(phase);assert.equal(operation,'process');return grant();});
|
|
34
|
+
try{
|
|
35
|
+
const before=await x.runtime.status();
|
|
36
|
+
const output=await x.runtime.observeNative({args:['process','4']});
|
|
37
|
+
assert.equal(output.result.status,'ok');
|
|
38
|
+
assert.equal(output.receipt.machine_id,before.machine_id);
|
|
39
|
+
assert.equal(output.receipt.generation_manifest_sha256,before.generation_manifest_sha256);
|
|
40
|
+
assert.equal(output.receipt.authority.grant_id,GRANT);
|
|
41
|
+
assert.deepEqual(phases,['pre','post']);
|
|
42
|
+
const check=independentlyVerifyR67RuntimeObservation({
|
|
43
|
+
receipt:output.receipt,result:output.result,requestArgs:['process','4'],
|
|
44
|
+
machinePublicSpkiBase64:before.public_key_spki_base64,
|
|
45
|
+
expectedMachineStatus:before,expectedPolicySha256:POLICY,expectedGrantId:GRANT
|
|
46
|
+
});
|
|
47
|
+
assert.equal(check.ok,true,check.reason);
|
|
48
|
+
assert.equal(check.signature_valid,true);
|
|
49
|
+
}finally{await x.cleanup();}
|
|
50
|
+
});
|
|
51
|
+
test('R67 runtime observer denies missing authority, changing lease and revoked grant before signing',async()=>{
|
|
52
|
+
const noAuthority=await fixture(null);
|
|
53
|
+
try{await assert.rejects(()=>noAuthority.runtime.observeNative({args:['process','4']}),/authority/i);}
|
|
54
|
+
finally{await noAuthority.cleanup();}
|
|
55
|
+
let armed=true,rotate=false;
|
|
56
|
+
const x=await fixture(async({phase})=>{
|
|
57
|
+
if(phase==='post'&&rotate)armed=false;
|
|
58
|
+
if(!armed)throw new Error('LOCAL_AUTHORITY_REVOKED');
|
|
59
|
+
return grant();
|
|
60
|
+
});
|
|
61
|
+
try{
|
|
62
|
+
rotate=true;
|
|
63
|
+
await assert.rejects(()=>x.runtime.observeNative({args:['process','4']}),/LOCAL_AUTHORITY_REVOKED/);
|
|
64
|
+
assert.equal(armed,false);
|
|
65
|
+
}finally{await x.cleanup();}
|
|
66
|
+
});
|
|
67
|
+
test('R67 runtime observation verifier rejects changed result, request, generation, grant and signature',async()=>{
|
|
68
|
+
const x=await fixture(async()=>grant());
|
|
69
|
+
try{
|
|
70
|
+
const before=await x.runtime.status();
|
|
71
|
+
const xResult=await x.runtime.observeNative({args:['process','4']});
|
|
72
|
+
const verify=(receipt,result,args=['process','4'],status=before)=>independentlyVerifyR67RuntimeObservation({
|
|
73
|
+
receipt,result,requestArgs:args,machinePublicSpkiBase64:before.public_key_spki_base64,
|
|
74
|
+
expectedMachineStatus:status,expectedPolicySha256:POLICY,expectedGrantId:GRANT
|
|
75
|
+
}).ok;
|
|
76
|
+
assert.equal(verify(xResult.receipt,xResult.result),true);
|
|
77
|
+
const corrupt=structuredClone(xResult.result);corrupt.data.processes[0].name+='INJECTED';
|
|
78
|
+
assert.equal(verify(xResult.receipt,corrupt),false);
|
|
79
|
+
assert.equal(verify(xResult.receipt,xResult.result,['process','8']),false);
|
|
80
|
+
assert.equal(verify({...xResult.receipt,generation_manifest_sha256:'0'.repeat(64)},xResult.result),false);
|
|
81
|
+
assert.equal(verify({...xResult.receipt,authority:{...xResult.receipt.authority,grant_id:'wrong'}},xResult.result),false);
|
|
82
|
+
assert.equal(verify({...xResult.receipt,attestation:{...xResult.receipt.attestation,signature_base64:'AA=='}},xResult.result),false);
|
|
83
|
+
const after={...before,identity_generation:before.identity_generation+1};
|
|
84
|
+
assert.equal(verify(xResult.receipt,xResult.result,['process','4'],after),false);
|
|
85
|
+
}finally{await x.cleanup();}
|
|
86
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
test('R67 server-owned Native Observer is wired to live Coding standing epoch and explicitly opted in',async()=>{
|
|
5
|
+
const source=await readFile(new URL('../src/mcp-server.mjs',import.meta.url),'utf8');
|
|
6
|
+
assert.match(source,/import \{ createR67NativeObservationAuthority \} from '\.\/r67-native-observation-authority\.mjs'/);
|
|
7
|
+
assert.match(source,/let codingPolicy=null;/);
|
|
8
|
+
assert.match(source,/DEADBYTE_R67_NATIVE_OBSERVE \?\? '0'/);
|
|
9
|
+
assert.match(source,/getCodingPolicy:\(\)=>codingPolicy,enabled:nativeObserveMode==='1'/);
|
|
10
|
+
assert.match(source,/createMachineRuntime\(\{stateRoot:runtimeStateRoot,assertObservationAuthority:assertNativeObservationAuthority\}\)/);
|
|
11
|
+
assert.equal((source.match(/let codingPolicy=null;/g)??[]).length,1);
|
|
12
|
+
});
|