deadbyte-mcp 0.15.45 → 0.15.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/MANIFEST.SHA256 +52 -27
  2. package/PROVENANCE.json +260 -257
  3. package/README.txt +22 -21
  4. package/RELEASE.json +366 -346
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +501 -503
  7. package/bin/deadbyte-kernel-lock-r74.exe +0 -0
  8. package/bridge/package.json +1 -1
  9. package/controller/deadbyte-controller.ps1 +20 -6
  10. package/controller/deadbyte-process-policy.json +7 -7
  11. package/docs/R73-CAPABILITY-GAP-MATRIX.json +17 -0
  12. package/docs/R73-EXECUTION-CONTRACT.md +11 -0
  13. package/docs/R73-IMPLEMENTATION-CHANGELOG.md +12 -0
  14. package/docs/R73-REMAINING-DEFECTS.md +9 -0
  15. package/docs/R74-KERNEL-LOCK-BINARY.json +11 -0
  16. package/native/deadbyte-kernel-lock-r74.cpp +65 -0
  17. package/package.json +2 -2
  18. package/scripts/build-kernel-lock-r74.ps1 +28 -0
  19. package/scripts/gate-windows-r72.ps1 +76 -0
  20. package/scripts/npm-prepublish-contract-r74.mjs +113 -0
  21. package/scripts/npm-release-r48.mjs +4 -0
  22. package/scripts/npm-release-readiness-r72.mjs +76 -0
  23. package/scripts/npm-release-readiness-r74.mjs +46 -0
  24. package/scripts/r73-atomic-benchmark.mjs +39 -0
  25. package/src/atomic-mutation.mjs +99 -9
  26. package/src/deadbyte-cli.mjs +722 -721
  27. package/src/kernel-lock-r74.mjs +125 -0
  28. package/src/launcher-isolation.mjs +58 -0
  29. package/src/remote-authority.mjs +75 -75
  30. package/src/remote-mcp-machine-smoke-client.mjs +84 -84
  31. package/src/runtime-update.mjs +2056 -2008
  32. package/src/staging-input-isolation.mjs +79 -0
  33. package/src/version.mjs +1 -1
  34. package/test/atomic-mutation.test.mjs +68 -2
  35. package/test/commander-schema-differential-r48.test.mjs +1 -1
  36. package/test/deferred-slot-operation-behavior.test.mjs +2 -2
  37. package/test/r58-cleanup-retention.test.mjs +3 -3
  38. package/test/r58-concurrent-recovery.test.mjs +3 -3
  39. package/test/r58-stale-owner.test.mjs +1 -1
  40. package/test/r59-tool-surface-compat.test.mjs +2 -2
  41. package/test/r60-authority-coherence.test.mjs +10 -2
  42. package/test/r71-isolated-slot-rollback-restore.test.mjs +11 -11
  43. package/test/r71-release-identity.test.mjs +12 -10
  44. package/test/r72-launcher-isolation.test.mjs +99 -0
  45. package/test/r72-publication-fence.test.mjs +56 -0
  46. package/test/r72-publication-output-integrity.test.mjs +110 -0
  47. package/test/r72-remote-safe-contract.test.mjs +101 -0
  48. package/test/r72-staging-input-isolation.test.mjs +87 -0
  49. package/test/r72-staging-workspace-isolation.test.mjs +114 -0
  50. package/test/r73-package-boundary.test.mjs +22 -0
  51. package/test/r74-kernel-lock.test.mjs +150 -0
  52. package/test/r74-prepublish-contract.test.mjs +89 -0
  53. package/test/remote-session-cli.test.mjs +110 -64
