deadbyte-mcp 0.15.35 → 0.15.37
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 +91 -46
- package/PROVENANCE.json +76 -30
- package/README.txt +62 -8
- package/RELEASE.json +78 -23
- package/SBOM.cdx.json +10 -25
- package/TEST-MATRIX.json +109 -44
- package/bridge/package.json +4 -2
- package/bridge/src/bridge-health-r59.mjs +53 -0
- package/bridge/src/bridge.mjs +158 -26
- package/bridge/src/canonical-json-r60.mjs +13 -0
- package/bridge/src/r40-governor.mjs +56 -10
- package/bridge/src/reconnect-delay-r60.mjs +31 -0
- package/bridge/src/remote-call-claims-r60.mjs +157 -0
- package/bridge/src/remote-dispatch-r60.mjs +75 -0
- package/controller/deadbyte-controller.ps1 +76 -13
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R59-PROVENANCE.json +87 -0
- package/docs/R59-RELEASE.json +187 -0
- package/docs/R59-SBOM.cdx.json +68 -0
- package/docs/R59-TASK-CONTRACT.md +26 -0
- package/docs/R59-TEST-MATRIX.json +258 -0
- package/docs/R59-bridge-package.json +13 -0
- package/docs/R59-package-lock.json +184 -0
- package/docs/R59-package.json +106 -0
- package/docs/R59-version.mjs +6 -0
- package/docs/R60-TASK-CONTRACT.md +22 -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/full-regression-r60.mjs +32 -0
- package/scripts/gate-windows-r59-prepublish.ps1 +41 -0
- package/scripts/gate-windows-r60-prepublish.ps1 +74 -0
- package/scripts/r59-full-regression-capture.mjs +35 -0
- package/src/capability-resolver.mjs +25 -7
- package/src/capability-snapshot.mjs +56 -0
- package/src/compact-gateway-mcp-tools.mjs +10 -11
- package/src/deadbyte-cli.mjs +80 -34
- 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/remote-authority.mjs +56 -0
- package/src/remote-enforcement-probe-r60.mjs +17 -0
- package/src/remote-live-log.mjs +19 -2
- package/src/remote-log-decode-r60.mjs +32 -0
- package/src/remote-mcp-machine-smoke-client.mjs +19 -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 +20 -3
- package/src/runtime-plane-registry.mjs +74 -0
- package/src/tool-audit.mjs +5 -0
- 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/fixtures/r59-compact-surface-v0.15.35.json +222 -0
- package/test/observation-integration.test.mjs +2 -3
- package/test/optional-plane-degraded-r59.test.mjs +44 -0
- package/test/r48-release-contract.test.mjs +1 -1
- package/test/r49-windows-gate.test.mjs +1 -1
- package/test/r51-runtime-coherence.test.mjs +2 -2
- package/test/r58-bundled-bridge.test.mjs +3 -1
- package/test/r58-cleanup-retention.test.mjs +9 -3
- package/test/r58-concurrent-recovery.test.mjs +9 -3
- package/test/r58-controller-live-local.test.mjs +36 -4
- package/test/r58-controller-state-coherence.test.mjs +1 -1
- package/test/r58-release-contract.test.mjs +3 -2
- package/test/r58-stale-owner.test.mjs +2 -2
- package/test/r58-windows-gate.test.mjs +2 -1
- package/test/r59-release-contract.test.mjs +61 -0
- package/test/r59-tool-surface-compat.test.mjs +90 -0
- package/test/r60-authority-coherence.test.mjs +36 -0
- package/test/r60-authority-enforcement.test.mjs +28 -0
- package/test/r60-bridge-dispatch.test.mjs +119 -0
- package/test/r60-clock-discipline.test.mjs +15 -0
- package/test/r60-live-timeline.test.mjs +29 -0
- package/test/r60-reconnect-errors.test.mjs +21 -0
- package/test/r60-reconnect-jitter.test.mjs +18 -0
- package/test/r60-release-contract.test.mjs +81 -0
- package/test/r60-remote-call-recovery.test.mjs +170 -0
- package/test/r60-windows-log-decode.test.mjs +25 -0
- package/test/remote-live-log-r37.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +200 -43
- package/test/runtime-plane-registry-r59.test.mjs +40 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export function safeRemoteLogPrefixLength(bytes,encoding='utf8'){
|
|
4
|
+
if(!Buffer.isBuffer(bytes))throw new Error('remote log prefix bytes invalid');
|
|
5
|
+
if(encoding==='utf16le'){
|
|
6
|
+
let size=bytes.length-(bytes.length%2);
|
|
7
|
+
if(size>=2){const last=bytes.readUInt16LE(size-2);if(last>=0xd800&&last<=0xdbff)size-=2;}
|
|
8
|
+
return size;
|
|
9
|
+
}
|
|
10
|
+
if(encoding!=='utf8')throw new Error('remote log prefix encoding invalid');
|
|
11
|
+
let index=bytes.length-1;
|
|
12
|
+
while(index>=0&&(bytes[index]&0xc0)===0x80)index--;
|
|
13
|
+
if(index<0)return 0;
|
|
14
|
+
const lead=bytes[index];
|
|
15
|
+
const needed=lead<0x80?1:lead>=0xc2&&lead<=0xdf?2:lead>=0xe0&&lead<=0xef?3:lead>=0xf0&&lead<=0xf4?4:1;
|
|
16
|
+
return bytes.length-index<needed?index:bytes.length;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function decodeRemoteLogBytes(file,bytes,{encoding=null}={}){
|
|
20
|
+
if(typeof file!=='string'||!Buffer.isBuffer(bytes))throw new Error('remote log decoder input invalid');
|
|
21
|
+
const knownLog=/\.log$/i.test(path.basename(file));
|
|
22
|
+
if(!knownLog)return bytes.toString('utf8');
|
|
23
|
+
const utf16=encoding==='utf16le'||(bytes.length>=2&&bytes[0]===0xff&&bytes[1]===0xfe);
|
|
24
|
+
if(!utf16)return bytes.toString('utf8').replace(/^\uFEFF/,'');
|
|
25
|
+
return bytes.toString('utf16le').replace(/^\uFEFF/,'');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function boundedRemoteDiagnostic(value,maxChars=2048){
|
|
29
|
+
if(!Number.isInteger(maxChars)||maxChars<32||maxChars>8192)throw new Error('remote diagnostic bound invalid');
|
|
30
|
+
const line=String(value??'').replace(/\x1b\[[0-9;]*[A-Za-z]/g,'').replace(/[\x00-\x1f\x7f]+/g,' ').replace(/\s{2,}/g,' ').trim();
|
|
31
|
+
return line.length<=maxChars?line:`${line.slice(0,maxChars)}...`;
|
|
32
|
+
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
|
|
3
5
|
import { verifyMachinePingPublic } from './machine-identity.mjs';
|
|
6
|
+
import { parseActivePointer } from './release-slot.mjs';
|
|
7
|
+
import { probeRemoteEnforcement } from './remote-enforcement-probe-r60.mjs';
|
|
4
8
|
|
|
5
9
|
const endpointText = process.argv[2];
|
|
6
10
|
if (!endpointText) throw new Error('usage: node src/remote-mcp-machine-smoke-client.mjs https://host/mcp/path');
|
|
7
11
|
const endpoint = new URL(endpointText);
|
|
8
12
|
if (endpoint.protocol !== 'https:') throw new Error('machine live gate requires HTTPS');
|
|
13
|
+
const enforcementMode=process.argv[3]??null;
|
|
14
|
+
if(enforcementMode!==null&&!['default','desktop','host','full','revoked'].includes(enforcementMode))throw new Error('remote enforcement probe mode invalid');
|
|
9
15
|
|
|
10
16
|
const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
|
|
11
17
|
const client = new Client(
|
|
@@ -49,6 +55,17 @@ try {
|
|
|
49
55
|
}
|
|
50
56
|
const signatureVerified = await verifyMachinePingPublic({ ping, publicKeySpkiBase64: status.public_key_spki_base64 });
|
|
51
57
|
if (!signatureVerified) throw new Error('machine ping public Ed25519 verification failed');
|
|
58
|
+
if(enforcementMode!==null){
|
|
59
|
+
const stateRoot=process.env.DEADBYTE_STATE_ROOT;
|
|
60
|
+
if(!stateRoot||!path.isAbsolute(stateRoot))throw new Error('remote enforcement probe state root missing');
|
|
61
|
+
const pointer=parseActivePointer(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
62
|
+
for(const [actual,expected] of [
|
|
63
|
+
[status.release_id,pointer.release_id],
|
|
64
|
+
[status.release_manifest_sha256,pointer.manifest_sha256],
|
|
65
|
+
[status.generation_manifest_sha256,pointer.generation_manifest_sha256]
|
|
66
|
+
])if(actual!==expected)throw new Error('signed machine status does not bind active release/generation');
|
|
67
|
+
await probeRemoteEnforcement({client,mode:enforcementMode});
|
|
68
|
+
}
|
|
52
69
|
|
|
53
70
|
console.log(JSON.stringify({
|
|
54
71
|
status: 'passed',
|
|
@@ -59,7 +76,8 @@ try {
|
|
|
59
76
|
release_id: status.release_id,
|
|
60
77
|
release_manifest_sha256: status.release_manifest_sha256,
|
|
61
78
|
generation_manifest_sha256: status.generation_manifest_sha256,
|
|
62
|
-
signature_verified: true
|
|
79
|
+
signature_verified: true,
|
|
80
|
+
...(enforcementMode?{enforcement_mode:enforcementMode,enforcement_verified:true}:{})
|
|
63
81
|
}));
|
|
64
82
|
} finally {
|
|
65
83
|
await client.close().catch(() => {});
|
|
@@ -98,7 +98,7 @@ export async function runR40CompactParity(endpointText){
|
|
|
98
98
|
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
99
99
|
}
|
|
100
100
|
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
101
|
-
if(manifest.
|
|
101
|
+
if(!['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema)||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R40 compact capability manifest malformed');
|
|
102
102
|
if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
|
|
103
103
|
manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R40 compact manifest catalog binding mismatch');
|
|
104
104
|
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
@@ -98,7 +98,7 @@ export async function runR44CompactParity(endpointText){
|
|
|
98
98
|
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
99
99
|
}
|
|
100
100
|
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
101
|
-
if(manifest.
|
|
101
|
+
if(!['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema)||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R44 compact capability manifest malformed');
|
|
102
102
|
if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
|
|
103
103
|
manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R44 compact manifest catalog binding mismatch');
|
|
104
104
|
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
@@ -98,7 +98,7 @@ export async function runR45CompactParity(endpointText){
|
|
|
98
98
|
if(tool.outputSchema?.type!=='object'||tool.outputSchema?.additionalProperties!==false||!tool.outputSchema?.properties) throw new Error(`tool ${tool.name} missing strict output schema`);
|
|
99
99
|
}
|
|
100
100
|
const manifest=gateway(await client.callTool({name:'capability_manifest',arguments:{}}),'capability_manifest');
|
|
101
|
-
if(manifest.
|
|
101
|
+
if(!['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema)||manifest.profile!=='compact'||!Array.isArray(manifest.capabilities)||!/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256??''))) throw new Error('R45 compact capability manifest malformed');
|
|
102
102
|
if(manifest.tool_count!==names.length||JSON.stringify(manifest.tool_catalog)!==JSON.stringify(names)||
|
|
103
103
|
manifest.catalog_sha256!==sha256(Buffer.from(JSON.stringify(names),'utf8'))) throw new Error('R45 compact manifest catalog binding mismatch');
|
|
104
104
|
const required=manifest.capabilities.filter(item=>item?.production===true&&item?.host_only!==true);
|
|
@@ -127,7 +127,7 @@ try {
|
|
|
127
127
|
if (compactProfile) {
|
|
128
128
|
const manifest = gateway(await client.callTool({ name: 'capability_manifest', arguments: {} }), 'capability_manifest');
|
|
129
129
|
const manifestV1 = manifest.schema === 'deadbyte.capability-manifest.v1' && exactR36Compact;
|
|
130
|
-
const manifestV2 = manifest.
|
|
130
|
+
const manifestV2 = ['deadbyte.capability-manifest.v2','deadbyte.capability-manifest.v3'].includes(manifest.schema) && (exactR40Compact || exactR44Compact);
|
|
131
131
|
if ((!manifestV1 && !manifestV2) || manifest.profile !== 'compact' ||
|
|
132
132
|
!Array.isArray(manifest.capabilities) || !/^[0-9a-f]{64}$/.test(String(manifest.ledger_sha256 ?? ''))) {
|
|
133
133
|
throw new Error('compact capability manifest malformed');
|
|
@@ -6,8 +6,9 @@ import path from 'node:path';
|
|
|
6
6
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
7
7
|
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
8
8
|
import { resolveStateRoot } from './state-root.mjs';
|
|
9
|
+
import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
|
|
9
10
|
|
|
10
|
-
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'
|
|
11
|
+
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
|
|
11
12
|
// This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
|
|
12
13
|
// poll. A reused PID cannot keep a new foreground guardian alive.
|
|
13
14
|
let ownerDisconnected=false;
|
|
@@ -42,12 +43,23 @@ function markerMatches(marker,{ownerPid,nonce}){
|
|
|
42
43
|
marker?.owner_pid===ownerPid&&
|
|
43
44
|
marker?.session_nonce===nonce;
|
|
44
45
|
}
|
|
45
|
-
function invokeCleanup(powershell,launcher,command,stateRoot){
|
|
46
|
+
function invokeCleanup(powershell,launcher,command,stateRoot,probeMode=null){
|
|
46
47
|
const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{
|
|
47
|
-
stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot
|
|
48
|
+
stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot,
|
|
49
|
+
...(probeMode?{DEADBYTE_REMOTE_PROBE_MODE:probeMode}:{})}),timeout:probeMode?150000:30000
|
|
48
50
|
});
|
|
49
51
|
return !result.error&&result.status===0;
|
|
50
52
|
}
|
|
53
|
+
function verifyRevoked(powershell,launcher,stateRoot){
|
|
54
|
+
const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'status','-Json'],{
|
|
55
|
+
stdio:['ignore','pipe','pipe'],encoding:'utf8',windowsHide:true,shell:false,
|
|
56
|
+
env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
|
|
57
|
+
});
|
|
58
|
+
if(result.error||result.status!==0) return null;
|
|
59
|
+
try{
|
|
60
|
+
return assertAuthorityVector(parseAuthorityObservation(result.stdout),Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,false])));
|
|
61
|
+
}catch{return null;}
|
|
62
|
+
}
|
|
51
63
|
|
|
52
64
|
async function main(){
|
|
53
65
|
if(process.platform!=='win32') return;
|
|
@@ -82,6 +94,11 @@ async function main(){
|
|
|
82
94
|
if(!invokeCleanup(powershell,launcher,command,root)) failed.push(command);
|
|
83
95
|
}
|
|
84
96
|
if(failed.length) throw new Error('guardian cleanup failed; ownership marker retained: '+failed.join(','));
|
|
97
|
+
const observed=verifyRevoked(powershell,launcher,root);
|
|
98
|
+
if(!observed) throw new Error('guardian authority cleanup unverified; ownership marker retained');
|
|
99
|
+
if(observed.state==='ONLINE'&&!invokeCleanup(powershell,launcher,'machinetest',root,'revoked'))
|
|
100
|
+
throw new Error('guardian runtime enforcement cleanup unverified; ownership marker retained');
|
|
101
|
+
if(!invokeCleanup(powershell,launcher,'park',root)) throw new Error('guardian park failed; ownership marker retained');
|
|
85
102
|
const current=await readMarker(markerPath);
|
|
86
103
|
if(current&&markerMatches(current,{ownerPid,nonce})) await rm(markerPath,{force:true});
|
|
87
104
|
return;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const PLANE_ID=/^[a-z][a-z0-9_-]{0,63}$/;
|
|
2
|
+
|
|
3
|
+
function boundedReason(error){
|
|
4
|
+
const text=error instanceof Error?error.message:String(error);
|
|
5
|
+
return text.replace(/[\r\n\t]+/g,' ').slice(0,1024);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class RuntimePlaneUnavailableError extends Error {
|
|
9
|
+
constructor(plane,diagnostic={}){
|
|
10
|
+
super(`${plane} runtime unavailable: ${String(diagnostic.reason??diagnostic.code??'not ready')}`);
|
|
11
|
+
this.name='RuntimePlaneUnavailableError';
|
|
12
|
+
this.code='DEADBYTE_RUNTIME_UNAVAILABLE';
|
|
13
|
+
this.plane=plane;
|
|
14
|
+
this.diagnostic=diagnostic;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createUnavailableRuntime(plane,diagnostic={}){
|
|
19
|
+
if(typeof plane!=='string'||!PLANE_ID.test(plane)) throw new Error('runtime plane id invalid');
|
|
20
|
+
const fail=()=>Promise.reject(new RuntimePlaneUnavailableError(plane,diagnostic));
|
|
21
|
+
return new Proxy(Object.freeze({plane,unavailable:true,diagnostic}),{
|
|
22
|
+
get(target,property){
|
|
23
|
+
if(property in target) return target[property];
|
|
24
|
+
if(property==='then') return undefined;
|
|
25
|
+
return fail;
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createRuntimePlaneRegistry(){
|
|
31
|
+
const planes=new Map();
|
|
32
|
+
let epoch=0;
|
|
33
|
+
function update(id,next){
|
|
34
|
+
planes.set(id,Object.freeze({...next}));
|
|
35
|
+
epoch++;
|
|
36
|
+
return next.runtime??null;
|
|
37
|
+
}
|
|
38
|
+
function define(id,{configured,required=false,dependencies=[]}={}){
|
|
39
|
+
if(typeof id!=='string'||!PLANE_ID.test(id)||planes.has(id)) throw new Error(`runtime plane definition invalid: ${String(id)}`);
|
|
40
|
+
if(!Array.isArray(dependencies)||dependencies.some(dep=>typeof dep!=='string'||!PLANE_ID.test(dep))) throw new Error(`runtime plane dependencies invalid: ${id}`);
|
|
41
|
+
planes.set(id,Object.freeze({id,configured:configured===true,required:required===true,dependencies:[...new Set(dependencies)],state:configured===true?'STARTING':'DISABLED',ready:false,runtime:null,diagnostic:configured===true?null:{code:'RUNTIME_DISABLED',reason:'runtime is not configured',recoverable:false}}));
|
|
42
|
+
}
|
|
43
|
+
async function initialize(id,initializer,{policySha256=null}={}){
|
|
44
|
+
const current=planes.get(id);
|
|
45
|
+
if(!current) throw new Error(`runtime plane not defined: ${id}`);
|
|
46
|
+
if(!current.configured) return null;
|
|
47
|
+
for(const dependency of current.dependencies){
|
|
48
|
+
const dep=planes.get(dependency);
|
|
49
|
+
if(!dep||!dep.ready){
|
|
50
|
+
return update(id,{...current,state:'UNAVAILABLE',ready:false,runtime:null,policy_sha256:policySha256,
|
|
51
|
+
diagnostic:{code:'DEPENDENCY_UNAVAILABLE',reason:`dependency '${dependency}' is not ready`,recoverable:true}});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
try{
|
|
55
|
+
const runtime=await initializer();
|
|
56
|
+
if(!runtime) throw new Error('initializer returned no runtime');
|
|
57
|
+
return update(id,{...current,state:'READY',ready:true,runtime,policy_sha256:policySha256,diagnostic:null});
|
|
58
|
+
}catch(error){
|
|
59
|
+
if(current.required) throw error;
|
|
60
|
+
return update(id,{...current,state:'DEGRADED',ready:false,runtime:null,policy_sha256:policySha256,
|
|
61
|
+
diagnostic:{code:'INITIALIZATION_FAILED',reason:boundedReason(error),recoverable:true}});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function state(id){
|
|
65
|
+
const value=planes.get(id);
|
|
66
|
+
if(!value) throw new Error(`runtime plane not defined: ${id}`);
|
|
67
|
+
const {runtime,...publicState}=value;
|
|
68
|
+
return Object.freeze(publicState);
|
|
69
|
+
}
|
|
70
|
+
function runtime(id){return planes.get(id)?.runtime??null;}
|
|
71
|
+
function snapshot(){return Object.freeze({epoch,planes:Object.freeze(Object.fromEntries([...planes].map(([id])=>[id,state(id)])))});}
|
|
72
|
+
return Object.freeze({define,initialize,state,runtime,snapshot});
|
|
73
|
+
}
|
|
74
|
+
|
package/src/tool-audit.mjs
CHANGED
|
@@ -116,7 +116,12 @@ function summarizeOutcome(result,error,durationMs){
|
|
|
116
116
|
const structured=result?.structuredContent;
|
|
117
117
|
const status=typeof structured?.status==='string'?structured.status:(result?.isError===true?'error':'ok');
|
|
118
118
|
const isError=result?.isError===true;
|
|
119
|
+
const capability=structured?.result;
|
|
120
|
+
const snapshot=capability?.schema==='deadbyte.capability-manifest.v3'&&HEX64.test(String(capability.snapshot_id??''))&&
|
|
121
|
+
Number.isInteger(capability.state_epoch)&&capability.state_epoch>0
|
|
122
|
+
?{snapshot_id:capability.snapshot_id,state_epoch:capability.state_epoch}:{};
|
|
119
123
|
return {status:String(status).slice(0,128),is_error:isError,duration_ms:duration,receipt_sha256s:outcomeReceiptHashes(result),
|
|
124
|
+
...snapshot,
|
|
120
125
|
...(isError?{reason:resultOutcomeReason(result)}:{})};
|
|
121
126
|
}
|
|
122
127
|
|
package/src/version.mjs
CHANGED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createBridgeGovernor } from '../bridge/src/r40-governor.mjs';
|
|
4
|
+
import { createBridgeHealth } from '../bridge/src/bridge-health-r59.mjs';
|
|
5
|
+
|
|
6
|
+
const limits={
|
|
7
|
+
normal:{max_active:1,max_queue:2,queue_timeout_ms:1000},
|
|
8
|
+
autonomous:{max_active:1,max_queue:1,queue_timeout_ms:1000},
|
|
9
|
+
desktop_observe:{max_active:1,max_queue:1,queue_timeout_ms:1000},
|
|
10
|
+
desktop_action:{max_active:1,max_queue:1,queue_timeout_ms:1000}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
test('R59 bridge governor removes cancelled queued work and drains without replay',async()=>{
|
|
14
|
+
const governor=createBridgeGovernor({limits});
|
|
15
|
+
const first=await governor.acquire('machine_status');
|
|
16
|
+
const abort=new AbortController();
|
|
17
|
+
const queued=governor.acquire('machine_ping',{signal:abort.signal});
|
|
18
|
+
abort.abort(new Error('caller disconnected'));
|
|
19
|
+
await assert.rejects(queued,/caller disconnected|aborted/);
|
|
20
|
+
assert.equal(governor.snapshot().normal.queued,0);
|
|
21
|
+
assert.equal(governor.snapshot().normal.cancelled,1);
|
|
22
|
+
const queuedDuringRecovery=governor.acquire('machine_ping');
|
|
23
|
+
assert.equal(governor.reconcile('core generation changed'),1);
|
|
24
|
+
await assert.rejects(queuedDuringRecovery,/core generation changed/);
|
|
25
|
+
first.release();
|
|
26
|
+
const after=await governor.acquire('machine_status');
|
|
27
|
+
after.release();
|
|
28
|
+
const drain=governor.close('bridge reconciling');
|
|
29
|
+
await assert.rejects(governor.acquire('machine_status'),/closed/);
|
|
30
|
+
await drain;
|
|
31
|
+
assert.equal(governor.snapshot().normal.active,0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('R59 bridge health requires fresh child negotiation catalog and v3 capability attestation',()=>{
|
|
35
|
+
let now=100_000;
|
|
36
|
+
const health=createBridgeHealth({clock:()=>now,maxCapabilityAgeMs:15_000});
|
|
37
|
+
assert.equal(health.snapshot().http_status,503);
|
|
38
|
+
health.coreConnected({childIdentity:'core-abc',protocolEra:'modern',catalogSha256:'a'.repeat(64)});
|
|
39
|
+
assert.equal(health.snapshot().http_status,503);
|
|
40
|
+
health.capabilityObserved({schema:'deadbyte.capability-manifest.v3',snapshot_id:'b'.repeat(64),
|
|
41
|
+
state_epoch:3,observed_at_utc:new Date(now).toISOString(),valid_until_utc:new Date(now+5000).toISOString(),
|
|
42
|
+
bindings:{release_manifest_sha256:'c'.repeat(64),generation_manifest_sha256:'d'.repeat(64)}});
|
|
43
|
+
assert.equal(health.snapshot().state,'READY');
|
|
44
|
+
assert.equal(health.snapshot().http_status,200);
|
|
45
|
+
health.capabilityObserved({schema:'deadbyte.capability-manifest.v3',snapshot_id:'e'.repeat(64),state_epoch:4,
|
|
46
|
+
observed_at_utc:new Date(now).toISOString(),valid_until_utc:new Date(now+5000).toISOString(),
|
|
47
|
+
bindings:{release_manifest_sha256:'c'.repeat(64),generation_manifest_sha256:'d'.repeat(64)},
|
|
48
|
+
planes:{DEADBYTE_CODING_RUNTIME:{state:'DEGRADED',diagnostic:{code:'INITIALIZATION_FAILED'}}}});
|
|
49
|
+
assert.equal(health.snapshot().state,'DEGRADED');
|
|
50
|
+
assert.equal(health.snapshot().http_status,503);
|
|
51
|
+
health.capabilityObserved({schema:'deadbyte.capability-manifest.v3',snapshot_id:'b'.repeat(64),state_epoch:5,
|
|
52
|
+
observed_at_utc:new Date(now).toISOString(),valid_until_utc:new Date(now+5000).toISOString(),bindings:{}});
|
|
53
|
+
now+=15_001;
|
|
54
|
+
assert.equal(health.snapshot().state,'DEGRADED');
|
|
55
|
+
assert.equal(health.snapshot().http_status,503);
|
|
56
|
+
health.reconciling('core transport closed after dispatch');
|
|
57
|
+
const degraded=health.snapshot();
|
|
58
|
+
assert.equal(degraded.state,'RECONCILING');
|
|
59
|
+
assert.doesNotMatch(JSON.stringify(degraded),/token|secret|C:\\/i);
|
|
60
|
+
});
|
|
@@ -18,6 +18,22 @@ test('capability resolver enforces 5 canonical states', () => {
|
|
|
18
18
|
assert.equal(CAPABILITY_STATES.UNAVAILABLE, 'UNAVAILABLE');
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
+
test('R59 live plane state can only reduce signed capability availability', async()=>{
|
|
22
|
+
const ledger={entries:[{
|
|
23
|
+
id:'coding.search',compact_tool:'coding_inspect',full_tool:'coding_search',compact_op:'search',
|
|
24
|
+
authority:'coding',mutating:false,production:true,runtime_flag:'DEADBYTE_CODING_RUNTIME'
|
|
25
|
+
}]};
|
|
26
|
+
const result=await resolveCapabilityStates({
|
|
27
|
+
ledger,toolCatalog:['coding_inspect'],profile:'compact',env:{DEADBYTE_CODING_RUNTIME:'1'},
|
|
28
|
+
codingRuntime:{sessionStatus:async()=>({armed:true})},
|
|
29
|
+
runtimePlanes:{DEADBYTE_CODING_RUNTIME:{state:'DEGRADED',ready:false,diagnostic:{code:'INITIALIZATION_FAILED',reason:'missing root',recoverable:true}}}
|
|
30
|
+
});
|
|
31
|
+
assert.equal(result.capabilities[0].state,CAPABILITY_STATES.DEGRADED);
|
|
32
|
+
assert.equal(result.capabilities[0].ready,false);
|
|
33
|
+
assert.equal(result.capabilities[0].diagnostic.code,'INITIALIZATION_FAILED');
|
|
34
|
+
assert.deepEqual(result.effective_capabilities,[]);
|
|
35
|
+
});
|
|
36
|
+
|
|
21
37
|
test('deriveCapabilityRequirements maps dependencies accurately', () => {
|
|
22
38
|
const reqCodingRead = deriveCapabilityRequirements({
|
|
23
39
|
id: 'coding.search',
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createCapabilitySnapshotTracker } from '../src/capability-snapshot.mjs';
|
|
4
|
+
|
|
5
|
+
const H=value=>String(value).repeat(64).slice(0,64);
|
|
6
|
+
const ledger={schema:'deadbyte.capability-ledger.v1',ledger_sha256:H('a'),entries:[{
|
|
7
|
+
id:'machine.status',full_tool:'machine_status',compact_tool:'machine_observe',compact_op:'machine_status',
|
|
8
|
+
runtime_flag:'DEADBYTE_MACHINE_RUNTIME',authority:'none',mutating:false,receipt_class:'machine',production:true,host_only:false
|
|
9
|
+
}]};
|
|
10
|
+
const resolution={capabilities:[{...ledger.entries[0],tool:'machine_observe',operations:['machine_status'],requires:['DEADBYTE_MACHINE_RUNTIME'],state:'AVAILABLE',ready:true,diagnostic:null}],effective_capabilities:['machine.status'],unavailable_capabilities:[],degraded_capabilities:[]};
|
|
11
|
+
|
|
12
|
+
test('R59 capability snapshot v3 is a backward-compatible content-bound live overlay', () => {
|
|
13
|
+
let now=Date.parse('2026-10-02T12:00:00.000Z');
|
|
14
|
+
const tracker=createCapabilitySnapshotTracker({clock:()=>now,ttlMs:5000});
|
|
15
|
+
const input={profile:'compact',ledger,toolCatalog:['machine_observe'],catalogSha256:H('b'),resolution,
|
|
16
|
+
bindings:{release_id:'v0.15.36-test',release_manifest_sha256:H('c'),generation_manifest_sha256:H('d')},
|
|
17
|
+
planes:{machine:{state:'READY',ready:true,policy_sha256:null,diagnostic:null}}};
|
|
18
|
+
const first=tracker.snapshot(input);
|
|
19
|
+
assert.equal(first.schema,'deadbyte.capability-manifest.v3');
|
|
20
|
+
assert.equal(first.ledger_sha256,ledger.ledger_sha256);
|
|
21
|
+
assert.deepEqual(first.tool_catalog,['machine_observe']);
|
|
22
|
+
assert.equal(first.state_epoch,1);
|
|
23
|
+
assert.match(first.snapshot_id,/^[0-9a-f]{64}$/);
|
|
24
|
+
assert.equal(first.capabilities[0].selected_route.tool,'machine_observe');
|
|
25
|
+
now+=1000;
|
|
26
|
+
const same=tracker.snapshot(input);
|
|
27
|
+
assert.equal(same.snapshot_id,first.snapshot_id);
|
|
28
|
+
assert.equal(same.state_epoch,1);
|
|
29
|
+
assert.notEqual(same.observed_at_utc,first.observed_at_utc);
|
|
30
|
+
const changed=tracker.snapshot({...input,planes:{machine:{state:'DEGRADED',ready:false,policy_sha256:null,diagnostic:{code:'DOWN',reason:'probe failed'}}}});
|
|
31
|
+
assert.equal(changed.state_epoch,2);
|
|
32
|
+
assert.notEqual(changed.snapshot_id,first.snapshot_id);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('R59 capability snapshot rejects routes outside the signed ledger', () => {
|
|
36
|
+
const tracker=createCapabilitySnapshotTracker();
|
|
37
|
+
assert.throws(()=>tracker.snapshot({profile:'compact',ledger,toolCatalog:['invented'],catalogSha256:H('b'),
|
|
38
|
+
resolution:{...resolution,capabilities:[{...resolution.capabilities[0],id:'invented.capability'}]},bindings:{},planes:{}}),/outside signed ledger/);
|
|
39
|
+
});
|
|
@@ -72,7 +72,7 @@ test('R36 coding_inspect gateway calls runtime directly and never MCP-recurses',
|
|
|
72
72
|
assert.equal(result.structuredContent.result.path,'src/a.mjs');
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
test('
|
|
75
|
+
test('R59 capability manifest v3 preserves exact compact catalog attestation',async()=>{
|
|
76
76
|
const server=fakeServer();
|
|
77
77
|
registerCompactGatewayTools(server,{
|
|
78
78
|
profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'a'.repeat(64)},
|
|
@@ -81,13 +81,33 @@ test('R40 capability manifest v2 attests the exact registered compact catalog',a
|
|
|
81
81
|
});
|
|
82
82
|
const result=await server.tools.get('capability_manifest').handler({});
|
|
83
83
|
const manifest=result.structuredContent.result;
|
|
84
|
-
assert.equal(manifest.schema,'deadbyte.capability-manifest.
|
|
84
|
+
assert.equal(manifest.schema,'deadbyte.capability-manifest.v3');
|
|
85
85
|
assert.equal(manifest.profile,'compact');
|
|
86
86
|
assert.deepEqual(manifest.tool_catalog,[...server.tools.keys()].sort());
|
|
87
87
|
assert.equal(manifest.tool_count,server.tools.size);
|
|
88
88
|
assert.match(manifest.catalog_sha256,/^[0-9a-f]{64}$/);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
test('R59 capability manifest v3 preserves v2 catalog fields and adds live bindings',async()=>{
|
|
92
|
+
const server=fakeServer();
|
|
93
|
+
registerCompactGatewayTools(server,{
|
|
94
|
+
profile:'compact',ledger:{schema:'deadbyte.capability-ledger.v1',entries:[],ledger_sha256:'a'.repeat(64)},
|
|
95
|
+
codingRuntime:null,semanticRuntime:null,machineFsRuntime:null,processRuntime:null,observationRuntime:null,machineRuntime:null,desktopRuntime:null,
|
|
96
|
+
runtimePlanes:{DEADBYTE_CODING_RUNTIME:{state:'DEGRADED',ready:false,diagnostic:{code:'INITIALIZATION_FAILED',reason:'missing root',recoverable:true}}},
|
|
97
|
+
releaseBindingProvider:async()=>({release_id:'v0.15.36-test',release_manifest_sha256:'b'.repeat(64),generation_manifest_sha256:'c'.repeat(64)}),
|
|
98
|
+
catalogProvider:()=>[...server.tools.keys()]
|
|
99
|
+
});
|
|
100
|
+
const result=await server.tools.get('capability_manifest').handler({});
|
|
101
|
+
const manifest=result.structuredContent.result;
|
|
102
|
+
assert.equal(manifest.schema,'deadbyte.capability-manifest.v3');
|
|
103
|
+
assert.equal(manifest.tool_count,server.tools.size);
|
|
104
|
+
assert.deepEqual(manifest.tool_catalog,[...server.tools.keys()].sort());
|
|
105
|
+
assert.match(manifest.snapshot_id,/^[0-9a-f]{64}$/);
|
|
106
|
+
assert.equal(manifest.state_epoch,1);
|
|
107
|
+
assert.equal(manifest.bindings.release_id,'v0.15.36-test');
|
|
108
|
+
assert.equal(manifest.planes.DEADBYTE_CODING_RUNTIME.state,'DEGRADED');
|
|
109
|
+
});
|
|
110
|
+
|
|
91
111
|
|
|
92
112
|
test('R40 desktop_control is stable when runtime is absent and routes typed read-only operations when present',async()=>{
|
|
93
113
|
const server=fakeServer();
|
|
@@ -81,7 +81,7 @@ test('R44 real max-runtime compact stdio catalog is exactly 43 with verified ski
|
|
|
81
81
|
const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
|
|
82
82
|
assert.equal(manifest.isError,false);
|
|
83
83
|
const m=manifest.structuredContent.result;
|
|
84
|
-
assert.equal(m.schema,'deadbyte.capability-manifest.
|
|
84
|
+
assert.equal(m.schema,'deadbyte.capability-manifest.v3');
|
|
85
85
|
assert.equal(m.profile,'compact');
|
|
86
86
|
assert.equal(m.tool_count,43);
|
|
87
87
|
assert.deepEqual(m.tool_catalog,names);
|
|
@@ -81,7 +81,7 @@ test('R45 real max-runtime compact stdio catalog is exactly 43 with verified ski
|
|
|
81
81
|
const manifest=await client.callTool({name:'capability_manifest',arguments:{}});
|
|
82
82
|
assert.equal(manifest.isError,false);
|
|
83
83
|
const m=manifest.structuredContent.result;
|
|
84
|
-
assert.equal(m.schema,'deadbyte.capability-manifest.
|
|
84
|
+
assert.equal(m.schema,'deadbyte.capability-manifest.v3');
|
|
85
85
|
assert.equal(m.profile,'compact');
|
|
86
86
|
assert.equal(m.tool_count,43);
|
|
87
87
|
assert.deepEqual(m.tool_catalog,names);
|
package/test/contained.test.mjs
CHANGED
|
@@ -714,7 +714,8 @@ test('MCP contained_transform schema is a compile-time operation enum with no ex
|
|
|
714
714
|
assert.ok(containedStart >= 0 && containedEnd > containedStart);
|
|
715
715
|
assert.match(contained, /z\.enum\(CONTAINED_TRANSFORM_OPERATIONS\)/);
|
|
716
716
|
assert.match(contained, /inputSchema: z\.strictObject\(\{/);
|
|
717
|
-
assert.match(server, /if \(
|
|
717
|
+
assert.match(server, /if \(planeRegistry\.state\('host'\)\.configured\)/);
|
|
718
|
+
assert.match(server, /requirePlane\('host',hostPolicy\)/);
|
|
718
719
|
for (const name of ['host_file_read', 'host_file_write', 'host_exec']) assert.match(server, new RegExp(`'${name}'`));
|
|
719
720
|
assert.doesNotMatch(contained, /inputSchema: z\.object\(\{/);
|
|
720
721
|
assert.doesNotMatch(contained, /executable:\s*z\.|argv:\s*z\.|environment:\s*z\.|challenge:\s*z\.|request_digest:\s*z\.|worker_id:\s*z\./);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
const controllerPath=new URL('../controller/deadbyte-controller.ps1',import.meta.url);
|
|
9
|
+
function quote(value){return `'${String(value).replaceAll("'","''")}'`;}
|
|
10
|
+
function functionBlock(source,name,nextName){
|
|
11
|
+
const start=source.indexOf(`function ${name}`),end=source.indexOf(`function ${nextName}`,start+1);
|
|
12
|
+
assert.ok(start>=0&&end>start,`missing ${name}`);
|
|
13
|
+
return source.slice(start,end);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('R59 controller refuses to arm process or coding policy with a missing root',async()=>{
|
|
17
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r59-controller-preflight-'));
|
|
18
|
+
try{
|
|
19
|
+
const runtime=path.join(root,'runtime');
|
|
20
|
+
const missing=path.join(root,'missing');
|
|
21
|
+
const coding=path.join(root,'coding.json'),processPolicy=path.join(root,'process.json');
|
|
22
|
+
await writeFile(coding,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
23
|
+
lease_file:path.join(runtime,'coding-grant.json'),roots:{project:{path:missing}}}));
|
|
24
|
+
await writeFile(processPolicy,JSON.stringify({schema:'deadbyte.process-policy.v1',enabled:true,
|
|
25
|
+
grant_file:path.join(runtime,'process-grant.json'),roots:{project:{path:missing}},profiles:{node:{}}}));
|
|
26
|
+
const source=await readFile(controllerPath,'utf8');
|
|
27
|
+
const blocks=[functionBlock(source,'Assert-PolicyRootsAvailable','Get-HostPolicyMetadata'),
|
|
28
|
+
functionBlock(source,'Get-ProcessPolicyMetadata','Get-ProcessGrantStatus'),
|
|
29
|
+
functionBlock(source,'Get-CodingPolicyMetadata','Get-CodingLeaseStatus')].join('\n');
|
|
30
|
+
const script=`$ErrorActionPreference='Stop';$RuntimeDir=${quote(runtime)};function Fail([string]$m){throw $m};function Get-ExecutableHash($p){return '${'a'.repeat(64)}'};${blocks};`+
|
|
31
|
+
`$errors=@();foreach($case in @(@{kind='process';cfg=@{ProcessPolicyFile=${quote(processPolicy)}}},@{kind='coding';cfg=@{CodingPolicyFile=${quote(coding)}}})){try{if($case.kind -eq 'process'){Get-ProcessPolicyMetadata $case.cfg|Out-Null}else{Get-CodingPolicyMetadata $case.cfg|Out-Null};throw 'unexpected pass'}catch{$errors+=$_.Exception.Message}};$errors|ConvertTo-Json -Compress`;
|
|
32
|
+
const result=spawnSync('powershell',['-NoLogo','-NoProfile','-NonInteractive','-Command',script],{encoding:'utf8'});
|
|
33
|
+
assert.equal(result.status,0,result.stderr);
|
|
34
|
+
const errors=JSON.parse(result.stdout.trim());
|
|
35
|
+
assert.equal(errors.length,2);
|
|
36
|
+
assert.match(errors[0],/process policy root 'project' missing/);
|
|
37
|
+
assert.match(errors[1],/coding policy root 'project' missing/);
|
|
38
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
39
|
+
});
|