deadbyte-mcp 0.15.34 → 0.15.36
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 +100 -38
- package/PROVENANCE.json +87 -41
- package/README.txt +72 -14
- package/RELEASE.json +187 -150
- package/SBOM.cdx.json +25 -3
- package/TEST-MATRIX.json +258 -151
- package/bridge/package.json +13 -0
- package/bridge/src/bridge-health-r59.mjs +52 -0
- package/bridge/src/bridge.mjs +487 -0
- package/bridge/src/node-http-adapter.mjs +56 -0
- package/bridge/src/public-tool-definitions.mjs +14 -0
- package/bridge/src/r40-governor.mjs +206 -0
- package/controller/deadbyte-controller.ps1 +152 -18
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R59-TASK-CONTRACT.md +26 -0
- package/package.json +7 -3
- package/proof/r59-full-regression-current.json +13 -0
- package/proof/r59-full-regression-current.stderr.log +0 -0
- package/proof/r59-full-regression-current.stdout.log +1268 -0
- package/scripts/bridge-benchmark-r58.mjs +67 -0
- package/scripts/deploy-r48.mjs +2 -3
- package/scripts/gate-windows-r58.ps1 +33 -0
- package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
- package/scripts/owner-probe-r58.mjs +31 -0
- package/scripts/performance-r58.mjs +57 -0
- package/scripts/public-baseline-r58.mjs +72 -0
- package/scripts/public-surface-r58.mjs +84 -0
- package/scripts/r58-cross-plane.ps1 +6 -0
- package/scripts/r58-focused.ps1 +6 -0
- package/scripts/r59-full-regression-capture.mjs +35 -0
- package/scripts/windows-gate-evidence-r58.mjs +93 -0
- package/src/capability-resolver.mjs +25 -7
- package/src/capability-snapshot.mjs +52 -0
- package/src/compact-gateway-mcp-tools.mjs +10 -11
- package/src/deadbyte-cli.mjs +40 -12
- package/src/doctor-foreground-r58.mjs +44 -0
- package/src/doctor-installation-r58.mjs +63 -0
- package/src/doctor-r45.mjs +21 -4
- package/src/doctor-runtime-r58.mjs +82 -0
- package/src/doctor-transactions-r58.mjs +40 -0
- package/src/foreground-process-identity.mjs +27 -0
- package/src/foreground-session-lock.mjs +20 -0
- package/src/foreground-session-lock.ps1 +15 -0
- package/src/mcp-server.mjs +179 -118
- package/src/r40-compact-disarmed-probe.mjs +1 -1
- package/src/r44-compact-disarmed-probe.mjs +1 -1
- package/src/r45-compact-disarmed-probe.mjs +1 -1
- package/src/release-generation.mjs +1 -0
- package/src/release-slot.mjs +2 -1
- package/src/remote-mcp-r40-parity-client.mjs +1 -1
- package/src/remote-mcp-r44-parity-client.mjs +1 -1
- package/src/remote-mcp-r45-parity-client.mjs +1 -1
- package/src/remote-mcp-smoke-client.mjs +1 -1
- package/src/remote-session-guardian.mjs +8 -1
- package/src/runtime-plane-registry.mjs +74 -0
- package/src/runtime-update.mjs +85 -2
- package/src/version.mjs +1 -1
- package/test/bridge-runtime-r59.test.mjs +60 -0
- package/test/capability-resolver.test.mjs +16 -0
- package/test/capability-snapshot-r59.test.mjs +39 -0
- package/test/compact-gateway-r36.test.mjs +22 -2
- package/test/compact-max-catalog-r44.test.mjs +1 -1
- package/test/compact-max-catalog-r45.test.mjs +1 -1
- package/test/contained.test.mjs +2 -1
- package/test/controller-policy-preflight-r59.test.mjs +39 -0
- package/test/controller-r211.test.mjs +4 -1
- package/test/controller.test.mjs +2 -1
- package/test/fixtures/r59-compact-surface-v0.15.35.json +222 -0
- package/test/helpers/r58-doctor-fixture.mjs +26 -0
- package/test/observation-integration.test.mjs +2 -3
- package/test/optional-plane-degraded-r59.test.mjs +44 -0
- package/test/r49-windows-gate.test.mjs +2 -2
- package/test/r57-release-contract.test.mjs +5 -3
- package/test/r58-bridge-dependency-binding.test.mjs +62 -0
- package/test/r58-bundled-bridge.test.mjs +24 -0
- package/test/r58-cleanup-retention.test.mjs +45 -0
- package/test/r58-completed-update-receipt.test.mjs +76 -0
- package/test/r58-concurrent-recovery.test.mjs +54 -0
- package/test/r58-controller-core-observation.test.mjs +36 -0
- package/test/r58-controller-json.test.mjs +48 -0
- package/test/r58-controller-live-local.test.mjs +98 -0
- package/test/r58-controller-readonly.test.mjs +23 -0
- package/test/r58-controller-state-coherence.test.mjs +45 -0
- package/test/r58-doctor-cli.test.mjs +20 -0
- package/test/r58-doctor-foreground.test.mjs +50 -0
- package/test/r58-doctor-installation.test.mjs +55 -0
- package/test/r58-doctor-pointer.test.mjs +41 -0
- package/test/r58-doctor-runtime.test.mjs +45 -0
- package/test/r58-doctor-transactions.test.mjs +21 -0
- package/test/r58-guardian-channel.test.mjs +26 -0
- package/test/r58-launcher-shell.test.mjs +28 -0
- package/test/r58-owner-identity.test.mjs +20 -0
- package/test/r58-release-contract.test.mjs +50 -0
- package/test/r58-session-lock.test.mjs +26 -0
- package/test/r58-stale-owner.test.mjs +71 -0
- package/test/r58-windows-gate.test.mjs +32 -0
- package/test/r59-release-contract.test.mjs +61 -0
- package/test/r59-tool-surface-compat.test.mjs +90 -0
- package/test/release-generation.test.mjs +4 -1
- package/test/remote-session-cli.test.mjs +55 -2
- package/test/runtime-plane-registry-r59.test.mjs +40 -0
|
@@ -0,0 +1,50 @@
|
|
|
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(JSON.parse);
|
|
7
|
+
const currentPackage=await json('package.json');
|
|
8
|
+
|
|
9
|
+
test('R58 identity is coherent and parent is exact production-accepted 0.15.34',{skip:currentPackage.version!=='0.15.35'},async()=>{
|
|
10
|
+
const [pkg,lock,release,prov,matrix,sbom,version,bridgePkg]=await Promise.all([
|
|
11
|
+
json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),json('bridge/package.json')
|
|
12
|
+
]);
|
|
13
|
+
assert.equal(pkg.version,'0.15.35');
|
|
14
|
+
assert.equal(lock.version,pkg.version);
|
|
15
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
16
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.15\.35'/);
|
|
17
|
+
assert.equal(release.version,pkg.version);
|
|
18
|
+
assert.equal(release.train,'R58');
|
|
19
|
+
assert.equal(prov.version,pkg.version);
|
|
20
|
+
assert.equal(matrix.version,pkg.version);
|
|
21
|
+
assert.equal(matrix.train,'R58');
|
|
22
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
23
|
+
assert.equal(bridgePkg.version,pkg.version);
|
|
24
|
+
assert.deepEqual(release.runtime_predecessor,{
|
|
25
|
+
release_id:'v0.15.34-2199bb59f0e0fc20',
|
|
26
|
+
manifest_sha256:'2199bb59f0e0fc205d470d11f990d3ce3df444be5eba2a7d8dbc60d5f5b790bb',
|
|
27
|
+
generation_manifest_sha256:'1a20a62aaeb324a9b5c03d158952f35092d58fedf2ac7725820ba2b094a4a88f'
|
|
28
|
+
});
|
|
29
|
+
assert.equal(release.parent.tarball_sha256,'a6f7b21faf34793ca17dfb6d0e9ec03320f1468d011e75d8349098df82fc2639');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('R58 consolidation evidence preserves fail-closed and non-final claims',{skip:currentPackage.version!=='0.15.35'},async()=>{
|
|
33
|
+
const [release,prov,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
|
|
34
|
+
assert.equal(release.consolidation.bridge_bundled_inside_release,true);
|
|
35
|
+
assert.equal(release.consolidation.bridge_recursive_local_dependency_binding,true);
|
|
36
|
+
assert.equal(release.consolidation.bridge_dynamic_relative_import_fail_closed,true);
|
|
37
|
+
assert.equal(release.consolidation.bridge_path_escape_fail_closed,true);
|
|
38
|
+
assert.equal(release.consolidation.performance_acceptance,'PASS_BOUNDED_PREPUBLISH');
|
|
39
|
+
assert.equal(release.consolidation.cross_plane_acceptance,'PASS');
|
|
40
|
+
assert.equal(release.consolidation.public_surface_compatibility,'PASS_43_TOOLS_NO_SCHEMA_DRIFT');
|
|
41
|
+
assert.equal(matrix.observed_pre_release_evidence.cross_plane.status,'PASS');
|
|
42
|
+
assert.equal(matrix.observed_pre_release_evidence.public_surface.candidate_tool_count,43);
|
|
43
|
+
assert.equal(matrix.observed_pre_release_evidence.performance.status,'PASS_BOUNDED_PREPUBLISH');
|
|
44
|
+
assert.ok(['PENDING_R58_FRESH','PASS'].includes(matrix.observed_pre_release_evidence.full_regression.status));
|
|
45
|
+
assert.equal(prov.operator_override.four_hour_soak,'NOT_COMPLETED');
|
|
46
|
+
assert.equal(prov.operator_override.clean_machine,'NOT_COMPLETED');
|
|
47
|
+
assert.equal(prov.operator_override.stable_final_claim,false);
|
|
48
|
+
assert.equal(matrix.operator_override.stable_final_claim,false);
|
|
49
|
+
assert.equal(release.stabilization.stable_final_claim,false);
|
|
50
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdir,mkdtemp} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {spawn} from 'node:child_process';
|
|
6
|
+
import {setTimeout as delay} from 'node:timers/promises';
|
|
7
|
+
import {acquireForegroundSessionLock} from '../src/foreground-session-lock.mjs';
|
|
8
|
+
|
|
9
|
+
test('R58 startup lock excludes competitors and releases after owner hard death without stale lockfiles',{timeout:15000},async()=>{
|
|
10
|
+
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});const root=await mkdtemp(path.join(base,'session-lock-'));
|
|
11
|
+
const powershell=path.join(process.env.SystemRoot,'System32/WindowsPowerShell/v1.0/powershell.exe');
|
|
12
|
+
const moduleUrl=new URL('../src/foreground-session-lock.mjs',import.meta.url).href;
|
|
13
|
+
const script=`const {acquireForegroundSessionLock}=await import(${JSON.stringify(moduleUrl)});await acquireForegroundSessionLock(${JSON.stringify({root,powershell})});console.log('OWNER_LOCK_READY');process.stdin.resume();`;
|
|
14
|
+
const owner=spawn(process.execPath,['--input-type=module','-e',script],{windowsHide:true,stdio:['pipe','pipe','pipe']});let stdout='',stderr='';
|
|
15
|
+
owner.stdout.on('data',b=>{stdout+=b;});owner.stderr.on('data',b=>{stderr+=b;});const exited=new Promise(resolve=>owner.once('exit',resolve));
|
|
16
|
+
let lock;
|
|
17
|
+
try{
|
|
18
|
+
const until=Date.now()+4000;while(!stdout.includes('OWNER_LOCK_READY')&&owner.exitCode===null&&Date.now()<until)await delay(20);
|
|
19
|
+
assert.match(stdout,/OWNER_LOCK_READY/,stderr);
|
|
20
|
+
await assert.rejects(acquireForegroundSessionLock({root,powershell}),/already claimed/);
|
|
21
|
+
owner.kill('SIGKILL');await exited;
|
|
22
|
+
const deadline=Date.now()+5000;
|
|
23
|
+
while(Date.now()<deadline){try{lock=await acquireForegroundSessionLock({root,powershell});break;}catch(error){if(!/already claimed/.test(error.message))throw error;await delay(20);}}
|
|
24
|
+
assert.ok(lock,'dead owner must not require manual lockfile removal');lock.assertHeld();await lock.release();lock=null;
|
|
25
|
+
}finally{if(owner.exitCode===null&&owner.signalCode===null)owner.kill();if(lock)await lock.release();}
|
|
26
|
+
});
|
|
@@ -0,0 +1,71 @@
|
|
|
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 path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {spawn} from 'node:child_process';
|
|
7
|
+
import {queryForegroundIdentity} from '../src/foreground-process-identity.mjs';
|
|
8
|
+
|
|
9
|
+
test('R58 inconsistent owner PID binding cannot trigger stale-session authority cleanup', {timeout:20000}, async()=>{
|
|
10
|
+
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
|
|
11
|
+
const root=await mkdtemp(path.join(base,'owner-binding-'));await mkdir(path.join(root,'runtime'));await mkdir(path.join(root,'bin'));
|
|
12
|
+
const trace=path.join(root,'trace.txt');
|
|
13
|
+
await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)\n[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine)\nexit 0\n`);
|
|
14
|
+
const identity=queryForegroundIdentity(process.pid);
|
|
15
|
+
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:0,session_nonce:'c'.repeat(64),owner_identity:{...identity,pid:process.pid+1}};
|
|
16
|
+
const markerPath=path.join(root,'runtime','remote-session.json');const bytes=JSON.stringify(marker);await writeFile(markerPath,bytes);
|
|
17
|
+
const child=spawn(process.execPath,[fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url)),'remote'],{env:{...process.env,DEADBYTE_STATE_ROOT:root},windowsHide:true,stdio:['pipe','pipe','pipe']});
|
|
18
|
+
let stderr='';child.stderr.on('data',b=>{stderr+=b;});child.stdout.resume();
|
|
19
|
+
const code=await new Promise(resolve=>child.once('close',resolve));
|
|
20
|
+
await writeFile(path.join(root,'observation.json'),JSON.stringify({code,stderr}),{flag:'wx'});
|
|
21
|
+
assert.notEqual(code,0);assert.match(stderr,/ownership identity|binding/i);
|
|
22
|
+
assert.equal(await readFile(trace,'utf8').catch(e=>e.code==='ENOENT'?'':Promise.reject(e)),'','inconsistent marker must cause no controller actions');
|
|
23
|
+
assert.equal(await readFile(markerPath,'utf8'),bytes,'retain ambiguous ownership evidence');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('R58 live unrelated PID is not accepted as a remote foreground owner', {timeout:20000}, async()=>{
|
|
27
|
+
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';
|
|
28
|
+
await mkdir(base,{recursive:true});const root=await mkdtemp(path.join(base,'stale-owner-'));
|
|
29
|
+
await mkdir(path.join(root,'runtime'));await mkdir(path.join(root,'bin'));
|
|
30
|
+
const trace=path.join(root,'trace.txt');
|
|
31
|
+
await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)\n[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine)\nexit 0\n`);
|
|
32
|
+
// The test runner is alive but neither a remote CLI nor a guardian. This
|
|
33
|
+
// represents a stale numeric PID now occupied by an unrelated process.
|
|
34
|
+
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:process.pid,session_nonce:'a'.repeat(64),started_at_utc:'2000-01-01T00:00:00.000Z'};
|
|
35
|
+
await writeFile(path.join(root,'runtime','remote-session.json'),JSON.stringify(marker));
|
|
36
|
+
const child=spawn(process.execPath,[fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url)),'remote'],{env:{...process.env,DEADBYTE_STATE_ROOT:root},windowsHide:true,stdio:['pipe','pipe','pipe']});
|
|
37
|
+
let stdout='',stderr='';child.stdout.on('data',b=>{stdout+=b;});child.stderr.on('data',b=>{stderr+=b;});
|
|
38
|
+
const code=await new Promise(resolve=>child.once('close',resolve));
|
|
39
|
+
await writeFile(path.join(root,'observation.json'),JSON.stringify({code,stdout,stderr}),{flag:'wx'});
|
|
40
|
+
assert.doesNotMatch(stderr,/remote session already active pid=/,'PID alone is not ownership proof');
|
|
41
|
+
// Fail-closed ambiguity is acceptable; do not kill the unrelated process or
|
|
42
|
+
// mutate authority based on unverified ownership. Automatic convergence still
|
|
43
|
+
// requires separately observed exact owner/guardian identity evidence.
|
|
44
|
+
assert.match(stderr,/identity|ownership|ambiguous|unverified/i);
|
|
45
|
+
assert.deepEqual(JSON.parse(await readFile(path.join(root,'runtime','remote-session.json'),'utf8')),marker);
|
|
46
|
+
assert.notEqual(code,0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('R58 bound stale birth identity reconciles on the first invocation without killing a reused PID', {timeout:20000}, async()=>{
|
|
50
|
+
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';
|
|
51
|
+
await mkdir(base,{recursive:true});const root=await mkdtemp(path.join(base,'stale-birth-'));
|
|
52
|
+
await mkdir(path.join(root,'runtime'));await mkdir(path.join(root,'bin'));
|
|
53
|
+
const trace=path.join(root,'trace.txt');
|
|
54
|
+
await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)\n[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine)\nexit 0\n`);
|
|
55
|
+
const current=queryForegroundIdentity(process.pid);
|
|
56
|
+
const stale={...current,creation_utc:'2000-01-01T00:00:00.000Z'};
|
|
57
|
+
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:process.pid,session_nonce:'b'.repeat(64),owner_identity:stale,guardian_identity:stale};
|
|
58
|
+
const markerPath=path.join(root,'runtime','remote-session.json');
|
|
59
|
+
await writeFile(markerPath,JSON.stringify(marker));
|
|
60
|
+
const child=spawn(process.execPath,[fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url)),'remote'],{env:{...process.env,DEADBYTE_STATE_ROOT:root},windowsHide:true,stdio:['pipe','pipe','pipe']});
|
|
61
|
+
let stdout='',stderr='';child.stdout.on('data',b=>{stdout+=b;});child.stderr.on('data',b=>{stderr+=b;});
|
|
62
|
+
const code=await new Promise(resolve=>child.once('close',resolve));
|
|
63
|
+
await writeFile(path.join(root,'observation.json'),JSON.stringify({code,stdout,stderr,simulation:'birth identity drift at a live unrelated numeric PID'}),{flag:'wx'});
|
|
64
|
+
assert.match(stdout,/Stale remote session/);
|
|
65
|
+
assert.doesNotMatch(stderr,/already active|ownership unverified/);
|
|
66
|
+
assert.deepEqual((await readFile(trace,'utf8')).trim().split(/\r?\n/).slice(0,6),['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park']);
|
|
67
|
+
assert.match(stderr,/without a verified ONLINE runtime/,'fixture deliberately has no real runtime; recovery must already have converged');
|
|
68
|
+
await assert.rejects(readFile(markerPath),{code:'ENOENT'});
|
|
69
|
+
assert.equal(foregroundAlive(),true,'unrelated live process must not be killed');
|
|
70
|
+
function foregroundAlive(){try{process.kill(process.pid,0);return true;}catch{return false;}}
|
|
71
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
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 currentPackage=JSON.parse(await read('package.json'));
|
|
7
|
+
|
|
8
|
+
test('R58 canonical Windows gate is final-candidate bound and leaves historical gates immutable',{skip:currentPackage.version!=='0.15.35'},async()=>{
|
|
9
|
+
const [pkgText,gate,evidence,r57Gate,r57Evidence]=await Promise.all([
|
|
10
|
+
read('package.json'),read('scripts/gate-windows-r58.ps1'),read('scripts/windows-gate-evidence-r58.mjs'),
|
|
11
|
+
read('scripts/gate-windows-r57.ps1'),read('scripts/windows-gate-evidence-r57.mjs')
|
|
12
|
+
]);
|
|
13
|
+
const pkg=JSON.parse(pkgText);
|
|
14
|
+
assert.match(pkg.scripts['gate:windows'],/gate-windows-r58\.ps1$/);
|
|
15
|
+
assert.match(pkg.scripts['r58:focused:test'],/scripts\/r58-focused\.ps1$/);
|
|
16
|
+
assert.match(pkg.scripts['r58:cross-plane:test'],/scripts\/r58-cross-plane\.ps1$/);
|
|
17
|
+
assert.match(gate,/DEADBYTE 0\.15\.35 \/ R58 WINDOWS GATE: PASS/);
|
|
18
|
+
assert.match(gate,/r58-package-boundary/);
|
|
19
|
+
assert.match(gate,/r58-process-policy/);
|
|
20
|
+
assert.match(gate,/r58-focused-contracts/);
|
|
21
|
+
assert.match(gate,/full_regression\.status -ne 'PASS'/);
|
|
22
|
+
assert.match(evidence,/deadbyte\.r58-windows-gate\.v1/);
|
|
23
|
+
assert.match(evidence,/pkg\.version==='0\.15\.35'/);
|
|
24
|
+
assert.match(evidence,/release\.train==='R58'/);
|
|
25
|
+
assert.match(evidence,/release\.parent\?\.version==='0\.15\.34'/);
|
|
26
|
+
assert.match(evidence,/health\.assessment!==['"]FAIL['"]/);
|
|
27
|
+
assert.match(evidence,/\['ok','warn'\]\.includes\(health\.status\)/);
|
|
28
|
+
assert.match(evidence,/four_hour_soak_completed:false/);
|
|
29
|
+
assert.match(evidence,/clean_machine_completed:false/);
|
|
30
|
+
assert.match(r57Gate,/DEADBYTE 0\.15\.34 \/ R57 WINDOWS GATE: PASS/);
|
|
31
|
+
assert.match(r57Evidence,/deadbyte\.r57-windows-gate\.v1/);
|
|
32
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
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(JSON.parse);
|
|
7
|
+
|
|
8
|
+
const PARENT={
|
|
9
|
+
version:'0.15.35',
|
|
10
|
+
release_id:'v0.15.35-1b5715b276facbc0',
|
|
11
|
+
manifest_sha256:'1b5715b276facbc00f8e341a554472cb4829e5af60e221c357c41b66053af9d4',
|
|
12
|
+
generation_manifest_sha256:'047e73b62d7b7819b84201a221a09b7e647cacf21f82101ebed4fbd7e23fc430',
|
|
13
|
+
tarball_sha256:'8259e30946d28dd138c1e2ce2f483fe1370d1658544f2adbfebafa4db8875bba'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
test('R59 identity is coherent and binds exact accepted 0.15.35 predecessor',async()=>{
|
|
17
|
+
const [pkg,lock,release,prov,matrix,sbom,version,bridgePkg]=await Promise.all([
|
|
18
|
+
json('package.json'),json('package-lock.json'),json('RELEASE.json'),json('PROVENANCE.json'),
|
|
19
|
+
json('TEST-MATRIX.json'),json('SBOM.cdx.json'),read('src/version.mjs'),json('bridge/package.json')
|
|
20
|
+
]);
|
|
21
|
+
assert.equal(pkg.version,'0.15.36');
|
|
22
|
+
assert.equal(lock.version,pkg.version);
|
|
23
|
+
assert.equal(lock.packages[''].version,pkg.version);
|
|
24
|
+
assert.match(version,/DEADBYTE_VERSION = '0\.15\.36'/);
|
|
25
|
+
assert.equal(bridgePkg.version,pkg.version);
|
|
26
|
+
assert.equal(release.version,pkg.version);
|
|
27
|
+
assert.equal(release.train,'R59');
|
|
28
|
+
assert.equal(prov.version,pkg.version);
|
|
29
|
+
assert.equal(matrix.version,pkg.version);
|
|
30
|
+
assert.equal(matrix.train,'R59');
|
|
31
|
+
assert.equal(sbom.metadata.component.version,pkg.version);
|
|
32
|
+
assert.equal(release.parent.version,PARENT.version);
|
|
33
|
+
assert.equal(release.parent.release_id,PARENT.release_id);
|
|
34
|
+
assert.equal(release.parent.manifest_sha256,PARENT.manifest_sha256);
|
|
35
|
+
assert.equal(release.parent.generation_manifest_sha256,PARENT.generation_manifest_sha256);
|
|
36
|
+
assert.equal(release.parent.tarball_sha256,PARENT.tarball_sha256);
|
|
37
|
+
assert.equal(release.runtime_predecessor.release_id,PARENT.release_id);
|
|
38
|
+
assert.equal(release.runtime_predecessor.manifest_sha256,PARENT.manifest_sha256);
|
|
39
|
+
assert.equal(release.runtime_predecessor.generation_manifest_sha256,PARENT.generation_manifest_sha256);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('R59 release records runtime hardening without expanding public authority',async()=>{
|
|
43
|
+
const [release,prov,matrix]=await Promise.all([json('RELEASE.json'),json('PROVENANCE.json'),json('TEST-MATRIX.json')]);
|
|
44
|
+
assert.equal(release.r59_runtime_hardening.optional_plane_degradation,true);
|
|
45
|
+
assert.equal(release.r59_runtime_hardening.capability_manifest_v3,true);
|
|
46
|
+
assert.equal(release.r59_runtime_hardening.live_overlay_authority_ceiling,true);
|
|
47
|
+
assert.equal(release.r59_runtime_hardening.bridge_bounded_reconciliation,true);
|
|
48
|
+
assert.equal(release.r59_runtime_hardening.mutation_unknown_outcome_no_replay,true);
|
|
49
|
+
assert.equal(release.r59_runtime_hardening.controller_policy_root_preflight,true);
|
|
50
|
+
assert.equal(release.r59_runtime_hardening.public_surface_compatibility,'PASS_43_TOOLS_NO_SCHEMA_DRIFT');
|
|
51
|
+
assert.equal(release.r59_runtime_hardening.frontdoor_signed_ping,'PASS');
|
|
52
|
+
assert.equal(matrix.observed_pre_release_evidence.r59_contracts.status,'PASS');
|
|
53
|
+
assert.equal(matrix.observed_pre_release_evidence.r59_contracts.tests,19);
|
|
54
|
+
assert.equal(matrix.observed_pre_release_evidence.r59_public_surface.status,'PASS');
|
|
55
|
+
assert.equal(matrix.observed_pre_release_evidence.r59_public_surface.tool_count,43);
|
|
56
|
+
assert.equal(prov.operator_override.four_hour_soak,'NOT_COMPLETED');
|
|
57
|
+
assert.equal(prov.operator_override.clean_machine,'NOT_COMPLETED');
|
|
58
|
+
assert.equal(prov.operator_override.stable_final_claim,false);
|
|
59
|
+
assert.equal(matrix.operator_override.stable_final_claim,false);
|
|
60
|
+
assert.equal(release.stabilization.stable_final_claim,false);
|
|
61
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { Client } from '@modelcontextprotocol/client';
|
|
8
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
|
|
9
|
+
import { createR34Fixture } from './helpers/autonomous-r34-fixture.mjs';
|
|
10
|
+
import { snapshotToolsListResult, diffToolSurface } from '../src/tool-surface-ledger.mjs';
|
|
11
|
+
|
|
12
|
+
const here=path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const repoRoot=path.resolve(here,'..');
|
|
14
|
+
const fixturePath=path.join(here,'fixtures','r59-compact-surface-v0.15.35.json');
|
|
15
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
16
|
+
const absent=async p=>{try{await access(p);return false;}catch(e){if(e?.code==='ENOENT')return true;throw e;}};
|
|
17
|
+
|
|
18
|
+
async function captureCurrentSurface(){
|
|
19
|
+
const f=await createR34Fixture({authority:'disarmed'});
|
|
20
|
+
const processPolicyPath=path.join(f.root,'process-policy.json');
|
|
21
|
+
const processGrant=path.join(f.root,'process-grant.json');
|
|
22
|
+
const processState=path.join(f.root,'process-state');
|
|
23
|
+
const hostPolicyPath=path.join(f.root,'host-policy.json');
|
|
24
|
+
const hostArm=path.join(f.root,'host-arm.json');
|
|
25
|
+
const desktopPolicyPath=path.join(f.root,'desktop-policy.json');
|
|
26
|
+
const desktopGrant=path.join(f.root,'desktop-grant.json');
|
|
27
|
+
const nodeSha=sha(await readFile(process.execPath));
|
|
28
|
+
await mkdir(processState,{recursive:true});
|
|
29
|
+
await writeFile(processPolicyPath,JSON.stringify({
|
|
30
|
+
schema:'deadbyte.process-policy.v1',enabled:true,grant_file:processGrant,state_dir:processState,
|
|
31
|
+
native_host:{path:process.execPath,sha256:nodeSha},roots:{core:{path:f.project,deny_paths:[]}},
|
|
32
|
+
profiles:{node:{executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[],
|
|
33
|
+
transport:'pipe',max_args:8,max_arg_bytes:1024,max_sessions:2,max_input_bytes:65536,
|
|
34
|
+
max_retained_output_bytes:262144,env_allow:['PATH','TEMP','TMP'],input_actions:{ping:'PING\\r\\n'}}}
|
|
35
|
+
},null,2));
|
|
36
|
+
await writeFile(hostPolicyPath,JSON.stringify({
|
|
37
|
+
schema:'deadbyte.host-policy.v1',enabled:true,arm_file:hostArm,max_read_bytes:262144,max_write_bytes:1048576,
|
|
38
|
+
roots:{core:{path:f.project,read:true,write:true,exec:true}},commands:{node_script:{kind:'script',
|
|
39
|
+
executable:process.execPath,executable_sha256:nodeSha,root_ids:['core'],prefix_args:[],extensions:['.mjs'],
|
|
40
|
+
timeout_ms:5000,max_output_bytes:65536}}
|
|
41
|
+
},null,2));
|
|
42
|
+
const desktopTemplate=JSON.parse(await readFile(path.join(repoRoot,'controller','deadbyte-desktop-policy.json'),'utf8'));
|
|
43
|
+
desktopTemplate.grant_file=desktopGrant;
|
|
44
|
+
desktopTemplate.evidence_dir=path.join(f.root,'desktop-evidence');
|
|
45
|
+
await writeFile(desktopPolicyPath,JSON.stringify(desktopTemplate,null,2));
|
|
46
|
+
|
|
47
|
+
const authorityFiles=[processGrant,hostArm,desktopGrant,f.codingPolicy.lease_file,f.policy.lease_file];
|
|
48
|
+
for(const p of authorityFiles) assert.equal(await absent(p),true,'authority unexpectedly armed: '+p);
|
|
49
|
+
|
|
50
|
+
const client=new Client({name:'deadbyte-r59-surface-compat',version:'0.15.35'},{versionNegotiation:{mode:{pin:'2026-07-28'}}});
|
|
51
|
+
const env={...getDefaultEnvironment(),DEADBYTE_CAPABILITY_PROFILE:'compact',DEADBYTE_AUDIT_RUNTIME:'0',
|
|
52
|
+
DEADBYTE_STATE_ROOT:path.join(f.root,'state'),DEADBYTE_WORKSPACE_ROOT:f.root,
|
|
53
|
+
DEADBYTE_SIGNING_KEY:f.keys.privateKeyPath,DEADBYTE_VERIFY_KEY:f.keys.publicKeyPath,
|
|
54
|
+
DEADBYTE_PRIVILEGED_POLICY:hostPolicyPath,DEADBYTE_MACHINE_RUNTIME:'1',DEADBYTE_AGENT_RUNTIME:'0',
|
|
55
|
+
DEADBYTE_PROCESS_RUNTIME:'1',DEADBYTE_PROCESS_POLICY:processPolicyPath,DEADBYTE_CODING_RUNTIME:'1',
|
|
56
|
+
DEADBYTE_CODING_POLICY:path.join(f.root,'coding-policy.json'),DEADBYTE_MACHINE_FS_RUNTIME:'1',
|
|
57
|
+
DEADBYTE_OBSERVATION_RUNTIME:'1',DEADBYTE_DESKTOP_RUNTIME:'1',DEADBYTE_DESKTOP_POLICY:desktopPolicyPath,
|
|
58
|
+
DEADBYTE_AUTONOMOUS_RUNTIME:'1',DEADBYTE_AUTONOMOUS_POLICY:path.join(f.root,'autonomous-policy.json'),
|
|
59
|
+
DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE:'external'};
|
|
60
|
+
const transport=new StdioClientTransport({command:process.execPath,args:[path.join(repoRoot,'src','mcp-server.mjs')],
|
|
61
|
+
cwd:repoRoot,env,stderr:'pipe'});
|
|
62
|
+
try{
|
|
63
|
+
await client.connect(transport);
|
|
64
|
+
const listed=await client.listTools();
|
|
65
|
+
assert.equal(listed.tools.length,43,'candidate expected compact 43-tool surface');
|
|
66
|
+
return snapshotToolsListResult(listed);
|
|
67
|
+
} finally {
|
|
68
|
+
await client.close().catch(()=>{});
|
|
69
|
+
for(const p of authorityFiles) assert.equal(await absent(p),true,'authority file appeared: '+p);
|
|
70
|
+
await f.cleanup();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
test('R59 compact client-visible tool names and schemas match sealed 0.15.35 baseline',async()=>{
|
|
75
|
+
const raw=JSON.parse(await readFile(fixturePath,'utf8'));
|
|
76
|
+
assert.equal(raw.schema,'deadbyte.tool-surface-baseline.v1');
|
|
77
|
+
assert.equal(raw.release_id,'v0.15.35-1b5715b276facbc0');
|
|
78
|
+
assert.match(raw.surface_sha256,/^[0-9a-f]{64}$/);
|
|
79
|
+
assert.equal(raw.tools.length,43);
|
|
80
|
+
const baseline={schema:'deadbyte.tool-surface.v1',tools:raw.tools,surface_sha256:raw.surface_sha256};
|
|
81
|
+
const current=await captureCurrentSurface();
|
|
82
|
+
const diff=diffToolSurface(baseline,current);
|
|
83
|
+
assert.deepEqual(diff.added,[]);
|
|
84
|
+
assert.deepEqual(diff.removed,[]);
|
|
85
|
+
assert.deepEqual(diff.schema_changed,[]);
|
|
86
|
+
assert.equal(diff.unchanged.length,43);
|
|
87
|
+
assert.equal(current.surface_sha256,baseline.surface_sha256);
|
|
88
|
+
console.log(JSON.stringify({baseline_release:raw.release_id,surface_sha256:current.surface_sha256,
|
|
89
|
+
unchanged:diff.unchanged.length,added:diff.added,removed:diff.removed,schema_changed:diff.schema_changed}));
|
|
90
|
+
});
|
|
@@ -35,7 +35,7 @@ test('R27 generation bundle rebinds authority and mutable state outside the rele
|
|
|
35
35
|
await writeFile(semanticConfigPath,semanticConfigBytes);
|
|
36
36
|
const currentConfig={AgentRuntimeEnabled:true,ProcessRuntimeEnabled:true,CodingRuntimeEnabled:true,MachineFsRuntimeEnabled:true,ObservationRuntimeEnabled:true,DesktopRuntimeEnabled:true,AutonomousRuntimeEnabled:true,AutonomousSupervisorEnabled:true,SemanticLspConfigFile:semanticConfigPath,SemanticLspConfigSha256:sha(semanticConfigBytes),
|
|
37
37
|
Supervisor:{FilePath:'C:/node.exe',FileSha256:EXE,Arguments:'src\\autonomous-supervisor-host.mjs',WorkingDirectory:'C:/release',ScriptPath:'C:/release/src/autonomous-supervisor-host.mjs',ScriptSha256:'2'.repeat(64)},
|
|
38
|
-
Bridge:{FilePath:'C:/node.exe',FileSha256:EXE,Arguments:'bridge.mjs',WorkingDirectory:'C:/bridge',ScriptPath:'C:/bridge/bridge.mjs',ScriptSha256:EXE},
|
|
38
|
+
Bridge:{FilePath:'C:/node.exe',FileSha256:EXE,Arguments:'bridge.mjs',WorkingDirectory:'C:/bridge',ScriptPath:'C:/bridge/bridge.mjs',ScriptSha256:EXE,Dependencies:[{Name:'bridge-validator',Path:'C:/bridge/public-tool-definitions.mjs',Sha256:EXE}]},
|
|
39
39
|
Cloud:{FilePath:'C:/deadbyte-tunnel-host.exe',FileSha256:EXE,Arguments:'"C:/cloudflared.exe" "C:/secret/tunnel-token.txt" "C:/bridge"',WorkingDirectory:'C:/release',Transport:'deadbyte.named-tunnel.v2',Dependencies:[
|
|
40
40
|
{Name:'cloudflared',Path:'C:/cloudflared.exe',Sha256:EXE},{Name:'tunnel-token',Path:'C:/secret/tunnel-token.txt',Sha256:EXE}
|
|
41
41
|
]},
|
|
@@ -70,6 +70,9 @@ test('R27 generation bundle rebinds authority and mutable state outside the rele
|
|
|
70
70
|
const build=args=>createGenerationBundle({...args,systemRoot});
|
|
71
71
|
const result=await build({stateRoot:state,releaseRoot:release,developmentRoot:development,workspaceRoot:workspace,releaseId:'v0.8.3-aaaaaaaaaaaaaaaa',currentConfig,codingPolicy:coding,autonomousPolicy:autonomous,hostPolicy:host,processPolicy,desktopPolicy});
|
|
72
72
|
const verified=await verifyGenerationBundle(result.generation_root,result.generation_manifest_sha256);
|
|
73
|
+
const bridgeConfig=await readFile(result.config_path,'utf8');
|
|
74
|
+
const bridgeBlock=bridgeConfig.slice(bridgeConfig.indexOf(' Bridge = @{'),bridgeConfig.indexOf(' Cloud = @{'));
|
|
75
|
+
assert.match(bridgeBlock,/Name = 'bridge-validator'; Path = 'C:\/bridge\/public-tool-definitions.mjs'; Sha256 = '1{64}'/,'generation must not discard explicitly pinned Bridge dependencies');
|
|
73
76
|
assert.equal(verified.files,7);
|
|
74
77
|
const generatedProcess=JSON.parse(await readFile(result.process_policy_path,'utf8'));
|
|
75
78
|
assert.equal(generatedProcess.grant_file,path.join(path.resolve(state),'runtime','process-grant.json'));
|
|
@@ -401,7 +401,60 @@ winTest('remote warning lines use deterministic ASCII fallback on piped Windows
|
|
|
401
401
|
}
|
|
402
402
|
});
|
|
403
403
|
|
|
404
|
-
winTest('
|
|
404
|
+
winTest('R58 concurrent cold remote claims have one winner and no loser cleanup',async()=>{
|
|
405
|
+
const fx=await fixture({statusOnline:true});const children=[],outputs=[];
|
|
406
|
+
try{
|
|
407
|
+
for(let i=0;i<3;i++){
|
|
408
|
+
const child=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
409
|
+
children.push(child);outputs.push(await collect(child));
|
|
410
|
+
}
|
|
411
|
+
const commands=await waitFor(fx.trace,items=>items.includes('hoststatus'));
|
|
412
|
+
assert.ok(commands.includes('hoststatus'),outputs.map(o=>o.stderr).join('\n'));
|
|
413
|
+
const deadline=Date.now()+5000;
|
|
414
|
+
while(Date.now()<deadline&&children.filter(c=>c.exitCode===null).length>1)await sleep(50);
|
|
415
|
+
const live=children.filter(c=>c.exitCode===null);
|
|
416
|
+
assert.equal(live.length,1,'exactly one verified foreground may survive');
|
|
417
|
+
const trace=await traceCommands(fx.trace);
|
|
418
|
+
for(const arm of ['armprocess','armcoding','armautonomous'])assert.equal(trace.filter(x=>x===arm).length,1);
|
|
419
|
+
assert.ok(!trace.some(x=>x.startsWith('disarm')||x==='park'),'losers must not revoke winner authority');
|
|
420
|
+
for(let i=0;i<children.length;i++)if(children[i]!==live[0])assert.match(outputs[i].stderr,/remote session already (claimed|active)/,'concurrent rejection must have deterministic ownership vocabulary');
|
|
421
|
+
const marker=JSON.parse(await readFile(path.join(fx.state,'runtime','remote-session.json'),'utf8'));
|
|
422
|
+
assert.equal(marker.owner_pid,live[0].pid);
|
|
423
|
+
live[0].stdin.end();assert.equal((await outputs[children.indexOf(live[0])].closed).code,0);
|
|
424
|
+
}finally{
|
|
425
|
+
for(const child of children)if(child.exitCode===null)child.kill();
|
|
426
|
+
await rm(fx.dir,{recursive:true,force:true});
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
winTest('R58 duplicate remote verifies the real live owner and leaves its authority untouched',async()=>{
|
|
431
|
+
const fx=await fixture({statusOnline:true});let first=null,second=null;
|
|
432
|
+
try{
|
|
433
|
+
first=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
434
|
+
const firstOutput=await collect(first);
|
|
435
|
+
const commands=await waitFor(fx.trace,items=>items.includes('hoststatus'));
|
|
436
|
+
assert.ok(commands.includes('hoststatus'),firstOutput.stderr);
|
|
437
|
+
const before=await traceCommands(fx.trace);
|
|
438
|
+
const markerPath=path.join(fx.state,'runtime','remote-session.json');
|
|
439
|
+
const marker=JSON.parse(await readFile(markerPath,'utf8'));
|
|
440
|
+
assert.equal(marker.owner_identity.pid,first.pid);
|
|
441
|
+
assert.equal(marker.guardian_identity.parent_pid,first.pid);
|
|
442
|
+
second=spawn(process.execPath,[cliPath,'remote'],{env:fx.env,stdio:['pipe','pipe','pipe'],windowsHide:true});
|
|
443
|
+
const secondOutput=await collect(second);
|
|
444
|
+
const stopped=await secondOutput.closed;
|
|
445
|
+
assert.notEqual(stopped.code,0);
|
|
446
|
+
assert.match(secondOutput.stderr,/remote session already active/);
|
|
447
|
+
assert.deepEqual(await traceCommands(fx.trace),before,'duplicate must not run any controller operation');
|
|
448
|
+
assert.deepEqual(JSON.parse(await readFile(markerPath,'utf8')),marker);
|
|
449
|
+
assert.equal(first.exitCode,null,'verified legitimate foreground must survive');
|
|
450
|
+
first.stdin.end();assert.equal((await firstOutput.closed).code,0,firstOutput.stderr);
|
|
451
|
+
}finally{
|
|
452
|
+
if(first?.exitCode===null)first.kill();if(second?.exitCode===null)second.kill();
|
|
453
|
+
await rm(fx.dir,{recursive:true,force:true});
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
winTest('bare remote refuses unverified legacy ownership without touching authority',async()=>{
|
|
405
458
|
const fx=await fixture();
|
|
406
459
|
let child=null;
|
|
407
460
|
try{
|
|
@@ -416,7 +469,7 @@ winTest('bare remote refuses a second live foreground session without touching a
|
|
|
416
469
|
assert.equal(ended.timeout,undefined);
|
|
417
470
|
assert.notEqual(ended.code,0);
|
|
418
471
|
assert.deepEqual(await traceCommands(fx.trace),[]);
|
|
419
|
-
assert.match(output.stderr,/remote session
|
|
472
|
+
assert.match(output.stderr,/remote session ownership unverified/i);
|
|
420
473
|
}finally{
|
|
421
474
|
if(child?.exitCode===null) child.kill();
|
|
422
475
|
await rm(fx.dir,{recursive:true,force:true});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createRuntimePlaneRegistry, createUnavailableRuntime } from '../src/runtime-plane-registry.mjs';
|
|
4
|
+
|
|
5
|
+
test('R59 optional plane failure is isolated and reported as DEGRADED', async () => {
|
|
6
|
+
const registry=createRuntimePlaneRegistry();
|
|
7
|
+
registry.define('coding',{configured:true,required:false,dependencies:[]});
|
|
8
|
+
const value=await registry.initialize('coding',async()=>{throw new Error('missing coding root');});
|
|
9
|
+
assert.equal(value,null);
|
|
10
|
+
const state=registry.state('coding');
|
|
11
|
+
assert.equal(state.state,'DEGRADED');
|
|
12
|
+
assert.equal(state.ready,false);
|
|
13
|
+
assert.equal(state.diagnostic.code,'INITIALIZATION_FAILED');
|
|
14
|
+
assert.match(state.diagnostic.reason,/missing coding root/);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('R59 dependent plane becomes unavailable without invoking its initializer', async () => {
|
|
18
|
+
const registry=createRuntimePlaneRegistry();
|
|
19
|
+
registry.define('coding',{configured:true,required:false,dependencies:[]});
|
|
20
|
+
registry.define('autonomous',{configured:true,required:false,dependencies:['coding']});
|
|
21
|
+
await registry.initialize('coding',async()=>{throw new Error('coding down');});
|
|
22
|
+
let called=false;
|
|
23
|
+
const value=await registry.initialize('autonomous',async()=>{called=true;return {};});
|
|
24
|
+
assert.equal(value,null);
|
|
25
|
+
assert.equal(called,false);
|
|
26
|
+
assert.equal(registry.state('autonomous').state,'UNAVAILABLE');
|
|
27
|
+
assert.equal(registry.state('autonomous').diagnostic.code,'DEPENDENCY_UNAVAILABLE');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('R59 mandatory plane failure remains fail closed', async () => {
|
|
31
|
+
const registry=createRuntimePlaneRegistry();
|
|
32
|
+
registry.define('machine',{configured:true,required:true,dependencies:[]});
|
|
33
|
+
await assert.rejects(registry.initialize('machine',async()=>{throw new Error('identity invalid');}),/identity invalid/);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('R59 unavailable runtime never performs a delegated operation', async () => {
|
|
37
|
+
const runtime=createUnavailableRuntime('coding',{code:'INITIALIZATION_FAILED',reason:'missing root'});
|
|
38
|
+
await assert.rejects(runtime.fileRead({}),error=>error.code==='DEADBYTE_RUNTIME_UNAVAILABLE'&&/missing root/.test(error.message));
|
|
39
|
+
});
|
|
40
|
+
|