@@ -0,0 +1,125 @@
1
+ import {spawn} from 'node:child_process';
2
+ import {createHash} from 'node:crypto';
3
+ import {lstat, readFile} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {performance} from 'node:perf_hooks';
6
+ import {fileURLToPath} from 'node:url';
7
+
8
+ const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
9
+ const exePath = path.join(root, 'bin', 'deadbyte-kernel-lock-r74.exe');
10
+ const metaPath = path.join(root, 'docs', 'R74-KERNEL-LOCK-BINARY.json');
11
+
12
+ const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
13
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
14
+
15
+ export async function verifyKernelLockBinary() {
16
+ if (process.platform !== 'win32') {
17
+ return { ok: false, reason: 'unsupported platform; win32 required' };
18
+ }
19
+ try {
20
+ const meta = JSON.parse(await readFile(metaPath, 'utf8'));
21
+ if (meta?.schema !== 'deadbyte.r74-kernel-lock-binary.v1' ||
22
+ meta?.exe !== 'bin/deadbyte-kernel-lock-r74.exe' ||
23
+ !/^[0-9a-f]{64}$/.test(String(meta?.sha256 ?? ''))) {
24
+ return { ok: false, reason: 'R74 kernel lock metadata invalid' };
25
+ }
26
+ const info = await lstat(exePath);
27
+ if (!info.isFile() || info.isSymbolicLink()) {
28
+ return { ok: false, reason: 'R74 kernel lock executable is not a regular file' };
29
+ }
30
+ const bytes = await readFile(exePath);
31
+ const hash = sha256Bytes(bytes);
32
+ if (hash !== meta.sha256) {
33
+ return { ok: false, reason: 'R74 kernel lock executable hash pin mismatch' };
34
+ }
35
+ return { ok: true, path: exePath, sha256: hash };
36
+ } catch (err) {
37
+ return { ok: false, reason: err?.message ?? String(err) };
38
+ }
39
+ }
40
+
41
+ export async function acquireKernelExclusiveLock(lockPath, { timeoutMs = 30000, pollMs = 20 } = {}) {
42
+ const verified = await verifyKernelLockBinary();
43
+ if (!verified.ok) {
44
+ throw new Error(`atomic mutation lock recovery blocked: native exclusive locking unavailable (${verified.reason})`);
45
+ }
46
+
47
+ const started = performance.now();
48
+ while (true) {
49
+ let child = null;
50
+ try {
51
+ const outcome = await new Promise((resolve, reject) => {
52
+ child = spawn(verified.path, ['acquire', path.resolve(lockPath)], {
53
+ stdio: ['pipe', 'pipe', 'pipe'],
54
+ windowsHide: true,
55
+ shell: false
56
+ });
57
+ let line = '';
58
+ let resolved = false;
59
+
60
+ child.stdout.on('data', chunk => {
61
+ if (resolved) return;
62
+ line += chunk.toString('utf8');
63
+ const newline = line.indexOf('\n');
64
+ if (newline !== -1) {
65
+ resolved = true;
66
+ try {
67
+ const parsed = JSON.parse(line.slice(0, newline).trim());
68
+ resolve(parsed);
69
+ } catch (parseError) {
70
+ reject(parseError);
71
+ }
72
+ }
73
+ });
74
+
75
+ child.once('error', err => {
76
+ if (!resolved) {
77
+ resolved = true;
78
+ reject(err);
79
+ }
80
+ });
81
+
82
+ child.once('close', code => {
83
+ if (!resolved) {
84
+ resolved = true;
85
+ resolve({ status: 'exited', code });
86
+ }
87
+ });
88
+ });
89
+
90
+ if (outcome.status === 'acquired') {
91
+ const lockChild = child;
92
+ const release = async () => {
93
+ if (lockChild.exitCode !== null) {
94
+ throw new Error('atomic mutation lock ownership lost: lock process exited unexpectedly');
95
+ }
96
+ try {
97
+ lockChild.stdin.write('q\n');
98
+ lockChild.stdin.end();
99
+ } catch {}
100
+ await new Promise(resolve => {
101
+ if (lockChild.exitCode !== null) return resolve();
102
+ lockChild.once('close', resolve);
103
+ });
104
+ };
105
+ release.pid = outcome.pid;
106
+ return release;
107
+ }
108
+
109
+ if (outcome.status === 'locked') {
110
+ if (performance.now() - started >= timeoutMs) {
111
+ throw new Error('atomic mutation lock timed out');
112
+ }
113
+ await sleep(pollMs);
114
+ continue;
115
+ }
116
+
117
+ throw new Error(`atomic mutation lock acquisition failed: ${JSON.stringify(outcome)}`);
118
+ } catch (err) {
119
+ if (child && child.exitCode === null) {
120
+ child.kill('SIGKILL');
121
+ }
122
+ throw err;
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,58 @@
1
+ /** R72: state-root-owned launcher routing. No staging caller may write the user-wide Downloads launcher. */
2
+ import {lstatSync} from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ const normalize=file=>process.platform==='win32'?path.resolve(file).toLowerCase():path.resolve(file);
7
+ function contains(parent,child){
8
+ const rel=path.relative(parent,child);
9
+ return rel===''||(!path.isAbsolute(rel)&&rel!=='..'&&!rel.startsWith(`..${path.sep}`));
10
+ }
11
+ function assertNoReparseParts(file){
12
+ let cursor=path.resolve(file);
13
+ while(true){
14
+ try{
15
+ const info=lstatSync(cursor);
16
+ if(info.isSymbolicLink())throw new Error(`staging launcher boundary is a reparse/symlink: ${cursor}`);
17
+ }catch(error){if(error?.code!=='ENOENT')throw error;}
18
+ const parent=path.dirname(cursor);
19
+ if(parent===cursor)break;
20
+ cursor=parent;
21
+ }
22
+ }
23
+ export function canonicalLauncherPaths(stateRoot,{home=os.homedir()}={}){
24
+ if(typeof stateRoot!=='string'||!path.isAbsolute(stateRoot)||stateRoot.includes('\0'))throw new Error('launcher state root must be absolute');
25
+ const state=path.resolve(stateRoot),productionState=path.resolve(home,'.deadbyte-mcp');
26
+ const production=normalize(state)===normalize(productionState);
27
+ if(!production){
28
+ if(contains(normalize(productionState),normalize(state))||contains(normalize(state),normalize(productionState)))
29
+ throw new Error('staging state overlaps production state root');
30
+ assertNoReparseParts(state);
31
+ assertNoReparseParts(path.join(state,'bin'));
32
+ assertNoReparseParts(path.join(state,'launchers'));
33
+ }
34
+ const top=production?path.join(home,'Downloads','DEADBYTE-MCP.cmd'):path.join(state,'launchers','DEADBYTE-MCP.cmd');
35
+ if(!production)assertNoReparseParts(top);
36
+ return Object.freeze({state,production,top,ps1:path.join(state,'bin','DEADBYTE-MCP.ps1'),cmd:path.join(state,'bin','DEADBYTE-MCP.cmd')});
37
+ }
38
+ export function assertCanonicalLaunchers(stateRoot,launchers){
39
+ const expected=canonicalLauncherPaths(stateRoot);
40
+ if(!launchers||typeof launchers!=='object')throw new Error('launcher paths absent');
41
+ for(const field of ['top','ps1','cmd']){
42
+ if(typeof launchers[field]!=='string'||!path.isAbsolute(launchers[field])||
43
+ normalize(launchers[field])!==normalize(expected[field]))
44
+ throw new Error(`launcher ${field} is not canonical for selected state root`);
45
+ }
46
+ return expected;
47
+ }
48
+ /** Relative to bin/ or launchers/, so the entry point always selects its own state root. */
49
+ export function renderStateBoundLauncherCmd(){
50
+ return [
51
+ '@echo off',
52
+ 'setlocal EnableExtensions DisableDelayedExpansion',
53
+ 'set "DEADBYTE_STATE_ROOT=%~dp0.."',
54
+ 'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
55
+ 'exit /b %ERRORLEVEL%',
56
+ ''
57
+ ].join('\r\n');
58
+ }
@@ -1,75 +1,75 @@
1
- import path from 'node:path';
2
-
3
- export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
4
-
5
- export function authorityVector(mode='default'){
6
- if(!['default','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
7
- return Object.freeze({
8
- process:true,coding:true,autonomous:true,
9
- desktop:mode==='desktop'||mode==='full',
10
- host:mode==='host'||mode==='full'
11
- });
12
- }
13
-
14
- export function authorityRestorePlan(snapshot){
15
- const disarm=[],arm=[];
16
- for(const plane of AUTHORITY_PLANES){
17
- const entry=snapshot?.authority?.[plane];
18
- if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
19
- (entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
20
- }
21
- return Object.freeze([...disarm,...arm]);
22
- }
23
-
24
- export function parseAuthorityObservation(raw){
25
- let value;
26
- try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
27
- catch{throw new Error('controller observation JSON invalid');}
28
- if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
29
- throw new Error('controller observation schema invalid');
30
- const authority={};
31
- for(const plane of AUTHORITY_PLANES){
32
- const entry=value.authority[plane];
33
- if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
34
- throw new Error(`controller authority ${plane} observation invalid`);
35
- const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
36
- if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
37
- let epochSeq=null,epochSha256=null;
38
- if(hasEpochSeq){
39
- if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
40
- throw new Error(`controller authority ${plane} epoch observation invalid`);
41
- epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
42
- }
43
- authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
44
- epoch_seq:epochSeq,epoch_sha256:epochSha256});
45
- }
46
- return Object.freeze({...value,authority:Object.freeze(authority)});
47
- }
48
-
49
- export function assertAuthorityVector(snapshot,expected){
50
- for(const plane of AUTHORITY_PLANES){
51
- const entry=snapshot?.authority?.[plane];
52
- if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
53
- if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
54
- if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
55
- }
56
- return snapshot;
57
- }
58
-
59
- export function authorityBanner(snapshot){
60
- return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
61
- }
62
-
63
- export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
64
- if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
65
- for(const name of ['bridge','core','cloud']){
66
- if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
67
- }
68
- if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
69
- if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
70
- if(coreRoot!==null){
71
- if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
72
- throw new Error('core root does not match active release');
73
- }
74
- return snapshot;
75
- }
1
+ import path from 'node:path';
2
+
3
+ export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
4
+
5
+ export function authorityVector(mode='default'){
6
+ if(!['default','operator','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
7
+ return Object.freeze({
8
+ process:mode!=='default',coding:mode!=='default',autonomous:mode!=='default',
9
+ desktop:mode==='desktop'||mode==='full',
10
+ host:mode==='host'||mode==='full'
11
+ });
12
+ }
13
+
14
+ export function authorityRestorePlan(snapshot){
15
+ const disarm=[],arm=[];
16
+ for(const plane of AUTHORITY_PLANES){
17
+ const entry=snapshot?.authority?.[plane];
18
+ if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
19
+ (entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
20
+ }
21
+ return Object.freeze([...disarm,...arm]);
22
+ }
23
+
24
+ export function parseAuthorityObservation(raw){
25
+ let value;
26
+ try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
27
+ catch{throw new Error('controller observation JSON invalid');}
28
+ if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
29
+ throw new Error('controller observation schema invalid');
30
+ const authority={};
31
+ for(const plane of AUTHORITY_PLANES){
32
+ const entry=value.authority[plane];
33
+ if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
34
+ throw new Error(`controller authority ${plane} observation invalid`);
35
+ const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
36
+ if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
37
+ let epochSeq=null,epochSha256=null;
38
+ if(hasEpochSeq){
39
+ if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
40
+ throw new Error(`controller authority ${plane} epoch observation invalid`);
41
+ epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
42
+ }
43
+ authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
44
+ epoch_seq:epochSeq,epoch_sha256:epochSha256});
45
+ }
46
+ return Object.freeze({...value,authority:Object.freeze(authority)});
47
+ }
48
+
49
+ export function assertAuthorityVector(snapshot,expected){
50
+ for(const plane of AUTHORITY_PLANES){
51
+ const entry=snapshot?.authority?.[plane];
52
+ if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
53
+ if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
54
+ if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
55
+ }
56
+ return snapshot;
57
+ }
58
+
59
+ export function authorityBanner(snapshot){
60
+ return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
61
+ }
62
+
63
+ export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
64
+ if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
65
+ for(const name of ['bridge','core','cloud']){
66
+ if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
67
+ }
68
+ if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
69
+ if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
70
+ if(coreRoot!==null){
71
+ if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
72
+ throw new Error('core root does not match active release');
73
+ }
74
+ return snapshot;
75
+ }
@@ -1,84 +1,84 @@
1
- import { randomBytes } from 'node:crypto';
2
- import { readFile } from 'node:fs/promises';
3
- import path from 'node:path';
4
- import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
5
- import { verifyMachinePingPublic } from './machine-identity.mjs';
6
- import { parseActivePointer } from './release-slot.mjs';
7
- import { probeRemoteEnforcement } from './remote-enforcement-probe-r60.mjs';
8
-
9
- const endpointText = process.argv[2];
10
- if (!endpointText) throw new Error('usage: node src/remote-mcp-machine-smoke-client.mjs https://host/mcp/path');
11
- const endpoint = new URL(endpointText);
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');
15
-
16
- const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
17
- const client = new Client(
18
- { name: 'deadbyte-r29-machine-gate', version: '0.8.5' },
19
- { versionNegotiation: { mode: { pin: '2026-07-28' } } }
20
- );
21
- const transport = new StreamableHTTPClientTransport(endpoint);
22
-
23
- function structured(result, label) {
24
- if (result.isError) throw new Error(`${label} returned tool error: ${JSON.stringify(result.structuredContent ?? result.content)}`);
25
- if (!result.structuredContent || typeof result.structuredContent !== 'object') throw new Error(`${label} returned no structured object`);
26
- return result.structuredContent;
27
- }
28
-
29
- try {
30
- await client.connect(transport);
31
- const listed = await client.listTools();
32
- const names = listed.tools.map(tool => tool.name).sort();
33
- for (const name of machineTools) if (!names.includes(name)) throw new Error(`machine tool missing: ${name}`);
34
- for (const tool of listed.tools.filter(tool => machineTools.includes(tool.name))) {
35
- if (tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false || !tool.outputSchema?.properties) {
36
- throw new Error(`machine tool ${tool.name} missing strict output schema`);
37
- }
38
- }
39
-
40
- const status = structured(await client.callTool({ name: 'machine_status', arguments: {} }), 'machine_status');
41
- if (status.status !== 'ok') throw new Error(`machine_status status=${String(status.status)}`);
42
- const machines = structured(await client.callTool({ name: 'machine_list', arguments: {} }), 'machine_list');
43
- if (machines.status !== 'ok' || !Array.isArray(machines.machines)) throw new Error('machine_list invalid');
44
- const self = machines.machines.find(item => item.machine_id === status.machine_id);
45
- if (!self) throw new Error('machine_list does not contain current machine');
46
- for (const field of ['public_key_spki_base64','identity_generation','online_epoch','release_id','release_manifest_sha256','generation_manifest_sha256']) {
47
- if (self[field] !== status[field]) throw new Error(`machine_list/status mismatch: ${field}`);
48
- }
49
-
50
- const nonce = randomBytes(32).toString('hex');
51
- const ping = structured(await client.callTool({ name: 'machine_ping', arguments: { nonce } }), 'machine_ping');
52
- if (ping.status !== 'ok' || ping.nonce !== nonce || ping.machine_id !== status.machine_id) throw new Error('machine_ping identity/nonce mismatch');
53
- for (const field of ['identity_generation','online_epoch','release_id','release_manifest_sha256','generation_manifest_sha256']) {
54
- if (ping[field] !== status[field]) throw new Error(`machine_ping/status mismatch: ${field}`);
55
- }
56
- const signatureVerified = await verifyMachinePingPublic({ ping, publicKeySpkiBase64: status.public_key_spki_base64 });
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
- }
69
-
70
- console.log(JSON.stringify({
71
- status: 'passed',
72
- protocol_revision: client.getNegotiatedProtocolVersion(),
73
- tool_count: names.length,
74
- machine_id: status.machine_id,
75
- identity_generation: status.identity_generation,
76
- release_id: status.release_id,
77
- release_manifest_sha256: status.release_manifest_sha256,
78
- generation_manifest_sha256: status.generation_manifest_sha256,
79
- signature_verified: true,
80
- ...(enforcementMode?{enforcement_mode:enforcementMode,enforcement_verified:true}:{})
81
- }));
82
- } finally {
83
- await client.close().catch(() => {});
84
- }
1
+ import { randomBytes } from 'node:crypto';
2
+ import { readFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
5
+ import { verifyMachinePingPublic } from './machine-identity.mjs';
6
+ import { parseActivePointer } from './release-slot.mjs';
7
+ import { probeRemoteEnforcement } from './remote-enforcement-probe-r60.mjs';
8
+
9
+ const endpointText = process.argv[2];
10
+ if (!endpointText) throw new Error('usage: node src/remote-mcp-machine-smoke-client.mjs https://host/mcp/path');
11
+ const endpoint = new URL(endpointText);
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','operator','desktop','host','full','revoked'].includes(enforcementMode))throw new Error('remote enforcement probe mode invalid');
15
+
16
+ const machineTools = ['machine_list', 'machine_ping', 'machine_revoke', 'machine_status'];
17
+ const client = new Client(
18
+ { name: 'deadbyte-r29-machine-gate', version: '0.8.5' },
19
+ { versionNegotiation: { mode: { pin: '2026-07-28' } } }
20
+ );
21
+ const transport = new StreamableHTTPClientTransport(endpoint);
22
+
23
+ function structured(result, label) {
24
+ if (result.isError) throw new Error(`${label} returned tool error: ${JSON.stringify(result.structuredContent ?? result.content)}`);
25
+ if (!result.structuredContent || typeof result.structuredContent !== 'object') throw new Error(`${label} returned no structured object`);
26
+ return result.structuredContent;
27
+ }
28
+
29
+ try {
30
+ await client.connect(transport);
31
+ const listed = await client.listTools();
32
+ const names = listed.tools.map(tool => tool.name).sort();
33
+ for (const name of machineTools) if (!names.includes(name)) throw new Error(`machine tool missing: ${name}`);
34
+ for (const tool of listed.tools.filter(tool => machineTools.includes(tool.name))) {
35
+ if (tool.outputSchema?.type !== 'object' || tool.outputSchema?.additionalProperties !== false || !tool.outputSchema?.properties) {
36
+ throw new Error(`machine tool ${tool.name} missing strict output schema`);
37
+ }
38
+ }
39
+
40
+ const status = structured(await client.callTool({ name: 'machine_status', arguments: {} }), 'machine_status');
41
+ if (status.status !== 'ok') throw new Error(`machine_status status=${String(status.status)}`);
42
+ const machines = structured(await client.callTool({ name: 'machine_list', arguments: {} }), 'machine_list');
43
+ if (machines.status !== 'ok' || !Array.isArray(machines.machines)) throw new Error('machine_list invalid');
44
+ const self = machines.machines.find(item => item.machine_id === status.machine_id);
45
+ if (!self) throw new Error('machine_list does not contain current machine');
46
+ for (const field of ['public_key_spki_base64','identity_generation','online_epoch','release_id','release_manifest_sha256','generation_manifest_sha256']) {
47
+ if (self[field] !== status[field]) throw new Error(`machine_list/status mismatch: ${field}`);
48
+ }
49
+
50
+ const nonce = randomBytes(32).toString('hex');
51
+ const ping = structured(await client.callTool({ name: 'machine_ping', arguments: { nonce } }), 'machine_ping');
52
+ if (ping.status !== 'ok' || ping.nonce !== nonce || ping.machine_id !== status.machine_id) throw new Error('machine_ping identity/nonce mismatch');
53
+ for (const field of ['identity_generation','online_epoch','release_id','release_manifest_sha256','generation_manifest_sha256']) {
54
+ if (ping[field] !== status[field]) throw new Error(`machine_ping/status mismatch: ${field}`);
55
+ }
56
+ const signatureVerified = await verifyMachinePingPublic({ ping, publicKeySpkiBase64: status.public_key_spki_base64 });
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
+ }
69
+
70
+ console.log(JSON.stringify({
71
+ status: 'passed',
72
+ protocol_revision: client.getNegotiatedProtocolVersion(),
73
+ tool_count: names.length,
74
+ machine_id: status.machine_id,
75
+ identity_generation: status.identity_generation,
76
+ release_id: status.release_id,
77
+ release_manifest_sha256: status.release_manifest_sha256,
78
+ generation_manifest_sha256: status.generation_manifest_sha256,
79
+ signature_verified: true,
80
+ ...(enforcementMode?{enforcement_mode:enforcementMode,enforcement_verified:true}:{})
81
+ }));
82
+ } finally {
83
+ await client.close().catch(() => {});
84
+ }